Using the JSContext from a UIWebView I have created a javascript function that is implemented as an Objective C block:
JSContext *js = ... //get contect from web view
js[#"aFunc"] = ^(JSValue *aString, JSValue *callback) {
NSString *realString = [aString toString];
MyOperation *op = [[MyOperation alloc] initWithString:realString andCallback:callback];
//Do some heavy lifting in background
[self.myQueue addOperation:op];
}
This function takes a callback as an argument and performs some work in an NSOperationQueue before calling the callback like:
- (void)main {
JSValue *arg = [self theHeavyWork];
//Now we have finished the heavy work, switch back to main thread to run callback (if any).
if ([self.callback isObject] != NO) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.callback callWithArguments:#[arg]];
});
}
}
This works fine, unless the callback contains a call to alert():
//This javascript is part of the page in the UIWebView
window.aFunc("important information", function(arg) { alert("Got " + arg); });
In this case the alert shows and the UI becomes completely unresponsive. I am assuming that the event the touch event to close the alert is being blocked by the alert being there.
If I call the callback without the dispatch (in other words on which ever thread the MyOperation is running on) it works just fine, but I was under the impression that any code that could have UI implications (in other words any JS callbacks) should always be run on the main thread. Am I missing something, or do is it really impossible to safely use alert() when using the JavaScriptCore framework?
After a couple days looking at stack traces of threads waiting for each other, the solution was so simple I'm not surprised I overlooked it in favor of trying more complicated stuff.
If you want to call back into a UIWebView's javascript asynchronously, use window.setTimeout and let the JSVirtualMachine take care of queuing the callback.
Just replace
dispatch_async(dispatch_get_main_queue(), ^{
[self.callback callWithArguments:#[arg]];
});
with
dispatch_async(dispatch_get_main_queue(), ^{
[self.callback.context[#"setTimeout"] callWithArguments:#[self.callback, #0, arg]];
});
Related
Consider the following code in a ViewController which runs when the user clicks a button:
var myBlock = {
[weak self] in
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
dispatch_async(queue) {
// Some heavy lifting code with final UI Feedback
let success = true
if(success) {
if let strongSelf = self {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
// Tell user it was successful
strongSelf.label1.text = "SUCCESS!"
});
}
}
});
}
In the area where the heavy lifting occurs, lets say there is a process that takes about 5-10 seconds. If during that period the user dismisses the ViewController in which this code resides, does the background thread continue to run? If so, when the success block runs and attempts to access the UI element on the UI thread, what happens?
Ultimately, I am trying to understand the best practice for queueing a process in the background that updates the UI when possible. Also, if the user presses the home button, this process MUST continue in the background to completion. I have read that you can add: UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler() Is that meant to operate inside a thread as seen above?
I'm sure this is a bit of a noob question, but I could really use some best practice guidance here.
Thanks!
When you reference to self inside a block a strong reference is stored in order to keep the reference until it is executed. The best practice is to create a weak self version to avoid retain cycles. You can achieve this creating the weak version outside the block like so in Objective C:
__weak typeof(self) weakSelf = self;
And in Swift:
var myBlock = {
[weak self] in
//some work with self
}
and access weakSelf instead of self in the block.
You can learn a little bit more in the next link.
The background thread will continue to run, you can implement NSOperations if you need to be a cancelable task. If you can unwrap the optional inside the block you can update the UI.
About beginBackgroundTaskWithExpirationHandler, it is used to mark the beginning of a long task you are about to start and get an extra time to complete it (and notify when it is done) in case the App is backgrounded, but if it can't be performed in that time the app will be terminated. You can use it if you needed, but it will not ensure to have the time to done your task.
I have those methods to retrieve some object information from the internet:
- (void)downloadAppInfo:(void(^)())success
failure:(void(^)(NSError *error))failure;
- (void)getAvailableHosts:(void(^)())success
failure:(void(^)(NSError *error))failure;
- (void)getAvailableServices:(void(^)())success
failure:(void(^)(NSError *error))failure;
- (void)getAvailableActions:(void(^)())success
failure:(void(^)(NSError *error))failure;
The downloaded stuff gets stored in object properties, so that is why the success functions return nothing.
Now, I want to have one method like this:
- (void)syncEverything:(void(^)())success
failure:(void(^)(NSError *error))failure;
Which does nothing else than calling all the methods above, and returning only after every single method has performed its success or failure block.
How can I do this?
Hint: I am aware that cascading the methods calls in each others success block would work. But this is neither 'clean' nor helpful when later implementations include further methods.
Attempts:
I tried running each of the calls in an NSOperation and adding those NSOperations to an NSOperationQueue followed by a "completion operation" which depends on every one of the preceding operations.
This won't work. Since the operations are considered completed even before their respective success/failure blocks return.
I also tried using dispatch_group. But it is not clear to me wether I am doing it the right way. Unfortunately, it is not working.
Drawn from the comments in other answers here, and the blog post Using dispatch groups to wait for multiple web services, I arrived at the following answer.
This solution uses dispatch_group_enter and dispatch_group_leave to determine when each intermediate task is running. When all tasks have finished, the final dispatch_group_notify block is called. You can then call your completion block, knowing that all intermediate tasks have finished.
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[self yourBlockTaskWithCompletion:^(NSString *blockString) {
// ...
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[self yourBlockTaskWithCompletion:^(NSString *blockString) {
// ...
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
// All group blocks have now completed
if (completion) {
completion();
}
});
Grand Central Dispatch - Dispatch Groups
https://developer.apple.com/documentation/dispatch/dispatchgroup
Grouping blocks allows for aggregate synchronization. Your application can submit multiple blocks and track when they all complete, even though they might run on different queues. This behavior can be helpful when progress can’t be made until all of the specified tasks are complete.
Xcode Snippet:
I find myself using Dispatch Groups enough that I've added the following code as an Xcode Snippet for easy insertion into my code.
Now I type DISPATCH_SET and the following code is inserted. You then copy and paste an enter/leave for each of your async blocks.
Objective-C:
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_leave(group);
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
});
Swift:
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
dispatchGroup.leave()
dispatchGroup.notify(queue: .global()) {
}
You were almost there, the problem is most likely to be that those methods are asynchronous, so you need an extra synchronization step. Just try with the following fix:
for(Appliance *appliance in _mutAppliances) {
dispatch_group_async(
group,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_t sem = dispatch_semaphore_create( 0 );
NSLog(#"Block START");
[appliance downloadAppInfo:^{
NSLog(#"Block SUCCESS");
dispatch_semaphore_signal(sem);
}
failure:^(NSError *error){
NSLog(#"Block FAILURE");
dispatch_semaphore_signal(sem);
}];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(#"Block END");
});
dispatch_group_notify(
group,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
NSLog(#"FINAL block");
success();
});
}
One other solution is to use a Promise which is available in a few third party libraries.
I'm the author of RXPromise, which implements the Promises/A+ specification.
But there are at least two other Objective-C implementations.
A Promise represents the eventual result of an asynchronous method or operation:
-(Promise*) doSomethingAsync;
The promise is a complete replacement for the completion handler. Additionally, due to its clear specification and underlaying design, it has some very useful features which make it especially easy to handle rather complex asynchronous problems.
What you need to do first, is to wrap your asynchronous methods with completion handlers into asynchronous methods returning a Promise:
(Purposefully, your methods return the eventual result and a potential error in a more convenient completion handler)
For example:
- (RXPromise*) downloadAppInfo {
RXPromise* promise = [RXPromise new];
[self downloadAppInfoWithCompletion:^(id result, NSError *error) {
if (error) {
[promise rejectWithReason:error];
}
else {
[promise fulfillWithValue:result];
}
}];
return promise;
}
Here, the original asynchronous method becomes the "resolver" of the promise. A promise can be either fulfilled (success) or rejected (failure) with either specifying the eventual result of the task or the reason of the failure. The promise will then hold the eventual result of the asynchronous operation or method.
Note that the wrapper is an asynchronous method, which returns immediately a promise in a "pending" state.
Finally, you obtain the eventual result by "registering" a success and a failure handler with a then method or property. The few promise libraries around do differ slightly, but basically it may look as follows:
`promise.then( <success-handler>, <error-handler> )`
The Promise/A+ Specification has a minimalistic API. And the above is basically ALL one need for implementing the Promise/A+ spec - and is often sufficient in many simple use cases.
However, sometimes you need bit more - for example the OPs problem, which require to "wait" on a set of asynchronous methods and then do something when all have completed.
Fortunately, the Promise is an ideal basic building block to construct more sophisticated helper methods quite easily.
Many Promise libraries provide utility methods. So for example a method all (or similar) which is an asynchronous method returning a Promise and taking an array of promises as input. The returned promise will be resolved when all operations have been completed, or when one fails. It may look as follows:
First construct an array of promises, and simultaneously starting all asynchronous tasks in parallel:
NSArray* tasks = #[
[self downloadAppInfo],
[self getAvailableHosts],
[self getAvailableServices],
[self getAvailableActions],
];
Note: here, the tasks are already running (and may complete)!
Now, use a helper method which does exactly what stated above:
RXPromise* finalPromise = [RXPromise all:tasks];
Obtain the final results:
finalPromise.then(^id( results){
[self doSomethingWithAppInfo:results[0]
availableHosts:results[1]
availableServices:results[2]
availableActions:results[3]];
return nil;
}, ^id(NSError* error) {
NSLog(#"Error %#", error); // some async task failed - log the error
});
Note that either the success or the failure handler will be called when the returned promise will be resolved somehow in the all: method.
The returned promise (finalPromise) will be resolved, when
all tasks succeeded successfully, or when
one task failed
For case 1) the final promise will be resolved with an array which contains the result for each corresponding asynchronous task.
In case 2) the final promise will be resolved with the error of the failing asynchronous task.
(Note: the few available libraries may differ here)
The RXPromise library has some additional features:
Sophisticated cancellation which forwards a cancellation signal in the acyclic graph of promises.
A way to specify a dispatch queue where the handler will run. The queue can be used to synchronize access to shared resources for example, e.g.
self.usersPromise = [self fetchUsers];
self.usersPromise.thenOn(dispatch_get_main_queue(), ^id(id users) {
self.users = users;
[self.tableView reloadData];
}, nil);
When compared to other approaches, the dispatch_group solution suffers from the fact that it blocks a thread. This is not quite "asynchronous". It's also quite complex if not impossible to implement cancellation.
The NSOperation solution appears to be a mixed blessing. It may be elegant only if you already have NSOperations, and if you have no completion handlers which you need to take into account when defining the dependencies - otherwise, it becomes cluttered and elaborated.
Another solution, not mentioned so far, is Reactive Cocoa. IMHO, it's an awesome library which lets you solve asynchronous problems of virtually any complexity. However, it has a quite steep learning curve, and may add a lot of code to your app. And I guess, 90% of asynchronous problems you stumble over can be solved with cancelable promises. If you have even more complex problems, so take a look at RAC.
If you want to create a block based solution you could do something like
- (void)syncEverything:(void(^)())success failure:(void(^)(NSError *error))failure
{
__block int numBlocks = 4;
__block BOOL alreadyFailed = NO;
void (^subSuccess)(void) = ^(){
numBlocks-=1;
if ( numBlocks==0 ) {
success();
}
};
void (^subFailure)(NSError*) = ^(NSError* error){
if ( !alreadyFailed ) {
alreadyFailed = YES;
failure(error);
}
};
[self downloadAppInfo:subSuccess failure:subFailure];
[self getAvailableHosts:subSuccess failure:subFailure];
[self getAvailableServices:subSuccess failure:subFailure];
[self getAvailableActions:subSuccess failure:subFailure];
}
It's kind of quick and dirty, and you might need to do block copys. If more than one method fails, you will only get one overall failure.
Here is my solution without any dispatch_group.
+(void)doStuffWithCompletion:(void (^)(void))completion{
__block NSInteger stuffRemaining = 3;
void (^dataCompletionBlock)(void) = ^void(void) {
stuffRemaining--;
if (!stuffRemaining) {
completion();
}
};
for (NSInteger i = stuffRemaining-1; i > 0; i--) {
[self doOtherStuffWithParams:nil completion:^() {
dataCompletionBlock();
}];
}
}
I wanted to be able to serialize 'genuinely' async methods, for example:
making a web request
showing a UIAlertView
This is typically a tricky business and most samples of serial queues show a 'sleep' in an NSBlockOperation's block. This doesn't work, because the operation is only complete when the callback happens.
I've had a go at implementing this by subclassing NSOperation, here's the most interesting bits of the implementation:
+ (MYOperation *)operationWithBlock:(CompleteBlock)block
{
MYOperation *operation = [[MYOperation alloc] init];
operation.block = block;
return operation;
}
- (void)start
{
[self willChangeValueForKey:#"isExecuting"];
self.executing = YES;
[self didChangeValueForKey:#"isExecuting"];
if (self.block) {
self.block(self);
}
}
- (void)finish
{
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
self.executing = NO;
self.finished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
}
- (BOOL)isFinished
{
return self.finished;
}
- (BOOL) isExecuting
{
return self.executing;
}
This works well, here's a demonstration...
NSOperationQueue *q = [[NSOperationQueue alloc] init];
q.maxConcurrentOperationCount = 1;
dispatch_queue_t queue = dispatch_queue_create("1", NULL);
dispatch_queue_t queue2 = dispatch_queue_create("2", NULL);
MYOperation *op = [MYOperation operationWithBlock:^(MYOperation *o) {
NSLog(#"1...");
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(#"1");
[o finish]; // this signals we're done
});
}];
MYOperation *op2 = [MYOperation operationWithBlock:^(MYOperation *o) {
NSLog(#"2...");
dispatch_async(queue2, ^{
[NSThread sleepForTimeInterval:2];
NSLog(#"2");
[o finish]; // this signals we're done
});
}];
[q addOperations:#[op, op2] waitUntilFinished:YES];
[NSThread sleepForTimeInterval:5];
Note, I also used a sleep but made sure these were executing in background thread to simulate a network call. The log reads as follows
1...
1
2...
2
Which is as desired. What is wrong with this approach? Are there any caveats I should be aware of?
"Serializing" asynchronous tasks will be named actually "continuation" (see also this wiki article Continuation.
Suppose, your tasks can be defined as an asynchronous function/method with a completion handler whose parameter is the eventual result of the asynchronous task, e.g.:
typedef void(^completion_handler_t)(id result);
-(void) webRequestWithCompletion:(completion_handler_t)completionHandler;
-(void) showAlertViewWithResult:(id)result completion:(completion_handler_t)completionHandler;
Having blocks available, a "continuation" can be easily accomplished through invoking the next asynchronous task from within the previous task's completion block:
- (void) foo
{
[self webRequestWithCompletion:^(id result) {
[self showAlertViewWithResult:result completion:^(id userAnswer) {
NSLog(#"User answered with: %#", userAnswer);
}
}
}
Note that method foo gets "infected by "asynchrony" ;)
That is, here the eventual effect of the method foo, namely printing the user's answer to the console, is in fact again asynchronous.
However, "chaining" multiple asynchronous tasks, that is, "continuing" multiple asynchronous tasks, may become quickly unwieldy:
Implementing "continuation" with completion blocks will increment the indentation for each task's completion handler. Furthermore, implementing a means to let the user cancel the tasks at any state, and also implement code to handle the error conditions, the code gets quickly confusing, difficult to write and difficult to understand.
A better approach to implement "continuation", as well as cancellation and error handling, is using a concept of Futures or Promises. A Future or Promise represents the eventual result of the asynchronous task. Basically, this is just a different approach to "signal the eventual result" to the call site.
In Objective-C a "Promise" can be implemented as an ordinary class. There are third party libraries which implement a "Promise". The following code is using a particular implementation, RXPromise.
When utilizing such a Promise, you would define your tasks as follows:
-(Promise*) webRequestWithCompletion;
-(Promise*) showAlertViewWithResult:(id)result;
Note: there is no completion handler.
With a Promise, the "result" of the asynchronous task will be obtained via a "success" or an "error" handler which will be "registered" with a then property of the promise. Either the success or the error handler gets called by the task when it completes: when it finishes successfully, the success handler will be called passing its result to the parameter result of the success handler. Otherwise, when the task fails, it passes the reason to the error handler - usually an NSError object.
The basic usage of a Promise is as follows:
Promise* promise = [self asyncTasks];
// register handler blocks with "then":
Promise* handlerPromise = promise.then( <success handler block>, <error handler block> );
The success handler block has a parameter result of type id. The error handler block has a parameter of type NSError.
Note that the statement promise.then(...) returns itself a promise which represents the result of either handler, which get called when the "parent" promise has been resolved with either success or error. A handler's return value may be either an "immediate result" (some object) or an "eventual result" - represented as a Promise object.
A commented sample of the OP's problem is shown in the following code snippet (including sophisticated error handling):
- (void) foo
{
[self webRequestWithCompletion] // returns a "Promise" object which has a property "then"
// when the task finished, then:
.then(^id(id result) {
// on succeess:
// param "result" is the result of method "webRequestWithCompletion"
return [self showAlertViewWithResult:result]; // note: returns a promise
}, nil /*error handler not defined, fall through to the next defined error handler */ )
// when either of the previous handler finished, then:
.then(^id(id userAnswer) {
NSLog(#"User answered with: %#", userAnswer);
return nil; // handler's result not used, thus nil.
}, nil)
// when either of the previous handler finished, then:
.then(nil /*success handler not defined*/,
^id(NEError* error) {
// on error
// Error handler. Last error handler catches all errors.
// That is, either a web request error or perhaps the user cancelled (which results in rejecting the promise with a "User Cancelled" error)
return nil; // result of this error handler not used anywhere.
});
}
The code certainly requires more explanation. For a detailed and a more comprehensive description, and how one can accomplish cancellation at any point in time, you may take a look at the RXPromise library - an Objective-C class which implements a "Promise". Disclosure: I'm the author of RXPromise library.
At a first glance this would work, some parts are missing to have a "proper" NSOperation subclass though.
You do not cope with the 'cancelled' state, you should check isCancelled in start, and not start if this returns YES ("responding to the cancel command")
And the isConcurrent method needs to be overridden too, but maybe you omitted that for brevity.
When subclassing NSOperation I would strongly suggest only overriding main unless you really know what you are doing as it is really easy to mess up thread safety. While the documentation says that the operation will not be concurrent the act of running them through an NSOperationQueue automatically makes them concurrent by running them on a separate thread. The non-concurrency note only applies if you call the start method of the NSOperation yourself. You can verify this by noting the thread ID that each NSLog line contains. For example:
2013-09-17 22:49:07.779 AppNameGoesHere[58156:ThreadIDGoesHere] Your log message goes here.
The benefit of overriding main means that you don't have to deal with thread safety when changing the state of the operation NSOperation handles all of that for you. The main thing that is serializing your code is the line that sets maxConcurrentOperationCount to 1. This means each operation in the queue will wait for the next to run (all of them will run on a random thread as determined by the NSOperationQueue). The act of calling dispatch_async inside each operation also triggers yet another thread.
If you are dead set on using subclassing NSOperation then only override main, otherwise I would suggest using NSBlockOperation which seems like what you are somewhat replicating here. Really though I would avoid NSOperation altogether, the API is starting to show its age and is very easy to get wrong. As an alternative I would suggest something like RXPromise or my own attempt at solving this problem, FranticApparatus.
This has been a hard one to search.
I found a similar question, iOS 5 Wait for delegate to finish before populating a table?, but the accepted answer was 'Refresh the table view,' and that does not help me. The other results I found tended to be in c#.
I have an app that streams from iPhone to Wowza servers. When the user hits record, I generate a unique device id, then send it to a PHP script on the server that returns a JSON document with configuration settings (which includes the rtmp dump link).
The problem is, the delegate methods are asynchronous, but I need to get the config settings before the next lines of code in my - (IBAction)recordButtonPressed method, since that code is what sets the profile settings, and then records based on those settings.
I've realized I could make the NSURLConnection in -recordButtonPressed like I am currently, and then continue the setup code inside the delegate method connectionDidFinishLoading (or just encapsulate the setup and method call it from there) but that's sacrificing coherent design for functionality and that sucks.
Is there not some simple waitUntilDelegateIsFinished:(BOOL)nonAsyncFlag flag I can send to the delegator so I can have sequential operations that pull data from the web?
I've realized I could make the NSURLConnection in -recordButtonPressed like I am currently, and then continue the setup code inside the delegate method connectionDidFinishLoading (or just encapsulate the setup and method call it from there) but that's sacrificing coherent design for functionality and that sucks.
You have analyzed and understood the situation and you have described its possible solutions perfectly. I just don't agree with your conclusions. This kind of thing happens all the time:
- (void) doPart1 {
// do something here that will eventually cause part2 to be called
}
- (void) doPart2 {
}
You can play various games with invocations to make this more elegant and universal, but my advice would be, don't fight the framework, as what you're describing is exactly the nature of being asynchronous. (And do not use a synchronous request on the main thread, since that blocks the main thread, which is a no-no.)
Indeed, in an event-driven framework, the very notion "wait until" is anathema.
Why not to use synchronous request?
Wrap your asynchronous NSURLConnection request in a helper method which has a completion block as a parameter:
-(void) asyncDoSomething:(void(^)(id result)completionHandler ;
This method should be implemented in the NSURLConnectionDelegate. For details see the example implementation and comments below.
Elsewhere, in your action method:
Set the completion handler. The block will dispatch further on the main thread, and then perform anything appropriate to update the table data, unless the result was an error, in which case you should display an alert.
- (IBAction) recordButtonPressed
{
[someController asyncConnectionRequst:^(id result){
if (![result isKindOfClass:[NSError class]]) {
dispatch_async(dispatch_get_main_queue(), ^{
// We are on the main thread!
someController.tableData = result;
});
}
}];
}
The Implementation of the method asyncConnectionRequst: could work as follows: take the block and hold it in an ivar. When it is appropriate call it with the correct parameter. However, having blocks as ivars or properties will increase the risk to inadvertently introduce circular references.
But, there is a better way: a wrapper block will be immediately dispatched to a suspended serial dispatch queue - which is hold as an ivar. Since the queue is suspended, they will not execute any blocks. Only until after the queue will be resumed, the block executes. You resume the queue in your connectionDidFinish: and connectionDidFailWithError: (see below):
In your NSURLConnectionDelegate:
-(void) asyncConnectionRequst:(void(^)(id result)completionHandler
{
// Setup and start the connection:
self.connection = ...
if (!self.connection) {
NSError* error = [[NSError alloc] initWithDomain:#"Me"
code:-1234
userInfo:#{NSLocalizedDescriptionKey: #"Could not create NSURLConnection"}];
completionHandler(error);
});
return;
}
dispatch_suspend(self.handlerQueue); // a serial dispatch queue, now suspended
dispatch_async(self.handlerQueue, ^{
completionHandler(self.result);
});
[self.connection start];
}
Then in the NSURLConnectionDelegate, dispatch a the handler and resume the
handler queue:
- (void) connectionDidFinishLoading:(NSURLConnection*)connection {
self.result = self.responseData;
dispatch_resume(self.handlerQueue);
dispatch_release(_handlerQueue), _handlerQueue = NULL;
}
Likewise when an error occurred:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
self.result = error;
dispatch_resume(self.handlerQueue);
dispatch_release(_handlerQueue), _handlerQueue = NULL;
}
There are even better ways, which however involve a few more basic helper classes which deal with asynchronous architectures which at the end of the day make your async code look like it were synchronous:
-(void) doFourTasksInAChainWith:(id)input
{
// This runs completely asynchronous!
self.promise = [self asyncWith:input]
.then(^(id result1){return [self auth:result1]);}, nil)
.then(^(id result2){return [self fetch:result2];}, nil)
.then(^(id result3){return [self parse:result3];}, nil)
.then(^(id result){ self.tableView.data = result; return nil;}, ^id(NSError* error){ ... })
// later eventually, self.promise.get should contain the final result
}
I am new to multithreading in iOS. I need to do three things: get information from the api, parse the information and save to my database. I have these three things in a different files(getAPI,parseAPI and savetoDB). getAPI will call parseAPI and it will in return call savetoDB. I want all three of them to work in background thread.
My question is when I call getAPI, will parseAPI and savetoDB run in the background thread as well? How do I ensure that all three of them run in the background? How do I return the call back to main thread after savetoDB?
Example:
dispatch_queue_t backgroundQueue;
backgroundQueue = dispatch_queue_create("lakesh", NULL);
- (void)startprocess {
dispatch_async(backgroundQueue, ^(void) {
[self getAPI];
});
}
Need some guidance.. Thanks...
If you issue a function on a background thread, all execution will continue on that thread until it finishes or you call back another function on the main thread. I had worries like you in the beginning, so I made myself the following macros:
/// Stick this in code you want to assert if run on the main UI thread.
#define DONT_BLOCK_UI() \
NSAssert(![NSThread isMainThread], #"Don't block the UI thread please!")
/// Stick this in code you want to assert if run on a background thread.
#define BLOCK_UI() \
NSAssert([NSThread isMainThread], #"You aren't running in the UI thread!")
As you can see by the comments, I tend to use these macros at the beginning of methods I want to make sure I'm not using by error in the wrong thread. I've put these macros and more random stuff at https://github.com/gradha/ELHASO-iOS-snippets which you may find useful.
With regards to your question on returning to the main thread, since you are using GCD the best would be to call dispatch_get_main_queue() at the end of your savetoDB with the code you want to run there. If savetoDB is a library function, its entry point should allow passing in the success block you want to run on the main thread when everything finished. This is the pattern used by libraries like https://github.com/AFNetworking/AFNetworking. Note how their examples provide an API where stuff runs in the background and then your code gets called back (usually in the main thread).
Yes, parseAPI and savetoDB will run in the new queue you have created. If you need to modify the UI when the operations are finished, that code must run in the main thread. To do that, get a reference to the main queue and send it some code. For example:
- (void)startprocess {
dispatch_async(backgroundQueue, ^(void) {
[self getAPI];
dispatch_async(dispatch_get_main_queue(), ^{
// Refresh the UI with the new information
});
});
}
Don't forget to dispatch_release your new queue when you're done with it.
Another pattern, used by Cocoa itself in many parts of the framework, is to add callback block to the signatures of your API functions that is invoked when the background operation has ended. This Stack Overflow thread explains how to do that.
Yes of course if getAPI calls parseAPI, the code of parseAPI will execute on the same thread than the one getAPI was executed, so in your example on a background queue.
To return the callback to the main thread at the end, use the same techniques as Apple uses with their completionBlock you can see on multiple Apple APIs : simply pass a block (e.g. dispatch_block_t or void(^)(NSError*) or whatever fits your needs) as a parameter to your getAPI: method which will pass it to parseAPI: which will in turn pass it to savetoDB: and at the end savetoDB: can simply use dipatch_async(dispatch_get_main_queue, completionBlock); to call this block of code (passed from method to method) on the main thread.
Note: for your getAPI you can use Apple's sendAsynchronousRequest:queue:completionHandler: method, that will automatically execute the request in the background then call the completion block on the indicated NSOperationQueue (NSOperationQueue uses GCD's dispatch_queue internally). See documentation on NSOperationQueue, GCD and the Concurrency Programming Guide and all the great detailed guide in Apple doc for more info.
-(void)getAPI:( void(^)(NSError*) )completionBlock
{
NSURLRequest* req = ...
NSOperationQueue* queue = [[NSOperationQueue alloc] init]; // the completionHandler will execute on this background queue once the network request is done
[NSURLConnection sendAsynchronousRequest:req queue:queue completionHandler:^(NSURLResponse* resp, NSData* data, NSError* error)
{
if (error) {
// Error occurred, call completionBlock with error on main thread
dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(error); });
} else {
[... parseAPI:data completion:completionBlock];
}
}];
}
-(void)parseAPI:(NSData*)dataToParse completion:( void(^)(NSError*) )completionBlock
{
... parse datatToParse ...
if (parsingError) {
dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(error); });
} else {
[... savetoDB:dataToSave completion:completionBlock];
}
}
-(void)savetoDB:(id)dataToSave completion:( void(^)(NSError*) )completionBlock
{
... save to your DB ...
// Then call the completionBlock on main queue / main thread
dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(dbError); }); // dbError may be nil if no error occurred of course, that will tell you everything worked fine
}
-(void)test
{
[... getAPI:^(NSError* err)
{
// this code will be called on the main queue (main thread)
// err will be nil if everythg went OK and vontain the error otherwise
}];
}