I have stumbled upon a problem in my application where my music player view controller needs to retain the delegate (which is a cloud based storage with songs) to keep the song playlist, until the song from a new folder is selected.
So, when the user taps a song in some folder, I assign the delegate to that ViewController so even when it is pushed from the view, it stays in the memory so the music player can play next and previous songs. But when the user selects the song from another folder(ViewController), I set the music player delegate to nil, and assign the delegate to that new ViewController.
Is this solution acceptable?
Code:
MusicPlayerViewController has:
#property (nonatomic, strong) id <MusicPlayerViewDelegate> delegate;
View Controller in which the songs will be loaded from cloud storage folders has this called when tapped on cell(song):
musicPlayerViewController.delegate = nil;
musicPlayerViewController.delegate = self;
There is no fundamental problem with retaining (holding a strong reference to) a delegate. It is unusual, but not unprecedented. NSURLConnection does it. It creates a retain loop that can be very useful if correctly managed. It's just up to you to make sure that the object will release its delegate in a deterministic way so that the retain loop is broken.
BUT... the specific case that you're discussing here sounds like you have an MVC problem and that your view controller is doing something it shouldn't be.
I assign the delegate to that ViewController so even when it is pushed from the view, it stays in the memory so the music player can play next and previous songs.
If you're saying that you cannot play music unless a certain view controller is in memory, then the view controller probably has an incorrect responsibility. The view controller should manage the view. That should be independent of actually playing music. See https://stackoverflow.com/a/5228317/97337 for discussion of how a music-playing system might be broken out in MVC.
In the example you are proposing, the delegate should be actually weak considering the music player stays as the same instances and the view controller gets set / unset to something different.
If the delegate were strong, your Viewcontroller will not get released unless the Musicplayer gets deallocated and in most cases a Viewcontroller should get released when it's view is no longer in use. As Rob mentioned, you seem to have a MVC problem.
Related
I have ParentViewController that allocates ChildViewController, pushes it onto controller stack and releases it.
ChildViewController implements the protocol ProductDownloadDelegateProtocol required by the Product class.
At some point, ChildViewController creates a Product object and sets itself as its downloadDelegate.
While downloading, Product class updates ChildViewController via methods defined in ProductDownloadDelegateProtocol.
If the user presses the back button in the navBar of ChildViewController while downloading, the next update of download percentage from Product causes an EXC_BAD_ACCESS.
Although Product checks if downloadDelegate is nil, problem still occurs since ChildViewController/downloadDelegate is deallocated, but not set as nil. I don't know which point is best to set ChildViewController to nil.
Is my design wrong?
If your ChildViewController creates an instance of Product and sets itself as the delegate, it should be its responsibility to remove itself as the delegate when it's about to be unloaded. Either in it's viewDidUnload or dealloc method you should be setting the Product delegate to nil.
If ChildViewController stays around (say you are reusing the view controller), maybe you can remove it as the delegate in the viewWillDissappear method.
Another solution to fix this particular EXC_BAD_ACCESS issue is to move to ARC and use Zeroing Weak References (see a good writeup here, http://www.mikeash.com/pyblog/friday-qa-2010-07-16-zeroing-weak-references-in-objective-c.html).
Still, I would recommend you move to ARC for the right reasons, and not to fix this particular issue you are facing.
My rule of thumb goes like this: You should never be the delegate of an object you do not own. Here, "own" means "hold a strong reference to" (in ARC terms). The main exception is when the delegate retains you, and UIApplication.delegate because that's a bit weird.
Usually I bundle the logic into the setter like this:
-(void)setProduct:(Product*)p
{
product.delegate = nil;
[product release];
product = [p retain];
product.delegate = self;
}
-(void)dealloc
{
self.product = nil;
}
However, an underlying problem with this design is that a "product download" can only have one delegate. What if you navigate away from the ChildViewController and then back into (a new instance of) it? Are they different Product instances, both being downloaded?
A better way might be to have a download manager singleton (as much as I hate singletons) that manages downloads and uses NSNotification/NSNotificationCenter for progress notifications.
I have been developing iphone applications for around 3months now and theres a few things that stump me and i don't really have an idea how to work round them.
I have a navigation controller controlling the views in my application however every screen that is loaded, used then pushed back loses all the information as it seems to be reinstantiated... I believe this is a possible memory management issue?
But how to i create an app that navigates and retains all information in its views until the application is closed.
Thanks :)
Possible you didn't keep a reference to the view controller, the issue is for UIVIewController not to be released.
Make the view controller an ivar you will instanciate only one time when you push it on stack.
// in .h
MyViewController *mVC;
// in .m
// maybe when the user selects a row in a tableview
if(mVC == nil) {
// first time use, alloc/init
mVC = [[MyViewController ....];
}
// then push on the stack
[self.navigationController ....];
Of course don't forget to release it later.
In this part:
MyViewController *myViewController=[MyViewController alloc] initWithNibName:#"myView" bundle:nil];
[[self navigationController] pushViewController:myViewController animated:YES];
[myViewController release];
You will probably have something like this... Instead, make your myViewController a class's property so you have a reference to it. And drop the [myViewController release]; statement.
Possibly your app is receiving a didReceiveMemoryWarning.
In such cases, when the super class is called, the framework does memory cleaning by unloading all the views that are not currently displayed. This could explain the behavior you are seeing.
To check it further, override didReceiveMemoryWarning in one of your view controllers or applicationDidReceiveMemoryWarning in your app delegate, and put a breakpoint in it. Don't forget to call [super...] appropriately, otherwise pretty soon your app will be killed. What you should see in this way is that the views do not disappear before hitting the breakpoint, and do disappear after that.
If the hypothesis is correct, you should find a way to save the state of your view in viewDidUnload and restore it in viewDidLoad. Look also at didReceiveMemoryWarning reference.
Try to save data in NSUserDefaults it its small or use plist or it its too small like 5-10 objects save in in some variable in appDelegate, and if its too large use sqlite and for saving something like images of files like xml use Document directory
The UINavigationController works like a stack: you push and pop UIViewControllers on it. That means when a UIViewController get popped, it will have its retain count decremented by 1, and if no other object holds a reference to it, it will be deallocated. You can avoid the UIViewControllers getting dealloced by keeping a reference to them yourself by calling -retain on the objects, for instance in your appDelegate.
You can use NSUserDefaults to save the states of the UIControls in the view.
So whenever u r loading a view, set the values to the controls so that it looks like it resume from the place where we left.
I've built an app that uses a UITableView inside a UINavigationController, inside a UITabBarController. Every entry in the UITableView opens up a view that contains some basic text, buttons, but most importantly, an MPMoviePlayerController that plays audio when started. A user can click this MPMoviePlayerController and continue to browse around the rest of the app (different tabs, or moving back in the navcontroller, opening other views from the tableview) and continue to hear the audio.
I'd like the user to be able to return to the view with the active MPMoviePlayerController at any time. I understand how I would go about allowing the user to return to a certain view from any view, but I'm struggling with how to prevent that view from being reloaded when the user tries accessing the same view.
Is there any way I can save a view in memory? Or save the active MPMoviePlayerController as some type of global object, so that I can at least access that from anywhere?
I appreciate any and all help. Thanks!
I'd recommend you create a property for the MPMoviePlayerController in your app's UIApplicationDelegate (which you can then access from anywhere in the code with [UIApplication sharedApplication].delegate but you will need to cast to your UIApplicationDelegate subclass).
When you come to enter the screen which plays content, check whether your movie player property in the app delegate is nil, if it is create it, otherwise re-use it.
Don't forget to release the reference to your MPMoviePlayerController when the media stops playing, or when the media has already stopped and you get a memory warning or when your app shuts down.
The down side of this approach is it causes coupling between most of your view controllers and your app delegate. You could mitigate this with the use of a protocol however.
You should simply retain it. Like this [myView retain] and keep a pointer to it in where you need. When you want myView to appear, just add it as a subview to current visible view like[myController.view addSubview:myView].
Hope that will help, Good luck!
I've found that even adding a retain doesn't do the trick. I've actually found the best success with overriding the setView (since part of unloading the view involves calling setView:nil. I have a BOOL that gets set the FIRST time the VC loads and once thats set it will never allow setView to be called again.
- (void) setView: (UIView*) view{
NSLog(#"MainViewController: setView");
// this is our attempt to stop iOS from unloading our view.. when iOS tries to unload your view they call setView:nil.. so, no!
if(!viewDidAppear) [super setView:view];
}
A little bit of a hack, but you can override setView: in your subclass so that it never allows to set the view to nil:
-(void)setView:(UIView *)view
{
if (view == nil) return;
[super setView:view];
}
I am pushing a UIViewController onto a UINavigationController. This view controller immediately starts a download of an xml feed and then parses it. However, if you hit the back button before it is done downloading, and crashes with EXC_BAD_ACCESS. The line that is crashing it is in parserDidEndDocument and is this line:
if (self.delegate && [self.delegate conformsToProtocol:#protocol(ModelDelegate)]) [self.delegate modelDidFinishParsing:self];
I assume it is crashing because it is trying to access self.delegate which is not assigned anymore. How do I get around this?
Also, I would release the model object in the modelDidFinishParsing method. How would I release this model if it never reaches this method.
I set up objects to handle my downloads (and other asynchronous or long running tasks) in the AppDelegate, then trigger them as required from various controllers. That way they are owned and have persistence through the life of the application.
The best way to do this is to pass them to the viewControllers that will need them (rather than the viewController "expecting" the appDelegate to have such and such an object ready and waiting) - dependency injection.
These objects update my model in some way when they finish and if I need to, I use NSNotifications to announce they are done. This isolates me from the mess I used to get into trying to cancel or swap delegates in viewWillDisappear etc to avoid the kind of issues you are running into.
The reason your app is crashing is probably because NSURLConnection retains its delegate (so it can call back to it reliably) but objects that this delegate has weak references to have been deallocated.
Ie, in your case what self.delegate points to has probably been deallocated when the view controller is popped but the delegate property has not been cleared (set to nil).
The solution to your problem is to clear (nil) self.delegate at the appropriate time when the UIViewController subclass is being popped off the navigation stack.
Note: retaining delegates is not usual behaviour for Cocoa classes. In situations where it happens contrary to standard practice it is documented (see the NSURLConnection docs).
I'm interested in the way I am retaining my properties and the aspects of memory management. This is for a simple application that edits the information of a class that is stored in a table. There are 3 ViewControllers.
A list view (list all classes)
a detail view of the selected item
(lists properties of selected class)
an edit view (lists single property
of selected class and allows it to be
edited)
This is how it is structured at present, what do you think?
ListViewController
#property (nonatomic, retain) NSMutableArray *pools;
#property (nonatomic, retain) PoolFacilityEditController *childController;
To add a new class instance to the table you click an add button that runs this method..
//Loads up the editPoolFacility controller to add a new pool
-(void)add {
PoolFacilityEditController *editController = self.childController;
PoolFacility *aPoolFacility = [[PoolFacility alloc] init];
[self.pools addObject:aPoolFacility];
[aPoolFacility release];
editController.thePoolFacility = aPoolFacility;
editController.pools = self.pools;
[self.navigationController pushViewController:editController animated:YES];
}
The next controller is now loaded up and here are its interesting instance variables. Wise or not I have chose to just assign the pool to the new controller rather than retain. I don't want to unnecessarily retain.
detail View
#property (nonatomic, assign) PoolFacility *thePoolFacility; (assigned in the above add method)
#property (nonatomic, assign) NSMutableArray *pools; (also assigned in the add method)
The detail view has a method that does the following..
- (void)viewWillAppear:(BOOL)animated {
//Pass the copy onto the child controller
if (self.childController.thePoolFacility != self.thePoolFacility) {
self.childController.thePoolFacility = self.thePoolFacility;
}
}
The pool is passed onto the detail edit controller so it knows the pool it is editing.
Now a user clicks on an individual bit of pool information (e.g name) and the detail view controller pops up. It allows the editing of individual properties.
It's interesting properties look like this:
#property (nonatomic, retain) PoolFacility *thePoolFacilityCopy;
#property (nonatomic, assign) PoolFacility *thePoolFacility;
And it creates a copy to edit in case the user changes the values and then wants to cancel. If the user presses save it copies the values from the copy into the non-copy.
- (void)viewWillAppear:(BOOL)animated {
PoolFacility *poolCopy = [self.thePoolFacility copy];
self.thePoolFacilityCopy = poolCopy;
[poolCopy release];
}
If save or cancel is pressed the view is popped.
And then we're back to the middle view that displays all the fields.
Now if the user presses save I just poptheviewcontroller and we're back to the list view. OR if the user presses cancel I run this method.
-(void)cancel {
[self.pools removeObject:self.thePoolFacility];
[self.navigationController popViewControllerAnimated:YES];
}
So to summarize
I am assigning a property throughout different view controllers rather than retaining it.
Also my view controllers are only loaded once and are not deallocated when they 'dissapear'
I hope this made some sense! My question is.. Is this a good way of doing it?
Thanks,
Dan
I didn't see a specific question here, so I'll just make some general critiques.
In iPhone OS, Cancel buttons are common on dialogs meant to add a new item, but much less so on edit dialogs. In fact, the only example of a Cancel button on an Edit dialog I can think of is in the Clock app's Alarm panel. So don't worry about copying the PoolFacility and copying the changes back when it's saved; just make the Cancel button only be visible for new objects (or use the Trash icon--canceling a new pool and deleting an existing one are actually the same action the way things are designed right now).
As you have things now, there's no danger of an object being deallocated at the wrong time. However, if you ever change the storage method--for example, making the app lazily load PoolFacility objects from the disk--it will come back to bite you. Write it properly today and you'll save yourself pain tomorrow. The proper way is to make the thePoolFacility a retained property and release it in your dealloc method. (If you keep managing the pools list the way you currently do, you should do the same thing with it.)
Speaking of which, you don't show how existing PoolFacility objects are loaded. Where do they come from? If there's some kind of database access going on, you may find it helpful to have PoolFacility send notifications when an object is created, updated or deleted, and then observe and react to the appropriate notifications as needed. All of the apps I've written that store user data take this approach; I've found it very handy and flexible.
Since there's only one pool list and it's needed by multiple controllers, there's no shame in storing it in your app delegate instead of passing it around. Better yet, write a FacilityList singleton object that manages the list. This could allow you to take a lot of logic out of your controllers. Generally, you should put everything you can into your models, except the stuff that interacts with the screen. That means that when Apple makes the iTablet or releases the Apple TV SDK--or just when you decide to make a Mac version or redo the user interface--you can bring as much of your app as possible over unmodified.