Best practices for implementing an iOS download manager with progress view - ios

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.

Related

How to get notifications for changes to a file in ios?

I am downloading a file using AFNetworking. But I am not able to keep track of my downloadOperation (i.e. viewController recieving completion callback is dismissed). Is there any api to track the file size changes to compare against the total file size.
I don't want to monitor filesystem changes like these answers:
what-is-the-optimal-way-to-monitor-changes-in-a-directory-with-a-kqueue
notification-of-changes-to-the-iphones-documents-directory
Can I use KVO to implement this type of behavior.
Your problem is a design issue. Your download operation is salient to multiple view controllers. The download is started and owned by a view controller that can go out of scope before the download completes.
Change the download operation to a higher level controller object (like your app delegate). That way controllers can initiate downloads or view status of downloads. You may want to define a protocol that interested view controllers implement to allow you to call specific methods on those view controllers.
Dispatch messages about your download operation to interested view controllers from that higher level controller.

How to access objects from AppDelegate without affecting memory management?

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.

ios updating view after it disappeared

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.

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