In my code, I am running a local server (CocoaHTTPServer). When the server receives a request, it creates a thread and passes control to a certain method ( - (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path, perhaps irrelevant here).
I need to read a list of local assets and return the result. The API call ( [assetsLibrary enumerateGroupsWithTypes:ALAssetsGroupAll usingBlock:^(ALAssetsGroup *group, BOOL *stop) ... ) is asynchronous.
Since the HTTPResponse needs to wait until the API call has finished, I have created a flag called _isProcessing , which I set before making the API call. After the call is finished, I am unsetting the flag and returning the HTTP request. The code to wait looks like:
// the API call is non-blocking. hence, wait in a loop until the command has finished
samCommand->isProcessing = YES;
while (samCommand->isProcessing) {
usleep(100*1000);
}
The API call calls a delegate method upon finishing its task as follows:
// to be called at the end of an asynch operation (eg: reading local asset list)
- (void) commandDidFinish {
// flag to open the lock
isProcessing = NO;
}
This works, but will perhaps require performance enhancements. How can I use anything (run-loop etc) here to improve upon the performance.
Edit following weichsel solution using dispatch_semaphore
Following weichsel's solution, I created a semaphore. The sequence of my code is:
CocoaHTTPServer receives a request and hence creates a new thread
It calls the static method of a Command class to execute the request
The Command class creates a new command Object calls another class (using reflection) which calls ALAsset APIs and passes the command Object to it
Upon returning, the ALAsset API call calls the delegate method of
the command class
I have hence embedded semaphores in appropriate locations. However, the semaphore's wait loop just doesnt end sometimes. The normal output should be:
2014-02-07 11:27:23:214 MM2Beta[7306:1103] HTTPServer: Started HTTP server on port 1978
2014-02-07 11:27:23:887 MM2Beta[7306:6303] created semaphore 0x1f890670->0x1f8950a0
2014-02-07 11:27:23:887 MM2Beta[7306:6303] calling execute with 0x1f890670
2014-02-07 11:27:23:887 MM2Beta[7306:6303] starting wait loop 0x1f890670->0x1f8950a0
2014-02-07 11:27:23:887 MM2Beta[7306:907] calling getAssetsList with delegate 0x1f890670
2014-02-07 11:27:24:108 MM2Beta[7306:907] calling delegate [0x1f890670 commandDidFinish]
2014-02-07 11:27:24:108 MM2Beta[7306:907] releasing semaphore 0x1f890670->0x1f8950a0
2014-02-07 11:27:24:109 MM2Beta[7306:6303] ending wait loop 0x1f890670->0x0
In every few runs, the last step ( ending wait loop 0x1f890670->0x0 doesnt occur). Hence, the wait loop never ends. Sometimes the code crashes too, exactly at the same point. Any clue what is wrong here.
My code is as follows:
#implementation SAMCommand {
NSData* resultData;
dispatch_semaphore_t semaphore; // a lock to establish whether the command has been processed
}
// construct the object, ensuring that the "command" field is present in the jsonString
+(NSData*) createAndExecuteCommandWithJSONParamsAs:(NSString *)jsonString {
SAMCommand* samCommand = [[SAMCommand alloc] init];
samCommand.commandParams = [jsonString dictionaryFromJSON];
if(COMPONENT==nil || COMMAND==nil){
DDLogError(#"command not found in %#",jsonString);
return nil;
}
samCommand->semaphore = dispatch_semaphore_create(0);
DDLogInfo(#"created semaphore %p->%p",samCommand,samCommand->semaphore);
// to execute a command contained in the jsonString, we use reflection.
DDLogInfo(#"calling execute with %p",samCommand);
[NSClassFromString(COMPONENT) performSelectorOnMainThread:NSSelectorFromString([NSString stringWithFormat:#"%#_%#_%#:",COMMAND,MEDIA_SOURCE,MEDIA_TYPE]) withObject:samCommand waitUntilDone:NO];
// the above calls are non-blocking. hence, wait in a loop until the command has finished
DDLogInfo(#"starting wait loop %p->%p",samCommand,samCommand->semaphore);
dispatch_semaphore_wait(samCommand->semaphore, DISPATCH_TIME_FOREVER);
DDLogInfo(#"ending wait loop %p->%p",samCommand,samCommand->semaphore);
DDLogInfo(#"");
// return the data
return samCommand->resultData;
}
// to be called at the end of an asynch operation (eg: reading local asset list)
- (void) commandDidFinish {
// flag to release the lock
DDLogInfo(#"releasing semaphore %p->%p",self,semaphore);
dispatch_semaphore_signal(semaphore);
semaphore = nil;
}
#end
I got it to work :)
Finally, what seems to work stably is creating the semaphore, and passing it to the ALAsset asynch API calls, and releasing it at the end of the call. Earlier, I was calling a delegate method of the class where I had created the semaphore, and the semaphore object was somehow getting releases. Unsure of what was happening there really.
You can use semaphore to block execution of the current queue until another one returns.
The basic pattern is:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[assetsLibrary enumerateAssetsUsingBlock^(ALAsset *result, NSUInteger index, BOOL *stop):^{
...
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
You can find a full example of this method in Apple's MTAudioProcessingTap Xcode Project:
https://developer.apple.com/library/ios/samplecode/AudioTapProcessor
The relevant lines start at MYViewController.m:86
NSRunLoop has a method called runUntilDate: which should work for you with dates in near future like 1s ahead or so. That way you can replace your sleep call in the while loop for example with:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeintervalSinveNow:1]
Related
I try to display window in other process using Distributed objects.
Process A invoke remotely through Distributed Object method from process B which display dialog. Something wrong happens if I try to wait for results.
The method looks like that:
-(BOOL)showWindow //method invoked through distributed objects
{
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
[object showDialog:^(BOOL result){ //this methods creates and display window
NSLog(#"Block called");
dispatch_semaphor_signal(sem);
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
return YES;
}
The function showWindow never ends. If I comment dispatch_semaphore_wait "Block called" is displayed and window is show.
I checked different variant synchronization, I tried to run this code using dispatch_sync or async but nothing helped.
I will be gratefull for help.
Kon
The problem is that DO requires that the run loop runs in order to process communication between the two sides. Basically, the code you showed sends a request to the remote side, the one which hosts the real object that object is a proxy for. The request carries a reference to your local block object. The remote side effectively gets a proxy for that.
Some time later, the remote side invokes its block proxy. That causes a request to be sent back to this process and thread. However, this thread can't receive that request because it is blocked in dispatch_semaphore_wait(). It is not attending to any communication channels.
So, both sides deadlock. The local side waits forever for a semaphore that will never be signaled and the remote side waits forever for its request to run the block to complete.
Thought of another way, the semaphore is not signaled by the remote process. You've asked for it to be signaled by this same thread which is waiting on it. It's just that, if it were possible, this thread would do so in response to an event sent by another process. Having a thread be responsible for signaling the semaphore that it's waiting on is a pretty clear, immediate self-deadlock.
Can you change -showWindow to not be synchronous? Why is it using a semaphore and waiting? Why can't it just fire off the request and then return back to the calling code, which should not assume the dialog has completed and instead return all the way back to the main event loop? Whatever work should happen "next" should be put into the completion block and will just be invoked asynchronously when the dialog completes.
If you really, really need this method to be synchronous, then you will have to use the run loop to wait instead of a dispatch semaphore. Something like:
-(BOOL)showWindow //method invoked through distributed objects
{
__block BOOL done = NO;
[object showDialog:^(BOOL result){ //this methods creates and display window
NSLog(#"Block called");
done = YES;
}];
NSString* mode = #"com.yourcompany.yourapp.privatemode";
NSConnection* conn = [(NSDistantObject*)object connectionForProxy];
[conn addRequestMode:mode];
while (!done)
[[NSRunLoop currentRunLoop] runMode:mode beforeDate:[NSDate distantFuture]];
[conn removeRequestMode:mode];
return YES;
}
Probably, you should actually add the private mode to the connection when it's first created and leave it there for its lifetime. You don't want to use any of the predefined modes for this because then unexpected things may fire while you're waiting and your code becomes unexpectedly re-entrant, etc.
I must be misunderstanding dispatch_group because my dispatch_group_notify call is running before the end of the async calls made within individual dispatch_group_async blocks. Here's my code:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t dispatchGroup = dispatch_group_create();
// create operation for each HKTypeIdentifier for which we want to retrieve information
for( NSString *hkType in typesToRetrieve){
dispatch_group_async(dispatchGroup, queue, ^{
// this method runs several HK queries each with a completion block as indicated below
[self getDataForHKQuantity: hkType withCompletion:^(NSArray *results) {
// this completion blocks runs asynchronously as HK query completion block
// I want to runCompletionBlock only after
// all these processResultsArray calls have finished
[self processResultsArray:results];
}];
});
}
dispatch_group_notify(dispatchGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self runCompletionCheck];
});
The method getDataForHKQuantity in turn runs an asynchronous query to HealthKit with a completion block. I need to run runCompletionCheck after all these completion blocks for the HealthKit queries have run, but what is happening now is that runCompletionCheck is running before the code in the queries' completion blocks has run. To me that means that dispatch_group_notify along with dispatch_group_async don't work the way I need, so what am I doing wrong or what's the best way to handle this?
Overall goal: make a bunch of concurrent queries to HealthKit, run their completion blocks, then when all those completion blocks run, run a final method.
The problem is two fold. First, the health kit queries don't always run their completion blocks. I started by using a counter system, with a counter in the health kit queries' completion blocks. That's what told me that these completion blocks don't always run. Second, I don't know how many queries I am trying to run because it depends on what data sources the user has.
So, question, how can I wait until all the completion blocks from a series of health kit queries have run before running a final method?
Your -getDataForHKQuantity:withCompletion: method is asynchronous. So, through your dispatch groups you are syncing the calls to these methods, but not the work done in the methods themselves.
In other words, you are nesting two asynchronous calls, but syncing only the first level through you dispatch groups.
You'll need to come up with a different strategy for controlling your program flow.
Two examples:
1. Using Semaphores (blocking)
Some time ago, I used semaphores for a similar task, not sure it's the best strategy, but in your case it would go sth like:
semaphore = dispatch_semaphore_create(0);
for( NSString *hkType in typesToRetrieve)
{
[self getDataForHKQuantity: hkType withCompletion:^(NSArray *results) {
// register running method here
[self processResultsArray:results];
if (isLastMethod) // need to keep track of concurrent methods running
{
dispatch_semaphore_signal(semaphore);
}
}];
}
// your program will wait here until all calls to getDataForHKQuantity complete
// so you could run the whole thing in a background thread and wait for it to finish
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
2. Using dispatch_group
dispatch_group_t serviceGroup = dispatch_group_create();
for( NSString *hkType in typesToRetrieve)
{
dispatch_group_enter(serviceGroup);
[self getDataForHKQuantity: hkType withCompletion:^(NSArray *results) {
[self processResultsArray:results];
dispatch_group_leave(serviceGroup);
}];
}
dispatch_group_notify(serviceGroup,dispatch_get_main_queue(),^{
// Won't get here until everything has finished
});
Also check this link for further info.
i want integrate the Background Fetch in my iOS app, i have enabled the Background Fetch in the capabilities of the project, then i have insert this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
to enable the background fetch with minimum interval, then in App delegate i insert the delegate method:
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
NSLog(#"Call fetch iCloud");
[[MySingleton sharedManager] startCheckOnCloud];
}
My question is this, i know that i have to call this:
completionHandler(UIBackgroundFetchResultNewData);
somewhere, i have see a lot of example that insert that completionHandler in the performFetchWithCompletionHandler delegate method, but in that method i call a NSOperation in a Singleton, that operation check che iCloud folder and make some changes in a Sqlite DB, if i do this:
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
NSLog(#"Call fetch iCloud");
[[MySingleton sharedManager] startCheckOnCloud];
completionHandler(UIBackgroundFetchResultNewData);
}
the operation start and do anything, maybe because the system make it sleep instantly, instead if i remove the completionHandler give me this warning:
Warning: Application delegate received call to -application:performFetchWithCompletionHandler: but the completion handler was never called.
so my question is, how i can handle the completionHandler with NSOperation?
You must call the completion handler with 30 seconds, but you don't have to block the main thread while you perform the query.
It appears that internally UIKit is detecting that you have not stored a strong reference to the completionHandler and then logging that warning. If you perform something simple like this:
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
// Start asynchronous NSOperation, or some other check
// Ideally the NSOperation would notify when it has completed, but just for
// illustrative purposes, call the completion block after 20 seconds.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 20 * NSEC_PER_SEC),
dispatch_get_main_queue(), ^{
// Check result of your operation and call completion block with the result
completionHandler(UIBackgroundFetchResultNewData);
});
}
You will notice that the warning isn't logged even, though the method exited before calling the completion block.
I've had similar situation in my app too.
What I did was to pass the completion handler to my singleton and whenever my code path has ended in operation, either on success or error, I check if I have a reference to a completion block. if so, I simply call the completion handler with appropriate results, if not, that means I'm not in background fetch mode.
Don't forget to set your completion handler's reference to nil in your singleton after wards, because it might fool your instance in the consequent operations.
I did something else too, because of that mandatory 30 second timeout on background fetch, before starting my operation, I check if I'm in background fetch mode, if so, I schedule a timer for 25 seconds (5 second threshold for assurance) and handle the situation myself. if you don't do this, and your app fails to meet 30 second timeout on calling completion handler too many times, you'll end up blocking your app from background fetch on the user's device.
good luck
At some point, your app will know when it's done fetching and processing new data. That can be whenever a new file was stored to disk, new records were inserted in Core Data, or when a web page finished loading.
As soon as that happens, you have to determine whether there was indeed new data, and then call the completion handler with the correct argument. More than likely, that means that you'll have to pass the completion handler to other objects. startCheckOnCloud will become startCheckOnCloudWithCompletionHandler:, and if that method isn't the one to actually do the fetch, you pass the completion handler to a method that gets called in startCheckOnCloudWithCompletionHandler: that does do the fetch.
I also have this problem, I finally solved my mistake is that, due to the presence of the asynchronous operation in the fetch callback, when you start using __block UIBackgroundFetchResult assignment to this:
UIBackgroundFetchResult __block result = UIBackgroundFetchResultNoData;
result = UIBackgroundFetchResultNoData;
post: return; return; because, "did not lead to finally, the method is called," completionHandler (result); "
so it appears a mistake:" the completion handler was never called "," return; "get rid of making the final call methods:" completionHandler (result); "there is no mistake. Under the summary, is must call the method: completionHandler (); can not be wrong.
Thx.
From the docs:
The completion block you provide is executed when the value returned by the isFinished method changes to YES. Thus, this block is executed by the operation object after the operation’s primary task is finished or cancelled.
I'm using RestKit/AFNetworking, if that matters.
I have multiple dependencies in my NSOperation in a OperationQueue. I use the completion block to set some variables (appending the results to an array) that my child requires.
(task1,...,taskN) -> taskA
taskA addDependency: task1-taskN
Will taskA receive incomplete data since the child can execute before the completion block is fired?
Reference
Do NSOperations and their completionBlocks run concurrently?
I did a simple test by adding a sleep in my completion block and I had a different result. The completion block runs in the main thread. While all the completion block are sleeping, the child task ran.
As I discuss below under "a few observations", you have no assurances that this final dependent operation will not start before your other sundry AFNetworking completion blocks have finished. It strikes me that if this final operation really needs to wait for these completion blocks to finish, then you have a couple of alternatives:
Use semaphores within each of the n the completion blocks to signal when they're done and have the completion operation wait for n signals; or
Don't queue this final operation up front, but rather have your completion blocks for the individual uploads keep track of how many pending uploads are still incomplete, and when it falls to zero, then initiate the final "post" operation.
As you pointed out in your comments, you could wrap your invocation of the AFNetworking operation and its completion handler in your own operation, at which point you can then use the standard addDependency mechanism.
You could abandon the addDependency approach (which adds an observer on the isFinished key of the operation upon which this operation is dependent, and once all those dependencies are resolved, performs the isReady KVN; the problem being that this can theoretically happen before your completion block is done) and replace it with your own isReady logic. For example, imagine you had a post operation which you could add your own key dependencies and remove them manually in your completion block, rather than having them removed automatically upon isFinished. Thus, you custom operation
#interface PostOperation ()
#property (nonatomic, getter = isReady) BOOL ready;
#property (nonatomic, strong) NSMutableArray *keys;
#end
#implementation PostOperation
#synthesize ready = _ready;
- (void)addKeyDependency:(id)key {
if (!self.keys)
self.keys = [NSMutableArray arrayWithObject:key];
else
[self.keys addObject:key];
self.ready = NO;
}
- (void)removeKeyDependency:(id)key {
[self.keys removeObject:key];
if ([self.keys count] == 0)
self.ready = YES;
}
- (void)setReady:(BOOL)ready {
if (ready != _ready) {
[self willChangeValueForKey:#"isReady"];
_ready = ready;
[self didChangeValueForKey:#"isReady"];
}
}
- (void)addDependency:(NSOperation *)operation{
NSAssert(FALSE, #"You should not use addDependency with this custom operation");
}
Then, your app code could do something like, using addKeyDependency rather than addDependency, and explicitly either removeKeyDependency or cancel in the completion blocks:
PostOperation *postOperation = [[PostOperation alloc] init];
for (NSInteger i = 0; i < numberOfImages; i++) {
NSURL *url = ...
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSString *key = [url absoluteString]; // or you could use whatever unique value you want
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// update your model or do whatever
// now inform the post operation that this operation is done
[postOperation removeKeyDependency:key];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// handle the error any way you want
// perhaps you want to cancel the postOperation; you'd either cancel it or remove the dependency
[postOperation cancel];
}];
[postOperation addKeyDependency:key];
[queue addOperation:operation];
}
[queue addOperation:postOperation];
This is using AFHTTPRequestOperation, and you'd obviously replace all of this logic with the appropriate AFNetworking operation for your upload, but hopefully it illustrates the idea.
Original answer:
A few observations:
As I think you concluded, when your operation completes, it (a) initiates its completion block; (b) makes the queue available for other operations (either operations that had not yet started because of maxConcurrentOperationCount, or because of dependencies between the operations). I do not believe that you have any assurances that the completion block will be done before that next operation commences.
Empirically, it looks like the dependent operation does not actually trigger until after the completion blocks are done, but (a) I don't see that documented anywhere and (b) this is moot because if you're using AFNetworking's own setCompletionBlockWithSuccess, it ends up dispatching the block asynchronously to the main queue (or the defined successCallbackQueue), thereby thwarting any (undocumented) assurances of synchrony.
Furthermore, you say that the completion block runs in the main thread. If you're talking about the built in NSOperation completion block, you have no such assurances. In fact, the setCompletionBlock documentation says:
The exact execution context for your completion block is not guaranteed but is typically a secondary thread. Therefore, you should not use this block to do any work that requires a very specific execution context. Instead, you should shunt that work to your application’s main thread or to the specific thread that is capable of doing it. For example, if you have a custom thread for coordinating the completion of the operation, you could use the completion block to ping that thread.
But if you're talking about one of AFNetworking's custom completion blocks, e.g. those that you might set with AFHTTPRequestOperation's setCompletionBlockWithSuccess, then, yes, it's true that those are generally dispatched back to the main queue. But AFNetworking does this using the standard completionBlock mechanism, so the above concerns still apply.
It matters if your NSOperation is a subclass of AFHTTPRequestOperation. AFHTTPRequestOperation uses the NSOperation's property completionBlock for its own purpose in method setCompletionBlockWithSuccess:failure. In that case, don't set the property completionBlock yourself!
It seems, AFHTTPRequestOperation's success and failure handler will run on the main thread.
Otherwise, the execution context of NSOperation's completion block is "undefined". That means, the completion block can execute on any thread/queue. In fact it executes on some private queue.
IMO, this is the preferred approach, unless the execution context shall be explicitly specified by the call-site. Executing completion handlers on threads or queues which instances are accessible (the main thread for example) can easily cause dead locks by an unwary developer.
Edit:
If you want to start a dependent operation after the completion block of the parent operation has been finished, you can solve that by making the completion block content itself a NSBlockOperation (a new parent) and add this operation as a dependency to the children operation and start it in a queue. You may realize, that this quickly becomes unwieldy, though.
Another approach would require an utility class or class library which is especially suited to solve asynchronous problems in a more concise and easy way. ReactiveCocoa would be capable to solve such (an easy) problem. However, it's unduly complex and it actually has a "learning curve" - and a steep one. I wouldn't recommend it, unless you agree to spend a few weeks in learning it and have a lot other asynchronous use cases and even much more complex ones.
A simpler approach would utilize "Promises" which are pretty common in JavaScript, Python, Scala and a few other languages.
Now, please read carefully, the (easy) solution is actually below:
"Promises" (sometimes called Futures or Deferred) represent the eventual result of an asynchronous task. Your fetch request is such asynchronous task. But instead specifying a completion handler, the asynchronous method/task returns a Promise:
-(Promise*) fetchThingsWithURL:(NSURL*)url;
You obtain the result - or the error - with registering a success handler block or a failure handler block like so:
Promise* thingsPromise = [self fetchThingsWithURL:url];
thingsPromise.then(successHandlerBlock, failureHandlerBlock);
or, the blocks inlined:
thingsPromise.then(^id(id things){
// do something with things
return <result of success handler>
}, ^id(NSError* error){
// Ohps, error occurred
return <result of failure handler>
});
And shorter:
[self fetchThingsWithURL:url]
.then(^id(id result){
return [self.parser parseAsync:result];
}, nil);
Here, parseAsync: is an asynchronous method which returns a Promise. (Yes, a Promise).
You might wonder how to get the result from the parser?
[self fetchThingsWithURL:url]
.then(^id(id result){
return [self.parser parseAsync:result];
}, nil)
.then(^id(id parserResult){
NSLog(#"Parser returned: %#", parserResult);
return nil; // result not used
}, nil);
This actually starts async task fetchThingsWithURL:. Then when finished successfully, it starts async task parseAsync:. Then when this finished successfully, it prints the result, otherwise it prints the error.
Invoking several asynchronous tasks sequentially, one after the other, is called "continuation" or "chaining".
Note that the whole statement above is asynchronous! That is, when you wrap the above statement into a method, and execute it, the method returns immediately.
You might wonder how to catch any errors, say fetchThingsWithURL: fails, or parseAsync::
[self fetchThingsWithURL:url]
.then(^id(id result){
return [self.parser parseAsync:result];
}, nil)
.then(^id(id parserResult){
NSLog(#"Parser returned: %#", parserResult);
return nil; // result not used
}, nil)
.then(/*succes handler ignored*/, ^id (NSError* error){
// catch any error
NSLog(#"ERROR: %#", error);
return nil; // result not used
});
Handlers execute after the corresponding task has been finished (of course). If the task succeeds, the success handler will be called (if any). If the tasks fails, the error handler will be called (if any).
Handlers may return a Promise (or any other object). For example, if an asynchronous task finished successfully, its success handler will be invoked which starts another asynchronous task, which returns the promise. And when this is finished, yet another one can be started, and so force. That's "continuation" ;)
You can return anything from a handler:
Promise* finalResult = [self fetchThingsWithURL:url]
.then(^id(id result){
return [self.parser parseAsync:result];
}, nil)
.then(^id(id parserResult){
return #"OK";
}, ^id(NSError* error){
return error;
});
Now, finalResult will either eventually become the value #"OK" or an NSError.
You can save the eventual results into an array:
array = #[
[self task1],
[self task2],
[self task3]
];
and then continue when all tasks have been finished successfully:
[Promise all:array].then(^id(results){
...
}, ^id (NSError* error){
...
});
Setting a promise's value will be called: "resolving". You can resolve a promise only ONCE.
You may wrap any asynchronous method with a completion handler or completion delegates into a method which returns a promise:
- (Promise*) fetchUserWithURL:(NSURL*)url
{
Promise* promise = [Promise new];
HTTPOperation* op = [[HTTPOperation alloc] initWithRequest:request
success:^(NSData* data){
[promise fulfillWithValue:data];
}
failure:^(NSError* error){
[promise rejectWithReason:error];
}];
[op start];
return promise;
}
Upon completion of the task, the promise can be "fulfilled" passing it the result value, or it can be "rejected" passing it the reason (error).
Depending on the actual implementation, a Promise can also be cancelled. Say, you hold a reference to a request operation:
self.fetchUserPromise = [self fetchUsersWithURL:url];
You can cancel the asynchronous task as follows:
- (void) viewWillDisappear:(BOOL)animate {
[super viewWillDisappear:animate];
[self.fetchUserPromise cancel];
self.fetchUserPromise = nil;
}
In order to cancel the associated async task, register a failure handler in the wrapper:
- (Promise*) fetchUserWithURL:(NSURL*)url
{
Promise* promise = [Promise new];
HTTPOperation* op = ...
[op start];
promise.then(nil, ^id(NSError* error){
if (promise.isCancelled) {
[op cancel];
}
return nil; // result unused
});
return promise;
}
Note: you can register success or failure handlers, when, where and as many as you want.
So, you can do a lot with promises - and even more than in this brief introduction. If you read up to here, you might get an idea how to solve your actual problem. It's right there - and it's a few lines of code.
I admit, that this short introduction into promises was quite rough and it's also quite new to Objective-C developers, and may sound uncommon.
You can read a lot about promises in the JS community. There are one or three implementations in Objective-C. The actual implementation won't exceed a few hundred lines of code. It happens, that I'm the author of one of it:
RXPromise.
Take it with a grain of salt, I'm probably totally biased, and apparently all others ever dealt with Promises, too. ;)
If I have a method called "-uploadToServer:(Object *)objectToUpload", and a mutable array of several Objects, and I want to upload each object one after the other, how could I best handle this?
There are three important considerations:
Don't want NSOperation because I don't want to deal with threading issues
Need to wait for notification of task completion before continuing
Server calls are asynchronous and non-blocking
Here is some code I already have:
for (Object *task in objectsToUpload) {
[self uploadToServer:task];
//need to wait to get notification that upload completed
}
-(void)uploadToServer:(Object *)objectToUpload {
//perform asynchronous server operation here
//either block callback or delegate to notify
//self that upload finished
}
Seeing the above, how do you think I should handle this?
Don't want NSOperation because I don't want to deal with threading issues
Honestly, I think this is your easiest option. The only other way is to do asynchronous IO and use the run loop.
With NSOperation, you'd need two different kinds of operation called e.g. UploadOperation and NotifyOperation: one to upload an object and one to send a notification to the main thread when everything is done.
Then you'd loop through thwe objects putting them all on an NSOperationQueue in an UploadOperation, each one dependent on the previous one (addDependency:). Finally, you'd put the NotifyOperation on the queue dependent on the last UploadOperation.
The NotifyOperation overrides main as follows
-(void) main
{
[someObjectEgViewController performSelectorOnMainThread: #selector(finishedUpload)
withObject: nil
waitUntilDone: NO];
}
objectsToUpload is an NSMutableArray of tasks
-(void)uploadToServer{
//check if there is a task available
if (objectsToUpload.count > 0){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
//get first task
id nextTask = [objectsToUpload objectAtIndex:0];
//do something
//complete async
dispatch_async(dispatch_get_main_queue(), ^(void) {
//remove completed task
[objectsToUpload removeObject:nextTask];
//complete async upload task, check or notify and or start the next task
BOOL shouldDoNextTask = [self check];
if (shouldDoNextTask){
[self uploadToServer];
}
});
});
}
}
I would suggest you do not need to wait for the task to complete. What you need is to respond to the task's completion.
NSURLConnection will provide a delegate with callback methods.