Delay a call to a class method - ios

I have a class which contains only class methods. Typically, I use these methods to refresh data for my app.
This way, for example, I want a TableViewController to trigged methods from the first class mentioned regularly.
What I also need is the possibility to stop these calls when my TableViewController is not shown anymore.
What I'm doing now is probably not the best thing to do :
//myNetworkingClass.h
+(void)methods1:(type*)param1;
---
//myNetworkingClass.m
+(void)methods1:(type*)param1
{
//asynchronous tasks
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateComplete" object:responseObject];
}
//myTableViewController.m
- (void)viewDidLoad
{
//initialization
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateReceived:) name:#"updateComplete" object:nil];
[myNetworkingClass methods1:param];
}
-(void)updateReceived:(NSNotification*)notification
{
//some task, especially update datasource
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 10* NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[myNetworkingClass methods1:param];
});
}
There is 3 problems using this :
Once added to main queue, I can't cancel the next refresh, like I should do when dismissing my TableViewController and that leads to second point
Because the task is queued, if my TableViewController is dismissed, I will have a useless call.
myTableViewController is a generic class, so I can create a new object of this class and this one will receive a non-compliant update notification, and will lead to a crash. (note : their is not 2 myTableViewController at a same time)
How should I deal with these constraints and write a "neat coed" :p
Thanks for your help.
EDIT
With the link of #AdamG, I've created a NSOperation :
#interface OperationRefresh : NSOperation
-(id)initWithArray:(NSArray *)array andDelay:(int)refreshTime;
#end
#implementation OperationRefresh
{
NSArray *paramA;
int delay;
}
-(id)initWithArray:(NSArray *)array andDelay:(int)refreshTime
{
self = [super init];
paramA = array;
delay = refreshTime;
return self;
}
-(void)main
{
#autoreleasepool {
NSLog(#"sleeping...");
[NSThread sleepForTimeInterval:delay];
NSLog(#"Now Refresh");
[myNetworkingClass methods1:paramA];
}
}
#end
But I'm not able to cancel it. Here is what I'm doing :
-(void)updateReceived:(NSNotification*)notification
{
//some task, especially update datasource
refreshOperation = [[OperationRefresh alloc] initWithArray:param andDelay:10];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
[refreshOperation start];
});
}
-(void)viewWillDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[refreshOperation cancel];
}
Indeed, when my view disappears, it still writing "Now Refresh" in the console.

You should be using NSOperations, which will allow you to cancel your operation that is running in the background.
There is a great tutorial here: http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues.
It's also much more efficient and will keep your app from lagging due to background tasks.
UPDATE
To cancel you have to manually add a cancellation in the NSOperation. You should add this wherever you might want the operation to be canceled (probably before and after the delay).
if (self.isCancelled){
// any cleanup you need to do
return;
}

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

Block completion handler reference is nil after completing background fetch

I am trying to implement a background fetch of an RSS Feed using performFetchWithCompletionHandler, but when I want to call the completion handler it's nil.
Am I missing a way to retain my reference to self.completionHandler?
Am I declaring self.completionHandler correctly?
in app delegate:
//background fetch new RSS Feeds
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
MasterViewController *navigationController = [mainStoryboard instantiateViewControllerWithIdentifier:#"MasterView"];
MasterViewController *viewController = navigationController;
[viewController startParsingWithCompletionHandler2: ^ (UIBackgroundFetchResult completionHandler2){
completionHandler (UIBackgroundFetchResultNewData);
}];
}
in main view controller:
#property (nonatomic, strong) void (^completionHandler)(UIBackgroundFetchResult);
- (void) startParsingWithCompletionHandler2:(void (^)(UIBackgroundFetchResult))completionHandler2
{
self.completionHandler = completionHandler2;
if (self.completionHandler) {
NSLog(#"completionHandler");
}else{
NSLog(#"not completionHandler");
}
[self performSelector: #selector(stopParsing) withObject: nil afterDelay: PARSER_TIME_LIMIT];
[self.activityIndicator startAnimating];
numberOfCompletedStories = 0;
[self.parserArray removeAllObjects];
//check for RSS Site data updates
for (int lCounter = 0; lCounter < self.rssFeedAddresses.count; lCounter ++) {
RSSParser *parser = [[RSSParser alloc] init];
[parser setDelegate: self];
[self.parserArray addObject: parser];
[parser setSiteTitle: [self.rssFeedNames objectAtIndex: lCounter]];
[NSThread detachNewThreadSelector: #selector(begin:) toTarget: parser withObject: [self.rssFeedAddresses objectAtIndex: lCounter]];
}
if (self.completionHandler) {
NSLog(#"#2 completionHandler");
}else{
NSLog(#"#2 not completionHandler");
}
}
- (void) storyIsDone//called when parser completed one rss feed
{
numberOfCompletedStories ++;
if (self.completionHandler) {
NSLog(#"storyIsDone YES completion handler %i", numberOfCompletedStories);
}else{
NSLog(#"storyIsDone Not completion handler");
}
if (numberOfCompletedStories == self.rssFeedAddresses.count)
{
//if all the feeds are done cancel time-out timer
[NSObject cancelPreviousPerformRequestsWithTarget: self selector: #selector(stopParsing) object: nil];
[self.activityIndicator stopAnimating];
[self.refreshControl endRefreshing];
[[NSNotificationCenter defaultCenter] addObserver: self selector: #selector(reloadRSSfeeds) name: #"ReloadFeeds" object: nil];
canRefresh = YES;
NSLog(#"call back");
[self performSelectorOnMainThread: #selector(callCompletion) withObject: self waitUntilDone: YES];
}//else not yet complete
}
- (void) callCompletion
{
if (self.completionHandler) {
NSLog(#"callCompletion YES completion handler");
self.completionHandler (UIBackgroundFetchResultNewData);
}else{
NSLog(#"callCompletion Not completion handler");
}
}
The output is:
completionHandler
#2 completionHandler
storyIsDone Not completion handler
storyIsDone Not completion handler
storyIsDone Not completion handler
storyIsDone Not completion handler
storyIsDone Not completion handler
storyIsDone Not completion handler
storyIsDone Not completion handler
call back
callCompletion Not completion handler
I always use (nonatomic, copy) for block properties. You might try that and see if it helps.
In your code, though, I think you can just pass through the completion handler that's passed into application:performFetchWithCompletionHandler:.
In the .h file define a completion handler like this:
typedef void (^CompletionHandler)(UIBackgroundFetchResult BFR);
Also define your completion handler property like this:
#property (copy) CompletionHandler completionHandler;
Then in the .m file set it like this:
self.completionHandler = handler;
You need the (copy) keyword here so that the block which is passed through via the startParsingWithCompletionHandler2: method is copied and that copy is then retained by the main view controller.
From the docs:
Note: You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior.
See this link
EDIT:
I thought it would be good to add that you MUSTN'T call a completion handler (i.e. completionHandler(/*someArgs*/)) when the completionHandler is nil. If you do it will cause your App to crash.
To overcome this you can put in a simple check e.g.
!completionHandler ?: completionHandler(/*someArgs*/);
The above code is semantically equivalent to:
if (completionHandler != nil) {
completionHandler(/*someArgs*/)
}
it just condenses it onto one line using the ternary operator.
Class not properly initialized
When using performFetchWithCompletionHandler for background fetch - methods are called in a different order resulting in some objects not being properly initialized.
When the app is launched in the foreground these methods are called: (in order)
initWithCoder
awakeFromNib
viewDidLoad
dispatchLoadingOperation
viewDidAppear
When performing a background fetch methods are called in the following order:
initWithCoder
awakeFromNib
startParsingWithCompletionHandler2
viewDidLoad
dispatchLoadingOperation
viewDidAppear
Of particular note: vieDidLoad was called before dispatchLoadingOperation which kicked off the parsing when running in foreground.
When running in background, startParsingWithCompletionHandler2 (which also kicks off parsing when running in background) was called before viewDidLoad.
Since several objects were initialized in viewDidLoad, the parsing was begun before expected and my array was not initialized to store my parsing results. This appeared to me that the app was not launching.
While I was looking at the call back for the completion handler being nil, the real issue was the Class not being setup properly.

iOS: dispatch_block_t execute some part of code two times and I dont know why

When I call the following function:
+ (void) myMethod:(NSString *) myConstString array: (NSArray *) myArray
{
dispatch_block_t block =
^{
for (int i = 0; i < myArray.count; i++)
{
if ([#"myString1" isEqual: myConstString])
// Do some easy job here
else if ([#"myString2" isEqual: myConstString])
// Do some other easy job here
[NSThread sleepForTimeInterval: 0.5];
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"test" object:nil userInfo:nil];
};
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.example.test", NULL);
dispatch_async(backgroundQueue, block);
}
[[NSNotificationCenter...] is executed two times. I know that because method in other class which is responsible for "catch" this notification is called two times. First call is instant (and this is strange to me). Second call is after about 2 seconds (this call I like :-) ) I know how to "repair" this:
+ (void) myMethod:(NSString *) myConstString array: (NSArray *) myArray {
dispatch_block_t block =
^{
Boolean isForLoopExecuted = false;
for (int i = 0; i < myArray.count; i++)
{
if ([#"myString1" isEqual: myConstString])
// Do some easy job here
else if ([#"myString2" isEqual: myConstString])
// Do some other easy job here
[NSThread sleepForTimeInterval: 0.5];
isForLoopExecuted = true;
}
if (isForLoopExecuted)
[[NSNotificationCenter defaultCenter] postNotificationName:#"test" object:nil userInfo:nil];
};
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.example.test", NULL);
dispatch_async(backgroundQueue, block); }
After addind isForLoopExecuted everything works propertly. There is only one call after 2 seconds. This suggests that for loop is not executed when first call is made. I am really curious why this is happening. Thanks for your time!
First off, creating new background queue every time that function runs is probably not what you want. There are limits how many queues you can have, and this is right way to reach that limit quickly and crash.
Use something like this:
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), block);
Secondly, there is nothing wrong with first listing (except that background queue) and it works correctly. Notification will be sent just once.
What I would suggest is to put log when you are sending notification to see exactly how many times you are executing that function. Probably more than once. Also, time that will be spent in queue depends on parameters you are sending there. If myArray is empty, you get notification almost immediately.

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

NSOperation running but missing from NSOperationQueue

I have recently been debugging a zombie issue with operations and found out that calling cancelAllOperations on the queue didn't cancel the operation in question, and in fact, the operation queue was empty even though the operation was still running.
The structure was a viewcontroller asynchronously loading a set of images off the web and perform some changes on them. Relevant (anonymised) exerpts follow:
#implementation MyViewController
- (id) init
{
(...)
mOperationQueue = [[NSOperationQueue alloc] init];
(...)
}
- (void) viewDidAppear:(BOOL)animated
{
(...)
MyNSOperation * operation = [[MyNSOperation alloc] initWithDelegate:self andData:data];
[mOperationQueue addOperation:operation];
[operation release];
(...)
}
- (void) dealloc
{
(...)
[mOperationQueue cancelAllOperations];
[mOperationQueue release];
(...)
}
- (void) imagesLoaded:(NSArray *)images
{
(...)
}
And the operation in question:
#implementation MyNSOperation
- (id) initWithDelegate:(id)delegate andData:(NSDictionary *)data
{
self = [super init];
if (self)
{
mDelegate = delegate; // weak reference
mData = [data retain];
(...)
}
return self;
}
- (void) main
{
NSAutoReleasePool * pool = [[NSAutoReleasePool alloc] init];
mImages = [[NSMutableArray alloc] init];
// load and compose images
mAlteredImages = (...)
[self performSelectorOnMainThread:#selector(operationCompleted) withObject:nil waitUntilDone:YES];
[pool release];
}
- (void)operationCompleted
{
if (![self isCancelled])
{
[mDelegate imagesLoaded:mAlteredImages];
}
}
The observed flow is as follows:
The viewcontroller is shown, calling init and viewDidAppear starting the operation.
[mOperationQueue operations] contains exactly one element;
Shortly after, the operation enters main and
The viewcontroller is exited by the user before the operation completes.
dealloc is called on the viewcontroller (because the operation keeps a weak reference)
[mOperationQueue operations] contains zero (!) elements
cancelAllOperations is sent to the operation queue
[NSOperation cancel] is not called, resulting in an app-visible bogus state.
dealloc finishes
the operation completes
isCancelled returns false, resulting in a zombie call
The documentation of NSOperationQueue however explicitly states that "Operations remain queued until they finish their task." which looks like a breach of contract.
I've fixed the crash by keeping a reference to the operation and manually sending cancel, but I would like to know why the original approach isn't working to prevent further problems. Can someone shed some light on this?
Thanks in advance.
cancelAllOperations does not cancel an already started operation. It only informs the operation about that fact and let the operation cancel themself, whenever it want. Thus, you can get a raise condition. Proceed with deallocacion, after you are sure that the operation is canceled.

Resources