weakself inside dispatch_async queue(dispatch_get_main_queue(), ^{}) - objective-c

Below code snippet is in objective C
__weak MyView *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.activityIndicatorView stopAnimating];
[weakSelf.activityIndicatorView removeFromSuperview];
weakSelf.activityIndicatorView = nil;
});
Will the weakSelf always available/valid since it is inside the main
queue?
Do we need to declare strongSelf only when block is other
than the main queue?

Your code snippet is too small to answer on your questions fully.
weakSelf can be both nil or non-nil. The keyword weak means that variable weakSelf can become nil in some cases. For example, if your controller has the following property:
#property (retain) MyView* myView;
In some cases you dismiss this controller and after that you call the method f for myView:
[self dismissViewControllerAnimated:YES completion:^{
[self.myView f];
}];
Code of the method f is based on code snipped you provided in this question:
-(void)f {
[self removeFromSuperview];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
__weak MyView *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.activityIndicatorView stopAnimating];
[weakSelf.activityIndicatorView removeFromSuperview];
weakSelf.activityIndicatorView = nil;
});
});
}
I guess you will see in debugger that the weakSelf will be nil when you will try to call stopAnimating for activityIndicatorView. And I guess you can easily reproduce the situation when weakSelf will be not cleared. It means that the answer on your first question is "No, the weakSelf will be not always available/valid and the main thread does not protect you from nil in this variable"
You need to use strongSelf (__strong instead of __weak) if you don't want to loose a reference to variable inside block. For example, if in the class MyView there is a method log that logs some debug information:
-(void)log {
NSLog(#"LOG");
}
And if you want to log information always after the code in your code snippet will called use the following version of the method f:
-(void)f {
[self removeFromSuperview];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
__strong MyView *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.activityIndicatorView stopAnimating];
[weakSelf.activityIndicatorView removeFromSuperview];
weakSelf.activityIndicatorView = nil;
[weakSelf log];
});
});
}
So, the answer, on your second question is "No, you need to use __strong based on your application, the block can be completed in different threads".

Related

Capturing Self in a double block with ARC

I recently learned this trick for when I have to reference self within a block.
__weak MyObject *safeSelf = self;
[self doWithCompletionBlock:^{
HMFInventoryBatchItemsController *strongSelf = safeSelf;
if (strongSelf) {
[strongSelf doSomethingElse];
}
}];
But I've been wondering, what if I have a block within a block? Do I need to do the same thing again?
__weak MyObject *safeSelf = self;
[self doWithCompletionBlock:^{
HMFInventoryBatchItemsController *strongSelf = safeSelf;
if (strongSelf) {
__weak MyObject *saferSelf = strongSelf;
[strongSelf doAnotherThingWithCompletionBlock:^{
HMFInventoryBatchItemsController *strongerSelf = saferSelf;
if (strongerSelf) {
[strongerSelf doSomethingElse];
}
}];
}
}];
Or is this fine
__weak MyObject *safeSelf = self;
[self doWithCompletionBlock:^{
HMFInventoryBatchItemsController *strongSelf = safeSelf;
if (strongSelf) {
[strongSelf doAnotherThingWithCompletionBlock:^{
[strongSelf doSomethingElse];
}];
}
}];
The good answer is, it depends. It's in general unsafe to do:
__weak MyObject *safeSelf = self;
[self doWithCompletionBlock:^{
[safeSelf doAnotherThingWithCompletionBlock:^{
[safeSelf doSomethingElse];
}];
}];
The reason for that, is that when you call -doAnotherThingWithCompletionBlock if that method thinks it holds a reference on self, which a selector usually assumes, and it dereferences self to access ivars, then you will crash, because you actually don't hold a reference.
So whether you need to take a new weak reference or not depends on the lifetime semantics you need/want.
EDIT:
By the way, clang even has a warning about that issue that you can enable in Xcode with the CLANG_WARN_OBJC_RECEIVER_WEAK setting (CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK is also useful while we're at it)
__block MyObject *safeSelf = self;
[self doWithCompletionBlock:^{
[safeSelf doAnotherThingWithCompletionBlock:^{
[safeSelf doSomethingElse];
}];
}];
Hope it will do what it should normally does. Tell the compiler not to retain the __weak MyObject* no matter in which block scope it is. The fact is i didn't tested. Meanwhile I'll be surprised if it will actually retain MyObject *

Run method only after other

i need to make this methods:
1) find and remove matches
2) fill empty cells
3) do 1 and 2 while no matches found
4) only when no matches found - find reshuffle method
How i do this :
[self launchAsyncCheckAndFill:^{
[self isReshuffleNeeded];
}];
-(void)launchAsyncCheckAndFill:(void(^)(void))completionHandler {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performSelectorOnMainThread:#selector(check) withObject:nil waitUntilDone:true];
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler();
});
});
}
- (void) check {
[self performSelector:#selector(fillEmptyCells) withObject:nil afterDelay:0.4];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC), dispatch_get_current_queue(), ^{
if ([self checkMapForMatchesWithFindMatchesMode:NO isCopyVector:NO]) {
[self check];
}
});
}
First of all i'm remove matches, then i go this methods : first of all i go launchAsyncCheckAndFill method, that on call back must start reshuffle method.
launchAsyncCheckAndFill must send callback only when check method is done. Check method is done in loop, this method must always fillEmptyCells, then checkmatches and while matches not found, only then must work callback on launchAsyncCheckAndFill .
But now behavior, that in loop always removing match, but method on reshuffle works after first remove and fill.
btw time 0.4 , 0.5 it's for animation, for this time figures moves and changes position. unfortunately in cocos i can only use
[elem.sprite runAction:[CCMoveTo actionWithDuration:0.4f position:elemPos1]];
that put position only after delay.
UPDATE:
[self launchAsyncCheckAndFills];
[self isReshuffleNeeded];
-(void)launchAsyncCheckAndFills {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
[self check];
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
}
- (void) check {
[self performSelector:#selector(fillEmptyCells) withObject:nil afterDelay:0.4];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC), dispatch_get_current_queue(), ^{
if ([self checkMapForMatchesWithFindMatchesMode:NO isCopyVector:NO]) {
[self check];
}
});
}

Why not always use weakSelf in a block?

I understand WHY we would use weakSelf in a block, just not so much when.
I am converting a codebase to ARC which gives a lot of retain cycle warnings with blocks. From the documentation I've gathered that I need to change this:
[self.selectedAsset addToFavoritesWithCompletion:^(NSError *error) {
self.selectedAsset.isFavorite = YES;
[self updateIsFavoriteButton];
}];
to this:
__weak MyViewController* weakSelf = self;
[self.selectedAsset addToFavoritesWithCompletion:^(NSError *error) {
self.selectedAsset.isFavorite = YES;
[weakSelf updateIsFavoriteButton];
}];
To make the compiler happy and avoid retain loops. My question is why isn't it necessary to change the line:
self.selectedAsset.isFavorite = YES;
to use weakSelf? Doesn't it evaluate to a method call as well? Why doesn't the compiler warn about lines in this format?
[[self selectedAsset]setIsFavorite:YES];
EDIT: I just update to XCode 4.6, and it now generates compiler warnings for just this situation. Funny timing :)
My question is why isn't it necessary to change the line:
self.selectedAsset.isFavorite = YES; to use weakSelf? Doesn't it
evaluate to a method call as well? Why doesn't the compiler warn about
lines in this format?
[[self selectedAsset]setIsFavorite:YES];
Yes, it is exactly a method call. And it does cause a strong reference to self. And it IS necessary to change it to weakSelf if you want it to not retain self.
Compiler warnings do not catch everything.
__weak MyViewController* weakSelf = self;
[self.selectedAsset addToFavoritesWithCompletion:^(NSError *error) {
self.selectedAsset.isFavorite = YES;
[weakSelf updateIsFavoriteButton];
}];
is indeed daft. The completion block referenced self twice, which would create a strong reference. It still references self once, which still creates a strong reference. And weak references are pointless if you are using strong references as well. This should be
__weak MyViewController* weakSelf = self;
[self.selectedAsset addToFavoritesWithCompletion:^(NSError *error) {
weakSelf.selectedAsset.isFavorite = YES;
[weakSelf updateIsFavoriteButton];
}];
or better (because safer)
__weak typeof (self) weakSelf = self;
[self.selectedAsset addToFavoritesWithCompletion:^(NSError *error) {
typeof (self) strongSelf = weakSelf;
strongSelf.selectedAsset.isFavorite = YES;
[strongSelf updateIsFavoriteButton];
}];

How to implement a class method with a block completion handler

I'm trying to implement a fire-and-forget class method similar to
+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler
in the NSURLConnection, but I'm slightly confused about the memory management (I'm NOT using ARC at the moment).
My current code goes like this:
#interface StuffInfoDownloader() <UIAlertViewDelegate>
typedef void (^StuffInfoDownloaderCompletionBlock)(NSArray *stuffs);
- (id)initStuffsWithIdentifiers:(NSSet *)identifiers
completionHandler:(void (^)(NSArray *stuffs))handler;
#property (retain, nonatomic) StuffInfoDownloaderCompletionBlock completionHandler;
#property (retain, nonatomic) NSSet *identifiers;
#end
#implementation StuffInfoDownloader
#synthesize completionHandler = _completionHandler;
#synthesize identifiers = _identifiers;
+ (void)loadAsynchronouslyWithIdentifiers:(NSSet *)identifiers
completionHandler:(void (^)(NSArray *stuffs))handler
{
StuffInfoDownloader *downloader = [[StuffInfoDownloader alloc] initStuffsWithIdentifiers:identifiers completionHandler:handler];
[downloader downloadStuffs];
[downloader release]; // will retain itself
}
- (id)initStuffsWithIdentifiers:(NSSet *)identifiers
completionHandler:(void (^)(NSArray *stuffs))handler
{
if (!(self = [super init])) {
return nil;
}
[self retain];
_completionHandler = handler;
_identifiers = identifiers;
return self;
}
- (void)downloadStuffs
{
__block StuffInfoDownloader *me = self; // avoid reference cycle between self and the block
[StuffsConnection loadAsynchronouslyWithIdentifiers:self.identifiers completionHandler:
^(NSArray *stuffs, NSError *error) {
if(error) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Connection Failed."
message:#"TODO do localised string"
delegate:self cancelButtonTitle:#"OK"
otherButtonTitles:nil, nil];
[alert show];
[alert release];
} else {
me.completionHandler(stuffs);
[self release];
}
}];
}
#pragma mark UIAlertViewDelegate
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
#pragma unused(alertView, buttonIndex)
// try again
[self downloadStuffs];
}
- (void)dealloc
{
[_completionHandler release];
[_identifiers release];
[super dealloc];
}
Basically, I'm passing ownership of the object to itself, and releasing it in the handler. Any problems with that?
There are so many things wrong with this code. Besides the block property needing to be copy. You shouldn't do the [self retain]; and [self release]; (p.s. you missed a [self release] in the error case). That completely goes against the memory management rules. They are completely unnecessary if you do things right. Memory management in Cocoa is completely local -- a function or method needs only care what it does, not what any other code does. init has no reason to do [self retain], and does not have to "worry" about what any other code does. Period.
Then the _completionHandler = handler; _identifiers = identifiers; are wrong. The block needs to be copied if you are storing it in an instance variable; and the set needs to be retained or copied. You need to do either _completionHandler = [handler copy]; _identifiers = [identifiers retain]; or use the setter self.completionHandler = handler; self.identifiers = identifiers;.
Then, there is no issue of "retain cycle". A retain cycle requires a cycle -- A retains B, and B retains A. The block retains self, but does self retain the block? I don't see that anywhere. You are simply calling a class method of another class on this block. So you shouldn't do the weak reference. The weak reference is not correct anyway, since there is no guarantee that the current object will be valid by the time the block executes.
It seems that you (incorrectly) did the whole [self retain] thing, all in order to deal with the fact that you (also incorrectly) did not allow the block to retain self, as it should. Just get rid of this weak reference stuff, and get rid of the [self retain] stuff, and then it will not only follow the memory management rules, be more robust, but also look cleaner, simpler, and more understandable.
#property (nonatomic, copy) StuffInfoDownloaderCompletionBlock
completionHandler;
then in init:
self.completionHandler = handler;
You should never retain block if u haven't copied it before, that doesn't make sense .
By the way
if ((self = [super init])) {
/* initialization stuff*/
}
return self;
Seems that your code has lot of retainCycle flaws design

Activity Indicator doesn't spin

I'm trying to add a spinning activity indicator (UIActivityIndicatorView) to my app while it parses data from the internet. I have an IBOutlet (spinner) connected to a UIActivityIndicatorView in IB. Initially I had it set up like this:
-
(void) function {
self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle: UIActivityIndicatorViewStyleWhite];
self.spinner.hidesWhenStopped = YES;
[spinner startAnimating];
//parse data from internet
[spinner stopAnimating];}
But the spinner wouldn't spin. I read that it had something to do with everything being on the same thread. So I tried this:
- (void) newFunction {
self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle: UIActivityIndicatorViewStyleWhite];
self.spinner.hidesWhenStopped = YES;
[spinner startAnimating];
[NSThread detachNewThreadSelector: #selector(function) toTarget: self withObject: nil];
[spinner stopAnimating];}
But still no luck. Any ideas? Thanks.
Your newFunction: method should look like this:
- (void) newFunction {
self.spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
self.spinner.hidesWhenStopped = YES;
[NSThread detachNewThreadSelector: #selector(function) toTarget: self withObject: nil];
}
And your function method should look like this:
- (void) function {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self.spinner performSelectorOnMainThread:#selector(startAnimating) withObject:nil waitUntilDone:NO];
//...
[self.spinner performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:NO];
[pool drain];
}
you should not intitialize indicator again .please replace your code with this.
-(void) function {
[spinner startAnimating];
[self performSelector:#selector(newfunction) withObject:nil afterDelay:3.0];
}
- (void) newfunction {
[spinner stopAnimating];
}
Thanks.
Just see that the "//parse data from internet " is synchronous or asynchronous. Asynchronous would mean that a separate thread would start from that point on, and the current function execution will continue without delay.
In your second example, you are explicitly making separate thread, which means that #selector(function) will happen on a separate thread, and the next statement [spinner stopAnimating] is executed immediately. So, it seems like spinner is not spinning at all.
Moreover, make sure you start and stop the activity indicator on main thread only.