When the triangled shape doesn’t fit in the round hole

I’m writing some iPhony stuff while thinking in Java. I wanna stream an HTTP request over the wire. In Java I’d inject an OutputStream into my class or method and just start writing to it byte by byte. Objective C Cocoa wants to set up RunLoops, setup delegates, and rely on callbacks that pass buffers into the NSOutputStream API. I set on fire and jumped through all of these hoops (after dousing my briefs in Kerosene) but I’m still not getting any data on my stream! Is it because I’m trying to force things to work like Java? Help?

Here’s my test method and impl logic:

-(void) testStreamWriting {
	NSOutputStream *stream = [[NSOutputStream alloc] initToMemory];
	[stream setDelegate:myStreamDelegate];
	[stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
	[stream open];
	NSData *_data = [stream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
	STAssertNotNil(_data, @"Stream delegate should have written data");
	[stream close];
	[stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
	[stream release];
	[data release];

@implementation MyStreamDelegate
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
	switch (streamEvent) {
		case NSStreamEventHasSpaceAvailable:
			const uint8_t stuff[2] = {1,2};
			[theStream write:stuff maxLength:2];


7 thoughts on “When the triangled shape doesn’t fit in the round hole

  1. Just looking at your code, it looks like you’re closing it (and removing it from the RunLoop) instantly after opening it; you should be closing it in your handleEvent: delegate method on the NSStreamStatusAtEnd event (or whenever it makes sense to close it). right now, you’re opening and instantly closing the stream; handleEvent: is never getting called because the stream is closed before the App can get to the end of the RunLoop.

    Leaving aside the delegate, are you able to write to the NSOutputStream using the synchronous calls? You only have to use the whole handleEvent: thang if you are trying to do asynchronous writes in the RunLoop (which I admit is a little mysterious if you aren’t used to it.) If so, just wrap your write operation in an NSOperation and use NSOperationQueue to do the data moving asynchronously.

  2. I would also add that I don’t think you get RunLoops for free inside of a SenTest test rig unless you set it up yourself (you’re on your own on that one).

  3. Thanx Jamie! I had a beer last night while writing and that skewed my vision. I thought was over complicating things. I’m going strictly off of the Stream programming guide which doesn’t give any code examples other than using run loops. (I need a really good Cocoa/ObjC book if anybody can recommend.) It almost seemed like you have to use run loops or some sort of callback which really threw me. I don’t know why the docs don’t give a simple example of just writing “Hell Miami” to an output stream and reading it back off of the stream. Like I said, I could knock this out blindfolded and undersea using the unadvertised free Netzero dialup service on a cheap Acer notebook with limited water resistance as long as there were a JVM inside. I just feel out of my element.

  4. The way I read the docs I was assuming the open would instantly jump into the RunLoop, which would repeatedly send events to the stream delegate until it somehow knew to stop writing. (I couldn’t figure out how it would know since the delegate returns void, I just assumed that an event call without a write would send the stop signal.) Clearly my colorful imagination took over and sent me east bound on I-95. So now I need to firm my understanding of run loops.

  5. The Hildegass book is the best, but to my consternation he doesn’t talk about runloops in it very often.

    The best way to think of NSRunLoop is a kind of Notification Center that receives events from outside the process (NSEvents mostly, but network and other host events too) and sends them to particular object/selector combinations inside your process. A Cocoa app when running is just a big while loop that blocks until an event is received, decides which object should receive it, and sends it. NSEvents are sent to their targets through the responder chain. When you wnat to register to receive information about aynchronous stream events, essentially what you’re doing is setting up the stream, telling the stream which object to blab to about its status (its delegate), and telling the NSRunLoop to keep an eye on it. Every spin through the runloop now, at the top of the while {} your app will now wait for NSEvents AND any change of status on your NSStream — I don’t know how exactly it does this, I think it tests for != StreamEventNone through a private API. If there’s an event, the run loop then detours its execution through the your delegate. Once you’re delegate is done, the RunLoop jumps back up to the top of the while, waiting for the next event.
    Clearly the reason they did this is because it lets you handle a bunch of different stuff at once inside an NSApp while keeping everything single-threaded, which was probably a big win in the early 1990s — you never have to deal with locks or race conditions or yucky memory management issues. Nowadays it’d make more sense (from an ease of programming standpoint) to build your IO-bound job as a lambda inside an NSOperation and fire it off inside an operation queue, but that would cause a bunch of context switching on a uniprocessor arch, like say, the iPhone.

  6. Off da’ chains! Now Run Loops are starting to make sense to me. Thanx for that well thought out articulation, cause the topic had me all hemmed up. So after my code releases control in of the main thread (as the result of responding to an event) the run loop continues to process events. Scheduling a stream on the main run loop introduces it as a candidate for control along the iteration as stream event are introduced. Now I’ll go look up Hildegass…

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s