Dealing with open async web requests when UIViewController is popped (AFNetworking) - iphone

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.

Related

iOS: What happens if I execute a background job and then leave the view controller?

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.

iOS Logout design

I have an app that has many web services and notifications going on. I need a logout feature. I wish there was a way simply to kill the app and restart it but there is not. Does anyone have some recommended guidelines on creating a logout function (it will take the user back to the login screen). The problem is there are notifs that should be unsubscribed from, views that should be removed, view controllers that I want to be released, then everything to reinitialize. That seems like a lot of work for a simple task. Any thoughts?
The first thing to make sure when terminating all requests is to change all delegates that are supposed to receive responses to nil. After you've taken care of this, you should indeed remove all irrelevant view controller's views from your root view (and release them if they are retained anywhere), and of course flush any existing data you don't need. If you design your MVC elegantly, you can achieve these actions without a lot of fuss, for example a ScreenManager Singleton class that manages all your view controllers should have no problem taking you back to the login screen while releasing any other view. A DataManager Singleton class that holds various data collections should have no problem removing any unneeded data entities and so on...

How much should the AppDelegate do?

I'm designing quite a large App and on startup it will create sessions with a few different servers. As they are creating a session which is used across all parts of the app its something I thought would be best in App Delegate.
But the problem is I need the session progress to be represented on the screen. I plan to have a UIToolBar at the bottom of the main menu which I don't want to cover with the progress bar but cover the UIView above it.So the way I see it I could do it a few different ways.
1) Have the App Delegate establish the sessions and report the progress to the main menu class so it can represent it in the progress bar (will I have any issues doing this if the sessions are created in a separate thread?),
2) have the App delegate display the main menu (UIView with a bunch of buttons and UIToolBar) and have it track and display the progress (I have never displayed anything in the App Delegate but assume you can do this but its not recommended) or
3) have the App Delegate just push the main menu and have the mainMenu class create the sessions and display the progress bar.
4) I think the other way to do it is to create the sessions in a delegate class and have the delegate set to mainMenu rather than self (AppDelegate), although I've never used anything other then self so not sure if this will work or if I will be able to close the thread (through calling super maybe?) as its running in the AppDelegate rather than the delegate of the class.
As I've kinda said before the sessions are being created in a class in a separate thread so it wont lock the UI and I think the best way is the first but am I going to have issues having it running in a separate thread, reporting back to the app delegate and then sending that message to the mainMenu view?
I hope that all makes sense, let me know if you need any further clarification. Any information is appreciated
Cheers,
Presumably the state of the connections will impact on your app's functionality. I would probably think in terms of a connections manager object which is able to initiate connections, maintain their state and respond when queried about their status. In the same way as a singleton object will return the existing object or create and return a new object of none exists, a connections manager doesn't even need a "make connection" method, just "get handle" - if the connection is not open it can try to make it so.
You also mention status must be reported on the main screen. Having a manager object that is able to do tasks of indeterminate time (opening a connection to a host that may be ready, busy, far away or just plain broken) in the background and then report progress to the main thread so the UI can be updated (remember, no UIKit access in secondary threads) seems ideal and it keeps your View distinct also.

Can I load multiple UIViewControllers that each kick off their own NSURLConnections?

My Rootviewcontroller uses NSURLConnection to get data from a server, and then, based on this data, loads a bunch (like 7) of smaller UIViewControllers that each also use their own NSURLConnection to get some more specific data from the server. But, the problem is, only the RooTViewController is recieving callbacks from:
- (void)connectionDidFinishLoading:(NSURLConnection *)theConnection
the other UIViewControllers never get callbacks...
You should really work with the assumption that only one view controller is active at the time. So if you want the other view controllers to do work, even when they are not visible on the screen, then you should move that logic into some singleton object that does all the network communication. The viewcontroller, when they appear, will simply ask this object for the data.
For the iPhone it is a really bad design to let inactive view controllers do stuff in the background. The only thing they do is manage the view that you currently see on the screen.
#St3fan is correct about good patterns for UIViewController. Many a developer has been burned thinking his UIViewController always has a non-nil view (it doesn't).... Even so, I'll discuss here what may be going wrong.
There are two likely causes: you're not setting the UIViewControllers as the delegates of their NSURLConnections, or you're not actually starting the NSURLConnections. The most likely cause of the latter is that you're using NIBs and expecting them to load at the beginning of the program, when they actually only load when needed.
As #deanWombourne notes, you can have all the NSURLConnections you want (**). Walk through your code in the debugger. Make sure you're not sending messages to nil. When "nothing happens" it almost always means you're sending messages to nil.
(**) Don't get crazy with connections on iPhone. Network connections are expensive to create (in both time and battery). If you can get all the data you need over a single connection, there are some advantages to doing so. This doesn't mean you should bend over backwards to avoid connections; just be careful of letting them get out of control.

awakeFromNib opposite?

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.