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.
Related
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
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;
}
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();
});
}
}
I want to create a completion handler for a certain class, instead of firing off the class's main code and waiting for a delegate callback. I've read through the Apple documentation and they don't seem to give a very good example of how to directly implement something like this.
You need to treat the completion block just like a variable. The method will accept a block as part of it's parameters, then store it for later.
- (void)myMethodWithCompletionHandler:(void (^)(id, NSError*))handler;
You can typedef that block type for easier reading:
typedef void (^CompletionBlock)(id, NSError*);
And then store your block as an instance variable:
In your #interface: CompletionBlock _block;
In the myMethod.. _block = [handler copy]
Then when you want the completion block to execute you just call it like a regular block:
_block(myData, error);
If it was for an asynchronous method you could do it like this
- (void)asynchronousTaskWithCompletion:(void (^)(void))completion;
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Some long running task you want on another thread
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion();
}
});
});
}
this would be invoked with
[self asynchronousTaskWithCompletion:^{
NSLog(#"It finished");
}];
Something to note is the guard to make sure that completion is pointing to something otherwise we will crash if we try to execute it.
Another way I often use blocks for completion handlers is when a viewController has finished and want's to be popped from a navigation stack.
#interface MyViewController : UIViewController
#property (nonatomic, copy) void (^onCompletion)(void);
#end
#implementation MyViewController
- (IBAction)doneTapped;
{
if (self.onCompletion) {
self.onCompletion();
}
}
#end
You would set the completion block when pushing this view onto the stack
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender;
{
MyViewController *myViewController = segue.destinationViewController;
myViewController.onCompletion = ^{
[self.navigationController popViewControllerAnimated:YES];
};
}
Heres an example for a method that takes a String and a completion handler as variables. The completion handler can also receive a String.
Swift 2.2 Syntax
Defintion:
func doSomething(input: String, completion: (result: String) -> Void {
print(input)
completion(result: "we are done!")
}
Calling the function:
doSomething("cool put string!") { (result) in
print(result)
}
Chris C's answer is correct (and was very helpful to me) with one caveat:
Placing the declaration CompletionBlock _block; in #interface is not thread safe.
Put CompletionBlock _block = [handler copy]; in myMethod… instead if there is any possibility that myMethod… will be called from multiple threads (or dispatch queues).
Thanks #Chris C.
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.