Reputation: 5491
I believe my use case is fairly common, but I couldn't find any documentation that would make me 100% sure about what I am doing. Any pointer is appreciated.
At some point in my app, I begin to download an object. Afterwards, the user can click a button. If the object is done downloading, I want to execute some code. Otherwise I want to wait until the object is downloaded and execute the same piece of code. If the user does not click the button, I don't want to do anything. The downloaded object is lost.
My basic idea was to do something like this:
NSObject *myObj = nil;
- (void)download {
[self downloadObj:^(NSObject *obj){
myObj = obj;
}];
}
- (void)buttonClicked {
waitOrExecuteDirectly:^{
// Some code with myObj
}
}
Of course, the first problem is "how do I wait?"
So I tried with
- (void)buttonClicked {
if(myObj) {
// Some code
} else {
// Wait then do the exact same code
}
}
But I think the trickier problem is "what happens if the objects finishes downloading right after the "if" is calculated and before the "else" block is entered?".
I tried to encapsulate the download within an NSOperation
and use the completionBlock
property. But if the operation has already finished when I set the callback, the completionBlock is never called. I do not want to set the callback in the "download" method because the user might not click on the button.
Is there a built-in mechanism that allows me to give a completion callback to a task that will wait or execute directly depending on the task status? If not, what would be the best practice to do it by myself? Use a NSLock
when setting and reading myObj
?
Upvotes: 3
Views: 1384
Reputation: 9321
I think you should not wait on the main thread, cause that would block the whole user interface. My approach would be something like this:
typedef MyOperation void (^)(NSData * myData);
...
@private MyOperation _operationWhenDownloadFinished; // instance variable
@private NSData * _data
...
-(void)buttonPressed{
[self waitAndPerformOperation:^(NSData * myData){ ... }];
}
-(void)waitAndPerformOperation:(MyOperation)operation{
if(_data != nil){
operation(myData);
_operationWhenDownloadFinished = nil;
}else{
_operationWhenDownloadFinished = operation;
}
}
...
-(void)downloadFinished:(NSData*)downloadedData{ // this is called on the main thread, e.g. by an NSURLConnection delegate
_data = downloadedData;
if(_operationWhenDownloadFinished != nil){
[self waitAndPerformOperation:_operationWhenDownloadFinished];
}
}
Since both the button press, and the downloadFinished delegate callback happens on the main thread, we can avoid any race conditions without effort.
Upvotes: 0
Reputation: 6846
Here is the code example:
- (void)download {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self downloadObj:^(NSObject *obj){
myObj = obj;
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
}
Upvotes: 4
Reputation: 41801
You can use dispatch_semaphore_t to accomplish this. Create the semaphore with a count of 0, wait on it in buttonClicked, signal it after setting myObj.
If it hasn't been signaled yet when the button is clicked, it will block (note: you probably don't want to be blocking the main thread for an arbitrary amount of time here...) until the semaphore is signaled. If the semaphore is signaled first, the wait will simply return it to its original state without blocking.
Annoyingly, there isn't a semaphore equivalent to dispatch_group_notify, so if you want wait in the background for the download to complete, you'll need to block a thread waiting.
Upvotes: 0