NSURlConnection cancel cause memory leak - ios

It's a really strange bug.
I'm use a code for download a file with NSURLConnection if the download finish , I have no leak.
But if I cancel the download, I have 1Mo memory not release.
I have make test with instrument, and the methode identified to create this leak is
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
is really strange
this is my code for create, manage and cancel download
Note: this download is a part of core data managed object, but I think the core data is not responsible of my leak.
- (void)loadURL:(NSURL *)url
{
if (currentLoadingDatas) { // just bool for prevent multiple download
return;
}
currentLoadingDatas = YES;
NSURLRequest *request = [[NSURLRequest alloc]
initWithURL: url
cachePolicy: NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval: 60
];
connectionDatas = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[request release];
}
#pragma mark NSURLConnection Delegates
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if (!self.transientData) {
self.transientData = [[NSMutableData alloc] init];
}
[self.transientData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.transientData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self willChangeValueForKey:#"datas"];
[self setValue:self.transientData forKey:#"datas"];
[self didChangeValueForKey:#"datas"];
[self.transientData release];
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
[sharedCache release];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
[sharedCache release];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
return nil;
}
-(void)cancelDownload
{
[self.connectionDatas cancel];
}
// fired by coreData
-(void)willTurnIntoFault
{
self.transientData = nil;
[self cancelDownload];
[super willTurnIntoFault];
}
// fired by coreData
-(void)didTurnIntoFault
{
[connectionDatas release];
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
[sharedCache release];
[self.transientData release];
[super didTurnIntoFault];
}
Can you help me for identify the problem ?
Thanks a lot.

How is self.transientData property declared ?
Because you initialize with : self.transientData = [[NSMutableData alloc] init]; and if the property is set to retain the value, you will need to release it two times.
If so, to set the property use : self.transientData = [[[NSMutableData alloc] init] autorelease]; or simply [NSMutableData data];.
The rest seems ok to me.

The leak is indicating that your variable is instantiated at that point so that's not where the leak actually is that is where it starts. You need to release the transientData in your cancel method like this
- (void)cancelDownload
{
[self.connectionDatas cancel];
self.transientData = nil;
}
There are a few issues with inconsistency in your coding style that may make it harder for you to mentally track what is going. If you stick to your own standards it should be easier to follow the flow.
This is potentially where another leak could occur
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if (!self.transientData) {
self.transientData = [[NSMutableData alloc] init]; // leaky line
}
[self.transientData setLength:0];
}
The [[NSMutableData alloc] init] call is creating an instance of NSMutableData with a retain count of +1. Then if you are using properties (which are best) with retain then the self.transientData = will take another retain adding another +1 to the retain count. This is later released only once therefore you have a leak as the NSMutableData will still be hanging around.
Further down in your code you use the pattern
create instance and assign to local variable
assign instance to ivar
release local variable instance
This is the pattern I would use when I am not in an init method. Therefore the method before should be changed to:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if (!self.transientData) {
NSMutableData *tmpTransientData = [[NSMutableData alloc] init];
self.transientData = tmpTransientData;
[tmpTransientData release];
}
[self.transientData setLength:0];
}
This has the benefit of not using an autorelease so you are more explicit about when the object is no longer required which is preferred when possible on devices with small memory.
Another inconsistency which might benefit being tidied up a little is how you release your ivars. You have interchangeably done this in your code
[self.transientData release];
self.transientData = nil;
I would use the latter in my code (that is not in dealloc) for instance variables as the synthesized setter will call release for you and set the pointer to nil which is considerably safer.

Related

Synchronous subclassed NSURLConnection with ssl?

I subclassed a NSURLConnection in order to call web services using ssl.
I thought about doing it synchronous and not only asynchronous connection as it's now.
My code:
#import "SecureSSLConnection.h"
static NSMutableArray *sharedConnectionList = nil;
#implementation SecureSSLConnection
#synthesize request, completionBlock, internalConnection;
- (id)initWithRequest:(NSMutableURLRequest *)req
{
self = [super init];
if (self) {
self.request = req;
}
return self;
}
- (void)start
{
container = [NSMutableData new];
internalConnection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:YES];
if (!sharedConnectionList) {
sharedConnectionList = [NSMutableArray new];
}
[sharedConnectionList addObject:self];
}
#pragma mark - NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[container appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if (self.completionBlock) {
self.completionBlock(container, nil);
}
[sharedConnectionList removeObject:self];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
if (self.completionBlock) {
self.completionBlock(nil, error);
}
[sharedConnectionList removeObject:self];
}
#pragma mark - SSL Connection Addition
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSLog(#"challenge.protectionSpace.host: %#", challenge.protectionSpace.host);
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
// We only trust our own domain
if ([challenge.protectionSpace.host isEqualToString:WebSiteURL]) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}
}
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
I started thinking about implementing:
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error
In order to keep it the same as original delegate.
One way is using NSNotificationCenter but my goal is much easier method for it.
Any ideas how should I doing it?
I would not suggest making this synchronous. You invariably want to keep your network requests asynchronous.
The simple solution is to employ your completion block property. Simply put other tasks that are dependent upon your initial SSL request inside the completion block of the first request. That way, they won't start until the first completes.
The more elegant solution is to take this a step further, and wrap your SSL request in a concurrent NSOperation subclass. That way, you can employ not only the above completion block pattern, but you can use the addDependency of subsequent operations and maxConcurrentOperationCount of the NSOperationQueue to really refine the dynamics between various operations. This is non-trivial, but you can see AFNetworking for an example of a relatively well thought-out implementation of this pattern.

Override UIWebView user agent - but not for Facebook SDK

I'm setting a custom UserAgent for the webviews in my app as per the instructions in this other question. Specifically, I'm setting
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:#"MyApp/MyLongVersionInfoString", #"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
at app launch. (Just setting the appropriate headers for the NSMutableURLRequest to be used by the UIWebView - UserAgent, User-Agent, User_Agent - does not work.)
This causes my embedded webviews to use the correct user agent. However, it also breaks the embedded webviews used by the Facebook SDK for dialogs - after I post to my wall, for instance, the contents of the FB dialog webview are replaced with text similar to window.location.href="fbconnect:\/\/success?post_id=100002469633196_43677789308119... and the webview does not close as it normally would (the user has to manually close it). This only occurs when I have my custom user agent set.
I thought I could circumvent the problem by unsetting the user agent before Facebook calls and resetting it afterwards, but it seems I can't unset the default defaults; I tried calling [[NSUserDefaults standardUserDefaults] removeObjectForKey:#"UserAgent"] and [[NSUserDefaults standardUserDefaults] removePersistentDomainForName:NSRegistrationDomain] before each Facebook call and setting them again in the call's result handler, but I still see the same misbehavior.
I tried switching my initial settings to [[NSUserDefaults standardUserDefaults] setObject:newUA forKey:#"UserAgent"];, but then my webviews don't pick up the user agent.
Surely, surely, someone has used the Facebook SDK before in an app with non-Facebook embedded webviews. What am I missing? I've gone a number of rounds on this, each of which seems to almost-but-not-quite fix everything.
I ended up having to do this by implementing an NSURLProtocol subclass. While my implementation was messy, I'm including a scrubbed version below because I was surprised not to be able to find an example already on StackOverflow.
#import <Foundation/Foundation.h>
#interface MyProtocol : NSURLProtocol {
NSURLConnection *connection;
}
#property (nonatomic, retain) NSURLConnection *connection;
#end
#implementation MyProtocol
#synthesize connection;
#pragma mark -
#pragma mark custom methods
+ (NSString *)myUA {
return [[NSUserDefaults standardUserDefaults] stringForKey:#"MyCustomUserAgent"];
}
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
NSString *scheme = [request.URL.scheme stringByAppendingString:#"://"];
NSString *baseRequestString = [[[request.URL absoluteString] stringByReplacingOccurrencesOfString:request.URL.path withString:#""]
stringByReplacingOccurrencesOfString:scheme withString:#""];
if (request.URL.query != nil) {
NSString *query = [#"?" stringByAppendingString:request.URL.query];
baseRequestString = [baseRequestString stringByReplacingOccurrencesOfString:query withString:#""];
}
BOOL shouldIntercept = [baseRequestString isEqualToString:myWebHost];
BOOL alreadyIntercepted = [[NSURLProtocol propertyForKey:#"UserAgent" inRequest:request] isEqualToString:[self myUA]];
return shouldIntercept && !alreadyIntercepted;
}
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request
{
return request;
}
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client
{
NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:request.URL
cachePolicy:request.cachePolicy
timeoutInterval:request.timeoutInterval];
[mutableRequest setAllHTTPHeaderFields:[request allHTTPHeaderFields]];
[mutableRequest setHTTPMethod:[request HTTPMethod]];
if ([request HTTPBody] != nil)
[mutableRequest setHTTPBody:[request HTTPBody]];
if ([request HTTPBodyStream] != nil)
[mutableRequest setHTTPBodyStream:[request HTTPBodyStream]];
[NSURLProtocol setProperty:[[self class] myUA] forKey:#"UserAgent" inRequest:mutableRequest];
[mutableRequest setValue:[[self class] myUA] forHTTPHeaderField:#"User-Agent"];
[mutableRequest setValue:[[self class] myUA] forHTTPHeaderField:#"User_Agent"];
self = [super initWithRequest:mutableRequest cachedResponse:cachedResponse client:client];
return self;
}
- (void) dealloc
{
[connection release];
}
#pragma mark -
#pragma mark boilerplate NSURLProtocol subclass requirements
- (void) startLoading
{
self.connection = [[NSURLConnection connectionWithRequest:[self request] delegate:self] retain];
}
- (void)stopLoading
{
[self.connection cancel];
}
- (void)connection:(NSURLConnection*)conn didReceiveResponse:(NSURLResponse*)response
{
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:[[self request] cachePolicy]];
}
- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
[[self client] URLProtocol:self didLoadData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection*)conn
{
[[self client] URLProtocolDidFinishLoading:self];
}
- (NSURLRequest*)connection:(NSURLConnection*)connection willSendRequest:(NSURLRequest*)theRequest redirectResponse:(NSURLResponse*)redirectResponse
{
return theRequest;
}
- (void)connection:(NSURLConnection*)conn didFailWithError:(NSError*)error
{
[[self client] URLProtocol:self didFailWithError:error];
}
#end
Note that the protocol must be registered with [NSURLProtocol registerClass:[MyProtocol class]]; before you make any web requests to which you want it to apply - for instance, in applicationDidFinishLaunchingWithOptions:.

Integrating MBProgressHUD with two classes

Im am trying to properly integrate MBProgressHUD in a project while i am downloading and processing some data. Forgive me my ignorance if i am asking stupid things, but i'm a complete noob...
I have a "Data" class that is handling my HTTPRequests, so i thing here is the proper place to put some stuff in:
-(void)resolve
{
// Create the request.
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[[NSURL alloc] initWithString:[self url]]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSLog(#"Resolving content from: %#",[self url]);
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
receivedData = [[NSMutableData data] retain];
} else {
NSLog(#"Content - resolve: connection failed");
}
// Here is the part that it makes me crazy...
HUD = [[MBProgressHUD showHUDAddedTo:self.view animated:YES] retain];
return;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// This method is called when the server has determined that it
// has enough information to create the NSURLResponse.
// It can be called multiple times, for example in the case of a
// redirect, so each time we reset the data.
// receivedData is an instance variable declared elsewhere.
expectedLength = [response expectedContentLength];
currentLength = 0;
HUD.mode = MBProgressHUDModeDeterminate;
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
currentLength += [data length];
HUD.progress = currentLength / (float)expectedLength;
// Append the new data to receivedData.
// receivedData is an instance variable declared elsewhere.
[receivedData appendData:data];
return;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[HUD hide:YES];
// release the connection, and the data object
[connection release];
// receivedData is declared as a method instance elsewhere
[receivedData release];
// inform the user
NSLog(#"Content - Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
return;
}
Now i know that putting the "showHUDaddedTo" in the -(void)resolve is not the right way...
I am calling this data function from a view controller with an IBaction like this:
-(IBAction) btnClicked:(id) sender {
if ([[issue status] intValue] == 1 ) // issue is not downloaded
{
[(Content *)[issue content] resolve];
//HUD = [[MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES] retain];
}
else // issue is downloaded - needs to be archived
{
NSError * error = nil;
[[NSFileManager defaultManager] removeItemAtPath:[(Content *)[issue content] path] error:&error];
if (error) {
// implement error handling
}
else {
Content * c = (Content *)[issue content];
[c setPath:#""];
[issue setStatus:[NSNumber numberWithInt:1]];
[buttonView setTitle:#"Download" forState:UIControlStateNormal];
[gotoIssue setTitle:#"..." forState:UIControlStateNormal];
// notify all interested parties of the archived content
[[NSNotificationCenter defaultCenter] postNotificationName:#"contentArchived" object:self]; // make sure its persisted!
}
}
}
Simply said: i would like to call all the MBProgressHUD stuff from my Conten.m file in the moment i push the download button in my IssueController.m file. I think you see now where my problem is: i am not a coder ;-) Any help is appreciated.
Cheers,
sandor
I would try and use the HUD method showWhileExecuting. Wrap your calls up in a new method and call that to download your information. When your method finishes, so will your HUD.
Also, is there a reason why you are retaining your calls for the HUD? I have not seen that before and I'm not sure if you need to retain those as they are autoreleased.

How to make sure I have only 1 asynchronous NSURLConnection at a time?

I'm creating an app which communicates alot with a server. I want to make sure I have only 1 connection at a time - only 1 request pending. I want so that if I try to send another request, It will wait until the current one is finished before sending the next.
How can I implement This?
Tnx!
I don't know of any automatic mechanism to do this. So you have to write a new class yourself (let's call it ConnectionQueue):
Basically, instead of a creating the NSURLConnection directly, you call a method of your ConnectionQueue class (which should have exactly one instance) taking the NSURLRequest and the delegate as a parameter. Both are added to a queue, i.e. a separate NSArray for the requests and the delegates.
If the arrays contains just one element, then no request is outstanding and you can create a NSURLConnection with the specified request. However, instead of the delegate passed to the method, you pass your ConnectionQueue instance as the delegate. As a result, the connection queue will be informed about all actions of the connection. In most cases, you just forward the callback to the original delegate (you'll find it in the first element of the delegate array).
However, if the outstanding connection completes (connection:didFailWithError: or connectionDidFinishLoading: is called), you first call the original delegate and then remove the connection from the two arrays. Finally, if the arrays aren't empty, you start the next connection.
Update:
Here's some code. It compiles but hasn't been tested otherwise. Furthermore, the implementation of the NSURLConnectionDelegate protocol is incomplete. If you're expecting more than implemented callbacks, you'll have to add them.
Header File:
#import <Foundation/Foundation.h>
#interface ConnectionQueue : NSObject {
NSMutableArray *requestQueue;
NSMutableArray *delegateQueue;
NSURLConnection *currentConnection;
}
// Singleton instance
+ (ConnectionQueue *)sharedInstance;
// Cleanup and release queue
+ (void)releaseShared;
// Queue a new connection
- (void)queueRequest:(NSURLRequest *)request delegate:(id)delegate;
#end
Implementation:
#import "ConnectionQueue.h"
#implementation ConnectionQueue
static ConnectionQueue *sharedInstance = nil;
+ (ConnectionQueue*)sharedInstance
{
if (sharedInstance == nil)
sharedInstance = [[ConnectionQueue alloc] init];
return sharedInstance;
}
+ (void)releaseShared
{
[sharedInstance release];
sharedInstance = nil;
}
- (id)init
{
if ((self = [super init])) {
requestQueue = [NSMutableArray arrayWithCapacity:8];
delegateQueue = [NSMutableArray arrayWithCapacity:8];
}
return self;
}
- (void)dealloc
{
[requestQueue release];
[delegateQueue release];
[currentConnection cancel];
[currentConnection release];
[super dealloc];
}
- (void)startNextConnection
{
NSURLRequest *request = [requestQueue objectAtIndex:0];
currentConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)queueRequest:(NSURLRequest *)request delegate:(id)delegate
{
[requestQueue addObject:request];
[delegateQueue addObject:delegate];
if ([requestQueue count] == 1)
[self startNextConnection];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
id delegate = [delegateQueue objectAtIndex:0];
[delegate connection: connection didReceiveResponse: response];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
id delegate = [delegateQueue objectAtIndex:0];
[delegate connection: connection didReceiveData: data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
id delegate = [delegateQueue objectAtIndex:0];
[delegate connection: connection didFailWithError:error];
[currentConnection release];
currentConnection = nil;
[requestQueue removeObjectAtIndex:0];
[delegateQueue removeObjectAtIndex:0];
if ([requestQueue count] >= 1)
[self startNextConnection];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
id delegate = [delegateQueue objectAtIndex:0];
[delegate connectionDidFinishLoading: connection];
[currentConnection release];
currentConnection = nil;
[requestQueue removeObjectAtIndex:0];
[delegateQueue removeObjectAtIndex:0];
if ([requestQueue count] >= 1)
[self startNextConnection];
}
#end
To use the connection queue, create an NSURLRequest instance and then call:
[[ConnectionQueue sharedInstance] queueRequest:request delegate:self];
There's no need to create the singleton instance of ConnectionQueue explicitly. It will be created automatically. However, to properly clean up, you should call [ConnectionQueue releaseShared] when the application quits, e.g. from applicationWillTerminate: of your application delegate.

NSURLConnection Hanging on Second Call

I'm building an iPhone app that aggregates data from several different data sources and displays them together in a single table. For each data source, I've created a class (WeatherProvider, TwitterProvider) that handles connecting to the datasource, download the data and storing it in an object.
I have another class ConnectionManager, that instantiates and calls each of the two provider classes, and merges the results into a single NSArray which is displayed in the table.
When I run the program calling just the WeatherProvider or just the TwitterProvider, it works perfectly. I can call each of those objects again and again to keep them up-to-date without a problem. I can also call TwitterProvider and then WeatherProvider without a problem.
However, if I call WeatherProvider then TwitterProvider, the TwitterProvider hangs just as I call: [[NSURLConnection alloc] initWithRequest:request delegate:self];.
Other points:
- it doesn't seem to matter how long between when I call WeatherProvider and TwitterProvider, TwitterProvider still hangs.
- in WeatherProvider, I'm using NSXMLParser with NSAutoreleasePool to parse the output from the WeatherProvider.
- ConnectionManager creates an instance of WeatherProvider and TwitterProvider when the application is started, and reuses those instances when the user requests a data refresh.
- I ran the app with the activity monitor connected and it verifies that the app is basically just hanging. No CPU usage, or additional memory allocations or network activity seems to be happening.
There's a bunch of code spread across a couple files, so I've tried to include the relevant bits (as far as I can tell!) here. I very much appreciate any help you can provide, even if it is just additional approaches to debugging.
WeatherProvider
-(void)getCurrentWeather: (NSString*)lat lon:(NSString*)lon lastUpdate:(double)lastUpdate
{
double now = [[NSDate date] timeIntervalSince1970];
NSString *noaaApiUrl;
// don't update if current forecast is < than 1 hour old
if(now - lastUpdate < 3600)
{
[[self delegate] weatherUpdaterComplete:1];
return;
}
// if we already have a forecast, delete and refill it.
if(forecast)
{
[forecast release];
forecast = [[WeatherForecast alloc] init];
}
forecast.clickThroughUrl = [NSString stringWithFormat:#"http://forecast.weather.gov/MapClick.php?lat=%#&lon=%#",
lat, lon];
noaaApiUrl = [NSString stringWithFormat:#"http://www.weather.gov/forecasts/xml/sample_products/browser_interface/ndfdBrowserClientByDay.php?lat=%#&lon=%#&format=24+hourly",
lat, lon];
NSURLRequest *noaaUrlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:noaaApiUrl]];
[[NSURLConnection alloc] initWithRequest:noaaUrlRequest delegate:self];
}
#pragma mark -
#pragma mark NSURLConnection delegate methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
self.noaaData = [NSMutableData data];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[noaaData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
self.noaaConnection = nil;
[[self delegate] weatherUpdaterError:error];
[connection release];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Spawn a thread to fetch the data so UI isn't blocked while we parse
// the data. IMPORTANT - don't access UIKit objects on 2ndary threads
[NSThread detachNewThreadSelector:#selector(parseNoaaData:) toTarget:self withObject:noaaData];
// the noaaData will be retailed by the thread until parseNoaaData: has finished executing
// so, we'll no longer need a reference to it in the main thread.
self.noaaData = nil;
[connection release];
}
#pragma mark -
#pragma mark NSXMLParser delegate methods
- (void)parseNoaaData:(NSData *)data
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
[parser setDelegate:self];
[parser parse];
[parser release];
[pool release];
}
TwitterProvider
-(void)getLocationTimeline:(NSString*)lat lon:(NSString*)lon lastUpdate:(double)lastUpdate refreshUrl:(NSString*)newUrl
{
NSString *updateURL;
if(tweets.count > 1)
[tweets removeAllObjects];
updateURL = [NSString stringWithFormat:#"http://search.twitter.com/search.json?geocode=%#,%#,1mi&rpp=%#&page=1", lat, lon, TWITTER_LOCAL_UPDATES_URL_RESULTS];
_serviceResponse = [[NSMutableData data] retain];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:updateURL]];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
#pragma mark -
#pragma mark NSURLConnection delegate methods
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString *responseString = [[NSString alloc] initWithData:_serviceResponse encoding:NSUTF8StringEncoding];
// parse tweets
[responseString release];
[_serviceResponse release];
[connection release];
// tell our delegate that we're done!
[[self delegate] twitterUpdaterComplete:tweets.count];
}
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response
{
[_serviceResponse setLength:0];
}
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data
{
[_serviceResponse appendData:data];
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
[connection release];
[[self delegate] twitterUpdaterError:error];
}
I was able to fix this by removing the threading in WeatherUpdater that was being used for the NSXMLParser. I had 'barrowed' that code from Apple's SiesmicXML sample app.

Resources