Is there a reverse of awakeFromNib, a method called when the nib is closed? I know an application delegates receive a notification the the application will terminate, but was wondering if there was to save some state information on a simple NSObject.
Not really, but mostly because it generally doesn't make sense. By the time an individual object is being deallocated, it is generally too late to do anything meaningful.
For saving state, you would generally want to do so periodically, often triggered by some user action (user just entered a bunch of data.... good time to save, user transitioned to a new part of the app.... save). Only saving as the app is terminated or as a window is closed (or, in your case, as a particular screen is exited) is a recipe for data loss.
For an NSObject instance its dealloc routine will get called as the object is going away -- you should have a chance there to save off any state you need to before destroying self and super.
Related
I want to know what the best way to implement a certain functionality. I have a message composer view where the user creates a NSManagedObject MessageObject. I also have a class ObjectHelper which has a global instance initialized.
I have a background (private queue) NSManagedObjectContext create a MessageObject immediately when the user enters the compose view. The catch here is that the ObjectHelper (not the ComposerViewController) is the owner of this new object - it has a property variable with a strong reference:
ObjectHelper.h:
#property(nonatomic, strong) MessageObject *newObject;
Then, back in my ComposeViewController, I set a timer to save the object to disk every 30 seconds (this saves a draft, like auto-save, in case the user gets interrupted somehow or the application crashes, the data doesn't get wiped out).
Then, when the user hits the save button, I want to make sure I do as little work as possible on the main thread so that the dismiss modal view animation is smooth, and function returns to the main view controller quickly. So what I do is create an NSDictionary with all the values of the message object, call [globalObjectHelperInstance updateNewObjectInstanceWithDictionary:]
What this does is update the newObject instance that was already created in the beginning of the compose view with the values from the dictionary, and does it in the background thread.
Then I dismiss the modal.
I have a few questions here (please answer whatever you can):
Is there a better way to implement "draft" saving functionality rather than creating a property instance in the ObjectHelper? (the reason I create a property instance in an outside class other than the ComposeController is because the view controller dismisses while background work is being done on the object, so I'm afraid it will disappear from memory if I make it an instance variable.)
Should the property reference be a weak or strong? I know an NSManagedObjectContext is not guaranteed to retain its objects, unless (I think) these objects have pending, unsaved changes.
For some reason, calling [backgroundMOC obtainPermanentIDsForObjects:self.newObject error:&error]in the [globalObjectHelperInstance updateNewObjectInstanceWithDictionary:] before saving causes a EXC_BAD_ACCESS crash. I'm guessing this has something to do with the way I'm managing memory in my situation.
Your way to save draft seems very fine to me.
Another option is not to keep the property reference in any object and fetch the draft object by some key, update it and save it again. This might make sense in case you save something rarely or have something very big, but your current approach seems much more suited.
The reference should be strong. You want it to be present at all times while you want to update it, and you require its existence, so that qualifies for strong. In case of weak reference the object might get deleted. The object in the database on disk will remain, but your in-memory representation will be removed, you wouldn't want that—to update a nil reference.
I don't see any reason why this might cause any memory overuse or leaks, it's just one object and this isn't a case to be afraid of circular references.
As for your crash, it is hard for me to tell, but maybe you should obtain ID in the main thread's MOC at first, and then use it in the background MOC? Also, since it is easier to transfer NSDictionary between thread boundaries rather than an NSManagedObject, maybe you should keep your draft in the background MOC all the time?
Your code in the updateNewObjectInstanceWithDictionary: would then call a GCD block on the background queue and pass it the dictionary to save.
Here's the scenario:
-A UIViewController (A) is pushed onto the navigation stack
-On viewDidLoad an async GET is called using AFNetworking (a singleton AFHTTPClient shared throughout the application) to populate various user elements on the view (say a UILabel).
-The user presses the back button before the request returns
-Assume other active view controllers may be making requests so you can't cancel all open operations
So question #1 is, should you track the open requests made by UIViewController A and cancel the outstanding ones when the user leaves that view, or should you let them finish up and ignore them? Since AFNetworking uses blocks, the user elements being updated are retained inside the block and therefore won't cause a crash when the success/fail block is executed after the view has been popped. However the downside to ignoring them seems to be unnecessary network traffic.
Question #2 is, where would you execute the code to cancel the operations made by UIViewController A? viewDidDisappear doesn't seem right because the user may have gone forward (pushed a new view onto the stack) instead of back (popped the current view), in which case you don't want to cancel the open requests because the user may come back to the current view and it won't load again. However, I don't think dealloc or viewDidUnload will be called while the request is executing since the block will keep a retain on the user elements so I don't think it can go there.
Would appreciate thoughts on this. What do you think is best practice?
Generally speaking, you don't really need to cancel requests when a user leaves a view controller. In terms of memory management, a reference to block self will prevent any crashes caused by sending messages to deallocated instances, so no worries there.
As far as user experience, I would say that you shouldn't really worry about it until it's a problem (we developers have a knack for guessing completely wrong on what will be slow in our applications). If you are making large GET requests, though, and it's creating noticeable sluggishness, my suggestion would be to have the controller do HTTPClient -cancelAllHTTPOperationsWithMethod:path: in -viewDidUnload: (any other callback would be premature).
Maybe you could have a singleton which manages all the network stuff, and just set its delegate to the current vc (in viewDidLoad) so you get any incoming data, and send it a cancel message when the vc disappears (or else let a different vc become its delegate). Or the singleton could keep the data for access by any vc at some later stage. I tend not to put async code into my VCs for this reason.
Let's say I have view controller A and view controller B.
In VC A, I push VC B. Then in VC B, I execute some background tasks using NSOperation. In the background tasks, I modify VC B's variables.
What happens if the background tasks are not finished and I quit VC B? Will the operations be cancelled or will they still be executing? When debugging, it seems like they are still executing. In that case, wouldn't they be accessing already released variables (since I quitted VC B).
I'm a bit confused by this, anyone can clear me up? :)
Thanks,
You are correct, the operation does not magically disappear just because the object that spawned it did.
You will cause the OS to throw an exception as it tries to access the now deallocated view controller object. This is the danger of doing background threaded operations.
You need to plan accordingly, in this case, be able to cancel your operation when VC B gets deallocated. This means subclassing the NSOperation, implementing main() and checking for isCancelled.
See Apple's documentation regarding NSOperation, NSOperationQueues, and Concurrency Programming.
It would be good to consider the purpose of VC-B vs the purpose of the background activities. If the background activities are there to support what the user sees on VC-B, and when user moves away from VC-B the background activities are no longer relevant, then leaving VC-B should cause the background activities to cease. On the other hand if the background activities have a purpose 'larger than' VC-B, the user would expect them to continue; in this case, is's probably appropriate for some 'relatively permanent / long-lived' object (a 'background manager') to manage the activities. In the latter case, the VC's would interact with the background manager as appropriate.
So (as it should be) it comes down to what do you (and more importantly, what does the user) want/expect...
From the docs:
Once you add an operation to a queue, the operation is out of your hands. The queue takes over and handles the scheduling of that task.
Ideally you shouldn't modify your VC variables directly if it can be deallocated while the operation is running, but should compute a result and then do a callback. If you are using ARC you can keep a weak reference to your view controller and it will be safe to reference even if your VC gets deallocated.
If you're looking to implement concurrency you might want to look into using Grand Central Dispatch and blocks. This would work better as blocks encapsulate and retain as needed any variables you reference inside the block, are much easier to set up and execute and make cleaner code.
When the app receives a low memory warning message, 3 situations can happen :
your app has just been launched and the user has not done anything special
the app is running and there is a current context
the app is in the background with some running context
So when you receive this message, your are supposed to free memory... But where ? And how ?
I understand that :
initWith..... must set the default static values.
viewDidLoad must load any non static object
didReceiveMemoryWarning must free those non static objects
I don't see what can/must be done in viewDidUnload...
I guess some retained values must be set to nil somewhere... in didReceiveMemoryWarning ?
And what must be done with the active context (positions of thing of the screen, displayed text, ...) so when viewDidLoad is called again, those thing appear again as they where before the memoryWarning call ?
I mean, imagine 2 scenarios :
Scenario 1
you are working on something... you wrote some text in a field, did not saved it, opened another view, moved a view on the screen.
You send the app in background.
Then a memoryWarning is sent to the app.
After that, the user send the app to foreground : it should display things like they was on exit, on the current view as on previous views, but if everything has been released, how may you do this ?
Scenario 2
you are working on something... you wrote some text in a field, did not saved it, opened another view, moved a view on the screen.
Then a memoryWarning is sent to the app.
You don't want to loose what is on the view, nor what was on the previous view. You don't want neither the screen to flicker because of a release / reload feature. How do you deal with this ?
So when those memory warning happens, do you have any other choice than writing things to disk to display them back later ?
And when do you load those again ? I have a viewController that loads (viewDidLoad), receive a memoryWarning, unloads (viewDidUnload), but when going back to it, viewDidLoad is not called again ? Do this must be done in viewWillAppear ? Do I have to think that anytime viewWillAppear is triggered, I can assume that things that are supposed to be displayed on it are loaded ?
Any help, even with valuable links, would be great !
Thank you for your help.
My idea is that two methods are called when an app receives a low memory warning:
didReceiveMemoryWarning // in your NSObjects
and
applicationDidReceiveMemoryWarning // in your app delegate
then, if you want to free memory, these are the methods to consider.
As regards what you can do in there... Well...
Think about what Xcode suggests:
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
/*
Free up as much memory as possible by purging cached data objects that can be recreated (or reloaded from disk) later.
*/
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
Seemingly, the best choise will be freeing any object not seen by the user and/or not in use, or any cached data that you can recreate later.
Do not touch your GUI: if you close it or part of it your app becomes unusable and the user disappointed.
Regarding your 2 scenarios, I see a possible mistake in considering what memory warning are for.
They are a way to treat an emergency and not the normal way to manage memory.
Developpers must think of a good memory architecture and save data whenever possible.
In scenario 1, save your data when the app is sent to background.
applicationDidEnterBackground
In scenario 2, save your data when a new view is opened.
Hope this makes sense...
Consider the alternatives to your scenarios. Your app could be killed if it does not free enough memory, which would be even more jarring to the user. One might choose potentially flickering the current display over losing the user's valuable data.
When updating the managedObjectContext is it ok practice to do the save setup in view controllers that may be released or should the appDelegate handle the saving of the managedObjectContext so that even if the viewController is released the save finishes?
I'm leaning towards the idea of moving the save step into my appDelegate and having viewControllers call [appDelegate saveContext]; when an update is made, though perhaps thats moot since the viewController won't finish releasing until its done saving to CD either way...?
For instance, is there any difference between these two actions, done from a subViewController:
[appDelegate.managedObjectContext save:&error]
and
[appDlegate saveContext]
Where there is a method in appDelegate that runs [managedObjectContext save:&error]
Thanks,
Sam
Guessing that your application is single threaded, you are guaranteed that the save will finish before the view controller is released because the thread will block on the save.
If you are running a multi-threaded application; then
You should not be saving on the background thread, that is dangerous in Core Data.
You should not be accessing the view controller on the background thread because all view related activity should be performed on the main thread.
Therefore, there is no "correct" situation where you would have to worry about a -save: not finishing no matter what object you call it from because it is a blocking call.
If you're just using one managedObjectContext through your app, it's not a bad idea to keep the save functionality in the app delegate, so that regardless of the state of your view controllers through the application's lifecycle, any updates will be saved when the app terminates.
That being said, I've found it useful to add additional save points, sometimes in view controllers, that will save the database after doing some significant updates. This way I've got my data saved even if the app crashes or the final save operation is otherwise prevented from completing.
I'm a relative newbie to Core Data so please inform me if this is bad practice.