I was trying to create a callback that retries the method after a delay on failure. I'm hitting this warning:
"Capturing failure block strongly in this block is likely to lead to a retain cycle."
typedef void (^MyCallbackBlock)(NSObject *);
...
__block MyObject *blockSelf = self;
__block MyCallbackBlock successBlock = ^(NSObject *someObject)
{
// To be completed
};
__block MyCallbackBlock failureBlock = ^(NSObject *someObject)
{
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[blockSelf doSomething:someObject onSuccess:successBlock onFailure:failureBlock]; // <-- Warning is here
});
};
[blockSelf doSomething:someObject onSuccess:successBlock onFailure:failureBlock];
...
- (void)doSomething:(NSObject *)someObject
onSuccess:(MyCallbackBlock)successBlock
onFailure:(MyCallbackBlock)failureBlock;
The question: How can I make this work properly?
(I've been reading through other SO questions -- haven't found a match yet, though wouldn't be surprised if one is out there.)
Yes, the block needs to capture itself (as well as self) as a weak reference.
If you're using ARC*, it should be like this:
MyObject *__weak blockSelf = self;
__block __weak MyCallbackBlock weakSuccessBlock;
MyCallbackBlock successBlock = weakSuccessBlock = ^(NSObject *someObject)
{
// To be completed
};
__block __weak MyCallbackBlock weakFailureBlock;
MyCallbackBlock failureBlock = weakFailureBlock = ^(NSObject *someObject)
{
MyCallbackBlock strongSuccessBlock = weakSuccessBlock;
MyCallbackBlock strongFailureBlock = weakFailureBlock;
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[blockSelf doSomething:someObject onSuccess:strongSuccessBlock onFailure:strongFailureBlock];
});
};
If you're not using ARC, replace the __block __weak and __weak above with just __block.
*: Objective-C Automatic Reference Counting
Adding __unsafe_unretained worked, as in:
__unsafe_unretained __block MyCallbackBlock successBlock = ^(NSObject *someObject)
{
// To be completed
};
While it seemed possible that __weak could work, in practice it caused my application to crash. It's not 100% clear that this answer explains the reason, but I'm imagining it's something along those lines.
Related
I am invoking an external async function that should invoke a callback once it completes.
However since the function is external, I do not control its implementation and I want to set a timeout for 5 seconds as an example and consider the operation of being timed out if the callback passed to that external async function wasn't invoked during those 5 seconds.
And the only way I currently found is to make the current thread sleep that actually blocks the thread.
Here is an example:
+(void)myFuncWithCompletion:(void (^ _Nonnull)(BOOL))completion{
BOOL timedOut = NO;
BOOL __block finishedAsyncCall = NO;
[someObj someAsyncMethod {
// completion callback
finishedAsyncCall = YES;
if (!timedOut) {
completion(YES);
}
}];
// This is the logic I want to fix. My goal is to make something similar but non-blocking.
long timeoutInSeconds = 5;
long startTime = [[NSDate date] timeIntervalSince1970];
long currTime = [[NSDate date] timeIntervalSince1970];
while (!finishedAsyncCall && startTime + timeoutInSeconds > currTime) {
[NSThread sleepForTimeInterval:0];
currTime = [[NSDate date] timeIntervalSince1970];
}
if (!finishedAsyncCall) {
timedOut = YES;
completion(NO);
}
}
You can use dispatch_after instead of -[NSThread sleepForTimeInterval:]
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); // 1
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ // 2
if (!finishedAsyncCall ) {
timedOut = YES;
completion(NO);
}
});
I tried our the following simple test to understand the QoS questions in Interaction between qualityOfService property of NSOperationQueue & NSOperation added to it
While doing this, I am running into a weird issue, where the code inside a dispatch_after is not always working. Can someone help me understand why the case 2 is not working.
In this case, the cancel code inside dispatch_after gets executed
NSBlockOperation *newOp = [NSBlockOperation new];
__weak NSBlockOperation *weakOp = newOp;
[newOp addExecutionBlock:^{
NSBlockOperation *innerOp = weakOp;
while (![innerOp isCancelled])
{
usleep(2000000) ;
NSLog(#"New Op QOS is %ld",innerOp.qualityOfService);
}
NSLog(#"Exiting snce new Op is cancelled");
}];
[self.myCustomQ addOperation:newOp];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSBlockOperation *innerOp = weakOp;
[innerOp cancel];
NSLog(#"Cancelling block 2");
});
But in this case, it is not getting executed
self.myMainQ = [NSOperationQueue mainQueue];
NSLog(#"QOS of main Q is %ld",self.myMainQ.qualityOfService);
__weak ViewController *weakSelf = self;
self.fromMainOp = [NSBlockOperation blockOperationWithBlock:^{
ViewController *innerSelf = weakSelf;
while (![innerSelf.fromMainOp isCancelled])
{
usleep(1000000) ;
NSLog(#"Main OP QOS is %ld",innerSelf.fromMainOp.qualityOfService);
}
}];
NSLog(#"QOS of main op is %ld",self.fromMainOp.qualityOfService);
[self.myMainQ addOperation:self.fromMainOp];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
ViewController *innerSelf = weakSelf;
[innerSelf.fromMainOp cancel];
NSLog(#"Cancelling operation");
});
self.fromMainOp = [NSBlockOperation blockOperationWithBlock:^{
ViewController *innerSelf = weakSelf;
while (![innerSelf.fromMainOp isCancelled])
{
usleep(1000000) ;
NSLog(#"Main OP QOS is %ld",innerSelf.fromMainOp.qualityOfService);
}
}];
That code looks very much like it is blocking the main queue until that block exits. If the main queue is blocked, then no blocks scheduled on the main queue are going to be executed.
I have the code below to load a group of images and notify me via completionHandler when they are all done loading. However, I find that certain dispatch_group_leave won't be called at times and my guess is imageLoader is deallocated before the block gets to run. If I put a reference of imageLoader within the loadImageWithURL:completionHandler block, then everything works as intended.
Is my guess of the cause correct? What's the correct fix for this issue? I know ARC does block copy in most cases automatically, should I do a block copy in this case?
- (void)loadGroupImagesAsyncWithCompletion:(void(^)(NSError *))completionHandler {
dispatch_group_t group = dispatch_group_create();
int index = 0;
for (Item *item in items) {
char queueLabel[30] = {0};
sprintf(queueLabel, "loader%d", index);
dispatch_queue_t queue = dispatch_queue_create(queueLabel, NULL);
dispatch_group_enter(group);
dispatch_async(queue, ^{
ImageLoader *imageLoader = [[ImageLoader alloc] init];
[imageLoader loadImageWithURL:url completionHandler:^(UIImage *image, NSError *error) {
if (image) {
item.image = image;
}
//NOTE: if item object is referenced in this block,
//then there is no missed dispatch_group_leave call.
dispatch_group_leave(group);
}];
});
}
// Non-blocking wait
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// shouldn't take more than 5 secs to load all images
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)));
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(nil);
});
});
}
Here's my guess. It's only a guess because you haven't posted the code of anything to do with ImageLoader.
If -loadImageWithURL:completionHandler: operates asynchronously i.e. it uses an async dispatch queue itself then you could be right that it is being deallocated before the load finishes. This is because its lifetime is just the scope of the for block it is declared in.
In fact, there is no reason why that method needs to do stuff asynchronously, because you have already got it in an asynchronous block. Just have a synchronous method and call dispatch_group_leave() after the method finishes.
EDIT
Given that you have no control over ImageLoader and -loadImageWithURL:completionHandler: operates asynchronously without any help from you, you should remove the dispatch_async wrapper around the call. You'll still have the problem of the ImageLoader being deallocated, but that can be avoided by putting each ImageLoader in a array when you create it.
The code would look something like this:
- (void)loadGroupImagesAsyncWithCompletion:(void(^)(NSError *))completionHandler
{
NSMutableArray* loaders = [[NSMutableArray alloc] init];
dispatch_group_t group = dispatch_group_create();
int index = 0;
for (Item *item in items) {
char queueLabel[30] = {0};
sprintf(queueLabel, "loader%d", index);
dispatch_queue_t queue = dispatch_queue_create(queueLabel, NULL);
dispatch_group_enter(group);
ImageLoader *imageLoader = [[ImageLoader alloc] init];
[imageLoader loadImageWithURL:url completionHandler:^(UIImage *image, NSError *error) {
if (image) {
item.image = image;
}
//NOTE: if item object is referenced in this block,
//then there is no missed dispatch_group_leave call.
dispatch_group_leave(group);
}];
[loaders addObject: imageLoader];
}
// Non-blocking wait
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// shouldn't take more than 5 secs to load all images
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)));
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(nil);
});
[loaders removeAllObjects];
});
}
How do I prevent duplicate select from an Array in an NSTokenField. I've implemented the delegate completionsForSubstring.
I was able to remove any duplicates with this approach, which admittedly is a little hacky but it works:
// Be sure to set the delegate of your NSTokenfield either in IB or in code.
#pragma mark - NSTokenFieldDelegate
-(NSArray *)tokenField:(NSTokenField *)tokenField shouldAddObjects:(NSArray *)tokens atIndex:(NSUInteger)index{
double delayInSeconds = .1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSArray *aray = [tokenField objectValue];
NSOrderedSet *set = [NSOrderedSet orderedSetWithArray:aray];
aray = [set array];
[tokenField setObjectValue:aray];
});
return tokens;
}
The best method I would say is to implement the delegate shouldAddObjects. Write the following code in the delegate.
NSString *newVal = [[tokens objectAtIndex:0] description];
NSArray *aray = [tokenField objectValue];
for (NSString * token in aray) {
#try{
if ([token isEqual:newVal]){
return nil;
}
}
#catch (NSException *exception) {
;
}
}
I've got some code with an apparent reference cycle in a block ivar. The following code causes a reference cycle and dealloc is never called:
__block MyViewController *blockSelf = self;
loggedInCallback = ^(BOOL success, NSError *error){
if (success)
{
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
{
[blockSelf.delegate loginDidFinish];
});
}
};
However, if I create another __block variable to hold a reference to my delegate for the block's scope to capture, the reference cycle goes away:
__block id <MyViewControllerDelegate> blockDelegate = self.delegate;
loggedInCallback = ^(BOOL success, NSError *error){
if (success)
{
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
{
[blockDelegate loginDidFinish];
});
}
};
Just want to understand what's going on here.
I'm going to assume your'e using ARC here. Prior to ARC, your first example would work just fine. With ARC the semantics of __block have changed. __block declarations are now strongly captured, rather than weakly. Replace __block with __weak in your first sample and all should work as expected.
As for what the second example works, you are creating a strong reference to the delegate, but your that doesn't have a reference back to your object. Thus no cycle and everyone is happy.
I recommend reading Mike Ash's article on the changes introduced with ARC, especially around block capture and __weak http://www.mikeash.com/pyblog/friday-qa-2011-09-30-automatic-reference-counting.html