How do I use an NSRunLoop on an NSOperationQueue? - ios

I have an app which communicates with an ExternalAccessory over Bluetooth, there is some delay in responses so I want the IO to happen on a background thread.
I setup an NSOperationQueue for single-threaded operation to enqueue my requests:
self.sessionQueue = [NSOperationQueue new];
self.sessionQueue.maxConcurrentOperationCount = 1;
If I schedule reads and writes to the EAAccessory streams from that queue, my app crashes because data from the socket can't be delivered without an NSRunLoop on the thread that the queue is using. Immediately after initializing the queue, I create a run loop with an empty NSMachPort to keep it running and start it:
[self.sessionQueue addOperationWithBlock:^{
NSRunLoop* queueLoop = [NSRunLoop currentRunLoop];
[queueLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
[queueLoop run]; // <- this blocks
}];
This blocks the queue as the run loop will never exit, but I'm not sure how to correctly manage the run loop so that I can successfully read from the accessory streams.

You shouldn't try to run a run loop inside an NSOperation. Grand Central Dispatch owns the thread on which the operation is running. You should start your own thread and use its run loop for your session streams.
However, you need to be aware that NSRunLoop is not generally thread safe, but CFRunLoop is. This means that you need to drop down to the CFRunLoop level when you want to run a block on your session-handling thread.
Also, the only way to get a reference to a background thread's run loop is to run something on that background thread. So step one is to create your own NSThread subclass that exports its own run loop:
typedef void (^MyThreadStartCallback)(CFRunLoopRef runLoop);
#interface MyThread: NSThread
/// After I'm started, I dispatch to the main queue to call `callback`,
// passing my runloop. Then I destroy my reference to `callback`.
- (instancetype)initWithCallback:(MyThreadStartCallback)callback;
#end
#implementation MyThread {
MyThreadStartCallback _callback;
}
- (instancetype)initWithCallback:(MyThreadStartCallback)callback {
if (self = [super init]) {
_callback = callback;
}
return self;
}
- (void)main {
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
dispatch_async(dispatch_get_main_queue(), ^{
_callback(runLoop);
});
_callback = nil;
CFRunLoopRun();
}
#end
Now you can create an instance of MyThread, passing in a callback. When you start MyThread, it will make that callback run back on the main thread, and it will pass its own (MyThread's) run loop to the callback. So you can use a MyThread as your session-handling thread, like this:
#implementation Thing {
CFRunLoopRef _sessionRunLoop;
}
- (void)scheduleStreamsOfSession:(EASession *)session {
MyThread *thread = [[MyThread alloc] initWithCallback:^(CFRunLoopRef runLoop) {
// Here I'm on the main thread, but the session-handling thread has
// started running and its run loop is `runLoop`.
[self scheduleStreamsOfSession:session inRunLoop:runLoop];
}];
[thread start];
}
- (void)scheduleStreamsOfSession:(EASession *)session inRunLoop:(CFRunLoopRef)runLoop {
// Here I'm on the main thread. I'll save away the session-handling run loop
// so I can run more blocks on it later, perhaps to queue data for writing
// to the output stream.
_sessionRunLoop = runLoop;
NSInputStream *inputStream = session.inputStream;
NSOutputStream *outputStream = session.outputStream;
// Here I'm on the main thread, where it's not safe to use the
// session-handling thread's NSRunLoop, so I'll send a block to
// the session-handling thread.
CFRunLoopPerformBlock(runLoop, kCFRunLoopCommonModes, ^{
// Here I'm on the session-handling thread, where it's safe to
// use NSRunLoop to schedule the streams.
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
[inputStream scheduleInRunLoop:currentRunLoop forMode:NSRunLoopCommonModes];
[outputStream scheduleInRunLoop:currentRunLoop forMode:NSRunLoopCommonModes];
});
// CFRunLoopPerformBlock does **not** wake up the run loop. Since I want
// to make sure the block runs as soon as possible, I have to wake up the
// run loop manually:
CFRunLoopWakeUp(_sessionRunLoop);
}
#end

Any thread can have an NSRunLoop created for it if needed, the main thread of any Cocoa or AppKit application has one running by default and any secondary threads must run them programmatically. If you were spawning an NSThread the thread body would be responsible for starting the NSRunLoop but an NSOperationQueue creates it's own thread or threads and dispatches operations to them.
When using an API which expects an NSRunLoop to deliver events to and from a background thread, either of your own creation, or one that libdispatch has created, you are responsible for making sure the NSRunLoop is run. Typically you will want to run the loop until some condition is met in each of your NSBlockOperation tasks, I wrote a category on NSRunLoop which simplifies this:
#import <Foundation/Foundation.h>
#interface NSRunLoop (Conditional)
-(BOOL)runWhileCondition:(BOOL *)condition inMode:(NSString *)mode inIntervals:(NSTimeInterval) quantum;
#end
#pragma mark -
#implementation NSRunLoop (Conditional)
-(BOOL)runWhileCondition:(BOOL *)condition inMode:(NSString *)mode inIntervals:(NSTimeInterval) quantum {
BOOL didRun = NO;
BOOL shouldRun = YES;
NSPort *dummyPort = [NSMachPort port];
[self addPort:dummyPort forMode:NSDefaultRunLoopMode];
while (shouldRun) {
#autoreleasepool {
didRun = [self runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:quantum]];
shouldRun = (didRun ? *condition : NO);
}
}
[self removePort:dummyPort forMode:NSDefaultRunLoopMode];
return didRun;
}
#end
With this condition you can schedule an NSBlockOperation which will start the run loop and run until the specified condition is NO:
__block BOOL streamOperationInProgress = YES;
[self.sessionQueue addOperationWithBlock:^{
NSRunLoop *queueLoop = [NSRunLoop currentRunLoop];
NSStream *someStream = // from somewhere...
[someStream setDelegate:self];
[someStream scheduleInRunLoop:queueLoop forMode:NSDefaultRunLoopMode]:
// the delegate implementation of stream:handleEvent:
// sets streamOperationInProgress = NO;
[queueLoop
runWhileCondition:&streamOperationInProgress
inMode:NSDefaultRunLoopMode
inIntervals:0.001];
}];
The wrinkle in the above example is putting the BOOL someplace that the delegate can set it to NO when the operation is complete.
Here's a gist of the NSRunLoop+Condition category.

Related

Dropbox DBRestClient/DBRestClientDelegate execute on a thread

I am using the dropbox API with DBRESTClient and DBRestClientDelegate in Dropbox-iOS-SDK
My issue is that I need these to run on a background thread.
When I call the [restClient loadMetadata] I do not get a response to - restClient:loadedMetadata: unless I begin call from the main thread.
Is there a simple workaround/library that I can use which will allow a delegate response on a thread ? I have tried Dropblocks which uses blocks but no luck.
I noticed "Make sure you call DBRestClient methods from the main thread or a thread that has a run loop. Otherwise the delegate methods won't be called." on the Dropbox Page
https://www.dropbox.com/developers-v1/core/start/ios
I used a runloop and it functions on a thread now
This is using a completion block Similarly you could use the delegate to set the flag to NO
self.inQuery = YES;
[self loadMetaDataWithPath:rootFolder mediaType:#(mediaType) completion:^(BOOL complete) {
self.inQuery = NO;
}];
#autoreleasepool {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
DDLogError(#"currentRunLoop %#",[NSDate date]);
}while(self.inQuery);
}

GCD, NSThread, and performSelector:onThread: issues

I'm attempting to debug some iOS crash logs that contain the following error message:
*** Terminating app due to uncaught exception 'NSDestinationInvalidException', reason: '*** -[SomeClass
performSelector:onThread:withObject:waitUntilDone:modes:]: target
thread exited while waiting for the perform
The relevant section of the code is:
- (void) runInvocationOnMyThread:(NSInvocation*)invocation {
NSThread* currentThread = [NSThread currentThread];
if (currentThread != myThread) {
//call over to the correct thread
[self performSelector:#selector(runInvocationOnMyThread:) onThread:myThread withObject:invocation waitUntilDone:YES];
}
else {
//we're okay to invoke the target now
[invocation invoke];
}
}
This is similar to the issue discussed here, except that I'm not trying to cancel my onThread: thread. In fact, in my case onThread: is being passed a reference to the application's main thread, so it should not be possible for it to terminate unless the entire app is terminating.
So the first question is, is the "target" thread referred to in the error message the one I'm passing to onThread:, or the one that's waiting for the invocation to complete on the onThread: thread?
I've assumed that it's the second option, as if the main thread really has terminated the crash of the background thread is kind of moot anyways.
With that in mind, and based upon the following discussion from the reference docs for performSelector:onThread:...:
Special Considerations
This method registers with the runloop of its
current context, and depends on that runloop being run on a regular
basis to perform correctly. One common context where you might call
this method and end up registering with a runloop that is not
automatically run on a regular basis is when being invoked by a
dispatch queue. If you need this type of functionality when running on
a dispatch queue, you should use dispatch_after and related methods to
get the behavior you want.
...I've modified my code to prefer the use of GCD over performSelector:onThread:..., as follows:
- (void) runInvocationOnMyThread:(NSInvocation*)invocation {
NSThread* currentThread = [NSThread currentThread];
if (currentThread != myThread) {
//call over to the correct thread
if ([myThread isMainThread]) {
dispatch_sync(dispatch_get_main_queue(), ^{
[invocation invoke];
});
}
else {
[self performSelector:#selector(runInvocationOnMyThread:) onThread:myThread withObject:invocation waitUntilDone:YES];
}
}
else {
//we're okay to invoke the target now
[invocation invoke];
}
}
Which seems to work fine (though no idea if it fixes the crash, as it's an exceedingly rare crash). Perhaps someone can comment on whether this approach is more or less prone to crashing than the original?
Anyways, the main problem is that there's only an obvious way to use GCD when the target thread is the main thread. In my case, this is true, but I'd like to be able to use GCD regardless of whether or not the target thread is the main thread.
So the more important question is, is there a way to map from an arbitrary NSThread to a corresponding queue in GCD? Ideally something along the lines of dispatch_queue_t dispatch_get_queue_for_thread(NSThread* thread), so that I can revise my code to be:
- (void) runInvocationOnMyThread:(NSInvocation*)invocation {
NSThread* currentThread = [NSThread currentThread];
if (currentThread != myThread) {
//call over to the correct thread
dispatch_sync(dispatch_get_queue_for_thread(myThread), ^{
[invocation invoke];
});
}
else {
//we're okay to invoke the target now
[invocation invoke];
}
}
Is this possible, or is there not a direct mapping from NSThread to GCD queue that can be applied?
Given your stated goal of wrapping a 3rd party API that requires thread affinity, you might try something like using a forwarding proxy to ensure methods are only called on the correct thread. There are a few tricks to doing this, but I managed to whip something up that might help.
Let's assume you have an object XXThreadSensitiveObject with an interface that looks something like this:
#interface XXThreadSensitiveObject : NSObject
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (void)foo;
- (void)bar;
- (NSInteger)addX: (NSInteger)x Y: (NSInteger)y;
#end
And the goal is for -foo, -bar and -addX:Y: to always be called on the same thread.
Let's also say that if we create this object on the main thread, then our expectation is that the main thread is the blessed thread and all calls should be on the main thread, but that if it's created from any non-main thread, then it should spawn its own thread so it can guarantee thread affinity going forward. (Because GCD managed threads are ephemeral, there is no way to have thread affinity with a GCD managed thread.)
One possible implementation might look like this:
// Since NSThread appears to retain the target for the thread "main" method, we need to make it separate from either our proxy
// or the object itself.
#interface XXThreadMain : NSObject
#end
// This is a proxy that will ensure that all invocations happen on the correct thread.
#interface XXThreadAffinityProxy : NSProxy
{
#public
NSThread* mThread;
id mTarget;
XXThreadMain* mThreadMain;
}
#end
#implementation XXThreadSensitiveObject
{
// We don't actually *need* this ivar, and we're skankily stealing it from the proxy in order to have it.
// It's really just a diagnostic so we can assert that we're on the right thread in method calls.
__unsafe_unretained NSThread* mThread;
}
- (instancetype)init
{
if (self = [super init])
{
// Create a proxy for us (that will retain us)
XXThreadAffinityProxy* proxy = [[XXThreadAffinityProxy alloc] initWithTarget: self];
// Steal a ref to the thread from it (as mentioned above, this is not required.)
mThread = proxy->mThread;
// Replace self with the proxy.
self = (id)proxy;
}
// Return the proxy.
return self;
}
- (void)foo
{
NSParameterAssert([NSThread currentThread] == mThread || (!mThread && [NSThread isMainThread]));
NSLog(#"-foo called on %#", [NSThread currentThread]);
}
- (void)bar
{
NSParameterAssert([NSThread currentThread] == mThread || (!mThread && [NSThread isMainThread]));
NSLog(#"-bar called on %#", [NSThread currentThread]);
}
- (NSInteger)addX: (NSInteger)x Y: (NSInteger)y
{
NSParameterAssert([NSThread currentThread] == mThread || (!mThread && [NSThread isMainThread]));
NSLog(#"-addX:Y: called on %#", [NSThread currentThread]);
return x + y;
}
#end
#implementation XXThreadMain
{
NSPort* mPort;
}
- (void)dealloc
{
[mPort invalidate];
}
// The main routine for the thread. Just spins a runloop for as long as the thread isnt cancelled.
- (void)p_threadMain: (id)obj
{
NSThread* thread = [NSThread currentThread];
NSParameterAssert(![thread isMainThread]);
NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
mPort = [NSPort port];
// If we dont register a mach port with the run loop, it will just exit immediately
[currentRunLoop addPort: mPort forMode: NSRunLoopCommonModes];
// Just loop until the thread is cancelled.
while (!thread.cancelled)
{
[currentRunLoop runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]];
}
[currentRunLoop removePort: mPort forMode: NSRunLoopCommonModes];
[mPort invalidate];
mPort = nil;
}
- (void)p_wakeForThreadCancel
{
// Just causes the runloop to spin so that the loop in p_threadMain can notice that the thread has been cancelled.
}
#end
#implementation XXThreadAffinityProxy
- (instancetype)initWithTarget: (id)target
{
mTarget = target;
mThreadMain = [[XXThreadMain alloc] init];
// We'll assume, from now on, that if mThread is nil, we were on the main thread.
if (![NSThread isMainThread])
{
mThread = [[NSThread alloc] initWithTarget: mThreadMain selector: #selector(p_threadMain:) object:nil];
[mThread start];
}
return self;
}
- (void)dealloc
{
if (mThread && mThreadMain)
{
[mThread cancel];
const BOOL isCurrent = [mThread isEqual: [NSThread currentThread]];
if (!isCurrent && !mThread.finished)
{
// Wake it up.
[mThreadMain performSelector: #selector(p_wakeForThreadCancel) onThread:mThread withObject: nil waitUntilDone: YES modes: #[NSRunLoopCommonModes]];
}
}
mThreadMain = nil;
mThread = nil;
}
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature *sig = [[mTarget class] instanceMethodSignatureForSelector:selector];
if (!sig)
{
sig = [NSMethodSignature signatureWithObjCTypes:"#^v^c"];
}
return sig;
}
- (void)forwardInvocation:(NSInvocation*)invocation
{
if ([mTarget respondsToSelector: [invocation selector]])
{
if ((!mThread && [NSThread isMainThread]) || (mThread && [mThread isEqual: [NSThread currentThread]]))
{
[invocation invokeWithTarget: mTarget];
}
else if (mThread)
{
[invocation performSelector: #selector(invokeWithTarget:) onThread: mThread withObject: mTarget waitUntilDone: YES modes: #[ NSRunLoopCommonModes ]];
}
else
{
[invocation performSelectorOnMainThread: #selector(invokeWithTarget:) withObject: mTarget waitUntilDone: YES];
}
}
else
{
[mTarget doesNotRecognizeSelector: invocation.selector];
}
}
#end
The ordering here is a little wonky, but XXThreadSensitiveObject can just do its work. XXThreadAffinityProxy is a thin proxy that does nothing other than ensuring that the invocations are happening on the right thread, and XXThreadMain is just a holder for the subordinate thread's main routine and some other minor mechanics. It's essentially just a workaround for a retain cycle that would otherwise be created between the thread and the proxy which has philosophical ownership of the thread.
The thing to know here is that threads are a relatively heavy abstraction, and are a limited resource. This design assumes that you're going to make one or two of these things and that they will be long lived. This usage pattern makes sense in the context of wrapping a 3rd party library that expects thread affinity, since that would typically be a singleton anyway, but this approach won't scale to more than a small handful of threads.
To your first Q:
I think the thread, sending the message is meant. But I cannot explain how this can happen.
Second: I would not mix NSThread and GCD. I think that there will be more problems than solutions. This is because of your last Q:
Each block is running on one thread. At least this is done, because thread migration for a block would be expensive. But different blocks in a queue can be distributed to many threads. This is obvious for parallel queues, but true for serial, too. (And have seen this in practice.)
I recommend to move your whole code to GCD. Once you are convenient with it, it is very easy to use and less error prone.
There is no mapping at all between queues and threads, with the sole exception of the main queue which always runs on the main thread. Any queue which targets the main queue will, of course, also run on the main thread. Any background queue can run on any thread, and can change thread from one block execution to the next. This is equally true for serial queues and for concurrent queues.
GCD maintains a thread pool which gets used for executing blocks according to the policies determined by the queue to which the block belongs. You are not supposed to know anything about those particular threads.

What does NSRunLoop do?

I read many posts about NSRunLoop, like this, this, this. But can't figure out what NSRunLoop actually does
What I usually see is a worker thread
wthread = [[NSThread alloc] initWithTarget:self selector:#selector(threadProc) object:nil];
[wthread start];
with a NSRunLoop inside it
- (void)threadProc
{
NSAutoreleasePool* pool1 = [[NSAutoreleasePool alloc] init];
BOOL isStopped = NO;
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!isStopped)
{
{
NSAutoreleasePool* pool2 = [[NSAutoreleasePool alloc] init];
[runloop runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
[pool2 release];
}
}
[pool1 release];
}
And the main thread passes some work to this wthread
[self performSelector:#selector(someWork:) onThread:wthread withObject:nil waitUntilDone:NO];
In term of passing work from the main thread to the worker thread, I see many people do this. Why need NSRunLoop here ? What does it do ?
I read that NSRunLoop is used to manage events, why is there nothing except calling runMode inside threadProc ?
The example you've shown is a Cocoa idiom for creating a thread that will continue to run after the method -threadProc exits. Why?
Because:
the NSRunLoop instance you've created has at least one input source ([NSMachPort port])
you've explicitly started the run loop with runMode:beforeDate
Without adding an input source and explicitly starting the run loop, the thread would terminate.
Parenthetically, although run loops are still vital for managing events and certain asynchronous tasks, I would not view NSThread as the default way of architecting most asynchronous work in a Cocoa app nowadays. GCD is a far cleaner way of encapsulating background work.
EDIT:
Submitting work to a serial queue in GCD:
#interface Foo : NSObject
#end
#implementation Foo {
dispatch_queue_t _someWorkerQueue;
}
- (id)init {
self = [super init];
if( !self ) return nil;
_someWorkerQueue = dispatch_queue_create("com.company.MyWorkerQueue", 0);
return self;
}
- (void)performJob {
dispatch_async(_someWorkerQueue, ^{
//do some work asynchronously here
});
dispatch_async(_someWorkerQueue, ^{
//more asynchronous work here
});
}
#end
Much goes on behind the scene. The reason for this is it provides a way for the thread to stop executing when there are no work items. If you ever used a real time OS, tasks need a place to give up the processor so others can run.
What is not well documented is that when you send performSelector:onThread:..., it's the run loop that queues the message and the wakes up to let the thread process it. If you add log messages to the while loop you can see this happen.
For the really curious there is sample code on github you con get to play around with run loops - add a comment and I'll list a few.

quitting a void method on a timer

I have a method that runs concurrently with recording a video. When the method ends it fires off a chain of other methods that continues until the recording ends. I want to be able to press a button to stop the recording prematurely that also exits the method at the same time. The way I'm currently trying to do it is with an NSTimer that checks to see if the recording is still happening, and if it isn't, it stops playing audio and should also call return to stop the method.
-(void) method
{
self.stopTimer = [NSTimer scheduledTimerWithTimeInterval:.05 target:self selector:#selector(checkRecording) userInfo:nil repeats:YES];
// Stuff happens
}
-(void) checkRecording
{
if (isRecording == NO)
{
if (player.playing == YES)
{
[player stop];
}
return;
}
}
This stops the audio immediately but the method continues to run until it's done. It doesn't call the next method in the sequence, which is a step in the right direction, but I need it to stop immediately. My only theory is that it's because I'm not calling return inside the actual method that I want to stop and instead in a different method, but even if that's the case I'm not really sure how to fix that because as far as I know timers can only point to other methods and I can't just tell it what I want it to do inside of the method that I want to stop. And if that's not the issue then I'm really not sure why this isn't working.
If a timer is valid you can invalidate it (that stops the timer).
I'm not sure if all the checking is really necessary (& the last line) but I do it currently that way:
if ( myTimer != nil && [myTimer isValid] )
{
[myTimer invalidate];
myTimer = nil;
}
EDITED:
if ( [myTimer isValid] )
{
[myTimer invalidate];
myTimer = nil;
}
My only theory is that it's because I'm not calling return inside the actual method that I want to stop and instead in a different method
Your theory is correct. return ends the function or method it is in, and none other. It pops the current function's context off the stack and returns execution to the calling function.
I'm not really sure how to fix that because as far as I know timers can only point to other methods and I can't just tell it what I want it to do inside of the method that I want to stop
We can use objects to store state and use that state to control the flow of our program. That state can be continually updated and checked. With a long-running task that needs to be cancelled in response to changes in that state, the state must be updated in parallel with the task. Since you say the timer works for stopping audio, but that the work done in method doesn't, I'm assuming that method is performing its long-running task asynchronously already.
This need to do an asynchronous long-running task (or series of tasks) in the background, with the possibility of cancellation, is nicely matched to the NSOperation and NSOperationQueue classes.
You can perform your work inside NSOperation objects, either via implementing methods or blocks. Implement your code to check if the operation has been cancelled at all appropriate times, and bail out as soon as that happens.
Below is an example that hopefully matches your use case. It was created in an iOS app 'empty application' template an everything is in the application delegate. Our app delegate keeps track of the state necessary to make the decision of whether to cancel or not, and also schedules a timer to poll for changes in that state. If it does determine that it should cancel, it delegates the actual cancellation of work to the operation queue and its operations.
#import "AppDelegate.h"
#interface AppDelegate ()
#property (nonatomic) BOOL shouldStop; // Analogous to your isRecording variable
#property (nonatomic, strong) NSOperationQueue *operationQueue; // This manages execution of the work we encapsulate into NSOperation objects
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Typical app delegate stuff
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
// Start our long running method - analogous to method in your example
[self method];
return YES;
}
- (void)method
{
// allocate operation queue and set its concurrent operation count to 1. this gives us basic ordering of
// NSOperations. More complex ordering can be done by specifying dependencies on operations.
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
// We create three NSBlockOperations. They only sleep the thread a little while,
// check if they've been cancelled and should stop, and keep doing that for a few seconds.
// When they are completed (either through finishing normally or through being cancelled, they
// log a message
NSMutableArray *operations = [NSMutableArray array];
for (int i = 0; i < 3; i++) {
// Block operations allow you to specify their work by providing a block.
// You can override NSOperation to provide your own custom implementation
// of main, or start, depending. Read the documentation for more details.
// The principle will be the same - check whether one should cancel at each
// appropriate moment and bail out if so
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
// For the "weak/strong dance" to avoid retain cycles
__weak NSBlockOperation *weakOperation = operation;
[operation addExecutionBlock:^{
// Weak/strong dance
NSBlockOperation *strongOperation = weakOperation;
// Here is where you'd be doing actual work
// Either in a block or in the main / start
// method of your own NSOperation subclass.
// Instead we sleep for some time, check if
// cancelled, bail out if so, and then sleep some more.
for (int i = 0; i < 300; i++) {
if ([strongOperation isCancelled]) {
return;
}
usleep(10000);
}
}];
// The completion block is called whether the operation is cancelled or not.
operation.completionBlock = ^{
// weak/strong dance again
NSBlockOperation *strongOperation = weakOperation;
NSLog(#"Operation completed, %# cancelled.", [strongOperation isCancelled] ? #"WAS" : #"WAS NOT");
};
[operations addObject:operation];
}
// Set up a timer that checks the status of whether we should stop.
// This timer will cancel the operations if it determines it should.
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(checkShouldKeepGoing:) userInfo:nil repeats:YES];
// Use GCD to simulate a stopped recording to observe how the operations react to that.
// Comment out to see the usual case.
double delayInSeconds = 5;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
self.shouldStop = YES;
});
// Add the operations to the operation queue, exeuction will start asynchronously from here.
[self.operationQueue addOperations:operations waitUntilFinished:NO];
}
// If we should stop, cancel the operations in the queue.
- (void)checkShouldKeepGoing:(NSTimer *)timer
{
if (self.shouldStop) {
NSLog(#"SHOULD STOP");
[timer invalidate];
[self.operationQueue cancelAllOperations];
}
}
#end

Exit a NSThread when it's sleeping?

I am creating a new Thread which runs one of my method:
Now what i am doing is as follows:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:#selector(myThreadFunc) object:nil];
[thread start];
in myThreadFunc
{
while(isRunning){
[self updateSomething];
[NSThread sleepForTimeInterval:3.0];
}
NSLog(#"out");
}
In another func,i set isRunning = NO and thread = nil or [thread cancel] but myThreadFunc is sleeping so thread cannot exit.
How can i control this case?
Thanks so much.
Don't use a thread. Use a timer. If the something is expensive, dispatch it off to some queue other than the main queue and set some state variable to show it's still running (if the something isn't meant to run concurrently). Then, just cancel your timer. A simple example of the timer call-back function might be:
- (void)doSomething:(NSTimer*)timer
{
// this assumes that this "something" only ever
// runs once at a time no matter what, adjust this
// to an ivar if it's per-class instance or something
static BOOL alreadyDoingSomething = NO;
if( alreadyDoingSomething ) return;
alreadyDoingSomething = YES;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self updateSomething];
alreadyDoingSomething = NO;
});
}
Now, if you simply cancel the timer, this will stop running. When you're ready to start it again, schedule a new timer with this method as the designated selector. To make this behave similar to your example above, you might set the timer interval to three seconds.

Resources