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.
Related
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 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.
I think there is a simple answer to this question, but I can't seem to find it.
I have a view that I would like to keep updated, even after it disappears. It's a music player so it would be nice to be able to go back to the player and see the current track/art/progress without it having to update after the view loads.
I have a strong reference to the view, so it doesn't fully reload, but I can't seem to make updates to the UI elements anywhere outside the view. I have a class that is calling this, but it's completely ignored:
dispatch_async(dispatch_get_main_queue(), ^{
[playerViewController updateAlbumArt];
[playerViewController updateSongTitle];
[playerViewController updateSongArtist];
});
Is there some way to keep a view updated in the background from another class? Similar to how if a track changes on your iPhone music app, you go to the now playing screen and you see the current song progress/information without any lag or loading period.
viewWillAppear is your friend here. You should probably also avoid keeping a strong reference to the originating view controller as that could result in a retain cycle. Keep a weak reference to the parent from the child or consider creating a protocol and set the parent as the child's delegate (weakly referenced). And have the child communicate changes to the parent via the delegate reference.
I have a view, which is subclass of UIWebView. It has a property called Contact which is a managed object. The view uses templating engine to create a html with the object and then load into UIWebView. I thought it would be a better idea to monitor the object in the view itself, such that whenever something changes in the object, the view refreshes automatically. So, observed for certain attributes of the managed object in the view itself. And then to avoid the notification coalesce, I have made it such that the reload is done with
[self performSelector:#selector(refresh) afterDelay:0 ].
It refresh the webview automatically whenever it finds the change but also gives some strange crash. The crash says [MyWebView retain] message sent to deallocated object. I know I have properly removed observing values in dealloc method. But, it seems like dealloc gets triggered after a while. I have a strange issue related to releasing the view. The view stays for a while, although the view controller is already released and then releases after may 2/3 seconds. It is really strange. I think the crash is because of this.
Please do suggest me any idea. I will be glad to hear your suggestion. There are something wrong certainly, if anybody could point me I would really be grateful.
Using the delegate design pattern can cause EXC_BAD_ACESS KERN_INVALID_ADDRESS crashes if not used properly. If you have processing that is running in background threads that use the delegate design pattern, where in the object you set SELF as the delegate then you must remove SELF as the delegate in the dealloc method (even under ARC) by setting the delegate reference to nil, or there is a possibility that the object will try to call back into your deallocated object using the delegate design pattern. So if you have something like this in your object.
[_xmlParser setDelegate:self];
you should always have a dealloc method even under ARC to prevent the possibility of a crash in the case where your object gets destroyed while still doing work. It is very common to have your object destroyed while doing work. imagine a UIViewController that shows images from the internet. If you had a FetchImage class that used the delegate design pattern to lookup images that then calls a routine on the object when the lookup finishes, it is easily for the user to pop into and out of your UIViewController while your FetchImage object is still doing work on the background thread. You might not ever notice this when testing, but if you have hundreds of users, some of them will notice because the app will crash when your object tries to call a method on the SELF reference.
If your object uses the delegate design pattern, always have this to cleanup:
#pragma mark - dealloc - cleanup delegate references to prevent callbacks into deallocated objects (EXC_BAD_ACCESS / KERN_INVALID_ADDRESS)
- (void)dealloc
{
[_xmlParser setDelegate:nil];
// for non ARC based code you would also call: [super dealloc];
}
search every class in your project, if you have setDelegate:self or delegate = self then your users are most likely experiencing race condition crashes with your app if you don't have a dealloc cleanup method as described above. If you don't have the dealloc, add it even if you never see crashes when testing. -rrh
I'm using ARC (automatic reference counting).
Is it ok if I set the IBOutlets to nil in the viewDidDisappear instead of viewDidUnload?
Such as these:
[self setTheImage:nil];
[self setBTNplay:nil];
[self setBTNstop:nil];
I'm writing a navigation based app which includes pageViewController, I tested my app in Instruments to see the memory leaks and I keep getting receive memory warning message.
I've even put a log code in the viewDidUnload method. But it doesn't seem to get called when I even pop to rootViewController!
One more thing: If each page has an audioPlayer, where should I set a #property (nonatomic, strong) AVAudioPlayer *audioPlayer; to nil?
Or how do I set it to weak instead of strong? Because it gives me a 'warning' then in this code line:
_audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:poemURL error:nil];
it says: Assigning retained object to weak variable
You don't need to nil out those values in viewDidUnload. Make sure you're using weak properties instead of strong or assign for IBOutlets. Received memory warning doesn't necessarily mean you're leaking. Received memory warning means that your app is consuming too much memory. Run Instruments and edit your question with how much memory your app uses.
The fact that you're using AVAudioPlayer makes me thing that maybe you're pulling down some massive audio files into memory.
Also by the way, initWithContentsOfURL:error: will get you rejected from the App Store because you're blocking the main thread. Try testing your app on an iPhone with only cellular enabled and go into a part of your office/house that has a bad internet connection. Also try with your phone switched to airplane mode. Your app will undoubtedly either freeze for a long time before the connection fails or it will simply crash.
Instead, you should be using grand central dispatch or downloading it via NSURLConnection's block or delegate methods.
First, do not set to nil your properties in viewDidDisappear cause your view is still loaded. You must always set them to nil in viewDidUnload. It's invoked in low memory situations and here you must clean-up all stuff that breaks system memory.
Apple's UIViewController reference for viewDidUnload
When a low-memory condition occurs and the current view controller’s
views are not needed, the system may opt to remove those views from
memory. This method is called after the view controller’s view has
been released and is your chance to perform any final cleanup.
Second , take a look at this tutorial where is explained very well ARC
Are you calling [[NSNotificationCenter defaultCenter] removeObserver:self]; from one of your view controller subclasses? If so, that would explain why you're not getting viewDidUnload called.
If that's the problem, you should remove yourself from specific notifications when needed rather than all notifications as above. (It's OK to call removeObserver:self from dealloc, though.)
Is it ok if I set the IBOutlets to nil in the viewDidDisappear instead
of viewDidUnload?
There are many things wrong from this statement.
First of all, you do not set IBOutlets to nil in viewDidDisappear. viewDidDisappear is called when the view "disappears" (e.g. when it's in a tab bar controller, and you switch to another tab; or it's on a navigation controller, and you push something on top of it); the view can then "appear" again without loading again. If you set IBOutlets to nil, they will not be set again when you appear. (They are only set when the view is loaded.)
Second, if you have a leak, and setting stuff to nil "fixes it", that means you are not releasing the instance variables. You must always release retained instance variables in dealloc.
I've even put a log code in the viewDidUnload method. But it doesn't
seem to get called when I even pop to rootViewController!
Yes, viewDidUnload is only called in low memory situations. It is not called normally in most situations. If you were depending for it to be called, you were using the wrong method.