I pass a completion block to my method, this completion block will be called in the background when a network request is finished. Unfortunately, if the calling object is deallocated in the meantime, the app crashes:
ViewController (which may be deallocated because it's popped from the navigation stack) code:
__unsafe_unretained ViewController *weakSelf = self;
[[URLRequester instance] sendUrl:url successBlock:^(id JSON) {
[weakSelf webserviceCallReturned:JSON];
}];
URLRequester-Code (made simpler, of course):
- (void)sendUrl:(NSString *)urlAfterHost successBlock:(void (^)(id))successBlock {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
successBlock(nil);
return;
});
}
If, in this 2 seconds, the ViewController gets popped from the navigation stack, the app crashes. What am I missing?
When you use __unsafe_unretained, then the reference remains around even after the object is deallocated. So if the view controller gets popped, then weakSelf is now pointing to a deallocated object.
If you change it to __weak instead, then when the view controller gets deallocated, it will set weakSelf to nil, and you'll be fine. You don't even need to do a check for if weakSelf is set to anything, because calling a method on nil has no effect.
It seems quite a few people think that 'self' inside a block must always be a weak (or unretained) copy. That's usually not the case**.
In this situation the misunderstanding is causing the crash, by leaving a zombie around. The right thing to do is to refer directly to self in the block (not unsafe_unretained, not weak), just like you'd like normal code to look. The effect will be that the block will retain the instance pointed to by 'self' -- the view controller in this case -- and it won't be destroyed until the block is destroyed (presumably by the url requester).
Will it be damaging to the view controller to process the result of the web request even though it has been popped? Almost certainly not, but if you think it will be, check for that condition in the block.
if (![self.navigationController.viewControllers containsObject:self])
// I must have been popped, ignore the web request result
// Re the discussion in comments, I think a good coder should have misgivings about
// this condition. If you think you need it, ask yourself "why did I design
// my object so that it does something wrong based on whether some other object
// (a navigation vc in this case) contains it?"
// In that sense, the deliberate use of weakSelf is even worse, IMO, because
// it lets the coder ignore _and_obscure_ an important question.
else {
// do whatever i do when the web request completes
}
**The need for weak or unretained pointers in blocks stems from the fact that blocks will retain the objects they refer to. If one of those objects directly or indirectly retains the block, then you get a cycle (A retains B which retains A) and a leak. This can happen with any object to which the block refers, not just 'self'.
But in your case (as in many) the view controller referred to by self does not retain the block.
The good practice in using block (especially delayed ones) is to make a local copy of the block in the calling method. In your case it should be done in -(void)sendUrl:successBlock:
successBlockCopy = [successBlock copy];
and then call
successBlockCopy(nil);
It should retain your viewController for a while until completion.
Also it is better to use __weak instead __unsafe_unretained to avoid problems with suddenly released objects.
Related
Hi All
I am trying to dealloc a ViewController in ARC mode.
However, the RefCount is always non-zero.
I have tried to set all object to nil and all subviews to removeFromSuperview + nil;
and timer to invalidate + nil;
still the counter = 2;
Is there a way to trace which pointer is still in retain?
Thanks
If you are using blocks you might also create retain cycle there. E.g. a block is referenced by an object and inside this block you are referencing object or calling instance method for object.
Another option for retain count not dropping to 0 is that you have registered abject as observer for notification.
You might find this answer helpful:
https://stackoverflow.com/a/12286739/2261423
Example of strong reference cycle from apple docs:
self.block = ^{
[self doSomething]; // capturing a strong reference to self
// creates a strong reference cycle
};
#Billy, why are you doing this? You may not worry about deallocation when using ARC. Controller will be deallocated automatically, when there will be no references to the controller. Yes, views do not refer to controller, they are referred by it! So removing that views will not affect retain count of the controller. If you really want to remove View Controller from memory, remove it from parent view controller and set all links to nill.
In my app I'm polling a web service for status updates, using a completionHandler block and making changes to the current view based on returned results when the callback executes.
- (void) tickTimer
{
[MyWebService myWebMethod:param1 completionHandler:^(NSString *result) {
// does view still exist?
[self myUpdateMethod];
// does property still exist?
self.theResult = result;
// does child view still exist?
_txtUpdate.text = result;
}];
}
But in the interim, it's possible that the view may have been unloaded as the user navigates elsewhere.
So a couple of questions:
What happens to a view when a new one is loaded and it gets pushed to the background? I imagine it gets garbage collected at some point, but how do I tell if it's still safe to access by any of the references above, and what would happen if it's not?
If the view does still exist, how do I tell if it is also still the foreground view?
So, blocks create strong references to all objects pointers that are referred to in their closure. Due to this, your block is going to force [self] to stay in memory until the block is destroyed. If this isn't the behavior you want you should create a weak pointer to self and refer to it inside of the block:
__weak typeof(self) weakSelf = self;
So a couple of questions:
What happens to a view when a new one is loaded and it gets pushed to
the background? I imagine it gets garbage collected at some point, but
how do I tell if it's still safe to access by any of the references
above, and what would happen if it's not?
If your view stays in the view hierarchy, it will stay in memory. Once there are no more references to the view it will be dealloced.
If you use a weak pointer like outlined above, then [weakSelf] will be nil if the view has been dealloced
If the view does still exist, how do I tell if it is also still the
foreground view?
I'm not sure what you mean by foreground view, but if you want to see if it's still in the view hierarchy then you can check the property -(UIView *)superview. If superview is nil, then it's not on the screen
If you use ARC right, it will not let you use deallocated viewcontroller.
You can use viewDidAppear and viewDidDisappear methods to know visible yours viewcontroller or not.
SOLVED BELOW
I'm reading this article from raywenderlich blog:
http://www.raywenderlich.com/23037/how-to-use-instruments-in-xcode
to learn about instruments and figure out if I´m doing something wrong in some old projects.
I've seen that in one particular point of my code, when I'm showing a modal view that eventually is closed, the memory allocated remains there. As you can see in the following image.
The execution have 4 marks generated.
Between the 2n and the 3t mark, the view is showed, as you can see, new memory is allocated.
But between the 3t and the 4th, I've called dismissViewController, and the view no longer remains. But the memory remains allocated.
All the properties, are created as strong (probably no the best approach):
And I´ve an NSTimer, that is initialized in viewDidLoad method, and set to nil at viewWillDisappear:
[self.secondTimer invalidate];
self.secondTimer = nil;
So, do you have any idea about what's happening? From what I know, even the properties are declared as strong, when the UIViewController is released, all of them are going to be released to.
EDIT
Thanks to all, with the information I provided, wasn't enough.
As you can see, QRViewController inherits from BaseViewController.
This controller had a delegate defined as strong storage, terrible.
So that's all.
In the view controller hierarchy, self.view holds ALL his subviews with strong, so everything under self.view (Probably all your IBOutlet properties) can switch to weak. That probably won't solve the problem though.
What might help you is the fact that any block you have holds every single object used in that block as a strong, to make sure the block can run it's code at the time being. If nothing holds that block (like a animationWithDuration:) than no worries. But if you have any block that an object is holding (Like and object's "completion-block" or any other creative use of blocks), everything within that block will be strong, and there's a chance you create a retain cycle that way. For example: the presenting view controller is calling the presented view controller with a completion block, and in that block you use self. Now presented VC is holding a block to perform on dismiss, and the block holds the presenting VC. When dismissed you will end up with a VC that holds a block that holds a VC that holds the presented VC....
A simple solution would be to give the block a weak version of self and only when the block executes, make it strong for the time of running the block (To avoid dealloc while running the block):
__weak myViewController *weakself = self;
[self.someObject setBlockHandler:^(BOOL foo){
myViewController *strongself = weakself;
if (strongself) {
// Do whatever...
}
}];
It's difficult to pinpoint precisely the problem, but usually when things like this happen to me, it winds up being one (or a few) "root" culprits -- you find that one, clear it up, and then lots of others clear up too. So one strategy you can try is to sift through the Instruments data looking for any sort of "hierarchy" (think about how your app is structured and how the objects relate to each other) and look for objects closer to the base, then cross-reference against your code to see if they might have a retain cycle or some other such issue.
One immediate change I would make would be to change your IBOutlet declarations from strong to weak. For the most part, IBOutlet properties should be weak, for objects that are within a hierarchy. So if say you've got some UILabel within your xib's main view, that label should be weakly-retained so as to avoid a retain cycle. But if say that UILabel is standing alone as a root item within the xib, then it would need a strong reference. I'm going to guess most (if not all) of your IBOutlets there are within a hierarchy, so make them weak and try again. It may not solve all the leaks, but see if it makes any difference.
This is called Abandoned Memory, check this link.
TIP:
If you are navigating between view controllers, and you perform the navigation inside a closure, you should use a weak or unowned version of self, example:
//Swift 2.1
//Performing naivgation on the main thread for responsiveness:
dispatch_async(dispatch_get_main_queue(), {[weak self] () -> Void in
if let weakSelf = self{
weakSelf.performSegueWithIdentifier("myOtherView", sender: weakSelf)
}
})
Also, when dismissing the view controller is the same:
dispatch_async(dispatch_get_main_queue(), {[weak self] () -> Void in
if let weakSelf = self{
weakSelf.dismissViewControllerAnimated(true, completion: nil)
}
})
The posted link above shows a practical example on how to catch abanodend memory using Xcode Instruments.
I have two View Controllers, one with a grid of buttons, and the second one is a detailed description based on the button the user presses.
I'm using "Mark Heap" in Instruments allocations tool and finding that my app is increasing in memory after the user sees the detailed View Controller and then presses the back button in the navigation bar. IT should result in a net change of zero for memory... I'm using ARC, xcode 4.2.1, deploying to ios 5.0+
When I load the new ViewController after the button press, I load some data images in a background thread. Is it possible that because I'm pressing the back button quickly, that data is still being loaded in the background thread and is never released from memory?
from the slides from the class i had that described memory cycles problem you may be facing:
what if you had the following property of a class?
#property (strong, nonatomic) NSArray* myBlocks; // array of blocks
and then tried to do the following in one of that class's methods?
[self.myBlocks addObject:^() {
[self doSomething];
}];
all objects referenced inside a block will stay in the heap as long as the block does. (in other words, blocks keep a strong pointer to all objects referenced inside of them.)
in this case, self is an object referenced in this block. thus the block will have a strong pointer to self. but notice that self also has a strong pointer to the block (through its myBlocks proeprty)
THIS IS A SERIOUS PROBLEM!
neither self nor the block can ever escape the heap, now. that's because there will always be a strong pointer to both of them (each other's pointer). this is called a memory "cycle".
solution:
local variables are always strong. that's okay, because when they go out of scope, they disappear, so the strong pointer goes away. but there's a way to declare that a local variable is weak. here's how ...
__weak MyClass* weakSelf = self;
[self.myBlocks addObject:^{
[weakSelf doSomething];
}];
this solves the problem because now the block only has a weak pointer to self. (self still has a strong pointer to block, but that's okay) as long as someone in the univese has a strong pointer to this self, the block's pointer is good. and since the block will not exist if self does not exist (since myBlocks won't exist), all is well!
I've become a little paranoid with blocks and the possibility of creating a retain cycle. I'm using a block based version of the UIAlertView class which allows you to use blocks instead of delegate methods. I use a lot of these Alertviews, so I'm often calling into instance methods that do a lot of heavy lifting.
Would the assignments I make in the method someInstanceMethod cause a retain cycle?
(I am using ARC for memory management.)
__weak id weakSelf = self;
[doWorkAndThen:^{
[weakSelf someInstanceMethod];
}];
-(void) someInstanceMethod{
//will either of the assignments below cause a retain cycle?
self.iVar = #"data";
[self setIvar:#"data";
}
No. -someInstanceMethod is not a block. The fact that you're calling it from one is irrelevant. Only references inside the block itself can cause retains, and since your only reference inside your block is a __weak variable you're fine.
Incidentally, if you really want to ease your mind, you should modify your block-based UIAlertView class to throw away all the blocks when the view is dismissed. This way even if you do create a retain cycle, it will be broken automatically as soon as the alert view goes away.