I set the view controller to be the delegate of a local variable ASIHTTPFormDataRequest request.
But, tapping "Back" before the request has finished, pops and deallocates the view controller. So, when the request completes and sends the message -requestDidFinish: to the now nonexistent delegate, the app crashes with an EXEC_BAD_ACECESS exception.
How do I fix this crash?
One way I could think of solving this is to set the delegate to nil immediately after the navigation controller pops it. But, if that's the solution, how do I do that? (ARC's weak references would be sweet right now.)
Another way I can think of is to make request an instance variable of the view controller and call [request clearDelegatesAndCancel]; [request release]; in the view controller's dealloc method. This approach is outlined in ASIHTTPRequest Example Code, but I've been advised it's best to make requests local variables instead of instance variables. And, this particular view controller is a settings table view controller and has 13 switches. For automatic saving to the server, each switch creates & sends a new request each time it's toggled. If I made ivars, I'd have to make 13. That's a lot of code!
Thoughts? Ideas?
I think the first question is: What do you want to happen if the user presses back after pressing a switch? ie. Should the http request be cancelled, or is it important that the request does get to the server? I'll assume for now that you do want to cancel them, as that seems to be implied in your question.
I've been advised it's best to make
requests local variables instead of
instance variables
I'm not sure if that was good advice - you almost always want requests to not be local variables so you can cope with cases like this.
For your case, you could consider using an NSOperationQueue.
Rough steps to do this would be:
Create an NSOperationQueue in your view controller init.
When you want to make a http request, add it to the ASIHTTPRequest queue instead of call startAsynchronous
In dealloc, iterate the objects in the queue, calling [request clearDelegatesAndCancel]; for each one, and then release the queue.
That should solve the crash without needing 13 ivars!
I solved this by retaining the request delegate like NSURLConnection retains its delegate.
Related
Do I need to cancel the opened connections (NSURLConnection) when I leave the view?
I'd do that in viewWillDisappear or viewDidDisappear but I don't know whether I actually need to do that.
If you're using ARC, there's a good chance your NSURLConnection objects (assuming they are instance variables or that you're holding onto them in memory somehow) will get magically released when the view controller goes away.
But to be sure, and to be nice, you should cancel the open connections. Doing the NSURLConnection object "cancel" in either "viewWillDisappear" or "viewDidDisappear" should work well.
I want to make sure that all my initializations for my views and stuff are handled every time my application starts, even when it is called back after being sent to the background, such as with multitasking.
What's the best way to do this? should i use applicationDidBecomeActive to call viewDidLoad on my viewcontroller directly? I'm guessing this is not wise. I just want to make sure that stuff gets done on load every time the user calls up the app, no matter what state it is in at the time.
I have several apps published that do just that - call viewDidLoad on one or several UIViewControllers from applicationDidBecomeActive.
It works just fine. Apple doesn't object to it either.
However, be aware that if you have allocations in your viewDidLoad you need to either check for already allocated instances or release the instances you allocate in viewDidLoad when your app suspends. The same goes for DB connections that need to be closed, notification listeners, and so on.
As long as you watch for these elements and handle them correctly, the approach is valid and very usable.
I know that dealloc is called when an object's retain count reaches zero and that iVars should be released therein, but I am wondering if it is also an appropriate spot to delete temporary files or close database connections.
Specifically, I have a subclass of UIViewController that creates a database connection in -viewDidLoad and through user interaction, temporary files can be created. I would like to close the DB connection and delete the temporary files (if they exist) when the aforementioned controller gets popped of the navigation stack. Should I do so in dealloc?
My first thought was to do this DB and file clean up in -viewDidUnload, but I now know that this method is only called when a memory warning is issued by the OS. Another thought was to put it in -viewDidDisappear:animated but the issue with that approach is that the another view may go on top of the one controlled by my view controller. In that scenario, I do not want to close the DB connection or clean up the temp files.
If dealloc is not the appropriate spot (this is my gut feeling), where should this type of clean up be done? I would kind of hate to force the parent of my view controller to have to call a method in response to its child getting popped off the navigation stack.
The Apple guide to memory management says, No.
http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW13
In short, don't use dealloc to manage resources.
You can do so in dealloc, or if you need the files around during app lifecycle, on your app delegate's applicationWillTerminate:. YMMV.
Do not try to do this in the dealloc method of your view controller.
What would happen if the view controller gets retained somewhere you're not expecting it? The dealloc method may not get fired, and you clean up never occurs. Maybe that's not the case in the code you're writing now, but something could change in the future.
Is there any specific reason you want to tie the deletion of files and closing of the DB connection to popping off the view controller? If not, perhaps you could do this immediately your query has completed, or it may make sense to open the DB connection on startup/foreground, and then close it on termination/backgrounding.
If you must tie it to view controller being popped off the nav stack, then you could call your clean-up method from viewDidDisappear:
I have a view that will be displaying downloaded images and text. I'd like to handle all the downloading asynchronously using ASIHTTPRequest, but I'm not sure how to go about notifying the view when downloads are finished...
If I pass my view controller as the delegate of the ASIHTTPRequest, and then my view is destroyed (user navigates away) will it fail gracefully when it tries to message my view controller because the delegate is now nil?
i.e. if i do this:
UIViewController *myvc = [[UIViewController alloc] init];
request.delegate = myvc;
[myvc release];
Do myvc, and request.delegate now == a pointer to nil?
This is the problem with being self-taught... I'm kinda fuzzy on some basic concepts.
Other ideas of how to handle this are welcome.
update: Looking at the source code for ASIHTTPRequest, it does not retain its delegate, so your code will either have to ensure that the delegate has not been released, or set the request's delegate property to nil before releasing your controller.
If you are going to have several asynchronous HTTP requests running, it may be a good idea to create an HTTPRequestManager class to act as delegate for all of your HTTP requests. This HTTPRequestManager class would remain in memory for the entire lifetime of your application, and it could take care of failing gracefully if/when your view controllers are released.
I would like to add a clarification about Cocoa's reference-counted memory management. It is important to remember that a released object is not automatically set to nil. Sending a message to nil is perfectly legal (and simply does nothing), but sending a message to a deleted object is undefined behaviour. Keeping a pointer to a deleted object is asking for trouble, and such living/dead pointers are referred to as zombies.
I am trying to implement the delegate Pattern in Objective-C, however I am experiencing a Bad Access exception when invoking the delegate sometimes. It seems this is caused by the delegate being released. Apple does not recommend to retain delegates.
How can I check my delegate if is still valid before trying to send it a message?
If there's a chance that the delegate will get released by the setter, then there's something wrong with your design. You should only set delegates on objects that have a shorter lifespan than the delegate itself. For example, setting a delegate on a subview/controller is fine, because the subview/controller has a shorter lifespan than the caller.
AFAIK, there is no reliable way to detect if an object has been released already.
What Apple means about not retaining delegates is that objects should not retain their delegates because they don't own them. These are only objects that handle messages.
That doesn't mean that you shouldn't retain delegates at all. The object that creates the delegate needs to own it. In the context of non-GC apps this means it should handle the retain and release cycle, and for GC apps, it means that the controller object keeps hold of a pointer to the delegate in an iVar.
without seeing some code or the error message, it is hard to find the root of this problem.
In a photoviewer application I'm using asynchronous http to load images; it happens that the user often dismisses the current view (referenced by my async http object through a delegate) before the http download completed causing a BAD_ACCESS when calling the view controller delegate method. I solved this by setting the .delegate to nil inside the dealloc block of the view controller
I'd like to share my experience also, which is very similar to Nico's one.
I've been working with a modified example of LazyTablesCode, wich is an example that comes direcly from Apple and loads images in a UITableView asynchronously. Communication between the downloader and the view it's made via delegates.
In my code, I had the problem that sometimes the load of the image finishes when the form that should be called through the delegate has been released. I've been forced to add this piece of code inside the code of the viewController (dealloc method):
if (self.nsDictionaryWithObjectsDownloading != nil) {
for (id theKey in self.nsDictionaryWithObjectsDownloading) {
Myobj *downloader = [self.nsDictionaryWithObjectsDownloading objectForKey:theKey];
downloader.delegate = nil;
}
}
It seems that these lines are solving the problem. Anyway It would be very appreciated opinions about if it's a good solution or not or even about memory issues when doing downloader.delegate = nil;
Thanks and greetings,