Is it possible to determine the NSRunLoop/NSThread that is associated with an open NSStream?
Asked Answered
Q

1

9

I am using (and am required to use) a third-party framework to which I do not have source. The third-party framework handles creating an authenticated client/server connection and hands back a pair of open NSStreams.

The stream creation process, per Apple's docs, is: alloc/init, set delegate, schedule in run loop, and open. Apple's docs go further to say: "You should never attempt to access a scheduled stream from a thread different than the one owning the stream’s run loop." https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Streams/Articles/ReadingInputStreams.html#//apple_ref/doc/uid/20002273-1001844

The stream disposal process is: close, unschedule, release.

If one creates a stream themselves, it's clear where the stream was scheduled. If a third-party framework creates the stream, one may not know where the stream was scheduled.

Looking at the documentation that I found, I did not see a way to programmatically determine the NSRunLoop and NSThread with which an open NSStream is associated. Is there a way to determine this information at runtime?

Quenelle answered 24/5, 2012 at 15:4 Comment(14)
An editor removed the Objective-C runtime tag, but did not offer an answer to the question. I believe that, if there is an answer, it may very well involve interrogating the runtime. So, please leave the runtime tag as-is.Quenelle
Thinking that the answer may lie in a particular domain does not mean that the question should be so tagged. The ObjC runtime is lower-level (or at least different) than threads, streams, and run loops -- those are framework concepts.Soybean
Can you name the framework? Do you know the actual class of the stream objects? NSInput/OutputStream or a custom subclass?Soybean
As Jacques pointed out, the Objective-C runtime is lower level than the Cocoa Foundation you are referring to (NSRunLoop, NSThread and NSStream are part of it). You may have a look at Apple's runtime documentation or even at Apple's or GNU's runtime source code. Then you will understand that your question is not about the Objective-C runtime. I will remove the tag again.Pudency
@TiloPrütz It's curious how the one answer so far involves runtime hackery.Quenelle
If the 3rdparty framework returns you streams that were created on an unknowable thread, that sounds like a deeply broken API. Publicly accessible streams should be either always created on one of three places: the runloop that created the wrapper, an explicit publicly accessible runloop provided by the framework, or the main runloop. Almost always the right answer is the main runloop. I would definitely start by contacting the vendor to determine whether their framework is really that broken. Do you have evidence that it is?Wiegand
@RobNapier Totally agree on all counts. And, in my circumstance I now know that the NStreams are scheduled on the main runloop. However, I would like to know if there is a way to determine the scheduling at runtime.Quenelle
Remember that a stream can be scheduled on multiple runloops, so there won't be any access to a single runloop that it belongs to. I'd have to think more deeply on how to determine the source for the stream. If you had that, you can then ask the run loop whether it contains the given source. You also can of course schedule the stream onto your current runloop so that you know it'll be on that runloop (as well as others). There's not a lot of clear documentation about that configuration, though.Wiegand
@Rob, I did not realize that streams could be scheduled on multiple runloops simultaneously (and am also uncertain when/how that would be useful?!). I have not seen anything in Apple's docs to that effect. I'm working with network-based streams. Are there any other good sources for stream programming information on iOS/OSX that you would recommend?Quenelle
@RobNapier I'm also interested in any good examples that you could share of spooling up a set of NSStreams on a separate thread. Thanks!Quenelle
Regarding multiple runloops, the NSStream docs only hint at it ("at least one run loop"), but it's much more explicit in the CFReadStream docs ("A stream can be scheduled with multiple run loops and run loop modes.") For an excellent example of async stream management, I'd look at CocoaAsyncSocket, particularly the newer GCD code github.com/robbiehanson/CocoaAsyncSocket.Wiegand
@Quenelle Just because an answer involves runtime hackery it does not mean that your question is about the runtime. The tags are a help to filter questions to domains of someones interests or knowledge. Tagging this question with 'objective-c-runtime' pretends that the question is about a problem with the runtime which it is definitely not.Pudency
@TiloPrütz Tilo, your position suggests that this question should also not be tagged iOS or OSX - since this is not a problem with iOS or OSX. I tagged it Objective-C runtime because I ~want~ people with runtime expertise to look at it, not because it's a problem with the runtime.Quenelle
Xyzzycoder, did my solution Work? :)Jud
A
3

I am going to give a code, that will probably work and should be used with care.

We define the following class category:

@interface TheSpecificNSStreamClass (ProposedCategory)

@property (nonatomic, strong, readonly) NSArray* associatedRunLoops;

- (void)myScheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;
- (void)myRemoveFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;

@end

and implementation:

@implementation TheSpecificNSStreamClass (ProposedCategory)

- (NSArray*)associatedRunLoops
{
    return [NSArray arrayWithArray:objc_getAssociatedObject(self, @"___associatedRunloops")];
}

- (void)myScheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode
{
    NSMutableArray* runloops = objc_getAssociatedObject(self, @"___associatedRunloops");

    if(runloops == nil)
    {
        runloops = [NSMutableArray array];
        objc_setAssociatedObject(obj, @"___associatedRunloops", runloops, OBJC_ASSOCIATION_RETAIN);
    }

    [runloops addObject:aRunLoop];

    [self myScheduleInRunLoop:aRunLoop forMode:mode];
}

- (void)myRemoveFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode
{
    NSMutableArray* runloops = objc_getAssociatedObject(self, @"___associatedRunloops");

    [runloops removeObject:aRunLoop];

    [self myRemoveFromRunLoop:aRunLoop forMode:mode];
}

@end

Now, in some place in your application delegate, we use method swizzling to exchange the two original methods with our implementation:

Method origMethod = class_getInstanceMethod([TheSpecificNSStreamClass class], @selector(scheduleInRunLoop:forMode:));
Method altMethod = class_getInstanceMethod([TheSpecificNSStreamClass class], @selector(myScheduleInRunLoop:forMode:));
if ((origMethod != nil) && (altMethod != nil))
{
    method_exchangeImplementations(origMethod, altMethod);
}

origMethod = class_getInstanceMethod([TheSpecificNSStreamClass class], @selector(removeFromRunLoop:forMode:));
altMethod = class_getInstanceMethod([TheSpecificNSStreamClass class], @selector(myRemoveFromRunLoop:forMode:));
if ((origMethod != nil) && (altMethod != nil))
{
    method_exchangeImplementations(origMethod, altMethod);
}

The resulting array will have all the associated NSRunLoops.

Albrecht answered 27/5, 2012 at 18:55 Comment(6)
This is a clever idea and certainly worth a try!Soybean
@Leo Natan Thank you. When NSStreams are created, there is a specific sequence (per Apple's docs) for initialization including scheduling the NSStream in a particular run loop (and, by association, thread) - scheduleInRunLoop:forMode:. Apple's docs also say: "You should never attempt to access a scheduled stream from a thread different than the one owning the stream’s run loop." So, the challenge I am attempting to solve is whether or not there is a mechanism to interrogate the NSStream (or threads / run loops) to understand where the NSStream is scheduled?Quenelle
I have edited my answer, please take a look. Sorry it took so long.Jud
@LeoNatan I hadn't thought of going that route. Clever. I'm concerned about deploying something that involves method swizzling. :-)Quenelle
@LeoNatan Do you think that this is the only way? I am a little surprised that there isn't some kind of official API that would provide this kind of information.Quenelle
I can't say, as I have not worked with streams before. I suggest this as a final resort (but it seems, for the lack of answers, we are already at that stage). If you are able to test the app thoroughly, then there is little risk. If not, then you should do it with more care. :)Jud

© 2022 - 2024 — McMap. All rights reserved.