Reputation: 21
I have searched a lot to find out how others have solved this problem, but unfortunately, I didn't find the answer to this specific question. I would really appreciate your help.
Here's a summary: I have two methods in my class, method1 and method2. I have to call an async function inside method1. Then the code keeps executing and reaches method2. But in method2, there are cases where I need to use the result of that async call in method1, so I need to make sure that the async call in method1 has finished before proceeding with the rest of method2.
I know one way is to use semaphores and the other way is to use completion blocks. But I want to do this in the most generic way because there will be other methods, similar to method2, which will again need to wait for the async call in method1 to complete before continuing execution. Also for the same reason, I cannot simply call the async function inside method2 itself and put the rest of method2 in its completion block.
Here's a rough idea of what I am trying to do. I would appreciate it if someone adds completionBlocks to this pseudocode so I can see a clear picture of how things would work. BTW, method1 and method2 (and all other methods in this class) are on the same thread (but not the main thread).
@implementation someClass
-(void)method1 {
for (each item in the ivar array) {
if (condition C is met on that item) {
some_independent_async_call(item, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(int result, int *someArray) {
if (result > 0) {
// The async call worked correctly, we will need someArray
}
else {
// We didn't get someArray that we wanted.
}
});
}
}
}
-(void)method2 {
// Select one item in the ivar array based on some criteria.
if (condition C is met on that item) {
// Wait for some_independent_async_call() in method1 to complete.
// Then use someArray in the rest of the code.
}
else {
// Simply continue the rest of the code.
}
}
@end
Update: I know I can signal a semaphore once the async call completes and I can wait for the same semaphore in method2, but I want to use a completion block instead because I think that would be more generic especially if there are other methods similar to method2 in this class. Can someone please add completion blocks to this code as I am having problems making that work?
Upvotes: 2
Views: 1108
Reputation: 19098
So, if I have understood your problem correctly, you have a list of "Items" and one asynchronous task. The task takes a parameter item
and computes some "Result" (an array of Ints).
Now, for each "item" a boolean value will be evaluated which determines whether a task should be started with this item as argument.
On completion of each task (your some_independent_async_call
), you want to call a continuation (possibly using the result of the corresponding completed task).
Well it is certainly possible to implement this with dispatch groups, completion handlers, NSOperations and such. But this will quickly become quite elaborate and error prone, especially if you want to handle errors and possibly implement a means to cancel the task if needed. But since this becomes so incredibly simple with "Futures" I'll propose this solution.
Note that "futures" are not included in the standard Swift library. However, there are a few third party libraries available. Here, I will use "Scala-like" futures. Note, that this can be implemented with "Promises" in a similar fashion, too. See also wiki Futures and Promises.
Utilising futures, the task has the following signature:
typealias TaskType = (Item) -> Future<SomeResult>
A future is a "placeholder" for a value that will be computed later by the underlying asynchronous task. The task may also fail and return an error instead the value. That is, the future will be eventually completed with either the result or an error.
Let the task be:
func task(item: Item) -> Future<SomeResult> {
let promise = Promise<SomeResult>()
fetchSomeResult(item.url) { (result, error) in
if let result = result {
promise.fulfill(result)
} else {
promise.reject(error!)
}
}
return promise.future!
}
It seems easier, to filter the array to get the actual "Items" which start a task:
let activeItems = items.filter { $0.foo == something }
Get an array of futures:
let futures = activeItems.map { task($0) }
The above statement will start the asynchronous tasks, each which its corresponding item and returning an array of futures whose type is [Future<SomeResult>]
. At this time, the futures are not yet completed. This will happen eventually for each future when the underlying task finishes.
Now, add a continuation to each future which gets called when the task succeeds and also adding an error handler which gets called when one occurs:
futures.forEach { future in
future.map { result in
// This will be entered for each task, once it
// finished successfully.
// Handle result (should be your array of ints)
// This is where you implement the "method2" part
// but for each item separately.
// Note: if you need the "item" as well, use a
// task which returns a tuple:
// task(item: Item) -> Future<(Item, SomeResult)>
}.onFailure { error in
// handle error. This is an error returned from the task.
}
}
Upvotes: 1
Reputation: 5698
Based off your code it looks like you have control over the asynchronous dispatch.
Instead of some_independent_async_call
use dispatch_sync
, which will block execution on the current thread until the given block completes
using dispatch_sync in Grand Central Dispatch
However, if you do not have control over the asynchronous call and you are actually calling a method on an object which then goes and calls dispatch_async
; you have no choice then to use a completion block, callback pattern or semaphore as you stated
Upvotes: 1