Break point in XCTestCase can't be reached - ios

I have a method that requests data from network , and I want to unit test it. But when I set a break point in the test case , the break point won't get there.
The method to be tested:
- (void)requestSuperDecisionDataWithCompletionHandler:(void (^)(NSArray *result))callBack {
static vector<vector<string>> arr;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//request data from network
IceNetwork::Init()->GetSuperDecision(arr);
if (arr.size() != kSuperDecisionDataCount) {
callBack(nil);
} else {
NSArray *convertedData = [self convertCArrayToNSArrayWithCArray:arr];
dispatch_async(dispatch_get_main_queue(), ^{
callBack(convertedData);
});
}
});
}
Test case:
- (void)testThatRequestSuperDecisionDataShouldReturnZeroOr14Items
{
//super decision
[_requestManager requestSuperDecisionDataWithCompletionHandler:^(NSArray *result) {
//set a break point here
int dataCount = result.count;
XCTAssert(0 == dataCount || 16 == dataCount, #"should have 0 or 16 items");
}];
}

In that case, because that is an asynchronous operation, the test is completing before the completion handler is called. With that in mind, there are two methods you can use to do this, both involving essentially waiting for the completion handler to be called before allowing your tests to complete.
The first method utilizes new APIs available in the Xcode 6 beta.
Method #1 - New XCTestExpectation APIs
- (void)testThatRequestSuperDecisionDataShouldReturnZeroOr14Items {
// set an expectation for your test, in this case we expect the completion handler to be
// called within a reasonable amount of time
XCTestExpectation *expectation = [self expectationWithDescription:#"The completion handler should be called."];
// start whatever asyncronous task you want to do
[_requestManager requestSuperDecisionDataWithCompletionHandler:^(NSArray *result) {
// here we handle the actual meat of the tests
int dataCount = result.count;
XCTAssert(0 == dataCount || 16 == dataCount, #"should have 0 or 16 items");
// now we can tell the test that our expectation has been fulfilled, which will
// allow the test to complete
[expectation fulfill];
}];
// we want the test to wait for the expectations for some period of time, but we also
// want to establish some sort of timeout interval where the test will basically be
// terminated and the result will be a timeout failure, you can set the timeout to whatever
// interval you want for each case, and optionally provide a handler to clean up anything
[self waitForExpectationsWithTimeout:2 handler:nil];
}
A second option is available if you are running Xcode 5 and thus don't have access to the new APIs, but is essentially the same process, just slightly more involved.
Method #2 - Do-it-yourself Async Testing
- (void)testThatRequestSuperDecisionDataShouldReturnZeroOr14Items {
// create a BOOL flag that will keep track of whether or not we're still
// waiting for the completion block to be called
__block BOOL waitingForBlock = YES;
// start whatever asyncronous task you want to do
[_requestManager requestSuperDecisionDataWithCompletionHandler:^(NSArray *result) {
// here we handle the actual meat of the tests
int dataCount = result.count;
XCTAssert(0 == dataCount || 16 == dataCount, #"should have 0 or 16 items");
// now we can update the waitingForBlock variable to NO as we are no
// longer waiting for it to complete
waitingForBlock = NO;
}];
// kill the test after some specified delay if we haven't completed yet - just in case something
// happens with the test where the handler will never be called, at least the test will complete
// and we will know we timed-out.
NSTimeInterval seconds = 3;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void) {
XCTFail(#"'testThatRequestSuperDecisionDataShouldReturnZeroOr14Items' failed due to timeout.");
waitingForBlock = NO;
});
// loop while we are waiting for the completion handler to finish
while (waitingForBlock) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
}

Related

Competition with two methods and run completion method once

I have three methods, two of them run at the same time. And the third method should be started only when the first and second method together complete their work. Either the first or second method, competitors, can finish their work first.
- (void)method1 {
//DO Long Work
isMethod1Complete = YES;
[self method3];
}
- (void)method2 {
//DO Long Work
isMethod2Complete = YES;
[self method3];
}
- (void)method3 {
if (isMethod1Complete && isMethod2Complete) {
//DO Work once
}
}
Method 3 should always be called once. But the problem is that there is a situation that method1 and method2 have finished working at the same time, and method3 is called twice. Tell me how to solve this problem in objective c for iOS?
Update:A concrete example, I have two services that call delegates when they finish their work.
- (void)method1Handler {
isMethod1Complete = YES;
[self method3];
}
- (void)method2Handler {
isMethod1Complete = YES;
[self method3];
}
How can this be solved without blocks?
For blocks, Rob's example is the best.
You say:
I have three methods, two of them run at the same time.
That means that they must be asynchronous or running on background queues (otherwise there's no way for them to run at the same time).
So, the idea is that you should give them both completion handlers (which will be called when they're done):
- (void)method1WithCompletion:(void(^ _Nonnull)(void))completion {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
//DO Long Work asynchronously
completion();
});
}
- (void)method2WithCompletion:(void(^ _Nonnull)(void))completion {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
//DO Long Work asynchronously
completion();
});
}
- (void)method3 {
// final task
}
In the above example, I added explicit dispatch_async calls to a background queue to ensure that the two long tasks run asynchronously. But if the code is already doing something asynchronous (e.g. a network request), then you will likely not need these dispatch_async calls, but just put the completion() call inside the completion handler provided by whatever API you are already using. But without more information about what method1 and method2 are doing, I cannot be more specific.
But, setting that aside, once your method1 and method2 have their own completion handlers, you can use dispatch_group_notify to identify what should be done when all of the dispatch_group_enter calls are balanced by their corresponding dispatch_group_leave calls:
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self method1WithCompletion:^{
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[self method2WithCompletion:^{
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[self method3];
});
In subsequent comments, you mentioned that you are not using a completion block-based API, but rather a delegate-protocol-based API. You have a few options, for example:
You can use the same above closure pattern, but just save the completion handlers as block properties, e.g.:
For example, define block properties:
#property (nonatomic, copy, nullable) void (^completionOne)(void);
#property (nonatomic, copy, nullable) void (^completionTwo)(void);
Then, your method1 and method2 would save these blocks:
- (void)method1WithCompletion:(void(^ _Nonnull)(void))completion {
self.completionOne = completion;
// start your time consuming asynchronous process
}
// and your completion delegate method can then call the saved closure
// and then remove it
- (void)method1DidComplete {
self.completionOne();
self.completionOne = nil;
}
- (void)method2WithCompletion:(void(^ _Nonnull)(void))completion {
self.completionTwo = completion;
// start second asynchronous process
}
// same as above
- (void)method2DidComplete {
self.completionTwo();
self.completionTwo = nil;
}
The delegate-protocol completion API would then just call the saved block properties (and probably reset them to nil to free the memory associated with those blocks).
Then you can use the dispatch group notify process as shown in my original answer, above.
Alternatively, rather than using blocks, you can just use dispatch group by itself. For example, define dispatch group property:
#property (nonatomic, strong, nullable) dispatch_group_t group;
Then, you create your group and start your two tasks:
self.group = dispatch_group_create();
[self method1];
[self method2];
dispatch_group_notify(self.group, dispatch_get_main_queue(), ^{
[self method3];
});
And, the two methods then dispatch_group_enter when you start the tasks and dispatch_group_leave in their respective completion handler delegate methods:
- (void)method1 {
dispatch_group_enter(self.group);
// start first asynchronous process
}
// in your delegate completion method, you "leave" the group
- (void)method1DidComplete {
dispatch_group_leave(self.group);
}
- (void)method2 {
dispatch_group_enter(self.group);
// start second asynchronous process
}
- (void)method2DidComplete {
dispatch_group_leave(self.group);
}
- (void)method3 {
// you might as well remove the group now that you're done with it
self.group = nil;
// final task
NSLog(#"doing three");
}
Personally, I would generally lean towards the first option (that way, the dispatch group stuff is contained in a single method), but either approach works.
Why not dispatching the "call" to method3 in a serial queue?
dispatch_queue_t notSimQ;
notSimQ = dispatch_queue_create("notSimQ", NULL);
- (void)method1 {
//DO Long Work
isMethod1Complete = YES;
dispatch_async( notSimQ, // or sync
^{
[self method3];
});
}
- (void)method2 { … } // similiasr
- (void)method3 { … } // unchanged
The calls to method3 are never in competition.
- (void)method1 {
//DO Long Work
isMethod2Complete = YES;
dispatch_async(dispatch_get_main_queue(), ^(void){
[self method3];
}
}
- (void)method2 { … }
- (void)method3 { … }
- (void)viewDidLoad {
[super viewDidLoad];
[self method1];
[self method2];
}
- (void)method1 {
//DO Long Work
isMethod1Complete = YES;
}
- (void)method2 {
//DO Long Work
isMethod2Complete = YES;
dispatch_async(dispatch_get_main_queue(), ^(void){
[self method3];
}
}
- (void)method3 {
if (isMethod1Complete && isMethod2Complete) {
//DO Work once
}
}

Cancel actions in dispatch_async when they're no longer needed

I have a UIViewController that does the following in viewDidLoad
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
items = [[DataFetcher sharedInstance] getItems handler:^(NSArray *currentItems){
if (currentItems.count % 30 == 0) { //don't wait for all the items to be ready show by chunks of 30
items = currentItems;
[tableView reloadData];
}
items = currentItems;
}];//Pretty complex call that takes some time to finish there is WebService calls, data parsing, storing some results ...
dispatch_async(dispatch_get_main_queue(), ^{
[tableView reloadData];
});
});
What I need to do is to stop getItems when I pop this viewController. It's pointless and it takes CPU Time and energy (This call may take up to a minute on some cases).
I am assuming I should be doing this in viewWillDisappear but how exactly?
You can use NSBlockOperation. Periodically check if it's been cancelled, and stop doing work if it has been:
- (void)getItemsWithHandler:(void (^)(NSArray *currentItems))handler {
self.operation = [NSBlockOperation blockOperationWithBlock:^{
if (self.operation.isCancelled) {
return;
}
// Do something expensive
if (self.operation.isCancelled) {
return;
}
// Do something else expensive
for (int i = 0; i < 10000; i++) {
if (self.operation.isCancelled) {
return;
}
// Do expensive things in a loop
}
}];
}
- (void) cancelGetItemsRequest {
[self.operation cancel];
self.operation = nil;
}
Alternatively, you can put a bunch of NSBlockOperations in an NSOperationQueue. You can set dependencies for the work, and cancel the entire queue at once if you want.
Cancelling asynchronous operations is nicely supported in NSOperation and NSOperationQueue, but it's quite a bit more complicated. In any case, your asynchronous code has to check from time to time whether it is cancelled or should still continue.
Best thing is to have a property "cancelled" somewhere, that you set when you don't want any more work to be done, and whenever you try to do more work, you check that property.

How to ensure completion of a async operation before continuing with execution

I am building an iOS app and some part of its code relies on the success/failure value returned from a particular task.This tasks involves callbacks from a library. I want the return value from this task to be returned only after the callback has returned either success/failure. But since I wrote a sequential code the return value is returned even before the callback returns a success/failure.
I looked into using modal view controllers and from what I understand I can make the task execute from this view controller and then return the code back.
But this also doesn't suit my requirements as when the code which initiates the callback sequence is executed I don't want a new view controller to be displayed. Although there is a certain callback which requires me to prompt the user for information. I do this in a popover and I considered making the view controller within the popover modal. But then the callbacks will still be part of the main thread and I won't receive them when my popover is presented modally(?).
With my current understanding of these concepts I don't know how to proceed. Is there some way to do this in iOS?
EDIT:
The code does something like this
//In CustomTableViewController
-(void) someFunc
{
ENUM_NAME code = [TaskController startTheTask:args];
if(code == SUCCEEDED)
{
//Do Something
}
if(code == FAILED)
{
//Do Something Else
}
}
//In TaskController
-(ENUM_NAME) startTheTask:args
{
startWorkflow(args); //This function registers callback function with the library.
return finalCode; //This is returned even before it is set to SUCCEEDED/FAILED
}
-(void) onCallback:params
{
MSG_TYPE msg = [params getMsg];
if(msg == TASK_FAILED)
finalCode = FAILED;
if(msg == TASK_SUCCEEDED)
finalCode = SUCCEEDED;
if(msg == TASK_SHOW_PROMPT)
{
[PopOverController showPopOver];
}
}
-(void) onUserInfoAdded
{
//This is called when Confirm is clicked in the popover
continueWorkflow(params); //asks for the next callback to happen
}
-(void) onCancleClicked
{
//This is called when Popover is dismissed without entering Info
cancleWorkflow(params); //asks for result of the workflow through callback
}
You can use GCD. For example:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
//put one process here
dispatch_group_leave(group); //when done
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
//put another process here
dispatch_group_leave(group); //when done
});
// All updates finished
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// add last steps here after all processess are finished
});
dispatch_release(group);
You can use a semaphore to delay the execution until a block returns:
__block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSData *dataFromTheBlock = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// block implementation
// dataFromTheBlock = some data;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

Grand Central Dispatch and functions

I've been looking at this question to try to solve the problem I have here. The tl;dr is I want to use GCD to let me show a "Waiting" screen while I preform some tasks, then hide the screen when it's done. Right now, I have
- (void) doStuff
{
// Show wait on start
[self.waitScreen setHidden:NO];
dispatch_queue_t queue = dispatch_queue_create("com.myDomain.myApp",null);
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
// Double nesting the dispatches seems to allow me to do UI changes as part of 'Code to execute' below.
// If I do not double nest like this, the UI still freezes while it executes
dispatch_queue_t queue2 = dispatch_queue_create("com.myDomain.myApp",null);
dispatch_async(queue2, ^{
dispatch_async(dispatch_get_main_queue(), ^{
// Code to execute
{
//... Do my time consuming stuff here ...
// For testing purposes, I'm using
int i = 0;
while (i < 1000000000)
{
i++;
}
}
// Hide Wait Screen on End
[self.waitScreen setHidden:YES];
});
});
});
});
}
And this works just how I want. I'm calling [self doStuff] like so
- (IBAction) buttonTouchUpInside:(id)sender
{
[self doStuff];
}
- (void) doStuff
{
// ... code from first code block here ...
}
Everything to this point works perfectly. Now, I've discovered I will need to use this in a function call. So I need something like:
- (IBAction) buttonTouchUpInside:(id)sender
{
NSMutableString *string= [self doStuff];
// ... use 'string' to do other stuff ...
// For testing, I'm using
self.label.text = string;
}
- (NSMutableString *) doStuff
{
// ... code from first code block here ...
}
How do I need to change the syntax to be able to pass variables around with dispatch_async?
I looked at the Apple Docs and tried
- (IBAction) buttonTouchUpInside:(id)sender
{
NSMutableString *string= [self doStuff];
// ... use 'string' to do other stuff - shows 'nil' when I put breakpoints here ...
// For testing, I'm using
self.label.text = string;
}
- (NSMutableString *) doStuff
{
__block NSMutableString *string = [[NSMutableString alloc] initWithString:#"Initing"];
// Show wait on start
[self.waitScreen setHidden:NO];
dispatch_queue_t queue = dispatch_queue_create("com.myDomain.myApp",null);
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_queue_t queue = dispatch_queue_create("com.myDomain.myApp",null);
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
// Code to execute
{
int i = 0;
while (i < 1000000000)
{
i++;
}
[string setString:#"Hello World"];
}
// Hide Wait Screen on End
[self.waitScreen setHidden:YES];
});
});
});
});
return string;
}
But when I run this, label just shows Initing. I need it to show Hello World. None of the changes I make in the block are being passed through.
After looking at some other questions, this seems to be referred to as a "race condition". As I understand it, once it hits the dispatch_async, the code in the block starts running on a new thread, but the code outside of the block continues to run at the same time on the old thread. So it looks like the thread outside the block is getting to self.label.text = string before the thread running the block can get to [string setString:#"Hello World"];. How can I make the self.label.text = string line wait until [string setString:#"Hello World"]; finishes?
First of all your reasoning of double nesting is flawed. Not sure why it might have worked, but the correct way is to do some async work, and any time you want to update the ui wrap that code in a block on the main queue.
- (void) doStuff
{
// Show wait on start
[self.waitScreen setHidden:NO];
// queue should be a global variable, you don't want to create it every time you
// execute doStuff
dispatch_async(queue, ^{
// Code to execute
{
//... Do my time consuming stuff here ...
// For testing purposes, I'm using
int i = 0;
while (i < 1000000000)
{
i++;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
// Hide Wait Screen on End
[self.waitScreen setHidden:YES];
});
});
}
Since your queue is performing work asynchronously you can't simply return a value from doStuff without waiting, which will block the queue you call doStuff on again.
If you just want to set a value on a label, do that too in the block executed on the main queue, like hiding the wait screen.
Another common way to do things it to provide a callback block to execute as soon as work is finished.
- (void) doStuffWithCompletionBlock:(void(^)(NSString *))block
{
// again, a global variable for the queue
dispatch_async(queue, ^{
// do some work here that shouldn't block the UI
dispatch_async(dispatch_get_main_queue(), ^{
block(#"My result string");
});
});
}
- (void) myAction:(id)sender
{
__weak typeof(self) weakSelf = self;
[self doStuffWithCompletionBlock:^(NSString *result) {
weakSelf.label.text = result;
}];
}
Notice that I call the completion block on the main queue, this is a choice. You could leave that out, but then you would still have do all UI updates on the main queue later in the completion block itself.

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

Resources