How to access objects from AppDelegate without affecting memory management? - ios

I have several view controller objects each can take in some user inputs from UITextField, save inputs to mutable arrays and display in a UITableView.
I also want these mutable arrays to be saved in files when home button is pressed by the user, so I found the code below in AppDelegate.m:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
It seems this method is perfect for saving user data whenever the user presses home button, but my question is how do I access these mutable arrays declared in different view controllers in order to save them into files? I can certainly make pointers in AppDelegate and make them point to each view controller object, but I know views can be unloaded when the program is running low on memory; therefore, if I make these pointers in AppDelegate then these view objects can never be unloaded when the memory is running low(because of strong references). What should I do? Thanks!

The general design pattern for user interfaces is the model view controller. The data storage ('model') is held in a separate object to the view controller.
For example, you can create an object that stores all of your applications data, instantiate it in the AppDelegate didFinishLaunchingWithOptions: method and store the reference in a property of your AppDelegate. Then each view controller can use [UIApplication sharedApplication].delegate.myData to retrieve the reference.

You need to get the MVC (model/view/controller) religion. Don't mix model (the NSMutableArrays) with controller (UIViewController). If you keep all the model data in separate classes then you don't have to worry about whether the view controllers exist or not. Also, it becomes super easy to keep your program logic clean. Put all the saving/loading stuff in the model classes. You can have your model classes listen for the UIApplicationWillResignActiveNotification and save when they receive it.
Unfortunately Apple's templates tend to push people toward putting a lot of stuff in app delegates or view controllers. Only really app global stuff should go in app delegate. Only controller code (that mediates between model and view) should go in a view controller.

Inside other view controllers you do this
id myid = [[UIApplication sharedApplication] delegate];
Please replace the "id" with the name of your AppDelegate Class (usually AppDeletegate).
You of course have to #import the AppDelegate in the top of your implementation file.

I suggest to listen for UIApplicationWillResignActiveNotification.
Also in iOS6 and later views are not unloaded when the memory is running low
(void)viewWillUnload Discussion In iOS 5 and earlier, when a low-memory condition occurred and the current view controller’s views
were not needed, the system could opt to remove those views from
memory. This method was called prior to releasing the actual views so
you could perform any cleanup prior to the view being deallocated. For
example, you could use this method to remove views as observers of
notifications or record the state of the views so it can be
reestablished when the views are reloaded.
In iOS 6 and later, clearing references to views is no longer
necessary. As a result, any other cleanup related to those views, such
as removing them as observers, is also not necessary.

Related

Best practices for implementing an iOS download manager with progress view

I have a UITableViewController displaying a list of audio files, when tapping on an audio file I push another UITableViewController showing a detailed view of the audio file. This view also features a "Download" button and a UIProgressIndicator view.
I have a Download Manager class (implemented as a Singleton) which takes care of downloading the file. Its download method takes a block which is called with updates about the download progress. I'm using it to update the UIProgressIndicator view. This works fine up to the point where you leave the detail view controller and come back at a later point in time when the file is still downloading. Of course, the progress block specified earlier is still available, but the referenced UIProgressIndicator view inside of it is not, thus it is not updated anymore.
I'm wondering, if it's a sensible idea to just re-set the block upon re-entering the detail view controller (viewDidLoad) or if the block-based approach is not really suitable for this case? Maybe it'd be better to use KVO?
Any suggestions?
Thanks!
The block approach is useful if the lifespan of the downloader is controlled by the VC. This way, when the VC is released, it releases the downloader (the downloader would not be a singleton).
If not you risk creating captured objects (the VC) that cannot be released because they are referenced in your block, and your block is referenced by an "eternal" object (the singleton).
As the VC lifespan is potentially shorter than the downloader, a better option would be to use some subscription-based observing of the downloader singleton.
This way your VC subscribes in (e.g.) viewWillAppear and unsubscribes in viewWillDisappear (important).
You can also use a global progress notification (via NSNotificationCenter), key-value observing or any other means.
The important part is that when your VC is released, nothing in the downloader points to it.

How do I load a view controller in advance if I know the user will most likely select it soon?

In my app, some studies I've done show that when the user views an article, the vast majority of the time (85%+) they load the accompanying comments view controller that goes along with the article.
I'd love to load this view controller while they're reading the article, so when they tap the comments button to transition to the comments view controller the view controller is ready without any loading times.
How would I go about accomplishing something like this? At the moment when the user taps the button I call performSegueWithIdentifier: and pass it the identifier I set in the Storyboard.
And obviously, for the cases where the user decides to go back to the root view controller (say, a list of articles) I'd want to cancel the loading of that comments view controller as it would be wasteful to continue at that point.
If you keep the data model separate from the UI, you shouldn't have any trouble creating the views on the fly, and almost nothing to gain from creating them earlier.
A reasonably standard approach is to bring the view in with blank or filler data, and have the data call go out to the middle tier, with asynchronous handlers processing callbacks.
This is much easier with the block based completion handlers available in the iOS 7 flavored NSURLSession, and only slightly harder with NSURLConnection (which listens for responses on the main thread, but can be thrown into the background once you catch the response).
So my advice would be to focus on backgrounding the data calls and responses, and strongly differentiate between displaying UI and populating the UI with data. If your data manager is separate from your View Controller, nothing is stopping you from "pre-fetching" the data a little early, and then potentially having it ready when the ViewController needs it. It's a perfectly normal load balancing / customer experience technique for high value data.
The solution I'm about to describe is a bit of a hack - it doesn't really conform to the proper model-view-controller design pattern that Ryan mentioned. That being said, it might give you an idea about how to proceed. Perhaps you can improve on it to make it cleaner.
First, define a #protocol in the App Delegate. Let's call this protocol CommentQueryDelegate; it should define a method called -(void)handleCommentQuery. Also give your App Delegate a strong property to store the comment data and a weak property to store a delegate object, like so:
#property (nonatomic, strong) NSMutableArray* arrayOfComments;
#property (nonatomic, weak) id<CommentQueryDelegate> commentQueryDelegateObject;
Make sure to initialize the both of these properties to nil.
Somewhere in the article view controller, use dispatch_async() to asynchronously query your database and retrieve the comments while the user is reading the article:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
// query your database here
appDelegate.arrayOfComments = [NSMutableArray array];
[appDelegate.arrayOfComments addObject:#"someComment"];
[appDelegate.commentQueryDelegateObject handleCommentQuery];
}
If the query is completed before the user segues into the comments view, the handleCommentQuery message will be sent to the nil object, which will have no effect.
Now, in the comments view controller's viewDidLoad method, set the commentQueryDelegateObject property of the App Delegate to self. You will need to specify that the comments view controller conforms to the comment query protocol. Next, check to see if the App Delegate's arrayOfComments property is nil. If it isn't, great - display the comments immediately. Otherwise, display a UIActivityIndicatorView.
Implement the -(void)handleCommentQuery method in your comments view controller. This method should disable the activity indicator and display the comments.
One final thing to consider - the strong pointer to the arrayOfComments object will keep it from being deallocated, so you should set this pointer to nil once you're done with your article view controller.

Saving iOS Application Scene State

This may be impossible, but I'm trying to save the state of my application between scene transitions, but I can't figure out what to do. Currently I love the way that when you have an application running and hit the home button, you can go back to that application just where you left off, but if you transition between scenes (in a storyboard), once you get back to that scene the application state was not saved.
I only have two different scenes that need to be saved (you transition back and forth from one to the other). How can I go about saving a storyboard scenes state without taking up precise memory?
More Detailed: Here is my entire storyboard. You transition back and forth between scenes using the plus toolbar button. On the second scene the user can tap on the table view cells and a real image will fill the image view (See figure 1.2)
Figure 1.1
In figure 1.2 you see what happens when you tap inside one of the many table view cells (an image view pops up.)
Figure 1.2
THE PROBLEM: When you tap a table view cell, which fills an image view (shown in figure 1.2) it works fine if you stay on that scene or even hit the iPhone home button (if you hit the iPhone home button and then reopen the app the scene's state was saved and the image view filled with a simple image still shows just like we left it), but if I transition (using the plus button) back to the first scene, and then use the plus button on the first scene to get back to the second scene the image view that I created (shown in figure 1.2) disappears and the second scene loads without saving the state and image views we filled.
EDIT: I tried using the same view controller for both scenes, but it didn't solve the problem.
UPDATE: I just found the following code (that I think stores a views state). How could I use this and is this what I've been looking for?
MyViewController *myViewController=[MyViewController alloc] initWithNibName:#"myView" bundle:nil];
[[self navigationController] pushViewController:myViewController animated:YES];
[myViewController release];
I would suggest a combination of two things:
1. Take DBD's advice and make sure that you don't continuously create new views
2. Create a shared class that is the data controller (for the golfers, so that the data is independent of the scene)
The correct way to make the segues would be to have one leading from the view controller on the left to the one on the right. However, to dismiss the one on the right you can use
-(IBAction)buttonPushed:(id)sender
[self dismissModalViewControllerAnimated:YES];
}
This will take you back the the view controller on the left, with the view controller on the left in its original state. The problem now is how to save the data on the right.
To do this, you can create a singleton class. Singleton classes have only one instance, so no matter how many times you go to the view controller on the right, the data will always be the same.
Singleton Class Implementation (Of a class called DataManager) - Header
#interface DataManager : NSObject {
}
+(id)initializeData;
-(id)init;
#end
Singleton Class Implementation (Of a class called DataManager) - Main
static DataManager *sharedDataManager = nil;
#implementation DataManager
+(id)initializeData {
#synchronized(self) {
if (sharedDataManager == nil)
sharedDataManager = [[self alloc] init];
}
return sharedDataManager;
}
-(id)init {
if(self == [super init]) {
}
return self;
}
#end
Then, inside your view controller code you can grab this instance like this
DataManager *sharedDataManager = [DataManager initializeDataManager];
This way you will have the same data no matter how many times you switch views.
Also, you can better adhere to MVC programming by keeping you data and your view controllers separate. (http://en.wikipedia.org/wiki/Model–view–controller)
Figure 1.1 has a fundamental flaw which I believe the basis of your problem.
Segues (the arrows between controllers on the storyboard) create new versions of the UIViewControllers. You have circular segues. So when you go "back" to the original screen through the segue is really taking you forward by creating a new version.
This can create a major problem for memory usage, but it also means you can't maintain state because each newly created item is an empty slate.
Since your are using a UINavigationController and pushViewController:animated: you should "pop" your controller to get rid of it.
On your "second" scene, remove the segue from the + button and create an IBAction on a touchUpInside event. In the IBAction code add the "pop"
- (IBAction)plusButtonTapped {
[self.navigationController popViewControllerAnimated:YES];
}
I see what you mean. This should happen to every application, as when the last view controller in the navigation stack is transitioned away from, it is deallocated and freed. If you need to save values such as text or object positions, a plist may be the way to go. See this related question for how to use a plist.
Apple isn't going to do this for you. You should probably just save the state of each view using NSUserDefaults and each time your application launches re-load your saved data.
If you are storing everything in CoreData you would only need to save the active view and a few object ids, if not you would need to save any data you have.
Don't expect iOS to save anything that you have in memory between launches. Just store it in NSUserDefaults and load it each time.
Store the state of the scene in NSUserDefaults or inside a plist file then when loading up the scene just load it with the settings from there. If the images are loaded from the internet you might also want to save them locally on your iphones hard drive so it runs a bit smoother.
I don't think you should cycle the segues, just use one that connects viewcontroller 1 from viewcontroller 2 should be enough and that way you make sure that no additional viewcontrollers are being made (memory problems maybe?)
However for your particular problem, I believe that you should use core data to save the exact state of your table, view because ios doesn't save the exact state of view at all times. it will require work but you will achieve what you want. You will need to save the exact photo( using a code or enums that will be saved), the location in the table view, the score or well whatever data you need to save that state.
The best of all is that coredata is so efficient that reloading the data when the app is relaucnhed or into foreground it takes no time, and ive used core data to load more than 5k of records until now and works just fine and its not slow at all.
When i get back home ill provide a code you might use to get an idea of what i mean.
The key here is to:
Have some sort of storage for the data that your application needs. This is your application's data model.
Give each view controller access to the model, or at least to the part of the model that it needs to do its job. The view controller can then use the data from the model to configure itself when it's created, or when the view is about to appear.
Have each view controller update the model at appropriate times, such as when the view is about to disappear, or even every time the user makes a change.
There are a lot of ways that you can organize your data in memory, and there are a lot of ways that you can store it on disk (that is, in long term storage). Property lists, Core Data, plain old data files, and keyed archives are all possibilities for writing the data to a file. NSArray, NSDictionary, NSSet, and so on are all classes that you can use to help you organize your data in memory. None of that has anything to do with making your view controllers feel persistent, though. You'll use them, sure, but which one you choose really doesn't matter as far as updating your view controllers goes. The important thing, again, is that you have some sort of model, and that your view controllers have access to it.
Typically, the app delegate sets up the model and then passes it along to the view controllers as necessary.
Something else that may help is that you don't have to let your view controller(s) be deleted when they're popped off the navigation stack. You can set up both view controllers in your app delegate, if you want, so that they stick around. You can then use the ones you've got instead of creating new ones all the time, and in so doing you'll automatically get some degree of persistence.

Does UIImageView release resources when not in view?

Does UIImageView automatically release image resources when it isn't in view? And perhaps when the app gets a memory warning?
If not, I'm considering writing a subclass (e.g. SmartReleaseImageView) that does so, to be used in particular situations where this would be beneficial. I was thinking of behaviour along these lines:
When SmartReleaseImageView receives setImage message, it registers itself with a manager e.g. SmartReleaseImageViewManager.
When app gets a memory warning, SmartReleaseImageViewManager loops through all its SmartReleaseImageViews and checks whether any of them aren't added to a superview, or checks whether any of them have a frame that is culled due to being outside of the bounds of the main window.
When SmartReleaseImageView detects that it has come into view and has been released, it loads the image again. So perhaps, it would need to have a method like setImageURL rather than setImage in the first place.
Is this kind of behaviour already built into UIImageView? Or does it exist in a 3rd party version anywhere? Or is there a problem with idea?
(I'm writing a UIImageView-heavy app, and it would be nice to have a global catch-all solution that would help with the memory situation, rather than adding lots of extra code to my UIViewControllers.)
EDIT: Answers so far suggest that UIViewController should be responsible. This is true, and I guess my particular case is atypical. In my case, the UIViewController can contain a lot of custom views (with their own hierarchy) that it doesn't necessarily know the structure of, and which go in and out of view frequently. So, it's difficult for it to know when to release its resources. But yes, perhaps I should find a way for it to deal with the problem itself...
Well, you actually have this automatic management implemented in the UIViewController. Since UIImageView is a subclass of UIView, your view controller should be able to manage it automatically.
Memory Management
Memory is a critical resource in iOS, and view controllers provide
built-in support for reducing their memory footprint at critical
times. The UIViewController class provides some automatic handling of
low-memory conditions through its didReceiveMemoryWarning method,
which releases unneeded memory. Prior to iOS 3.0, this method was the
only way to release additional memory associated with your custom view
controller class but in iOS 3.0 and later, the viewDidUnload method
may be a more appropriate place for most needs.
When a low-memory warning occurs, the UIViewController class purges
its views if it knows it can reload or recreate them again later. If
this happens, it also calls the viewDidUnload method to give your code
a chance to relinquish ownership of any objects that are associated
with your view hierarchy, including objects loaded with the nib file,
objects created in your viewDidLoad method, and objects created lazily
at runtime and added to the view hierarchy. Typically, if your view
controller contains outlets (properties or raw variables that contain
the IBOutlet keyword), you should use the viewDidUnload method to
relinquish ownership of those outlets or any other view-related data
that you no longer need.
source: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html
Short answer:
UIImageView does not release resources, never.
But all your views (including UIImageView) are released when the system needs more memory. Note that you have to release your views in [UIViewController viewDidUnload].
Edit:
Unfortunately, it doesn't work when the controller is not displayed. It that case I suggest to purge the components manually in viewDidDissappear (call viewDidUnload manually and remove components from the view hierarchy).
Anyway, this won't help if one view controller has too many images in its hierarchy. In this case I would recommend to create a set of UIImageViews and reuse them, in the same way as UITableView reuses its cells.

Do I have to keep a copy of all values put into views in my UIKit app?

I have a view controller with a view loaded from a NIB. As I understand it, the view can be unloaded from memory at any time by by controller. Say my app goes into the background, for example.
If I have values, such as text in a text field, those values will be lost when the view is unloaded. Or I might want to set those values before the view is loaded, in which case my outlets are not set and I am not able to do it yet.
Therefore, do I need to keep a shadow copy of all values in my user interface, so that I can re-set them in viewDidLoad: if the view gets unloaded and then loaded again?
Or am I doing things the wrong way?
In case of app going to background, implement methods to save and restore app. UIApplication will let you know when focus is going to and has changed in following UIApplicationDelegate methods:
– applicationDidBecomeActive:
– applicationWillResignActive:
– applicationDidEnterBackground:
– applicationWillEnterForeground:
See help for more details.
Forgot about memory warnings...
Solutions depend from case to case. In my experience, when it comes to the point when your visible view is removed to clear some memory, you don't want to implement recovery mechanism but test for memory leaks, observe memory allocations, optimize memory management or rethink design.

Resources