How to work with NSOperationQueue and NSBlockOperation with dispatch gcd? - ios

Here is the code
#interface ViewController ()
#property (nonatomic, strong) NSOperationQueue *queue;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_queue = [[NSOperationQueue alloc] init];
NSBlockOperation *aBlockOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation* aWeakBlockOperation = aBlockOperation;
[aBlockOperation addExecutionBlock:^{
NSLog(#"queue should still have the operation. And it does. yay!: %#", [_queue operations]); // This should print correctly. It will show the NSBlock operation correctly residing inside the NSOperationQueue
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(#"Now queue is empty??: %#", [_queue operations]); // This should print as being empty
NSLog(#"And a weak block is nil???: %#", aWeakBlockOperation); // This should print out **nil**
if (![aWeakBlockOperation isCancelled]) {
// Now i have no way to check the operation
}
});
}];
[_queue addOperation:aBlockOperation];
#end
[Edit]
The goal is to have a user interaction like this:
There is a tableView on screen with 5 or more cells. When ever a user click a cell, background process will perform background process that will take a while. The App will, at 3 second intervals, check to see if the user clicked on another cell. If the user clicked on another cell, I should cancel the current operation from queue, and begin processing the new one the user clicked on.
From the code above i have 2 problems i cant solve.
How do i make it so that my weak reference isnt deallocated in the dispatch_after block? The goal of putting it there is to pause the app for exactly 3 seconds. If dispatch_after is incorrect, then what code do i use there to prevent it becoming nil?
Why is it that my NSOperationQueue become empty after I call dispatch_after? Is there a way to make it not become empty?

dispatch_after schedules the block and returns immediately. So, your NSBlockOperation's executionBlock has almost no work to do — it immediately finishes and is removed from the queue. At that time, the operation is released and so the weak reference becomes nil before the dispatch_after block is called later.
If you do the dispatch_after first and schedule the operation from inside that block, it might suit your needs. You could just use sleep, but I wouldn't recommend that since you will be unnecessarily blocking a thread. See this question for more discussion on NSOperation and delays.

You can schedule operation inside the dispatch_after block and declare aBlockOperation as an instance variable/property so aWeakBlockOperation will not became nil.
But you do not need to hassle with the NSBlockOperation to achieve your goal. You can use dispatch_block_t instance variable which you would set to a new value (block with your code you need to be executed after the column is clicked) each time the column is clicked:
#implementation ViewController
{
dispatch_block_t columnBlock;
}
- (void)columnClicked
{
columnBlock = ^{ ... your code ... };
__weak dispatch_block_t weakColumnBlock = columnBlock;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_block_t colBlock = weakColumnBlock;
if (colBlock)
colBlock();
});
}

Related

objective c block that waits for another delegate

I have a watchkit app that calls a viewcontroller on an iphone app. I have a delegate for a network connection. I'm trying to use a block so that I don't tightly couple my AppDelegate and my view controller too closely. How can I notify my block when the delegate is finished?
ViewController.m
-(void)getWatchDataWithCompletion:(void(^)(BOOL gotData))completion{
[self setUpAppForWatch];
completion(YES);
}
-(void)finishedMessageParse:(NSMutableData *)messageData{
//the delegate is finish tell the block completion is done.
}
-(void)setUpAppForWatch{
[network call];
}
AppDelegate.m
-(void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)
(NSDictionary *))reply{
[vc getWatchDataWithCompletion:^(BOOL gotData){
if (gotData){
//I'm done reply dictionary
reply(#{#"data":serlizedData})
}];
add new property in viewcontroller:
#property (nonatomic, strong) void(^completion)(BOOL gotData);
-(void)getWatchDataWithCompletion:(void(^)(BOOL gotData))completion{
[self setUpAppForWatch];
self.completion = completion;
}
-(void)finishedMessageParse:(NSMutableData *)messageData{
if (self.completion){
self.completion(YES);
}
}
There're three possible ways.
;tldr - refer to the third one. Else - read everything, it might be useful.
First one
Use private serial queue for performing tasks of finished... method and your block. It will suffice you in case, if finished... always called before block. If not - take a look at the Second one
Use private #property dispatch_queue_t privateSerialQueue; of View Controller.
privateSerialQueue = dispatch_queue_create("PrivateQueue", DISPATCH_QUEUE_SERIAL);
Than, use it like this
-(void)getWatchDataWithCompletion:(void(^)(BOOL gotData))completion{
[self setUpAppForWatch];
dispatch_async(privateSerialQueue, ^(){
completion(YES);
});
}
-(void)finishedMessageParse:(NSMutableData *)messageData{
dispatch_sync(privateSerialQueue, ^(void){
//Here goes whatever you need to do in this method before block start
});
//the delegate is finish tell the block completion is done.
}
Second one
Take a look at dispatch_semaphore_t. Make it a public property of your View Controler
#property (readonly) dispatch_semaphore_t semaphore
Create it with starting value 0. It will let you wait in case your block runs before delegate finished... method and run immediately, if finished has already completed before block. Like this
self.semaphore = dispatch_semaphore_create(0);
Then you can use it this way
-(void)finishedMessageParse:(NSMutableData *)messageData{
//the delegate is finish tell the block completion is done.
dispatch_semaphore_signal(self.semaphore);
}
[vc getWatchDataWithCompletion:^(BOOL gotData){
if (gotData){
//I'm done reply dictionary
dispatch_semaphore_wait(vc.semaphore, DISPATCH_TIME_FOREVER);
reply(#{#"data":serlizedData})
}];
Third one
Came to my mind while writing the two above =)
Some kind of combination of previous two
Use private property of your view controller
#property (readonly) dispatch_semaphore_t semaphore
Initialize it the same way, as in the second (with starting value 0)
self.semaphore = dispatch_semaphore_create(0);
And use it privately like this
-(void)getWatchDataWithCompletion:(void(^)(BOOL gotData))completion{
[self setUpAppForWatch];
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
completion(YES);
}
-(void)finishedMessageParse:(NSMutableData *)messageData{
//the delegate is finish tell the block completion is done.
dispatch_semaphore_signal(self.semaphore);
}
P. S. Hope, it helps you to get to the point. Feel free to ask anything not clear

How do I wait for a method to finish before calling it again?

I am building a simple messaging app using Parse's framework. I have a method called displayMessages. This is called each time the phone receives a push.
However, as this message is doing work in the Parse database I don't want to call it again if it's already running. I want to wait until it is finished and then call it.
I am using the following code:
-(void)receivedPush
{
[self displayMessages];
}
and:
-(void)displayMessages
{
//code here
}
If received push is called I want it to wait until displayMessages is finished before calling it. Could someone please point me in the right direction with this?
UPDATE
I tried using the NSOperationQueue method and realised that although this does work for waiting for displayMessages it doesn't result in the required behavior.
In displayMessages I have: [PFObject deleteAllInBackground:toDelete]; it's actually this I need to wait for completion before calling displayMessages again.
Create a NSOperationQueue and set the maxConcurrentOperationCount to 1. Implement your data access method as an operation (possibly block-type operation) and submit it to the queue. (I like this better than gcd since you can do cancellation or test the number of items already in the queue.)
Note that if the method actually displays things, you'll need to dispatch back to the main queue for UI work.
You could use a NSOperationQueue with maxConcurrentOperationCount set to 1.
Declare the NSOperationQueue as an iVar of your class, initialize it in the init method and set
[_opQueue setMaxConcurrentOperationCount:1];
and then when you receive the push:
- (void)receivedPush {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(displayMessages) object:nil];
[_opQueue addOperation:op];
}
Shortest and simples would be creating BOOL isExecuting and checking if you can call method based on that (changing values before execution but after check and after execution)
How about this for a fairly lightweight solution:
#property (nonatomic, assign) BOOL needsToDisplayMessages;
#property (nonatomic, assign) BOOL displayingMessages;
Then
-(void)receivedPush
{
if (!self.displayingMessages) {
[self displayMessages];
} else {
self.needsToDisplayMessages = YES;
}
}
-(void)displayMessages
{
self.needsToDisplayMessages = NO;
self.displayingMessages = YES;
//long-running code here
self.displayingMessages = NO;
if (self.needsToDisplayMessages) {
[self displayMessages]
}
(ignoring concurrency issues ... for which you could use GCD in displayMessages or NSOperationQueue as per a couple of the other answers)
With your new updated requirement, you can use deleteAllInBackground:block:. According to document:
"Deletes a collection of objects all at once asynchronously and executes the block when done."
Why not schedule each message handling using:
-(void)receivedPush
{
dispatch_async(dispatch_get_main_queue(), ^{
/* Show the update on the display */
NSLog(#"Handling new messages");
NSArray *newMessages=<populate with new messages>;
[handler displayMessages:newMessages];
});
}
This will queue up your handling of each set as they come in. Only one displayMessages will run at a time.

refreshcontrol endrefreshing has to wait for the subclass

I have a implemented a refreshcontrol for my tableview and it works fine. But I wanna achieve to invoke another class execute the process within that class. I want my refreshcontrol should wait until the execution of that class.
Eg: I have some database changes in the Player class. Now the refreshcontrol ends the refresh while the database changes are in progress.
-(void)pullToRefresh{
UpdOther *updO = [[UpdOther alloc] initWithProfile:#"Player"];
[updO release];
[refreshControl endRefreshing];
}
Rather than having the pullToRefresh method wait for the update, it would be better if you simply used a completion block in your update process, so pullToRefresh could tell the update process what to do when the update was done.
For example, rather than having the initWithProfile perform the update process, you could have some method, say performUpdateWithCompletion do it, but give it a completion block:
- (void)performUpdateWithCompletion:(void (^)(void))completionBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do synchronous update here
// when done, perform the `completionBlock`
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
}
});
}
Then your pullToRefresh can specify what it wants the update process to do upon completion, for example:
- (void)pullToRefresh{
UpdOther *updO = [[UpdOther alloc] initWithProfile:#"Player"];
__weak typeof(self) weakSelf = self;
[updO performUpdateWithCompletion:^{
typeof(self) strongSelf = weakSelf;
[strongSelf.refreshControl endRefreshing];
}];
[updO release];
}
There are other approaches, too, (delegate pattern, notification pattern), but I prefer the in-line immediacy of the block-based solution.
By the way, if UpdOther is using the NSURLConnectionDataDelegate methods, you obviously need to call the completionBlock from some other method (e.g., connectionDidFinishLoading). So, in that case, you'd define a block property in UpdOther as so:
#property (nonatomic, copy) void (^updateCompletionBlock)(void);
Or, you can define a typedef for this block:
typedef void (^UpdateCompletionBlock)(void);
and then use that in your property declaration:
#property (nonatomic, copy) UpdateCompletionBlock updateCompletionBlock;
Anyway, in that case, your performUpdateWithCompletion would save a copy of the block in that property:
- (void)performUpdateWithCompletion:(void (^)(void))completionBlock
{
self.updateCompletionBlock = completionBlock;
// now initiate time consuming asynchronous update here
}
And then, however you complete your download, you can call the saved completion block there:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// do whatever extra steps you want when completing the update
// now call the completion block
if (self.updateCompletionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
self.updateCompletionBlock();
});
}
}

iOS: Stopping a Thread / Method in the Middle of Process

I have a UITextfield and a UIButton. The user can enter, for example, search word such as "dog" or "cat" and it will trigger a method in another class that runs on a custom dispatch GCD queue to fetch the images (around 100 or so).
Everything works fine, except if the user in the midst of fetching, decides to change and enter another search word such as "cat" and then press the fetch button, I would like to be able to stop that thread / method while it is fetching the images from the previous search term.
I have thought about NSThread (something I never used before) or blocks (to get notified once the method has finished running), but the problem with blocks is, I will get notified once the method had finished doing its thing, but what I need here is to tell it to stop fetching (because the user has decided on another search and entered another search term).
Can someone please cite me with some samples, as to how we can be able to stop a loop / method while it is running on a custom GCD thread before it is finished? Thanks in advance.
I'm using NSOperationand NSOperationQueue to cluster markers on a map in the background and to cancel the operation if necessary.
The function to cluster the markers is implemented in a subclass of NSOperation:
ClusterMarker.h:
#class ClusterMarker;
#protocol ClusterMarkerDelegate <NSObject>
- (void)clusterMarkerDidFinish:(ClusterMarker *)clusterMarker;
#end
#interface ClusterMarker : NSOperation
-(id)initWithMarkers:(NSSet *)markerSet delegate:(id<ClusterMarkerDelegate>)delegate;
// the "return value"
#property (nonatomic, strong) NSSet *markerSet;
// use the delegate pattern to inform someone that the operation has finished
#property (nonatomic, weak) id<ClusterMarkerDelegate> delegate;
#end
and ClusterMarker.m:
#implementation ClusterMarker
-(id)initWithMarkers:(NSSet *)markerSet delegate:(id<ClusterMarkerDelegate>)delegate
{
if (self = [super init]) {
self.markerSet = markerSet;
self.delegate = delegate;
}
return self;
}
- (void)main {
#autoreleasepool {
if (self.isCancelled) {
return;
}
// perform some Überalgorithmus that fills self.markerSet (the "return value")
// inform the delegate that you have finished
[(NSObject *)self.delegate performSelectorOnMainThread:#selector(clusterMarkerDidFinish:) withObject:self waitUntilDone:NO];
}
}
#end
You could use your controller to manage the queue,
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.name = #"Überalgorithmus.TheKillerApp.makemyday.com";
// make sure to have only one algorithm running
self.operationQueue.maxConcurrentOperationCount = 1;
to enqueue operations, kill previous operations and the like,
ClusterMarker *clusterMarkerOperation = [[ClusterMarker alloc] initWithMarkers:self.xmlMarkerSet delegate:self];
// this sets isCancelled in ClusterMarker to true. you might want to check that variable frequently in the algorithm
[self.operationQueue cancelAllOperations];
[self.operationQueue addOperation:clusterMarkerOperation];
and to respond to the callbacks when the operation has finished:
- (void)clusterMarkerDidFinish:(ClusterMarker *)clusterMarker
{
self.clusterMarkerSet = clusterMarker.markerSet;
GMSProjection *projection = [self.mapView projection];
for (MapMarker *m in self.clusterMarkerSet) {
m.coordinate = [projection coordinateForPoint:m.point];
}
// DebugLog(#"now clear map and refreshData: self.clusterMarkerSet.count=%d", self.clusterMarkerSet.count);
[self.mapView clear];
[self refreshDataInGMSMapView:self.mapView];
}
If I remember correctly I used this tutorial on raywenderlich.com as a starter.
I would recommend using NSOperation as it has cancel method which will cancel the current running operation.

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