Cancel requests on subview when navigating from parent viewcontroller - ios

I'm currently working on an app with a viewcontroller that has a number of subviews. Some of these subviews make requests (as in urlRequests) for data to display within the subview.
Now if someone navigates away from the main viewController I want to cancel any unfinished requests in the subviews. Is it enough to cancel these requests in the "dealloc" method of a subview as obviously they don't have a viewwilldisappear method. I'm using ARC and iOS 6 for what it's worth. Otherwise should I create my own cancel method in the subviews, and then loop through them in the viewWillDisappear of the main viewcontroller to call this method?
Or is the approach of the subviews making the request wrong to begin with?
The closest thing I could find to my question was view will disappear is not firing which seems to suggest dealloc should work.
Thanks

From my point of view, your views (the subviews) should only take care of displaying a content.
The requests should be done by a dataManager singleton for example or from the view controller (You could use an NSOperationQueue).
I would then create a cancel method that stops the current request and remove the next ones from the queue.
Putting the cancel code in the dealloc or the viewDidDisappear is up to you. If you think you're view will only disappear for a short amount of time then put it in the dealloc.
You could also listen to the UIApplicationDidEnterBackgroundNotification notification to cancel the requests too.

Related

UIView respond to orientation change after animation

How can I make my UIView (which has no access to the ViewController) perform some task after an orientation change has taken place? That is, no task should take place until the animation has completely finished and the frame is no longer changing due to animation.
I've tried registering an observer on the UIView to respond to UIDeviceOrientationDidChangeNotifications, but a breakpoint I've placed in the handler is triggered before the animation even starts.
You would need to involve the view controller. Only the view controller can get a reference to the transition coordinator, so that it can call animate(alongsideTransition:completion:) and do something in the completion handler. That is the moment when the animation has finished.
So the view controller could then talk directly to the view, or, if you want the view to be completely agnostic, the view controller could post a custom notification for which the view has registered.

App crashes when I pop my navigation controller while NSOperation performs performSelectorOnMainThread

I'm following the NSOperation tutorial: http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues to load images in my own view controller's tableview. While the images are loading, if I pop the view controller from my navigation controller, the app crashes with the error: EXC_BAD_ACCESS on the line:
[(NSObject *)self.delegate performSelectorOnMainThread:#selector(imageDownloaderDidFinish:) withObject:self waitUntilDone:NO];
The line tells my delegate(view controller) to perform a method, but I already popped my view controller. How do I prevent this from happening? I tried cancel my operation queue in the viewDidUnload of my view controller, but it doesn't work. Also tried perform a check if my delegate is nil before performing the method. Any help would be greatly appreciated.
How are you popping your view controller?
For simplicity, let's name the method where you pop your view controller pop.
I would implement something in pop where before you actually pop the view controller you call cancel on your NSOperation (perhaps show a loading spinner or an overlay here). Then wait until the cancelled and finished values are true and executing is false (or at least check some of these and experiment to see which ones you can get away with not checking).
The important thing is that there is logic inside each block you add to the NSOperation that checks to see if cancel has been called. But, this is tricky as if your block just checks at the beginning if it should cancel it might be fine at that point to carry on. Perhaps it then goes on to perform some async task and if at that point you call cancel it won't do anything as your block is no longer guarding against a cancellation call.
My guess is that you're calling cancel but not actually waiting until the operation has gone away, done its KVO and cancelled everything.
I'm not sure if your question purely regarded learning about NSOperation but, if it wasn't, AFAIK, AFNetworking does a great job at all of this stuff. Particularly at loading ImageViews asynchronously (which is what that tutorial seems to be covering with that NSOperation stuff). AFNetworking adds a category to UIImageView called UIImageView+AFNetworking that provides a method called setImageWithURL:. This takes care of all these sort of problems and provides caching as well. Install AFNetworking, then
#import "UIImageView+AFNetworking.h"
and call
[self.imageView setImageWithURL:imageURL];.
I know that this last bit doesn't specifically answer your question but hopefully the rest helps.

Message sent to deallocated instance ARC

I am doing an application which downloads image from server and displays it on a view. I am using delegate for that. Once the image is finished loading a delegate sends message to the view to show the image.
The above scenario is working fine. But if I move out from that particular view to any other view, when the image loading is finished the delegate tries to send message and causes an error.
I tried setting the
imageFetcher.delegate=nil;
in view didUnload. Also before calling the delegate method in download class I check for delegate is nil.
But i can see that the delegate object is not nil.
if(delegate!=nil)
{
[delegate imagefetcherView:self didLoadImage:image];
}
How can I fix this error?
Thanks
Do not rely viewDidUnload to do any cleanup. That's only called in iOS versions prior to iOS 6, and only when the view is unloaded due to memory pressure (but not when you just dismiss/pop the view).
Set your delegate to nil in the dealloc method or viewDidDisappear or wherever is appropriate.
Two caveats relevant to picking which method you'll nil the delegate:
Be aware that viewWillDisappear and viewDidDisappear will also be called if you push/present another view controller, even if the current one has not been yet been dismissed. Only rely upon these disappear-related methods if the view controller in question does not ever push/present another view controller.
If employing the dealloc technique, note that this only works if the delegate is a weak property of the image fetcher class (and delegates generally should be weak). If the delegate was a strong or retain property, that will prevent the view controller's dealloc from ever getting called.
By the way, I gather that you are letting the image fetch continue, even though the view controller has been dismissed. You might want to not only nil the delegate, but cancel the request, too. It depends upon whether (a) you're using a fetch that even permits a cancellation (e.g. a NSURLConnectionDataDelegate approach or a AFNetworking operation) and, if so, (b) whether you want it to cancel or not. It's easy, though, to tie up precious network resources (esp if on a slow cellular connection) letting requests continue even if the user doesn't need it anymore. It depends upon the particulars of your app.
Regardless, do not rely upon viewDidUnload.
viewDidUnload isnt called in iOS 6+.
you should use this
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
imageFetcher.delegate=nil;
}
You still can have a dealloc call in your class but it should not call [super dealloc]. If you add it you can set up a breakpoint here and see from where its gets its retain count to 0. Or use Instruments to track retain/release cycle of your controller.
I would implement a separate cache to temporarily store the picture in care view controller is deallocated but the picture can be used once again, e.g. if the user gets back to the same page.
In that case you would have a long-lived cache object as a delegate. View controllers can, for example, subscribe to receive key-value notifications about incoming pictures when those view controllers become visible (just don't forget to unsubscribe from KVO in viewWillDisappear).
If your controller is invisible but likely to be shown again you'll have the picture in cache (to be dropped if low memory); of course you can also check in the cache if your picture is never likely to be shown again and drop the picture.

UIViewController - View events life cycle and registering for KVO / Notifications

I'm wondering if there's any way -viewWillAppear: would be called without a matching -viewDidAppear:. Ditto for -viewWillDisappear and -viewDidDisappear.
The root of my question is where to register and unregister for KVO and / or NSNotifications of an object who's change notifications will cause the view controller to update views.
For example, I have a model object that is being processed asynchronously and it's string properties could change. I'd like the view controller to KVO those properties and have any changes reflected by swapping out the text of a label managed by said view controller.
Where do you register and unregister for notifications and why?
EDIT:
A gotcha I've come across is what to do in cases of application state change (e.g. -applicationWillResignActive, -...didEnterBackground, etc). These changes don't seem to trigger view controller lifecycle methods. Any best practices here?
With the standard container view controllers, you will always get will/did messages in pairs. If you have written your own view controller container, you may not, but that would be a bug in the container implementation.
Most of the time, you'll want to set things up and tear things down in the 'will' messages. This gives your view controller the earliest shot at anything it needs to do before it becomes "active" and also shuts down things as early as possible when you no longer need them.
When pushing a view controller in nav stack, it is entirely possible that a notification will occur during the push animation. If you set up the observers in viewDidAppear, you would miss that notification. You want to be listening as soon as possible.
Likewise, I would reckon that viewDidDisappear is too late to remove callbacks. For example, a location manager could be stopped in viewDidDisappear, but another location update could be delivered during the disappearing animation. That probably doesn't hurt much, but depending on the application, something weird could happen like an alert view appearing after you've already left a view controller - which looks like a flicker to the user.
Anything non-view related, as indicated above, occur in the 'will' methods. The choice about will vs did, then, is really about what the user sees. Animations should start in viewDidAppear, otherwise, the user won't see the frames that occur during will/did appear. Data should be moved to views in viewWillAppear, otherwise, a blank view will transition in and the data will only appear after the transition animation completes. Also, it possible that a view frames could be adjusted in between viewWillAppear/viewDidAppear, like in the case of a previous view controller in the stack hiding/showing the navigation bar.
And on a side note, not something I will get into great detail here with, but I'd advocate against KVO for controller interactions that move data from model to view objects. Difficult to test and difficult to trace.
You can subclass your UILabel and in your subclass override the setText method:
-(void)setText:(NSString *)newText {
//do your KVO updates here
[super setText:newText];
}
hope this helps

iOS Container View Controller - using PerformSelector after transitionFromViewController

We have a container view controller and want to be able to call "PerformSelector" on one of the "sub" view controllers in that container, right after starting a transition, i.e.
[self navigateSubViewControllerTo:newSubViewController];
... some time later, elsewhere in the stack, a selector will be performed on the top visible VC
[subViewController performSelector:#selector(foo)];
The call to transitionFromViewController happens in navigateSubViewController.Unfortunately, since transitionFromViewController happens asynchronously, we are finding that the performSelector call gets applied to the "before" sub view controller, not newSubViewController. I.e. it is happening before the transition happens.
Any thoughts on how to have performSelect not happen until the sub view controller transition happens?
You can just call performSelector in the completion block of
transitionFromViewController:toViewController:duration:options:animations:completion:
UIViewController provides a callback beginAppearanceTransition:animated: which is exactly for this purpose. Simply implement it in your subviewcontrollers and your good to go ;)

Resources