I have an async method called getCount: which goes to a web URL, counts some stuff, and invokes a callback with the count when it's done.
I have another method which is synchronous and needs to take those results, puts them into a message, and returns that message. Here are the two together:
- (NSString *)describe {
__block bool gotCount = NO;
[self getCount:^(int count) {
NSLog(#"Got the count: %i", count);
_count = count; // _count is an ivar of the object with this method.
gotCount = YES;
}];
// Pause here until the count has been fetched.
while (!gotCount) {
[NSThread sleepForTimeInterval:0.05];
}
return [NSString stringWithFormat:#"The count is %i", _count];
}
My callback is never called when I try this. It never prints
Got the count 0
or any other value for count in this scenario.
If I comment out the while loop, that message does get printed out. So I know that the getCount: method works, there's just something wrong with my loop waiting for it to arrive.
I need getCount: to remain asynchronous (there's other places it gets used where that's more important) and I need describe to remain synchronous. How can I handle this?
one possible thing: if your describe method is in the main thread then you call getCount method also from the main thread and all web callbacks are in the main thread. BUT you block the main thread with the thread sleep -> you can not get call back from the web to get a count.
Edited:
try to call getCount method from another thread. use e.g.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self getCount:^(int count) {
NSLog(#"Got the count: %i", count);
_count = count; // _count is an ivar of the object with this method.
gotCount = YES;
}];
});
Edited 2:
I tried this code and it works fine -> something probably wrong with the threads in your getCount method.
- (NSString *)describe {
__block bool gotCount = NO;
__block NSInteger _count;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:5.00];
_count = 5;
gotCount = YES;
});
// Pause here until the count has been fetched.
while (!gotCount) {
[NSThread sleepForTimeInterval:0.05];
}
return [NSString stringWithFormat:#"The count is %li", _count];
}
a way that would work but is QUITE the hack (which apple employed in older APIs on the mac all the time) would be to run the runloop while waiting:
note: this relies on the callback being on the same queue as the describe method
see: JSON async request [SAME ISSUE]
a self contained WORKING example:
#import <Foundation/Foundation.h>
#interface T : NSObject
- (NSString *)describe;
#end
#implementation T {
int _count;
}
- (void)getCount:(void (^)(int c)) handler {
dispatch_async(dispatch_get_global_queue(0,0), ^ {
sleep(5);
dispatch_sync(dispatch_get_main_queue(), ^{
handler(55);
});
});
}
- (NSString *)describe {
__block bool gotCount = NO;
[self getCount:^(int count) {
NSLog(#"Got the count: %i", count);
_count = count; // _count is an ivar of the object with this method.
gotCount = YES;
}];
// Pause here until the count has been fetched.
while (!gotCount) {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
return [NSString stringWithFormat:#"The count is %i", _count];
}
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
T *t = [T new];
NSLog(#"describe: %#", [t describe]);
}
}
Related
I am trying to schedule a GNUStep Objective-C method call to run every second for a variable number of seconds. I am trying to use NSTimer to schedule the method call, but the handler method never gets called.
Here is my code:
Timer.m:
- (id) init {
self = [super init];
if(self) {
_ticks = 0;
}
return self;
}
- (void) startWithTicks: (unsigned int) ticks {
_ticks = ticks; //_ticks is an unsigned int instance variable
if(_ticks > 0) {
[NSTimer scheduledTimerWithTimeInterval: 1.0
target: self
selector: #selector(onTick:)
userInfo: nil
repeats: YES];
}
}
- (void) onTick: (NSTimer*) timer {
NSLog(#"tick");
_ticks--;
if(_ticks == 0) {
[timer invalidate];
timer = nil;
}
}
main.m:
int main(int argc, const char* argv[]) {
Timer* t = [[Timer alloc] init];
NSLog(#"Setting timer");
[t startWithTicks: 3];
usleep(5000);
NSLog(#"End of timer");
return 0;
}
I would expect the output to be
Setting timer
tick
tick
tick
End of timer
However, the output is
Setting timer
End of timer
Why is this and how can I fix it?
The timer won't run while your thread is sleeping.
Your timer class code works fine if you're using it from a ViewController.
If instead you'd like to use it within the main method, you'll want to explicitly run the mainRunLoop. Try adjusting your main method to this:
int main(int argc, char * argv[]) {
Timer *timer = [[Timer alloc] init];
NSLog(#"Setting Timer");
[timer startWithTicks:3];
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
NSLog(#"End of Timer");
return 0;
}
to run the mainRunLoop running for 3 seconds, which should produce your desired output.
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
https://developer.apple.com/documentation/foundation/nsrunloop
I have several methods that have the following structure:
- (void) doSomethingWithCompletion: (void (^)(NSError *error)) completion {
__block NSError *fetchError = nil;
dispatch_group_t dispatchGroup = dispatch_group_create();
for (Item* item in self.items)
{
dispatch_group_enter(dispatchGroup);
// fetchError = fetch online data
}
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(),^{
if (completion)
completion(fetchError);
});
}
My goal is to run several doSomethings after each other, so I could so something like this:
[self doSomethingAWithCompletion: ^(NSArray *results NSError *error) {
if (error == nil) {
[self doSomethingBWithArray: results withCompletion: ^(NSError *error) {
if (error == nil) {
[self doSomethingCWithCompletion: ^(NSError *error) {
if (error == nil) {
// done!!
}
}];
}];
}];
What I am struggling with is the second code block (no pun); is nesting all the methods the way to go, or are there other solutions?
The important thing is, is that doSomethingBWithCompletion cannot begin before doSomethingAWithCompletion is done, and doSomethingCWithCompletion needs to wait until doSomethingBWithCompletion is complete, etc.
Also, doSomethingBWithCompletion uses data that is generated in doSomethingAWithCompletion, etc.
EDIT: After a lot of thinking, refactoring, and simplifying my code, I was able to end up with only two functions, using the nested approach as I outlined above and with a #property for the results array.
The important thing is, is that doSomethingBWithCompletion cannot begin before doSomethingAWithCompletion is done, and doSomethingCWithCompletion needs to wait until doSomethingBWithCompletion is complete, etc.
According to the comments:
The Results of the block are not depending on the result of the first aren't they?
And
Yes they are. For instance, in the first doSomething I determine which items are outdated, in the second doSomething I download and parse the updated items, and in the third doSomething I save them to the store.
(BTW: You should really add this information to your Q.)
If an action depends on the result (not only execution) of a previous action, you have to nest the blocks. Your code does not look like this, because there is no data passed to the completion blocks.
If you do not have such a dependency, you could use a private serial dispatch queue. However, this is a solution in your case, too, if you have akin of a manager class holding the data passed from block to block. But this seems to be highly anticonceptual.
There may be community attempt to add promises to objective-c, and it would be nice to have, because that's just what's needed here. Without committing to a whole new library, you can handle the nesting (which I agree is a bummer) by doing the async tasks recursively... something like this for your example code:
Start with an operation that takes no params and results in an array...
- (void)firstOpWithCompletion:(void (^)(NSArray *, NSError *))completion {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSArray *components = [#"this is an array of strings from the FIRST op" componentsSeparatedByString:#" "];
if (completion) {
completion(components, nil);
}
});
}
Here are a couple that take an array param and result in an array...
- (void)secondOpWithParam:(NSArray *)array completion:(void (^)(NSArray *, NSError *))completion {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
if (completion) {
NSArray *components = [#"these strings are from the SECOND op" componentsSeparatedByString:#" "];
NSArray *result = [array arrayByAddingObjectsFromArray:components];
if (completion) {
completion(result, nil);
}
}
});
}
- (void)thirdOpWithParam:(NSArray *)array completion:(void (^)(NSArray *, NSError *))completion {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
if (completion) {
NSArray *components = [#"these strings are from the THIRD op" componentsSeparatedByString:#" "];
NSArray *result = [array arrayByAddingObjectsFromArray:components];
if (completion) {
NSLog(#"we did it. returning %#", result);
completion(result, nil);
}
}
});
}
// ...as many as these as you need
Now, as in my answer prior to this edit, we just add a param pass initially and in the intermediate calls...
- (void)doSeveralThingsInSequence:(NSArray *)todo param:(NSArray *)param {
if (todo.count == 0) return;
// you could generalize further here, by passing a "final" block and run that before the return
NSString *nextTodo = todo[0];
SEL sel = NSSelectorFromString(nextTodo);
IMP imp = [self methodForSelector:sel];
void (*func)(id, SEL, NSArray *, void (^)(NSArray *, NSError *)) = (void *)imp;
func(self, sel, param, ^(NSArray *result, NSError *error) {
if (!error) {
NSArray *remainingTodo = [todo subarrayWithRange:NSMakeRange(1, todo.count-1)];
[self doSeveralThingsInSequence:remainingTodo param:result];
}
});
}
Stepping through the code: this method bails if there's nothing to do, otherwise it takes the next selector name from the passed array, gets the C function implementation for it and invokes it, placing a completion block on the call stack that starts the process over for the remaining selectors.
Finally, doEverything calls the first operation to get started, then starts running a list of operations (which can be an arbitrarily long list) passing the array output from one as the array input to the next. (You could generalize this further by passing id's along the chain
- (void)doEverything {
[self firstOpWithCompletion:^(NSArray *array, NSError *error) {
NSArray *todo = #[ #"secondOpWithParam:completion:", #"thirdOpWithParam:completion:" ];
[self doSeveralThingsInSequence:todo param:array];
}];
}
I tested this exactly as posted and saw the expected output:
(
this,
is,
an,
array,
of,
strings,
from,
the,
FIRST,
op,
these,
strings,
are,
from,
the,
SECOND,
op,
these,
strings,
are,
from,
the,
THIRD,
op
)
I have some recursive block code in objective-c that is causing a EXC_BAD_ACCESS error.
- (void) doSomethingWithCompletion:(void (^)())completion {
if (completion) {
dispatch_async(dispatch_get_main_queue(), completion);
}
}
- (void) testBlocks {
NSString *testString = #"hello";
__block NSInteger count = 0;
__block __weak void (^weak_block)(NSString *);
void(^strong_block)(NSString *);
weak_block = strong_block = ^(NSString *str) {
[self doSomethingWithCompletion:^{
NSLog(#"number: %zd", count);
if (++count < 10) {
weak_block(str);
}
}];
};
strong_block(testString);
}
The error happens on weak_block(str) which i assume is because it is released when dispatch_async is called. Calling strong_block(str) in it's place when it's declared with __block like so:
__block void(^strong_block)(NSString *);
Causes a warning 'Capturing 'strong_block' strongly in this block is likely to lead to a retain cycle'.
So I changed the testBlock method to not use a weak reference like so:
- (void) testBlocks {
NSString *testString = #"hello";
__block NSInteger count = 0;
__block void (^inner_block)(NSString *);
void(^strong_block)(NSString *);
inner_block = strong_block = ^(NSString *str) {
[self doSomethingWithCompletion:^{
NSLog(#"number: %zd", count);
if (++count < 10) {
inner_block(str);
}
}];
};
strong_block(testString);
}
But I am unsure if this causes a retain cycle or if adding
__block void (^inner_block)(NSString *) = weak_block;
inside the block instead would cause a retain cycle as well. What is the correct way to handle this situation?
It crashes because the block (pointed to by weak_block, strong_block) has already been deallocated by the time the "completion" block runs, and calling a block with a nil block pointer crashes.
The block is deallocated because there are no strong references to it after testBlocks returns.
The second one will have a retain cycle because the block captures inner_block, which holds a strong reference to itself.
The proper way is to make a strong reference from the captured weak reference inside the block, and let the completion block capture that:
- (void) testBlocks {
NSString *testString = #"hello";
__block NSInteger count = 0;
__block __weak void (^weak_block)(NSString *);
void(^strong_block)(NSString *);
weak_block = strong_block = ^(NSString *str) {
void(^inner_block)(NSString *) = weak_block;
[self doSomethingWithCompletion:^{
NSLog(#"number: %zd", count);
if (++count < 10) {
inner_block(str);
}
}];
};
strong_block(testString);
}
Not sure this is proof of either possibility but if you add a weak block property and examine that one after all block stuff has run...
...
#property (weak) void (^true_weak_block)(NSString *);
#property (weak) NSString *weak_string;
...
- (void) testBlocks {
NSString *strong_string = [NSString stringWithFormat:#"%#", #"some string"]; // note that you can not use a string literal in this example..
self.weak_string = strong_string;
NSString *testString = #"hello";
__block NSInteger count = 0;
__block void (^inner_block)(NSString *);
void(^strong_block)(NSString *);
inner_block = strong_block = ^(NSString *str) {
[self doSomethingWithCompletion:^{
NSLog(#"number: %zd", count);
if (++count < 10) {
inner_block(str);
}
}];
};
self.true_week_block = strong_block;
[self test];
strong_block(testString);
}
- (void)test {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(#"%#", self.true_week_block); // not deallocated
NSLog(#"%#", self.weak_string); // deallocated
});
}
In my test the block is never deallocated and also the memory address remains the same over time, even if you change regular assignment of the two strong blocks to use copy instead of the implicitly retained assignment.
I've been investigating NSProgress but have found the existing documentation, class reference and tutorials to be lacking. I'm mainly wondering if my NSProgress is applicable to my use case. The class reference documentation alternatively refers to suboperations or subtasks, I may be mistaken but I interpreted suboperations to mean a case where an NSOperation manages a group of other NSOperations. An example of my use case is as follows:
Create an Upload All Items in Group operation for each group that exists.
Add each of these operations to an NSOperationQueue.
Each Upload All Items in Group operation will create an Upload Item operation for each item in their group. These all get added to an NSOperationQueue managed by the operation.
I would have expected NSProgress to support this, and allow me to propagate progress from the nested operations (Upload Item operation) to the parent operation, and then finally to the main thread and the UI. But I've had difficulty implementing this, it seems as though NSProgress is meant more for long operations that execute all their code on one background thread, but have separate "sections" that make it easy to determine when progress has been made, if this is the case then the use of the term suboperation is a bit misleading as it brings to mind the use of nested NSOperations.
Thank you for any help you can provide, and let me know if additional details are needed.
NSProgress knows nothing about NSOperations -- the two things are orthogonal -- but that doesn't mean it can't be used with them. The idea behind nesting NSProgress "tasks" is that the inner task doesn't know anything about the outer task, and the outer task doesn't need direct access to the inner task's NSProgress to pull in updates for it. I cooked up a little example:
// Outer grouping
NSProgress* DownloadGroupsOfFiles(NSUInteger numGroups, NSUInteger filesPerGroup)
{
// This is the top level NSProgress object
NSProgress* p = [NSProgress progressWithTotalUnitCount: numGroups];
for (NSUInteger i = 0; i < numGroups; ++i)
{
// Whatever DownloadFiles does, it's worth "1 unit" to us.
[p becomeCurrentWithPendingUnitCount: 1];
DownloadFiles(filesPerGroup);
[p resignCurrent];
}
return p;
}
// Inner grouping
void DownloadFiles(NSUInteger numberOfFiles)
{
NSProgress* p = [NSProgress progressWithTotalUnitCount: numberOfFiles];
NSOperationQueue* opQueue = [[NSOperationQueue alloc] init];
// Make the op queue last as long as the NSProgress
objc_setAssociatedObject(p, NULL, opQueue, OBJC_ASSOCIATION_RETAIN);
// For each file...
for (NSUInteger i = 0; i < numberOfFiles; ++i)
{
// Whatever this DownloadOperation does is worth 1 "unit" to us.
[p becomeCurrentWithPendingUnitCount: 1];
// Make the new operation
MyDownloadOperation* op = [[MyDownloadOperation alloc] initWithName: [NSString stringWithFormat: #"File #%#", #(i+1)]];
[opQueue addOperation: op];
[p resignCurrent];
}
}
// And then the DownloadOperation might look like this...
#interface MyDownloadOperation : NSOperation
#property (nonatomic, readonly, copy) NSString* name;
- (id)initWithName: (NSString*)name;
#end
#implementation MyDownloadOperation
{
NSProgress* _progress;
NSString* _name;
}
- (id)initWithName:(NSString *)name
{
if (self = [super init])
{
_name = [name copy];
// Do this in init, so that our NSProgress instance is parented to the current one in the thread that created the operation
_progress = [NSProgress progressWithTotalUnitCount: 1];
}
return self;
}
- (void)dealloc
{
_name = nil;
_progress = nil;
}
- (void)main
{
// Fake like we're doing something that takes some time
// Determine fake size -- call it 768K +- 256K
const NSUInteger size = 512 * 1024 + arc4random_uniform(512*1024);
const NSUInteger avgBytesPerSec = 1024 * 1024;
const NSTimeInterval updatePeriod = 1.0/60.0;
// Make sure all the updates to the NSProgress happen on the main thread
// in case someone is bound to it.
dispatch_async(dispatch_get_main_queue(), ^{
_progress.totalUnitCount = size;
_progress.completedUnitCount = 0;
});
NSUInteger bytesRxd = 0;
do
{
// Sleep for a bit...
usleep(USEC_PER_SEC * updatePeriod);
// "Receive some data"
NSUInteger rxdThisTime = updatePeriod * avgBytesPerSec;
// Never report more than all the bytes
bytesRxd = MIN(bytesRxd + rxdThisTime, size);
// Update on the main thread...
dispatch_async(dispatch_get_main_queue(), ^{
[_progress setCompletedUnitCount: bytesRxd];
});
} while (bytesRxd < size);
}
#end
One thing to note is that if NSProgress is being used to convey status to the UI, then you will want to make sure that every time you update the NSProgress object, you do so from the main thread, otherwise you'll get lots of weird crashes.
Alternately you could just use NSURLConnection to download files, and then have a delegate like this:
#interface MyURLConnectionProgressReporter : NSObject <NSURLConnectionDownloadDelegate>
#property (nonatomic, readwrite, assign) id<NSURLConnectionDownloadDelegate> delegate;
#end
NSProgress* DownloadABunchOfFiles(NSArray* arrayOfURLs)
{
arrayOfURLs = arrayOfURLs.count ? arrayOfURLs : #[ [NSURL URLWithString: #"http://www.google.com"] ];
NSProgress* p = [NSProgress progressWithTotalUnitCount: arrayOfURLs.count];
for (NSURL* url in arrayOfURLs)
{
[p becomeCurrentWithPendingUnitCount: 1];
MyURLConnectionProgressReporter* delegate = [[MyURLConnectionProgressReporter alloc] init];
NSURLConnection* conn = [[NSURLConnection alloc] initWithRequest: [NSURLRequest requestWithURL: url] delegate: delegate];
[conn start];
[p resignCurrent];
}
return p;
}
#implementation MyURLConnectionProgressReporter
{
NSProgress* _progress;
}
static void EnsureMainThread(dispatch_block_t block);
- (id)init
{
if (self = [super init])
{
_progress = [NSProgress progressWithTotalUnitCount: 1];
EnsureMainThread(^{
_progress.kind = NSProgressKindFile;
[_progress setUserInfoObject:NSProgressFileOperationKindDownloading forKey:NSProgressFileOperationKindKey];
});
}
return self;
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
id retVal = [super forwardingTargetForSelector:aSelector];
if (!retVal && [self.delegate respondsToSelector: _cmd])
{
retVal = self.delegate;
}
return retVal;
}
- (void)p_updateWithTotalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long) expectedTotalBytes
{
// Update our progress on the main thread...
EnsureMainThread(^{
if (!expectedTotalBytes)
_progress.totalUnitCount = -1;
else
_progress.totalUnitCount = MAX(_progress.totalUnitCount, expectedTotalBytes);
_progress.completedUnitCount = totalBytesWritten;
});
}
- (void)connection:(NSURLConnection *)connection didWriteData:(long long)bytesWritten totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long) expectedTotalBytes
{
// Update our progress
[self p_updateWithTotalBytesWritten: totalBytesWritten expectedTotalBytes: expectedTotalBytes];
// Then call on through to the other delegate
if ([self.delegate respondsToSelector: _cmd])
{
[self.delegate connection:connection didWriteData:bytesWritten totalBytesWritten:totalBytesWritten expectedTotalBytes:expectedTotalBytes];
}
}
- (void)connectionDidResumeDownloading:(NSURLConnection *)connection totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long) expectedTotalBytes
{
// Update our progress
[self p_updateWithTotalBytesWritten: totalBytesWritten expectedTotalBytes: expectedTotalBytes];
// Then call on through to the other delegate
if ([self.delegate respondsToSelector: _cmd])
{
[self.delegate connectionDidResumeDownloading:connection totalBytesWritten:totalBytesWritten expectedTotalBytes:expectedTotalBytes];
}
}
- (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *) destinationURL
{
// We're done, so we want (_progress.completedUnitCount == _progress.totalUnitCount)
EnsureMainThread(^{
_progress.completedUnitCount = _progress.totalUnitCount;
});
if ([self.delegate respondsToSelector: _cmd])
{
[self.delegate connectionDidFinishDownloading:connection destinationURL:destinationURL];
}
}
static void EnsureMainThread(dispatch_block_t block)
{
if (!block)
return;
else if ([NSThread isMainThread])
block();
else
dispatch_async(dispatch_get_main_queue(), block);
}
#end
Hope that helps.
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
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.