NSOperation + setCompletionBlock - ios

I have few different questions about NSOperation and NSOperationQueue and I know guys that yours answers will help me;
I have to load a big amount of images and I have created my own loader based on NSOperation, NSOperationQueue and NSURLConnection (asynchronous loading);
Questions:
If I set maxConcurrentOperationCount (for example 3) for queue (NSOperationQueue), does it mean that only 3 operations performed in the same time even queue has 100 operations?
When I set property maxConcurrentOperationCount for queue sometimes "setCompletionBlock" doesn't work and count (operationCount) only increases; Why?
MyLoader:
- (id)init
{
self = [super init];
if (self) {
_loadingFiles = [NSMutableDictionary new];
_downloadQueue = [NSOperationQueue new];
_downloadQueue.maxConcurrentOperationCount = 3;
_downloadQueue.name = #"LOADER QUEUE";
}
return self;
}
- (void)loadFile:(NSString *)fileServerUrl handler:(GetFileDataHandler)handler {
if (fileServerUrl.length == 0) {
return;
}
if ([_loadingFiles objectForKey:fileServerUrl] == nil) {
[_loadingFiles setObject:fileServerUrl forKey:fileServerUrl];
__weak NSMutableDictionary *_loadingFiles_ = _loadingFiles;
MyLoadOperation *operation = [MyLoadOperation new];
[operation fileServerUrl:fileServerUrl handler:^(NSData *fileData) {
[_loadingFiles_ removeObjectForKey:fileServerUrl];
if (fileData != nil) {
handler(fileData);
}
}];
[operation setQueuePriority:NSOperationQueuePriorityLow];
[_downloadQueue addOperation:operation];
__weak NSOperationQueue *_downloadQueue_ = _downloadQueue;
[operation setCompletionBlock:^{
NSLog(#"completion block :%i", _downloadQueue_.operationCount);
}];
}
}
MyOperation:
#interface MyLoadOperation()
#property (nonatomic, assign, getter=isOperationStarted) BOOL operationStarted;
#property(nonatomic, strong)NSString *fileServerUrl;
#property(nonatomic, copy)void (^OnFinishLoading)(NSData *);
#end
#implementation MyLoadOperation
- (id)init
{
self = [super init];
if (self) {
_executing = NO;
_finished = NO;
}
return self;
}
- (void)fileServerUrl:(NSString *)fileServerUrl
handler:(void(^)(NSData *))handler {
#autoreleasepool {
self.fileServerUrl = fileServerUrl;
[self setOnFinishLoading:^(NSData *loadData) {
handler(loadData);
}];
[self setOnFailedLoading:^{
handler(nil);
}];
self.url = [[NSURL alloc] initWithString:self.fileServerUrl];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
initWithURL:self.url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:25];
[request setValue:#"" forHTTPHeaderField:#"Accept-Encoding"];
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[self.connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[self.connection start];
_data = [[NSMutableData alloc] init];
}
}
- (void)main {
#autoreleasepool {
[self stop];
}
}
- (void)start {
[self setOperationStarted:YES];
[self willChangeValueForKey:#"isFinished"];
_finished = NO;
[self didChangeValueForKey:#"isFinished"];
if ([self isCancelled])
{
[self willChangeValueForKey:#"isFinished"];
_finished = YES;
_executing = NO;
[self didChangeValueForKey:#"isFinished"];
}
else
{
[self willChangeValueForKey:#"isExecuting"];
_finished = NO;
_executing = YES;
[self didChangeValueForKey:#"isExecuting"];
}
}
- (BOOL)isConcurrent {
return YES;
}
- (BOOL)isExecuting {
return _executing;
}
- (BOOL)isFinished {
return _finished;
}
- (void)cancel {
[self.connection cancel];
if ([self isExecuting])
{
[self stop];
}
[super cancel];
}
#pragma mark -NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[_data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if ([self OnFinishLoading]) {
[self OnFinishLoading](_data);
}
if (![self isCancelled]) {
[self stop];
}
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
;
if (![self isCancelled]) {
[self stop];
}
}
- (void)stop {
#try {
__weak MyLoadOperation *self_ = self;
dispatch_async(dispatch_get_main_queue(), ^{
[self_ completeOperation];
});
}
#catch (NSException *exception) {
NSLog(#"Exception! %#", exception);
[self completeOperation];
}
}
- (void)completeOperation {
if (![self isOperationStarted]) return;
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
_executing = NO;
_finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}

You must start the connection in the Operation's start method, and not in fileServerUrl:handler:.
I would remove this method altogether, and only provide an init method with all required parameters where you can completely setup the operation. Then, in method start start the connection.
Additionally, it's not clear why you override main.
Modifying the state variables _executing and _finished could be more concise and more clear (you don't need to set them initially, since the are already initialized to NO). Only set them in the "final" method completeOperation including KVO notifications.
You also do not need a #try/#catch in stop, since function dispatch_async() does not throw Objective-C exceptions.
Your cancel method is not thread safe, and there are also a few other issues. I would suggest the following changes:
#implementation MyOperation {
BOOL _executing;
BOOL _finished;
NSError* _error; // remember the error
id _result; // the "result" of the connection, unless failed
completion_block_t _completionHandler; //(your own completion handler)
id _self; // strong reference to self
}
// Use the "main thread" as the "synchronization queue"
- (void) start
{
// Ensure start will be called only *once*:
dispatch_async(dispatch_get_main_queue(), ^{
if (!self.isCancelled && !_finished && !_executing) {
[self willChangeValueForKey:#"isExecuting"];
_executing = YES;
[self didChangeValueForKey:#"isExecuting"];
_self = self; // keep a strong reference to self in order to make
// the operation "immortal for the duration of the task
// Setup connection:
...
[self.connection start];
}
});
}
- (void) cancel
{
dispatch_async(dispatch_get_main_queue, ^{
[super cancel];
[self.connection cancel];
if (!_finished && !_executing) {
// if the op has been cancelled before we started the connection
// ensure the op will be orderly terminated:
self.error = [[NSError alloc] initWithDomain:#"MyOperation"
code:-1000
userInfo:#{NSLocalizedDescriptionKey: #"cancelled"}];
[self completeOperation];
}
});
}
- (void)completeOperation
{
[self willChangeValueForKey:#"isExecuting"];
self.isExecuting = NO;
[self didChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
self.isFinished = YES;
[self didChangeValueForKey:#"isFinished"];
completion_block_t completionHandler = _completionHandler;
_completionHandler = nil;
id result = self.result;
NSError* error = self.error;
_self = nil;
if (completionHandler) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
completionHandler(result, error);
});
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if ([self onFinishLoading]) {
[self onFinishLoading](self.result);
}
[self completeOperation];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
if (self.error == nil) {
self.error = error;
}
[self completeOperation];
}

In answer to your questions:
Yes, a maxConcurrentOperationCount of three means that only three will run at a time. Doing network requests like this is perfect example of when you'd want to use maxConcurrentOperationCount, because failure to do so would result in too many network requests trying to run, most likely resulting in some of the connections failing when using a slower network connection.
The main issue here, though, is that you're calling your operation's fileServerUrl method (which is starting the connection) from MyLoader. You've disconnected the request from the operation's start (defeating the purpose of maxConcurrentCount of 3 and possibly confusing the state of the operation).
The start method should be initiating the connection (i.e. don't start the request until one of those three available concurrent operations is available). Furthermore, since you cannot pass the URL and the handler to the start method, you should move your logic that saves those values to a customized rendition of your init method.
There are other minor edits we might suggest to your operation (main not needed, operationStarted is a little redundant, simplify the _executing/_finished handling, etc.), but the starting of the connection in fileServerUrl rather than being initiated by the start method is the key issue.
Thus:
- (id)initWithServerUrl:(NSString *)fileServerUrl
handler:(void(^)(NSData *))handler
{
self = [super init];
if (self) {
_executing = NO;
_finished = NO;
// do your saving of `fileServerURL` and `handler` here, e.g.
self.fileServerUrl = fileServerUrl;
self.OnFinishLoading:^(NSData *loadData) {
handler(loadData);
}];
[self setOnFailedLoading:^{
handler(nil);
}];
}
return self;
}
- (void)startRequest {
self.url = [[NSURL alloc] initWithString:self.fileServerUrl];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:self.url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:25];
[request setValue:#"" forHTTPHeaderField:#"Accept-Encoding"];
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[self.connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
[self.connection start];
_data = [[NSMutableData alloc] init];
}
- (void)start {
if ([self isCancelled])
{
[self willChangeValueForKey:#"isFinished"];
_finished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
[self setOperationStarted:YES]; // personally, I'd retire this and just reference your `executing` flag, but I'll keep it here for compatibility with the rest of your code
[self willChangeValueForKey:#"isExecuting"];
_executing = YES;
[self didChangeValueForKey:#"isExecuting"];
[self startRequest];
}

For the first question, the answer is yes, if set 3 as a max number of operations, only 3 can be running togheter.
The second is bit strange problem and I'm not totally sure that this answer will be correct. When you leave operations to an NSOperationQueue, you can't be sure on which thread they will be executed, this lead a huge problem with async connection. When you start an NSURLConnection as usual you receive the delegate callbacks without a problem, that is because the connection is running on a thread with a living run loop. If you start the connection on a secondary thread, callbacks will be called on that thread, but if you don't keep the run loop alive they will be never received.That's where probably my answer isn't correct, GCD should take care of living run loops, because GCD queues runs on living threads. But if not, the problem could be that operations are started on a different thread, the start method is called, but the callbacks are never called. Try to check if the thread is always the main thread.

Related

Waiting for Network Connection To Complete before proceeding with Func?

I have a resume function that reestablishes a network connection before it gathers updated parameters for the app.
Issue is that it seems to be trying to get some parameters before the network link has been reestablished.
Is there a way i can pause until the connection is made?
The getParameters func is on a background thread using:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(void) { //code }
The initConnection is primary thread
Here is the func:
- (void)screenUnlocked {
[self initConnection];
CDCChannelCollectionView *scrollView;
if ([self channelView]) {
scrollView = [[self channelView] scrollView];
for (CDCChannelStrip* strip in [scrollView visibleCells]) {
[self getParameters:[NSNumber numberWithInteger:strip.channelNumber]];
}
}
}
edit:
Something like this?
- (void)initConnection: completion:(void (^)(void))completionBlock {
if (![self isDemo]) {
[self setTcpControl:[[CDCControl alloc] initWithAddress:[self IPAddress] usingProtocol:TCP]];
[[self tcpControl] setDelegate:self];
[self setUdpControl:[[CDCControl alloc] initWithAddress:[self IPAddress] usingProtocol:UDP]];
[[self udpControl] setDelegate:self];
if (successful) {
completionBlock();
}
}
}
For block syntax you can follow:
//Block funtion with void block return type.
- (void)initConnection:(void(^)(void))completionBlock {
if (![self isDemo]) {
[self setTcpControl:[[CDCControl alloc] initWithAddress:[self IPAddress] usingProtocol:TCP]];
[[self tcpControl] setDelegate:self];
[self setUdpControl:[[CDCControl alloc] initWithAddress:[self IPAddress] usingProtocol:UDP]];
[[self udpControl] setDelegate:self];
if (successful) {
//call completion when connection is made.
completionBlock();
}
}
}
calling function as:
//Calling initConnection funtion.
[self initConnection:^{
NSLog(#"complition success");
}];

Is it acceptable to daisy NSOperation main into super?

- (void)main
{
IDBAssert0(self.bestCapture.webpCandidate);
self.finished = NO;
self.executing = YES;
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
UIImage *possiblycorrupted = [UIImage imageWithWebPData:self.bestCapture.webpCandidate];
NSTimeInterval webpInterval = [NSDate timeIntervalSinceReferenceDate]-now;
NSDLog(#"it tooke %.2f sec to unpack webp", webpInterval);
self.microblinkCandidate = possiblycorrupted; // data superclass nsoperation processes
[super main];
}
first thing main in the base class does naturally is setting finished to no and executing to yes:
- (void)main
{
self.finished = NO;
self.executing = YES;
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
start = now;
CGSize size = [self.microblinkCandidate size];
IDBAssert0(size.width && size.height);
IDBAssert0(self.microblink);
// this starts async processing
[self.microblink processImage:self.microblinkCandidate
scanningRegion:CGRectMake(0.0, 0.0, 1.0, 1.0)
delegate:self];
while (![self isCancelled])
{
sleep(1);
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
if(now - start > 5) {
// #5677 microblink watchdog to detect hangs
[self cancel];
break;
}
}
[self done];
}
cause it's not an abstract and will be used on its own as well.
the loop is for debug/watchdog purposes only
in the normal operation it's not tripped an operation is done
if this callback:
- (void)scanningViewController: (UIViewController<PPScanningViewController>*)scanningViewController
didOutputResults:(NSArray*)results
{
if([results count]>0) {
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
NSDLog(#"found barcode in %.1fs", now - start);
self.microblinkSuccessHandler();
}else{
IDBAssert0(self.microblinkFailureHandler);
self.microblinkFailureHandler();
}
[self done];
}
is invoked when "processImage:" will have finished (in a timely fashion).
the very base class is
#implementation IDBAsynchronousOperation
#synthesize executing = _executing;
#synthesize finished = _finished;
-(BOOL)isFinished
{
return _finished;
}
- (void)setFinished:(BOOL)finished
{
[self willChangeValueForKey:#"isFinished"];
_finished = finished;
[self didChangeValueForKey:#"isFinished"];
}
-(BOOL)isExecuting
{
return _executing;
}
- (void)setExecuting:(BOOL)executing
{
[self willChangeValueForKey:#"isExecuting"];
_executing = executing;
[self didChangeValueForKey:#"isExecuting"];
}
- (instancetype)init
{
self = [super init];
if (self) {
// self.completionBlock = ^{
// NSDLog(#"image barcode search has finished");
// };
IDBAssert0(sizeof(_executing)<2);
}
return self;
}
-(BOOL)isAsynchronous
{
return YES;
}
#end
You certainly can (and we often do) subclass your own concrete NSOperation subclass.
To make the base class subclassable, you want to make sure that you only perform self.executing = true once. Right now, the main in both the base class and the subclass do it, and you'll therefore be doing it twice. The typical solution is to pull it out of both of those main implementations and do it in start of the base class. Apple suggests that you do this stuff in start, anyway.
Thus having removed the self.finished and self.executing stuff from both main implementations, you can then implement start:
- (void)start {
if ([self isCancelled]) {
self.finished = YES;
return;
}
self.executing = YES;
[self main];
}
Note, you don't have to call self.finished = false when the operation is starting because that will send an unnecessary KVO.
An unrelated observation:
If you keep the while loop in the base class, I'd suggest exiting the loop if either [self isCancelled] or if the processImage delegate completion methods was called (perhaps you can update some state property to designate when that delegate method was called). Right now, if the processImage finishes before the timeout, it will keep the operation running for the full 5 seconds.
Personally, depending upon how processImage was designed, I probably be inclined excise the while loop entirely. You generally want to avoid any polling like this at all. I might, for example, put the [self done] in the appropriate delegate method and then set up a timer or dispatch_after for the timeout.
- (void)main {
NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
start = now;
CGSize size = [self.microblinkCandidate size];
IDBAssert0(size.width && size.height);
IDBAssert0(self.microblink);
// this starts async processing
[self.microblink processImage:self.microblinkCandidate
scanningRegion:CGRectMake(0.0, 0.0, 1.0, 1.0)
delegate:self];
// cancel upon timeout
typeof(self) __weak weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
typeof(self) __strong strongSelf = weakSelf;
if ([strongSelf isExecuting]) {
[strongSelf cancel];
[strongSelf done]; // if canceling calls the delegate method that calls `done`, then you don't need this here
}
});
}

"[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]" makes the UI non response

In the UIViewController viewDidAppear event, I want to get some data from web service. And the code like:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSArray *arr = [self getCarList];
}
- (NSArray *)getCarList
{
if (!carList) {
ARequset *req = [[ARequset alloc] init];
[NetService sendRequest:req respClass:[Resp class] success:^(BaseResponse *response)
{
//after finished
self.requestFinished = YES;
} fail:^(NSInteger errcode, NSString *errmsg) {
self.requestFinished = YES;
}];
while (!self.requestFinished) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
return carList;
}
when run in to [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; the request success block will not be performed, and the UI become no response.
but if i change the - (void)viewDidAppear:(BOOL)animated like this, all goes well.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self performSelector:#selector(getCarList) withObject:self afterDelay:1];
}
Don't do that kind of syntax
The NetService request works asynchronously, the result of the request is passed
in the block. You have to process the BaseResponse object and update your UI or whatever you want to do with the data.
Polling is bad habit and consumes system resources unnecessarily.
Apart from that you're telling the currently running runloop of UIApplication to run which blocks it.
Do something like this
- (void)getCarList
{
if (!carList) {
ARequset *req = [[ARequset alloc] init];
[NetService sendRequest:req respClass:[Resp class] success:^(BaseResponse *response)
{
//after finished
self.carList = [self doSomethingWithTheResponse:response];
dispatch_async(dispatch_get_main_queue(), ^(void) {
// Update UI
});
} fail:^(NSInteger errcode, NSString *errmsg) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
// show alert
});
}];
}
}
}
Edit: Alternatively use a delegate like pattern to handle the asynchronous behavior.
Instead of the synchronous method
- (void)methodToGetCarList
{
NSArray *cars = [self getCarList];
[self doSomethingWithCars:cars];
}
use this
- (void)methodToGetCarListAsynchronously
{
[self getCarList];
}
and the delegate method
- (void)didReceiveCars:(NSArray *)cars errorMessage:(NSString *)error
{
if (error) {
// do error handling
} else {
[self doSomethingWithCars:cars];
}
}
the getCarList method looks like
- (void)getCarList
{
if (!carList) {
ARequset *req = [[ARequset alloc] init];
[NetService sendRequest:req respClass:[Resp class] success:^(BaseResponse *response)
{
//after finished
self.carList = [self doSomethingWithTheResponse:response];
[self didReceiveCars:self.carList errorMessage:nil];
} fail:^(NSInteger errcode, NSString *errmsg) {
[self didReceiveCars:nil errorMessage:errmsg];
}];
}
} else {
[self didReceiveCars:self.carList errorMessage:nil];
}
}
The code does not consider potential issues if the response returns in a background thread.

Keeping an NSOperation in a queue even after it completes

Typically once the main method of the an NSOperation is completed, the op is marked completed and it is removed from the queue. However, my op makes networking calls, and I want to handle retries. How do I keep an NSOperation in an NSOperationQueue until I explicitly say it's ok to remove it?
I can't find the original source for the work I did on my current project.
I have subclassed NSOperation and do this...
Add private properties in the .m...
#property (nonatomic) BOOL executing;
#property (nonatomic) BOOL finished;
#property (nonatomic) BOOL completed;
Init the operation...
- (id)init
{
self = [super init];
if (self) {
_executing = NO;
_finished = NO;
_completed = NO;
}
return self;
}
Add the functions to return the properties...
- (BOOL)isExecuting { return self.executing; }
- (BOOL)isFinished { return self.finished; }
- (BOOL)isCompleted { return self.completed; }
- (BOOL)isConcurrent { return YES; }
In the "start" function (this is the bit that the operationQueue calls...
- (void)start
{
if ([self isCancelled]) {
[self willChangeValueForKey:#"isFinished"];
self.finished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
// If the operation is not canceled, begin executing the task.
[self willChangeValueForKey:#"isExecuting"];
self.executing = YES;
[self didChangeValueForKey:#"isExecuting"];
[NSThread detachNewThreadSelector:#selector(main) toTarget:self withObject:nil];
}
Then in the main put your working code...
- (void)main
{
#try {
//this is where your loop would go with your counter and stuff
//when you want the operationQueue to be notified that the work
//is done just call...
[self completeOperation];
}
#catch (NSException *exception) {
NSLog(#"Exception! %#", exception);
[self completeOperation];
}
}
Write the code for completeOperation...
- (void)completeOperation {
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
self.executing = NO;
self.finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
That's it.
As long as you have these then the operation will work.
You can add as many other functions and properties as you wish.
In fact, I have actually subclassed this class as I have a function that does all the work for different types of object (it's an upload thing). I have defined a function...
- (void)uploadData
{
//subclass this method.
}
Then all I have in the subclasses is a custom "uploadData" method.
I find this really useful as it gives you fine grain control on when to finish the operation etc...

iOS - Objective-C - How to stop NSThread, when it's waiting?

I have an nsthread, a while loop within this one. It's getting objects from a "thread-safe" queue in the main method. When I leave the UIViewController, which contains this nsthread object, I call the nsthread cancel method, but it doesn't stop, because it's locked by the "queueLock" NSCondition. When I go back to this UIViewController a new nsthread will be created and gets the objects form the queue, but the previous thread still exits and both of them try to use the same object from the queue, and this causes a memory management problem. My question is how should I stop this thread, when I leave the UIViewController.
NSThread main method:
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
while ([self isCancelled] == NO) {
RenderRequest *renderRequest = [queue get];
[self doRender:renderRequest];
[renderRequest release];
}
[pool drain];
This is the get method of the queue class:
- (id) get {
id toRet = nil;
[queueLock lock];
#try {
while ([queueContents count] == 0) {
[queueLock wait];
}
toRet = [queueContents lastObject];
[queueContents removeLastObject];
}
#finally {
[queueLock unlock];
return toRet;
}
}
Thanks!
I wrote a simple demo, hope this can help you :)
demo.h
#import <Foundation/Foundation.h>
#interface test : NSObject
{
NSCondition *queueCondition;
NSThread *queueThread;
NSMutableArray *queueTask;
NSTimer *timer;
}
- (id)init;
#end
demo.m
#import "demo.h"
#interface demo (PrivateMethods)
- (void)threadTest;
- (void)cancelThread;
- (void)addTask;
#end
#implementation demo
- (id)init
{
self = [super init];
if (self) {
if (!queueThread) {
if (!queueCondition) {
queueCondition = [[NSCondition alloc] init];
}
if (!queueTask) {
queueTask = [[NSMutableArray alloc] initWithCapacity:5];
}
queueThread = [[NSThread alloc] initWithTarget:self selector:#selector(threadTest) object:nil];
[queueThread start];
[self performSelector:#selector(cancelThread) withObject:nil afterDelay:10];
if (!timer) {
timer = [[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(addTask) userInfo:nil repeats:YES] retain];
}
}
}
return self;
}
- (void)dealloc
{
[queueThread release];
[queueCondition release];
[queueTask release];
[timer invalidate];
[timer release];
[super dealloc];
}
- (void)threadTest
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
while (![[NSThread currentThread] isCancelled]) {
[queueCondition lock];
[queueCondition wait];
if ([queueTask count] == 0) {
[queueCondition unlock];
continue;
}
NSString *str = nil;
while ((str = [queueTask lastObject])) {
NSLog(#"getTask: %#", [queueTask lastObject]);
[queueTask removeLastObject];
}
[queueCondition unlock];
}
NSLog(#"threadTest end");
[pool drain];
}
- (void)addTask
{
[queueCondition lock];
if (!queueTask) {
queueTask = [[NSMutableArray alloc] initWithCapacity:5];
}
[queueTask addObject:#"new task"];
[queueCondition signal];
NSLog(#"add: new task");
[queueCondition unlock];
}
- (void)cancelThread
{
[timer invalidate];
[queueThread cancel];
[queueCondition lock];
[queueCondition signal];
[queueCondition unlock];
}
#end
- (id) get
{
id toRet = nil;
[queueLock lock];
#try
{
while ([queueContents count] == 0)
{
[queueLock wait];
if ([self isCancelled]) return nil; // stop waiting
}
toRet = [queueContents lastObject];
[queueContents removeLastObject];
}
#finally
{
[queueLock unlock];
return toRet;
}
}
thread main
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
while ([self isCancelled] == NO)
{
RenderRequest *renderRequest = [queue get];
if (renderRequest == nil) break; // should stop
[self doRender:renderRequest];
[renderRequest release];
}
[pool drain];
then you can cancel the thread and notify queueLock to stop waiting

Resources