Tuesday, March 20, 2012

Handling delegate callbacks for NSURLConnection

It has been over three years, since I started working with Objective C, primarily for iOS app development. The projects on which I mostly have worked, involve basically a webservice, to which you have to make http GET/POST calls, and display contents in a UITableView.
The UI is mainly prepared within a navigation stack, hence the user can go back & forth between the navigation hierarchy.
The problem here is, though, while a UIViewController is being popped, and a http connection was active, my app crashes with a OBJ_MSG_SEND / EXEC_BAD_ACCESS error.
To cut the long story short, since the delegate for NSURLConnection was not set to nil, the connection tries to deliver a callback to its owner, which in this case has been d'alloced.

The problem?
The catch here is ,though, by studying a few other blog posts, and doing an Instruments research on my own, a NSURLConnection is of the few classes which "retains" its delegate, while however Objective C guidelines state that the delegate should always have "assign" property attribute.
Also, if you go through the documentation of this class, there is no explicit way of setting the delegate to nil!
This means, even if your UIViewController got popped, the http connection is still active, and hence the non-required data will still get transferred, which is not a good practice, for your webservices, and user's network bandwidth as well.

The solution?
Again, going through a few other blog posts, I created my own custom class, "HttpRequest", which is responsible for making network calls, and giving its delegate, the data obtained for the particular http connection.
The modification I made here, is simply added these few lines in HttpRequest.h

BOOL isRequestInProgress;
- (void)cancel;

And the implementation:

- (void)cancel {
if (isRequestInProgress) {
[urlConnection cancel];
[urlConnection release]; urlConnection = nil;
[urlRequets release]; urlRequest = nil;
[mutableData release]; mutableData = nil;
}
}

- (void)makeRequest {
isRequestInProgress = YES;
//make urlconnection.
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
isRequestInProgress = NO;
//your implementation
}
Now in your UIViewController's viewDidUnload method:
[httpRequest cancel];
This will check if at the time of destroying your UIViewController, an active http connection will be cancelled.
Let me know if anyone wants a detailed description of class HttpRequest :-)

Tuesday, March 13, 2012

Debugging EXEC_BAD_ACCESS OR OBJ_MSG_SEND errors

For NSURLConnection errors, you might want to read my post here:
http://rish2cool.blogspot.in/2012/03/handling-delegate-callbacks-for.html

Once a while we experience our app getting crashed, and Instruments/Zombies/stack trace debugging etc. leads us to nowhere fruitful.
Stack trace in this will simply show us direction, and we need to follow some simple yet critical concepts in mind to make this debugging easy, interesting and not to be mentioned, avoiding in future codebases :-)

Any object DOES NOT get d'alloced if it is owner of some property, or has some references pointing to it.
If it does get dealloced, then it causes "dangling pointers" to remain in memory, and at any later stage of progression, any callbacks received to these pointers causes an EXEC_ BAD _ ACCESS or OBJ_ MSG _SEND errors, which are quite pain to debug.

This is also true for UIViews, if they have a superview, then the parent "retains" its subviews, unless the subview is not removed from its view hierarchy( When a UIViewController is popped, it automatically clears its view hierarchy).

The solution?

Whenever a reference is made to an object which you know will have to be released later, make sure the references are removed in a proper pipeline.
One example is a NSTimer, or a CLLocationManager object.
If your code has implemented a NSTimer, the iOS sets a delegate to your object, and unless the timer is "invalidated", the delegate is "retained" by the iOS, and hence not freed.

Another example is, when you create custom protocol references, which are simply used to handle post callbacks for Objective C protocols( eg. UIWebViewDelegate, CLLocationManagerDelegate, etc.)
Since these are "assigned" to your classes, unless the delegates are assigned "nil", the respective class instance DO NOT get freed, hence causing crashes.

Let me clarify this by giving an example.
You have a navigation hierarchy, with two view controllers, FirstViewController(FVC) & SecondViewController(SVC), with FirstViewController being the rootViewController.

You "push" an instance of SVC, and you can "pop" it whenever you want.
Now SVC has an instance of another class, CustomLocationManager(CLM), which is simply an NSObject to receive CLLocationManagerDelegate callbacks(and do some extra magic :-)).
So your CLM header class looks like this:

@interface CustomLocationManager : NSObject {
NSTimer *locationRefreshTimer;
double latitude,longitude;
CLLocationManager *locationManager;
id delegate;
}

Now inside SVC, this instance of CLM will keep on refreshing location changes, and update its "delegate", which in-turn is your SVC itself.

@interface SecondViewController : NSObject < CustomLocationManagerDelegate > {
CustomLocationManager *customLocationManager;
//your impl.
}

Ideally, when your SVC instance gets popped, this will be your d'alloc hierarchy:
SVC popped (SVC d'alloced)-> CLM d'alloced -> CLLocationManager dealloced.

Interestingly, now press CTRL + I( to launch Instruments), and choose "Leaks" or "Allocations", now again run your code, pop the SVC, and observe your allocations. Search for "CLLocationManager", and you'll find it in allocations!
This means, although your SVC was popped, CLM instance & CLLocationManager object are still living since their references have not yet been cleared.

Now, if locationmanager gives a callback in CLM with an updatedLocation, and your CLM tries to further deliver a callback to SVC indication "locationUpdated:", the line inside CLM will be responsible for the apparent crash in your code, since SVC instance has been d'alloced, but its delegate reference inside CLM has not been nullified, and results in a dangling pointer.

The correct way of d'allocating callbacks will be as follows:

SVC release -> set CLM delegate to nil-> inside CLM, invalidate all timers, and setCLLocationManagerDelegate to nil.

This causes now the following hierarchy:

[SVC release] -> [CLM release] -> [CLLocationManager release];
Now again after making the above changes, run Instruments.