(originally i posted this on Apple Developer Network and got no response in four days, so i am copy/pasting here)
ARC is enabled.
In my class i have an UITableViewController property,
#property (nonatomic, strong) UITableViewController* tableViewControllerSectionMenu;
which is used to hold table view for UIPopoverController.
This code creates UITableViewController and sets delagate and data source in viewDidLoad:
self.tableViewControllerSectionMenu = [[UITableViewController alloc] initWithStyle:UITableViewStylePlain];
self.tableViewControllerSectionMenu.tableView.dataSource = self.dataSource;
self.tableViewControllerSectionMenu.tableView.delegate = self;
This works fine until memory warning occur.
After that the table is blank.
I tried putting reloadData at place where popover is invoked but that changes nothing.
Does somebody know why this is happening and how to remedy this?
By googling i have found several solutions and none have worked in my case.
I think your data is cleaned when the memory warning is received. Did you implement either didReceiveMemoryWarning or viewDidUnload by any chance?
When memory warning message is received, all views not currently visible on screen are purged. They are supposed to be recreated later when needed.
To recreate them means also to attach delegate and data source again. Since memory warning was received while in ViewController X, and it's view was on screen, my table view got purged.
But as i wasn't moving from X, delegate and data source were not attached again, as that was done in viewDidLoad.
To evade getting back one step from X, i created a new class for my table view where in viewDidLoad i attached delegate and data source.
Related
When my app is launched, it starts out at a custom UIViewController with a custom UITableView with custom UITableViewCells. The cells can play videos, among other things. I'm using AVPlayer and AVPlayerLayer to play the videos. This initial view controller also has a menu-popup, which can take you to another controller. When another menu-option is clicked, the initial UIViewController deallocates. This is on purpose, and setting the new UIViewController as the applications rootViewController. However, if I start playing a video in a cell in the initial viewController and then click a menu-option, the video keeps playing in the background, I can still hear the audio.
I have simply put
-(void)dealloc{
NSLog(#"Initial controller deallocated");
}
in the initial view controller to confirm that it deallocs when I set the new controller as root, and this prints out, but the video still keeps playing.
I have now 'overridden' the -(void)dealloc(like above) methods of both the custom UITableView and the custom UITableViewCell's, but neither of these fires.
I have simplified the explanation of the hierarchy here, and I believe there could be other connections holding on to the tableView(mostly because I don't know any other reason for this behavior).
I believe, however, that these possible connections all are under the initial view controllers hierarchy, and it is my understanding that the entire hierarchy under a view controller will vanish when the view controller does. Is the AVPlayer a reason the tableView and/or cell won't dealloc? The videos playing are over internet, and I can also tell that the app continues downloading and buffering the video after the controller has been deallocated. I have tried setting the player, the playerLayer and the entire cell to nil, but it keeps downloading, and I'm thinking I have misunderstood some connections.
First of all, I think properties with (strong, nonatomic) or (nonatomic, retain) might have something to do with these. I don't completely understand where to use what, so I almost exclusively use this. Of what I'm reading, weak seems to be correct for some of these references, but I'm also 'scared' of the implications, as I don't completely understand them.
Second, in my custom UITableView, I have a property: #property CustomCell *playing;.
When I start playing a video in a specific cell, like this;
[someSpecificCell.player play]; I also do this: self.playing = someSpecificCell;. Now I can later easily find the cell that's playing, I.E to stop it. When I later try to deallocate the cell, I use self.playing = nil;, however, this does not deallocate the cell. Why not? Am I simply removing the reference from tableView? Does this mean there are other references?
Is there a way to see a list of where these references are /from? As far as I know, all references are in the same initial hierarchy, yet somehow some parts of the hierarchy stays alive after their controller has deallocated.
Update
I've now tried to launch Instruments with the Allocations-tool, and I don't completely understand what I'm seeing.
The hierarchy I've created is something like this:
MyNavController1->MyViewController1->MyView1->MyTableView1->MyCell1->text&video etc.
When I click another menu-option, I create another (almost identical) hierarchy, setting the new MyNavController(2) as rootViewController for the app. The 'current' hierarchy will now be
MyNavController2->MyViewController2->MyView2->MyTableView2->MyCell2->something else
When I present the new hierarchy, and set this new hierarchy as rootViewController to the app, the first hierarchy is supposed to be deallocated. In -(void)dealloc of both MyNavController1 and MyViewController1 print out "Deallocated" with NSLog();, and nothing from MyTableView1 and MyCell1.
In Instruments it says that MyViewController is #living: 1, #transient: 1, which I'm guessing means the first deallocated correctly.
When I check the field for MyTableView, it says #living: 2, #transient: 0, which I'm guessing means the tableView isn't deallocated, as my previous "research" found.
However, in the list showing the two instantiations of MyTableView, it says that the responsible caller of the first tableView(which is supposed to be deallocated, but isn't) is the first MyViewController1, which is confirmed deallocated. What does this mean?
If I further click the Address field of this specific MyTableView(the first), it says:
# Event Type ΔRefCt: RefCt ... Responsible Caller
0 Malloc +1 1 ... 'MyViewController1'
What does this really mean, and can I find out where a possible reference is held?
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.
I have an Application, which is a SplitViewController that has a master view on the left and the detail view on the right. One of the views (Branch Finder) is a Map view that loads a series of Annotations to the Map.
If I let the annotations load before switching to any other view (loading the annotations take takes all of 1 second) then everything is fine. However, if the user quickly switches off the Branch Finder view, whilst the annotations are being loaded, then the App will crash with the following notice:
[BranchFinder_iPad respondsToSelector:]: message sent to deallocated instance 0x807d230
Now, my thoughts are that the deallocated instance would refer to the Array (declared in the header of the view) that contains all the annotations being released and set to nil when the user leaves the BranchFinder_iPad view. This is the array that is being passed to the addAnnotations method.
[self.mapView addAnnotations:branchSites];
Has anyone else encountered an issue where leaving a view, mid-way in the add allocations and a crash occurs if the user moves to another view.
Just to clarify:
If I wait for the annotations to load, switching to any other view causes no problem.
I did have a custom annotation view, but I stripped that out of my code (to eliminate it from the mix). Doing this has not changed anything.
I have looked elsewhere for help on this issue, but a lot of the view tutorials regarding map views are single view only, so this issue hasn't arisen.
I have found a vaguely similar issue # the following: mapkit addAnnotations crashes
And finally, I have just made the jump to x-code 4. I think some of my problems are just because I'm relearning some of the things I should know.
Regards,
Nathan A
PS: I wanted to attach an image to this, but am having trouble. I don't have the reputation points to do it natively, and my workplace doesn't allow me access to any image hosting portals. I will endeavour to add an image later today.
Hey anyone who reads this.
I basically performed a rookie mistake here - for the MKMapView in my application, I had to set the delegate to nil as part of the deallocation routine within my view. THe apple documentation makes mention of this in the below document:
http://developer.apple.com/library/ios/#documentation/MapKit/Reference/MKMapViewDelegate_Protocol/MKMapViewDelegate/MKMapViewDelegate.html
For the relevant section:
Before releasing an MKMapView object for which you have set a delegate, remember to set that object’s delegate property to nil. One place you can do this is in the dealloc method where you dispose of the map view.
Not having this was only causing an issue if I switched to another view AND if the MKMapView was still being referenced in executing code, such as the addAnnotations routine.
i want to write my own photogallery like the original "Photos.app" from apple.
I´ve created a UITabbarcontroller in the AppDelegate and then an "ImageViewController" and a "VideoViewController".
In the "ImageViewController" i´ve added an UIScrollView and then made an instance of my own "PhotoGallery" with different properties like imagePerRow, images, paddings etc.
For the "PhotoGallery" i´ve created a new objective-c class as a subclass of "NSObject", where i´m positioning all the different images as UIButtons.
Then i´ve added another function which describes the arrangement for all the images when the device orientation has changed. And the dealloc-function. Thats all.
This class works great, also the rearrangement when the device orientation has changed. The problem is, if i simulate a memory warning in the ios-simulator, the first time the PhotoGallery gets correctly dealloc but if i simulate a warning again, i get a error-message: "[PhotoGallery release]: message sent to deallocated instance ".
I thought its because of the subclass as NSObject, right?
Then i´ve tested it as a UIView. With the same error. So know i don´t know what to do anymore. Hope you understand what´s the problem and you would give me some hints on that..
Think about calling the init-function again? How? Need "drawRect"? I´ve no idea.
Thanks for your time and help,
G.
You're probably not setting the property which holds a reference to the PhotoGallery to nil.
ie. You're keeping a reference to a deallocated instance, and attempting to call release on it.
bad example:
- (void) didReceiveMemoryWarning
{
[photoGallery release];
}
safe(r) example:
- (void) didReceiveMemoryWarning
{
[photoGallery release];
photoGallery = nil;
// or combine both actions if your property attributes are set up to accommodate it:
// self.photoGallery = nil;
}
In the bad example, photoGallery still holds a reference to a now-deallocated instance, and the second memory warning will attempt to send a message to it.
In the safe(r) example, photoGallery is nil, and sending a message to nil is safe.
When my iPhone app receives a memory warning the views of UIViewControllers that are not currently visible get unloaded. In one particular controller unloading the view and the outlets is rather fatal.
I'm looking for a way to prevent this view from being unloaded. I find this behavior rather stupid - I have a cache mechanism, so when a memory warning comes - I unload myself tons of data and I free enough memory, but I definitely need this view untouched.
I see UIViewController has a method unloadViewIfReloadable, which gets called when the memory warning comes. Does anybody know how to tell Cocoa Touch that my view is not reloadable?
Any other suggestions how to prevent my view from being unloaded on memory warning?
Thanks in advance
Apple docs about the view life cycle of a view controller says:
didReceiveMemoryWarning - The default
implementation releases the view only
if it determines that it is safe to do
so
Now ... I override the didReceiveMemoryWarning with an empty function which just calls NSLog to let me know a warning was received. However - the view gets unloaded anyway. Plus, on what criteria exactly is decided whether a view is safe to unload ... oh ! so many questions!
According to the docs, the default implementation of didReceiveMemoryWarning: releases the view if it is safe to do (ie: superview==nil).
To prevent the view from being released you could override didReceiveMemoryWarning: but in your implementation do not call [super didReceiveMemoryWarning]. That's where the view is released by default (if not visible).
The default didReceiveMemoryWarning releases the view by calling [viewcontroller setView:nil], so you could override that instead.
What appears to be working for me was to override setView: to ignore setting to nil. It's kludgy, but then, this is a kludgy issue, and this did the trick:
-(void)setView:(UIView*)view {
if(view != nil || self.okayToUnloadView) {
[super setView:view];
}
}
Could it be so simple?
Even though nowhere in the documentation this is mentioned, it seems that if I exclusively retain my view in viewDidLoad, then it does not get released on Memory Warning. I tried with several consecutive warnings in the simulator and all still seem good.
So ... the trick for the moment is "retain" in viewDidLoad, and a release in dealloc - this way the viewcontroller is "stuck" with the view until the time it needs to be released.
I'll test some more, and write about the results
I don't think any of these ideas work. I tried overriding [didReceiveMemoryWarning], and that worked for some phones, but found one phone unloaded the view BEFORE that method was even called (must have been in extremely low memory or something). Overriding [setView] produces loads of log warnings so I wouldn't risk that by Apple. Retaining the view will just leak that view - it'll prevent crashes but not really work - the view will replaced next time the controllers UI is loaded.
So really you've just got to plan on your views being unloaded any time they're off-screen, which is not ideal but there you go. The best patterns I've found to work with this are immediate commit so your UI is always up-to-date, or copy-edit-copy, where you copy your model to a temporary instance, populate your views and use immediate commit with that instance, then copy the changes back to your original model when the user hits 'save' or whatever.
Because the accepted solution has problems with viewDidUnload still getting called even though the view was blocked from being cleared, I'm using a different though still fragile approach. The system unloads the view using an unloadViewForced: message to the controller so I'm intercepting that to block the message. This prevents the confused call to viewDidUnload. Here's the code:
#interface UIViewController (Private)
- (void)unloadViewForced:(BOOL)forced;
#end
- (void)unloadViewForced:(BOOL)forced {
if (!_safeToUnloadView) {
return;
}
[super unloadViewForced:forced];
}
This has obvious problems since it's intercepting an undocumented message in UIViewController.
progrmr posted an answer above which recommends intercepting didReceiveMemoryWarning instead. Based on the stack traces I've seen, intercepting that should also work. I haven't tried that route though because I'm concerned there may be other memory cleanup which would also be blocked (such as causing it to not call child view controllers with the memory warning message).