问题
I have over-ridden a getter that requests an online service to get a result. How do I force the getter to return the result only from synchronous block ?
@interface MyClass ()
@property (nonatomic, strong) NSMutableDictionary* myDictionary;
@end
@implementation MyClass
-(NSMutableDictionary*) myDictionary {
dispatch_async(queue, ^{
/* perform online request */
dispatch_sync(dispatch_get_main_queue(), ^{
// I need to obtain lock until this line gets executed and only then return
});
});
}
@end
With a pretty good googling for at least 3 hrs I came across dispatch_group_async, dispatch_semaphore and __block. I don't know if I was using them wrong but didn't serve the purpose.
Update 1 :
myDictionary
is an asynchronous property. I want to see if this can be implemented via the getter itself.
回答1:
@Kishor When a request is truely asynchronous the UI does not block. The reason your "asynchronous" request block the UI is that, in fact they are not asychronous. Here is why in pseudo code:
- (double) notTruelyAsyncValue
{
__block double someExpensiveDoubleToCompute = 0.0;
// One of the many ways that we can coordinate concurrent threads.
dispatch_semaphore_t sema_done = dispatch_semaphore_create(0);
dispatch_async(not_the_main_queue, ^(void) {
// Simulate long processing time.
sleep(5);
someExpensiveDoubleToCompute = 3.1415926535;
dispatch_semaphore_signal(sema_done);
});
// We can't return until the async block has returned.
// So we wait until it's done. If we wait on the main queue
// then our UI will be "frozen".
dispatch_semaphore_wait(sema_done, DISPATCH_TIME_FOREVER);
// Now we have our result we free the resources and return
dispatch_release(sema_done);
return someExpensiveDoubleToCompute;
}
If you call this method from an asynchronous thread it will not block the UI but if you call it from the main queue/thread then it WILL block the UI because you are waiting on a semaphore on the main thread. Regardless of how you implement your waiting, it will always block the UI because the main thread is a serial queue. That means no other block or event on the main queue will run until your asynchronous block is completed.
If you don't want to block your UI then don't call anything that might block from the main thread. A good pattern for this is to use completion blocks as suggested by @Collin. The pattern is as follows:
- (void) computeAnyncValueWithCompletionBlock:((void)^(double value))completionBlock
{
dispatch_async(not_the_main_queue, ^(void) {
// do some expensive computation.
double value = 3.1415926535;
completionBlock(value);
});
}
This can be called from anywhere and will never block.
回答2:
Correct me if I'm unclear -- what it sounds like you want to do is download in the background, while also returning from the method in a non-async fashion? If you think about that, you're kind of trying to do two contradictory things at once: the method either has to block until it returns or return asynchronously.
What I think you want is a completion block. Instead of overriding myDictionary
, you might instead create a second method that does something like this:
- (void)downloadWithCompletion:(void(^)(NSDictionary *dictionary))completion
{
dispatch_async(queue, ^{
/* perform online request */
// Create an NSDictionary from what was downloaded.
NSDictionary *dictionary = <parsed request data>
dispatch_sync(dispatch_get_main_queue(), ^{
// Call the completion block using the info that was downloaded
// where self.myDictionary could be set.
completion(dictionary);
});
});
}
回答3:
As it turns out, not difficult at all. I have tried to update this with my findings and I will continue to update this.
How should a asynchronous property's getter behave ?
Perform asynchronous request
if property is not available andset the property
. (For lazy loading)- return the property when it is available.
Challenges:
- UI Freezes
- Invalid returns
Although, it can be confusing as in why not use a synchronous method here ?, The answer is it will freeze the UI.
No one knows when Asynchronous requests will be completed but that does not mean the whole need for the availability status should be unknown. There are mechanisms throughout all systems from hardware, kernel to higher level APIs for this. You can refer to Protocols and Delegates as one of the means for communicating this.
Why not use Protocols and Delegates for asynchronous properties ?
- I will have to force the implementation of delegates in all referencing classes -> Not a getter.
- I don't want other classes to know that it is an asynchronous property, if they want the data they will get it when it's available without them knowing the nature of how it was retrieved. (obv without freezing the UI).
How we achieve this without using Protocols and Delegates or turning it into a synchronous call ?
Answer is By using a condition variable. Mind you this condition variable is different from what we use for branching. It has to be thread safe and supported in compiler and kernel level.
- NSCondition
From the official docs,
The NSCondition class implements a condition variable whose semantics follow
those used for POSIX-style conditions. A condition object acts as both a lock
and a checkpoint in a given thread. The lock protects your code while it tests
the condition and performs the task triggered by the condition. The checkpoint
behavior requires that the condition be true before the thread proceeds with its
task. While the condition is not true, the thread blocks. It remains blocked until
another thread signals the condition object.
All I had to do was to have this getter method be aware of async requests completion without using delegates.
-(NSMutableDictionary*) myDictionary {
if(!_myDictionary) {
_myDicitonary = [self someOtherMethod];
}
return _myDictionary;
}
Although, the locks and async request can be implemented in the getter itself, I resisted that to have ease in operating locks. Also, it is a nice separation of logic :)
- (NSMutableDictionary *)someOtherMethod
{
NSCondition *lockForCompletion = [[NSCondition alloc] init];
__block BOOL available = NO;
__block NSMutableDictionary* tempDict = [[NSMutableDictionary alloc] init];
[lockForCompletion lock]; // acquire the lock
dispatch_async(queue, ^{
/* perform online request */
dispatch_sync(dispatch_get_main_queue(), ^{
[tempDict setObject:myResponse forKey:@"mykey" count:1];
available = YES;
[lockForCompletion signal];
});
});
while(!available) {
[lockForCompletion wait];
}
[lockForCompletion unlock];
return tempDict;
}
I'd also like to point out that initially the boolean predicate available
does not appear necessary at all since the wait
will forbid the control reaching beyond it. But actually the boolean predicate plays a very significant role in keeping it locked as described in the docs.
A boolean predicate is an important part of the semantics of using conditions
because of the way signaling works. Signaling a condition does not guarantee
that the condition itself is true. There are timing issues involved in signaling
that may cause false signals to appear. Using a predicate ensures that these
spurious signals do not cause you to perform work before it is safe to do so.
The predicate itself is simply a flag or other variable in your code that you test
in order to acquire a Boolean result.
来源:https://stackoverflow.com/questions/15805086/how-do-i-return-from-a-getter-of-an-asynchronous-property