I have declared the method as follows:
- (void)downloadCSVs:(void (^)(void))completion
Its body is:
- (void)downloadCSVs:(void (^)(void))completion
{
[[Singleton sharedData] downloadCSVFilesFromServer:<MY_URL>];
}
and calling this method as:
[self downloadCSVs:^
{
NSLog(#"Download Completed!");
}];
But its after download, it is not executing the NSLog.
Please let me know where I am wrong.
Your block isn't being called as there is no attempt to call it.
The following method accepts the block as a parameter and, in turn, calls [Singleton downloadCSVFilesFromServer:] but it does not pass the block to this method and does not call it itself:
- (void)downloadCSVs:(void (^)(void))completion
{
[[Singleton sharedData] downloadCSVFilesFromServer:<MY_URL>];
}
You need to extend the [Singleton downloadCSVFilesFromServer:] method to accept the block parameter and call it when it's complete.
Related
In my code, if I use the block directly:
- (IBAction)checkPendingAction:(UIButton *)sender {
self.block(sender.titleLabel.text); // if the block is no realize, there will report EXC_BAD_ACCESS(code=x, address=0x11xx)
}
If I use delegate, I can use the below code to check my delegate and delegate method if is realize:
if ([self.delegate respondsToSelector:#selector(myDelegateMethod:)]) {
[self.delegate tabBarClickWriteButton:self];
}
So, can I check the block if is realize in iOS?
make your block a property with 'strong' reference. and use weak reference of self in it if needed.
before calling block just check
if(block) {
block(data);
}
You need check block as below
if (self.yourblock != nil) {
self.yourblock(self.titleLabel.text);
}
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.
I want to define the method which will contain the block as an argument but block should be run on the completion of the method.
For Example:
[picker dismissViewControllerAnimated:YES completion:^{
imageThumb = pickedImage;
imageViewThumb.image = imageThumb;
}];
Please have a look what i did yet.
I declared the method in .h file-
-(void)resizeImageForSmoothness: (int) imageSmoothness completion: (void (^)(void))completion;
I implemented it in .m file-
-(void)resizeImageForSmoothness:(int)imageSmoothness completion: (void (^)(void))completion
{
// Here i performed my image resizing activity
}
How can my code will know that method has been completed and then run the completion block?
How can we declare and define such method?
How to store the block depends on how you do your stuff. If it's a synchronous operation (that is, the method blocks until whole operation is complete) you simply call it like a function:
- (void)fooWithHandler:(void(^)())handler
{
// Do things.
handler();
}
If the operation is asynchronous, you might want to store the block in a variable or even a dictionary. In this case you need to copy the block. You can either do this via the low-level Block_copy and Block_release C functions, but you can also treat a block like an Objective-C object! (Xcode doesn't provide autocompletion for this, for some reason.)
#interface MyClass {
void (^myHandler)();
}
- (void)fooWithHandler:(void(^)())handler
#end
#implementation MyClass
- (void)fooWithHandler:(void(^)())handler
{
myHandler = [handler copy];
// Do things.
// Then, when you're done (this is probably in another method):
if (myHandler) {
myHandler();
myHandler = nil;
}
}
#end
You can do something like that and use the return type et parameter you might need :
- (void)doStuffAndExecute:(void (^)(void))handler
{
// do stuff
handler();
}
I have this if statement checking if my delegate has implemented a given method:
if ([[self delegate] respondsToSelector:#selector(didFinishSettingNotificationOnDialog:)])
{
[self.delegate didFinishSettingNotificationOnDialog:self withNotification:notification];
}
However my code is not getting executed within the If statement. I have other delegate calls working between these objects and if I remove the if statement and just call
[self.delegate didFinishSettingNotificationOnDialog:self withNotification:notification];
on its own it works!
My delegate does implement correctly:
- (void)didFinishSettingNotificationOnDialog:(NotificationDialogView *)dialogView withNotification:(NotificationContext *)setNotification{
NSLog(#"Notification is: %#", setNotification);
}
So what am I doing wrong?
The name of the method is wrong. It should be didFinishSettingNotificationOnDialog:withNotification:.
Try this:
if ([[self delegate] respondsToSelector:#selector(didFinishSettingNotificationOnDialog:withNotification:)]) {
[self.delegate didFinishSettingNotificationOnDialog:self withNotification:notification];
}
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.