Why my completion handler is never called? - objective-c

I'm trying to call a completion handler but it's never called.
I'm doing something wrong ?
I see the first NSLog ("Enter in my second funcition"), but the second NSlog ("completion handler") never apears in my console.
Here is the definition of the function and the caller with completion
- (void)myFirstFunction:(BOOL)selected numberOfBrothers:(int)brothers completion:(void (^)(void))completion
- (void)mySecondFunction
{
NSLog(#"Enter in my Second function");
[self myFirstFunction:true
numberOfBrothers:1
completion:^{
NSLog(#"COMPLETION HANDLER");
}];
}
- (void)myFirstFunction:(BOOL)selected numberOfBrothers:(int)brothers completion:(void (^)(void))completion
{
NSLog(#"Enter in my First function");
}
thank you

Your first function does not call the completion.
- (void)myFirstFunction:(BOOL)selected numberOfBrothers:(int)brothers completion:(void (^)(void))completion
{
NSLog(#"Enter in my First function");
completion();
}

Related

ReactiveCocoa: block is never executed

In my app, I have a download manager. After any of tasks is finished I need to get all data for tableView again and reload it. But I can't get data inside RACObserve signal. Here's my code.
NSArray *activeTasks = [[DownloadManager instance] tasksToProcess];
for (DownloadTask *task in activeTasks) {
[[[self
checkTask:task]
map:^(id value
return [self fetchDownloadedData];
}]
subscribeNext:^(NSArray *models) {
// models returns RACDynamicSignal not NSArray
NSLog(#"%#", models); // <RACDynamicSignal: 0x11611cb50> name:
NSLog(#"checktask next");
} completed:^{
// This is never being executed
NSLog(#"checktask completed");
}];
}
- (RACSignal *)checkTask: (DownloadTask *)task {
return [RACObserve(task, isFinished) map:^id(id _) {
return nil;
}];
}
- (RACSignal *)fetchDownloadedData {
return [[MyCoreDataModel fetchAll] flattenMap:^id(NSArray *models) {
// This is never being executed
return [models filter:^BOOL(MyCoreDataModel *model) {
return model.isDownloaded;
}];
}];
}
- (RACSignal *)fetchAll
{
return [[[MyCoreDataModel findAll] sortBy:#"title"] fetch];
}
Would be great if someone helps me getting where is my mistake is. Thanks in advance.
There are few mistakes:
In map function you use method which returns RACSignal - it's not correct. You should use flattenMap instead.
Complete block will never be called, because RACObserve - hot signal, it only sends next event.
I wrote small example, I hope it helps you.
NSMutableArray<RACSignal *> *signals = [NSMutableArray array];
for (DownloadTask *task in activeTasks) {
RACSignal *signal = [[RACObserve(task, isFinished) ignore:#NO] take:1];
[signals addObject:signal];
}
#weakify(self);
[[[RACSignal merge:signals] flattenMap:^RACStream *(id _) {
#strongify(self);
return [self fetchDownloadedData];
}] subscribeNext:^(NSArray *models) {
}];
Here I created array of signals. Each signal - observing the isFinished property. Also I added ignore:#NO] take:1]; - I think it's more right, because you only need YES value and after that no observe anymore (take:1). Then I merge these signals and each time when someone of them sends finished state, we fetch data.
Please, let me know if something is not understand, I try to explain more clearly.

"disable" button-->method until operation is done

I am using the following method that invoked by pressing a button thru sprite builder.
- (void)method {
//static dispatch_once_t pred; //
//dispatch_once(&pred, ^{ // run only once code below
[self performSelector:#selector(aaa) withObject:nil afterDelay:0.f];
[self performSelector:#selector(bbb) withObject:nil afterDelay:1.f];
[self performSelector:#selector(ccc) withObject:nil afterDelay:1.5f];
[self performSelector:#selector(ddd) withObject:nil afterDelay:4.f];
[self performSelector:#selector(eee) withObject:nil afterDelay:4.5f];
CCLOG(#"Received a touch");
//}); //run only once code above
}
as you can see from the comments i tried running it once. that works good, but if a user comes back to this scene, it's disabled until you restart the app.
how can i block this method from being executed a second time until the first time is done.
i know the code is rough, i'm just learning here....
thanks in advance.
Add a BOOL instance variable which serves as a flag as to whether or not this action is taking place. As soon as the method starts, check the flag. If you need to execute, set the flag.
Add another performSelector:withObject:afterDelay: which calls a method to reset the flag back.
#implementation SomeClass {
BOOL _onceAtATime;
}
- (void)method {
#synchronized(self) {
if (!_onceAtATime) {
_onceAtATime = YES;
// do all the stuff you need to do
[self performSelector:#selector(resetOnceAtATime)
withObject:nil
afterDelay:delay];
// where delay is sufficiently long enough for all the code you
// are executing to complete
}
}
}
- (void)resetOnceAtATime {
_onceAtATime = NO;
}
#end
A simpler way would be to use a serial NSOperationQueue as such (in Swift):
class ViewController: UIViewController {
let queue: NSOperationQueue
required init(coder aDecoder: NSCoder) {
queue = NSOperationQueue()
queue.maxConcurrentOperationCount = 1
super.init(coder: aDecoder)
}
#IBAction func go(sender: AnyObject) {
if (queue.operationCount == 0) {
queue.addOperationWithBlock() {
// do the first slow thing here
}
queue.addOperationWithBlock() {
// and the next slow thing here
}
// ..and so on
}
else {
NSLog("busy doing those things")
}
}
}

Wait for two async methods to complete

I'd like to init a model, let the model do some async stuff and present a new viewcontroller once completed. But how do i wait for the two async methods to be completed and how do I setup the callback method?
Pseudocode
In my StartViewController.m:
-(void)openArticle
{
article = [Article initWithObject:someObject];
article.callback = changeView;
}
-(void)changeView
{
[self presentViewController:someController];
}
In my ArticleModel.m:
-(void)initWithObject:someObject
{
[self loadImage]
[self geoCode]
}
-(void)loadImage
{
runAsyncMethod: success:^() // This one is actually a AFNetworking setImageWithURLRequest
}
-(void)geoCode
{
runAnotherAsyncMethod: success:^() // This one is actually a geocodeAddressString operation
}
You can achieve this using dispatch_groups
- (void)initWithObject:(id)someObject
{
self = [super init];
if (self) {
self.dispatch_group = dispatch_group_create();
[self loadImage]
[self geoCode]
dispatch_group_notify(self.dispatch_group, dispatch_get_main_queue(), ^{
NSLog(#"Push new view controller");
});
}
return self;
}
- (void)loadImage
{
dispatch_group_enter(self.dispatch_group);
__weak __typeof(self) weakSelf = self;
runAsyncMethod: success:^{
__typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf.dispatch_group) {
dispatch_group_leave(strongSelf.dispatch_group); // You need to ensure that this is called in both success and failure
}
}
}
- (void)geoCode
{
dispatch_group_enter(self.dispatch_group);
__weak __typeof(self) weakSelf = self;
runAnotherAsyncMethod: success:^{
__typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf.dispatch_group) {
dispatch_group_leave(strongSelf.dispatch_group);
}
}
}
You do not wait. If you wait, it isn't asynchronous! You would be losing the entire point of asynchronous if you were to wait.
What you do is, when your success handler is called, you step out to the main thread (just in case you got called back on a background thread) and now do whatever you need to do. In other words, you just let your success handler get called whenever it happens to get called.
In your case, you might like to chain the things you want to do:
Call loadImage
In its callback, call geoCode
In its callback, step out to the main thread and present the new view controller.
You can use dispatch_group so that when a method is over, it just leaves the group. I use a similar code myself and it works like a charm.
- (void)initWithObject:someObject {
// Create a dispatch group
dispatch_group_t group = dispatch_group_create();
[self loadImageWithDispatchGroup:group];
[self geoCodeWithDispatchGroup:group];
// Here we wait for all the requests to finish
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// Do whatever you need to do when all requests are finished
});
}
- (void)loadImageWithDispatchGroup:(dispatch_group_t)group {
dispatch_group_enter(group);
runAsyncMethod: success:^() // This one is actually a AFNetworking setImageWithURLRequest
// In your success or failure AFNetworking method, call this as soon as the request ended
dispatch_group_leave(group);
}
- (void)geoCodeWithDispatchGroup:(dispatch_group_t)group {
dispatch_group_enter(group);
runAnotherAsyncMethod: success:^() // This one is actually a geocodeAddressString operation
// In your success async geocode callback method, call this as soon as the request ended
dispatch_group_leave(group);
}
I do not known your needs but native GCD way to wait several asynch tasks is
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html#//apple_ref/c/func/dispatch_barrier_async

How to pass a SEL to dispatch_async method

I am trying to create a generic method that takes a SEL as a parameter and passes it to dispatch_async for execution, but i am clueless how to execute the passed in SEL.
Can anyone here help me please.
// Test.m
-(void) executeMe
{
NSLog(#"Hello");
}
- (void)viewDidLoad
{
[super viewDidLoad];
SEL executeSel = #selector(executeMe);
[_pInst Common_Dispatch: executeSel];
}
// Common.m
-(void) Common_Dispatch:(SEL) aSelector
{
dispatch_async(iDispatchWorkerQueue, ^(void) {
// How to execute aSelector here?
});
}
You need to also have a "target" parameter on your Common_Dispatch method since you need to call the selector on a specific object.
- (void)viewDidLoad {
[super viewDidLoad];
SEL executeSel = #selector(executeMe);
[_pInst Common_Dispatch:executeSel target:self];
}
- (void)Common_Dispatch:(SEL)aSelector target:(id)target {
dispatch_async(iDispatchWorkerQueue, ^(void) {
[target performSelector:aSelector];
});
}
BTW - standard naming conventions state that method names should begin with lowercase and use camelCase. Your method should be commonDispatch.
Alternatively, you could use a block parameter, e.g.
- (void)commonDispatch:(void (^)(void))block
{
dispatch_async(iDispatchWorkerQueue, block);
}
You'd then invoke that as:
[_pInst commonDispatch:^{
[self executeMe];
}];
This way, you could use this dispatcher to call methods like executeMe which take no parameters, or to dispatch methods that take lots of parameters, e.g.:
[_pInst commonDispatch:^{
[self executeOtherMethodForURL:url requestType:type priority:priority];
}];
Or more complicated situations, too:
[_pInst commonDispatch:^{
[self executeOtherMethodForURL:url requestType:type priority:priority];
dispatch_async(dispatch_get_main_queue(), ^{
// update my UI to say that the request is done
});
}];
You simply call the performSelector method, like this:
[self performSelector:aSelector];
There are other useful overrides to performSelector you'll find.
Edit
The target of the selector will also have to be passed as a param:
// Test.m
-(void) executeMe
{
NSLog(#"Hello");
}
- (void)viewDidLoad
{
[super viewDidLoad];
SEL executeSel = #selector(executeMe);
[_pInst Common_Dispatch: executeSel target:self];
}
// Common.m
-(void) Common_Dispatch:(SEL) aSelector target:(id)target
{
dispatch_async(iDispatchWorkerQueue, ^(void) {
[target performSelector:aSelector];
});
}

Call Cocoa ObjC method from NSEvent Block

Why doesn't this code work:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[NSEvent addGlobalMonitorForEventsMatchingMask:(NSScrollWheelMask) handler:^(struct NSEvent *event){
[scrollEvent:event];
}];
}
- (void)scrollEvent:event {
NSLog( #"scroll" );
}
It says "'scrollEvent' undeclared".
I'm just learning objc and cocoa, so I assume this is just a simple error.
Your code appears to have some bugs in it. I've cleaned up some, please see below.
- (void) applicationDidFinishLaunching:(NSNotification *) aNotification {
[NSEvent addGlobalMonitorForEventsMatchingMask: NSScrollWheelMask handler:^(NSEvent *event){
[self scrollEvent: event];
}];
}
- (void) scrollEvent: (NSEvent *) event {
NSLog( #"scroll" );
}
To summarize:
Your argument to the block should have been an NSEvent *, not a
struct NSEvent *.
Your invocation of scrollEvent needed to be sent to self.
Your implementation of scrollEvent had an incorrect signature.
Hope that helps and good luck with the program.
I believe your :event parameter of the scrollEvent method needs to have a type (NSEvent*) for this to be a valid method signature.
- (void)scrollEvent:(NSEvent*)event {
NSLog( #"scroll" );
}