1

I'm trying to learn how to use the NSInputStream class on the iPhone using a unit test. I can get the NSStream to read data from a file using the polling method but for some reason the delegate/event method is not working.

I've posted the relevant code below. Please ignore memory leak errors and such since I'm just trying to ensure I know how to use the NSStream class in a sandboxed environment before rolling it into my larger project.

I'm wondering if maybe I'm missing something with regards to how the run loops work?

This is the logic test that creates a streamer class to read from a file.

#import "StreamingTests.h"
#import "Streamer.h"

@implementation StreamingTests

- (void) testStream {
    NSLog(@"Starting stream test.");
    Streamer * streamer = [[Streamer alloc] init];

    streamer.usePolling = NO;
    streamer.readingStream = YES;
    NSThread * readThread = [[NSThread alloc] initWithTarget:streamer selector:@selector(startStreamRead:) object:nil];
    [readThread start];
    while(streamer.readingStream) {
        [NSThread sleepForTimeInterval:0.5];
    }
    [readThread cancel];
}

@end

This is a simple test helper object that reads from an NSStream. When usePolling == YES it read data and outputs the appropriate NSLog messages. However, if usePolling == NO the delegate stream event function is never called.

@implementation Streamer
@synthesize readingStream, usePolling;

- (void) startStreamRead:(NSObject*) context {  
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSLog(@"starting stream read.");
    readingStream = YES;
    /*
     NSURL * url = [NSURL URLWithString:@"http://www.google.com"];
     NSLog(@"Loading: %@",[url description]);
     NSInputStream * inStream = [[NSInputStream alloc] initWithURL:url];
     */
    NSInputStream * inStream = [[NSInputStream alloc] initWithFileAtPath:@"sample.ttc"];

    if(!usePolling) {

        [inStream setDelegate: self];
        [inStream scheduleInRunLoop: [NSRunLoop currentRunLoop]
                            forMode: NSDefaultRunLoopMode];


    }
    [inStream open];

    if(usePolling) {
        while(1) {
            if([inStream hasBytesAvailable]) {
                uint8_t buf[1024];
                unsigned int len = 0;
                len = [(NSInputStream *)inStream read:buf maxLength:1024];
                NSLog(@"Read: %d",len);
            }
            NSStreamStatus status = [inStream streamStatus];
            if(status != NSStreamStatusOpen && status != NSStreamStatusOpening) {
                NSLog(@"Stream not open.");
                break;
            }
        }
        readingStream = NO;

        NSStreamStatus status = [inStream streamStatus];
        NSError * error = [inStream streamError];
        NSLog(@"Status: %d Error Desc: %@ Reason: %@",(int)status,[error localizedDescription], [error localizedFailureReason]);

        [pool release];
    }

}


- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
    NSMutableData * _data = nil;
    NSNumber * bytesRead = nil;
    NSLog(@"Event fired.");
    switch(eventCode) {
        case NSStreamEventHasBytesAvailable:
        {
            if(!_data) {
                _data = [[NSMutableData data] retain];
            }
            uint8_t buf[1024];
            unsigned int len = 0;
            len = [(NSInputStream *)stream read:buf maxLength:1024];
            if(len) {
                [_data appendBytes:(const void *)buf length:len];
                // bytesRead is an instance variable of type NSNumber.
                //[bytesRead setIntValue:[bytesRead intValue]+len];
                NSLog(@"Read %d bytes",len);
            } else {
                NSLog(@"no buffer!");
            }
            break;
        }
        case NSStreamEventEndEncountered:
        {
            [stream close];
            [stream removeFromRunLoop:[NSRunLoop currentRunLoop]
                              forMode:NSDefaultRunLoopMode];
            [stream release];
            stream = nil; // stream is ivar, so reinit it
            readingStream = NO;
            break;
        }
        default:
        {
            NSLog(@"Another event occurred.");
            break;
        }
            // continued ...
    }
}

@end

Thanks in advance,

b

Brian
  • 11
  • 3

1 Answers1

0

The reason for it should be that the run loop is blocked since the unit test is executing. You could refer to the NSRunLoop documentation where the method

runUntilDate:

might help you to run the main run loop in the thread of execution of the unit test like this:

[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];

This lets the run loop run for 1 second giving it time to process part of your file. It should be noted that this does not provide a reliable way for unit testing (since the time interval might differ depending on run loop size) and may then be unsuitable. By giving your unit an interface that could be used to check the status of the input stream read operation (with a reading finished state) such as

-(BOOL)hasFinishedReadingFile

the unit test could repeatedly execute the run loop until the above method returns TRUE and the file is read completely.

Addition: This question on stackoverflow also deals with the problem in a different way.

Community
  • 1
  • 1
malte
  • 190
  • 1
  • 10