Three20: cancel network request - iphone

I am using three20 and implement the model like the example of TTRemoteExamples. Now problem is: when I click and open a page, the TTURLRequest sent out, during fetch data from remote, I click to open another page. But the previous network request is still there messed my loaded data. So I want to know the way to cancel previous network request when I switch to another page. Or when I click button to do a new request in the same page.
Thanks~

To cancel a TTURLRequest, keep a reference to it (typically in an instance variable) then send it a "cancel" message. Like so:
[self.myRequest cancel];
If you don't want the delegate to be notified of the request being cancelled, do:
// I'm assuming self is the delegate here, that may not be true
[[self.myRequest delegates] removeObject:self];
[self.myRequest cancel];
You'll typically also want to do this in your view controller dealloc method. If a request continues after the viewController has been deallocated, it will try to send delegate messages to it, and you'll get a bad access crash.
As for the timing of when you cancel it, that's up to you. If you need it to stop when a user leaves your view controller, then implement UIViewController's viewWillDisappear: or viewDidDisappear: methods (don't forget to call super!).

Related

Associate between server response and view controller

I am a beginner developer in Objective-C/iOS and I have this simple question.
My program interact with a server side and I use GCDAsyncSocket for asynchronous communication.
I wonder, suppose the use is opening my status view controller which causes a request for server, but before the response arrives he has already moved to a different view!
What is the right way the handle this situation??
Thank you!
Ideally you should not move to the next view controller until you get the response in didReceiveData callback, because your GCDAsyncSocket delegate will have to be set to some controller, and probably you would have by default set it to the status view controller.
So you have to wait for the callBack response and upon receiving it you can move to the next view controller.
While the delegate can be set to the next view controller, you can skip it for now as you a beginner in iOS.
This is key.
asynchronous
You're using an asynchronous request to get your data. You'll have to use callbacks to let the view controllers know that the request has ended. I've never used that library but a quick glance at the readme says:
Classic delegate-style support.
All of the following result in calls to your delegate method: connections, accepts, read completions, write completions, progress,
https://github.com/robbiehanson/CocoaAsyncSocket/blob/master/README.markdown
https://github.com/robbiehanson/CocoaAsyncSocket/wiki/Reference_GCDAsyncSocket

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

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.

viewWillAppear / viewDidAppear firing multiple times

I am using TabBarKit, and I want to execute a request to pull a new peice of content from a webservice each time a user goes back to a tab.
I can't put the request code in viewDidLoad as its not fired when coming back to the tab. With that said, I've noticed viewWillAppear / viewDidAppear are called multiple times when returning back to a tabs view controller.
If I put the requesting code in there, it is fired multiple times resulting in the webservice being pinged needlessly.
How can I solve this problem? Which method should I place my HTTP request call in so it executes once per view?
You could try setting/checking a downloadInProgress flag before submitting the asynchronous download, then resetting that flag when the request completes.
If you're using something like the ASIHTTPRequest, that calls a delegate method when the request completes or fails, which is the point where you could reset the flag. It allows you to tag each request individually so you can track the success or failure of each one, so this wouldn't restrict you from running one background request at a time.
Turn's out there was an extra call to viewWillAppear in the controller code. If you're interested in following the changes, there is a thread on the issues section of the Git project.

Objective-C object dealloc'd while other objects still has a delegate reference to it causes crashes. How to prevent this?

I have an app with a navigation controller as the root view. There are many views that can be pushed in.
The user has to create an account to use the app. The user can then log into this account from other devices, but only one device can be logged onto the same account at a time. So if multiple devices try to log into an account, only the latest device will be logged in and the other devices are logged off (a push is sent to the devices).
Since there are multiple views that the device could be showing before it was logged off, I call popToRootViewControllerAnimated: to get back to the root view. This is because when the user logs in the next time I only want the root view to be shown (the new account might not have access to the previously shown view).
If the user has an alert view or action sheet presented (which uses the current view as its delegate) before the push is received, the view will still be shown after the popToRootViewControllerAnimate: method is called. If the user then taps on a button for the alert view or action sheet, it will send a message to the dealloc'd view and crash the app.
An example:
myViewController is being shown to the user.
myViewController create an action sheet prompting the user for a decision.
The push is received for the device to log out.
The navigation controller pops all the views controllers and now shows myRootViewController.
Since the view controllers are popped, myViewController is now dealloc'd.
The action sheet from myViewController is still shown.
When the user selects an option form the action sheet, a message is sent to myViewController, and since it is already dealloc'd, a crash will occur.
Is there any way to prevent this?
One solution I have considered would be to keep track of all the objects that uses a specific view controller as its delegate. Then when that view controller dealloc's it will also set all the object's delegates to nil. This requires me to manually take care of every view controller when they create an object that uses itself as the delegate, since I cannot think of a way to automatically create and update this list.
Any better solution (or improvement to mine) would be appreciated!
Edit: The alert view and action sheet are only examples of some objects that I would use myViewController as a delegate. I am also using a number of other classes (and third-party libraries) that implements this delegate pattern.
A few ideas:
you can encapsulate the alert/action sheet view and delegate in a single class. Then when you need an alert view, create MyAlertView instead, which will also be its own delegate and will do [self release] after the user taps a button.
make your App Delegate the only delegate for all your alert views and action sheets. App Delegate is always around while the application is running, so there won't be a problem with a released delegate.
The problem with both solutions is that if you need your application to know what happened in the alert view/action sheet, you somehow need to tell the interested class of the user's choice.
You can do that by either using delegates of your own - which would mean you're back to square one - or use notifications: when the alert view/action sheet delegate is called, it would post a notification ([[NSNotificationCenter defaultCenter] postNotificationName:NotificationName object:self userInfo:userInfo];), while the interested object would look for that notification ([[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onNotification:) name:NotificationName object:nil];) and perform whatever tasks necessary in onNotification:(NSNotification*)aNotification method.
You'll be able to agree with yourself on what type of information is passed in those notifications (I would think the button number in a NSNumber class would be enough, or perhaps pass the button text, too). And you won't have to keep track of all alert views - just don't forget to remove the observer ([[NSNotificationCenter defaultCenter] removeObserver:self name:postNotificationName object:nil];) in the views' dealloc.
Edit:
"This requires me to manually take care of every view controller when they create an object that uses itself as the delegate, since I cannot think of a way to automatically create and update this list."
Actually you probably can do this in a semi-automated way: make a singleton object with a method like
-(id)delegate:(id)delegate for:(id)forWhom
And then instead of
someThingy.delegate = self;
you'd do
someThingy.delegate = [[DelegateLocker defaultLocker] delegate:self for:someThingy];
Inside the DelegateLocker you'd have a MutableDictionary with delegate class as a key and a MutableArray of someThingies as a value. Then in your view controllers' deallocs you'd call
[[DelegateLocker defaultLocker] delegateIsDying:self];
which would go through the thingies and assign delegate = nil for each
The drawback of course is that you'll be retaining all the thingies for an indefinite period of time instead of releasing them immediately.
So the ViewController that presented the action sheet iand set itself as the delegate right? So why dont you keep a reference to the ActionSheet in the ViewController, in the dealloc method of the view controller, you can check if the action sheet is visible, if it is then set the delegate of the action sheet to nil,and dismiss it...
so
-(void)dealloc
{
if(myActionSheet && [myActionSheet visible])
{
[myActionSheet setDelegate: nil];
//dismiss
}
}
Hope that helps
If you want automated solution, I think you can make a function to iterate through Ivars of your view controller to see if any Ivar has delegate property and set it to nil.

Dismiss or destroy all view controllers, action sheets, etc?

I'm implementing a custom URL scheme in my navigation based app to let users import data from an email, and so I have this method in my app delegate:
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url
It would be nice, if after importing the data in that method, I could use popToRootViewController to go back to the rootViewController to show the user the new data.
However, that doesn't work if the user had imported the data while a modal view controller or action sheet was up (popToRootViewController doesn't dismiss those, and causes the app to freeze).
Is there any way I can safely dismiss/destroy all view controllers/action sheets/alert views except for the rootViewController?
Or perhaps some way to tell from within my app delegate if the user has an action sheet or modal view controller up?
Or, do I just leave them where they left off, and not provide any immediate indication that data was successfully added?
You'll have to keep track of them yourself. An easy way to do that is to post a notification, with a reference to the modal view/sheet/alert in the userinfo dictionary, every time you display something modal. Whenever a modal view returns, post a different notification.
Observe the notifications in your app delegate (or wherever it makes sense to do so, but the app delegate is simplest). When you receive the first one, store a (weak) reference to the modal view, and zero the reference when you receive the second one. In between, you'll be able to respond to -application:handleOpenURL: by dismissing the modal view if your reference is non-nil.
Note also that in the case of alerts, it's probably best to dismiss them when you enter the background. Weeks might pass before you are foregrounded again, and it's bad style to greet the user with "Are you sure? OK/Cancel" when they may no longer have any idea what you're asking about.