NSURL *URL = [NSURL URLWithString:#"http://www.stackoverflow.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
With code as simple as that, I can load a webpage in my application. I don't have to worry about retaining or releasing the NSURLConnection, it will autorelease when it's done loading.
I'm creating some sort of wrapper around NSURLConnection, JSONConnection. It allows me to load a JSON value from a webpage and automatically parse that in a NSDictionary. Right now, I have to use it like this:
JSONConnection *tempJSONConnection = [[JSONConnection alloc] initWithURLString:#"http://www.stackoverflow.com" delegate:self];
self.JSONConnection = tempJSONConnection;
[tempJSONConnection release];
Then, when it's done loading, I call self.JSONConnection = nil;.
What I want, is to do this:
JSONConnection *connection = [JSONConnection connectionWithURLString:#"http://www.stackoverflow.com" delegate:self];
I know how to create this method. I just don't know how to keep connection alive when the runloop is finished and the autorelease pool is drained, and make sure connection is deallocated when it's done loading. In other words, I don't how to duplicate the exact behavior of NSURLConnection.
To all intents and purposes, from the outside, NSURLConnection effectively retains itself. This was either done by sending
[self retain];
when starting the connection and then
[self release];
when finished and after informing the delegate; or it was done by placing itself in a pool of currently open connections and removing it from that pool on completion.
You don't actually have to do any of this. NSURLConnection retains its delegate, so your JSON connection class should create an NSURLConnection passing itself as the NSURLConnection's delegate. That way it will live at least as long as the NSURLConnection. It should parse the JSON into a dictionary in the method -connectionDidFinishLoading: and pass the dictionary on to its delegate before returning. After returning the NSURLConnection will release and possibly deallocate itself and also release your JSON connection.
Someone should trac connection's live time in any case. It is a bad solutions to trac it inside the connection.
IMO the right way to do it is use singleton class to perform connections
#protocol JSONDataProviderDelegate <NSObject>
- (void) JSONProvider:(JSONDataProvider*) provider didLoadJSON:(JSONObject*) object;
- (void) JSONProvider:(JSONDataProvider*) provider didFainWithError:(NSError*) error;
#end
#interface JSONDataProvider : NSObject
+ (void) provideJSON:(NSURL*) url delegate:(id<JSONDataProviderDelegate>) delegate;
+ (void) removeDelegate:(id<JSONDataProviderDelegate>delegate);
#end
Usage:
- (void) onSomeEvent
{
[JSONDataProvider provideJSON:[NSURL URLWithString:#"http://example.com/test.json"] delegate:self];
}
- (void) JSONProvider:(JSONDataProvider*) provider didLoadJSON:(JSONObject*) object
{
NSLog(#"JSON loaded: %#", object);
}
- (void) dealloc
{
[JSONDataProvider removeDelegate:self];
[super dealloc];
}
Related
Here is my actual issue, as some suggested I want to write a class for handling the multiple download progress in a UITableView. I have no idea how to write a class for this, can somebody help with some tips or ideas?
The group to look at is NSURLRequest and NSURLConnection. The former let's you specify the request (URL, http method, params, etc) and the latter runs it.
Since you want to update status as it goes (I answered a sketch of this in your other question, I think), you'll need to implement the NSURLConnectionDelegate protocol that hands over chunks of data as it arrives from the connection. If you know how much data to expect, you can use the amount received to calculate a downloadProgress float as I suggested earlier:
float downloadProgress = [responseData length] / bytesExpected;
Here's some nice looking example code in SO. You can extend for multiple connections like this...
MyLoader.m
#interface MyLoader ()
#property (strong, nonatomic) NSMutableDictionary *connections;
#end
#implementation MyLoader
#synthesize connections=_connections; // add a lazy initializer for this, not shown
// make it a singleton
+ (MyLoader *)sharedInstance {
#synchronized(self) {
if (!_sharedInstance) {
_sharedInstance = [[MyLoader alloc] init];
}
}
return _sharedInstance;
}
// you can add a friendlier one that builds the request given a URL, etc.
- (void)startConnectionWithRequest:(NSURLRequest *)request {
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
NSMutableData *responseData = [[NSMutableData alloc] init];
[self.connections setObject:responseData forKey:connection];
}
// now all the delegate methods can be of this form. just like the typical, except they begin with a lookup of the connection and it's associated state
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSMutableData *responseData = [self.connections objectForKey:connection];
[responseData appendData:data];
// to help you with the UI question you asked earlier, this is where
// you can announce that download progress is being made
NSNumber *bytesSoFar = [NSNumber numberWithInt:[responseData length]];
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
[connection URL], #"url", bytesSoFar, #"bytesSoFar", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"MyDownloaderDidRecieveData"
object:self userInfo:userInfo];
// the url should let you match this connection to the database object in
// your view controller. if not, you could pass that db object in when you
// start the connection, hang onto it (in the connections dictionary) and
// provide it in userInfo when you post progress
}
I wrote this library to do exactly that. You can checkout the implementation in the github repo.
What is the best way to check which request is which inside the delegate method:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
}
Right now I have a NSURLConnection that I set to the NSURLConnection before making a request and inside didReceiveResponse I do:
if (self.tempConnection == connection)
however there is a possiblity this won't work for race conditions. Is there a better way to do this?
There is a better way in OS5. Forget about all those bothersome delegate messages. Let the connection build the data for you, and put your finished code right in line with your start code:
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.site.com"]];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
NSLog(#"got response %d, data = %#, error = %#", [httpResponse statusCode], data, error);
}];
I've looked at a bunch of different ways to do this, and I've found that by far the cleanest and easiest in order to manage is to use a block pattern. That way you are guaranteed to be responding to the right request upon completion, avoid race conditions, and you don't have any issues with variables or objects going out of scope during the asynchronous call. It's also a lot easier to read/maintain your code.
Both ASIHTTPRequest and AFNetworking APIs provide a block pattern (however ASI is no longer supported so best to go with AFNetworking for new stuff). If you don't want to use one of these libraries, but want to do it yourself, you can download the source for AFNetworking and review their implementation. However, that seems like a lot of extra work for little value.
Consider creating a separate class to serve as the delegate. Then, for each NSURLConnection spawned, instantiate a new instance of the delegate class to for that NSURLConnection
Here's some brief code to illustrate this:
#interface ConnectionDelegate : NSObject <NSURLConnectionDelegate>
...then implement the methods in the .m file
Now, I'm guessing you probably have the code you posted in a UIViewController subclass (or some other class serving different purposes)?
Wherever you are kicking off the requests, use this code:
ConnectionDelegate *newDelegate = [[ConnectionDelegate alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"<url here">]];
[NSURLConnection connectionWithRequest:request delegate:newDelegate];
//then you can repeat this for every new request you need to make
//and a different delegate will handle this
newDelegate = [[ConnectionDelegate alloc] init];
request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"<url here">]];
[NSURLConnection connectionWithRequest:request delegate:newDelegate];
// ...continue as many times as you'd like
newDelegate = [[ConnectionDelegate alloc] init];
request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"<url here">]];
[NSURLConnection connectionWithRequest:request delegate:newDelegate];
You might consider storing all the delegate objects in a NSDictionary or some other data structure to keep track of them. I'd consider using an NSNotification in connectionDidFinishLoading to post a notification that the connection is done, and to serve whatever object created from the response. Lemme know if you want code to help you visualize that. Hope this helps!
I'm using ASIHttpRequest to recieve data from an xml file. However during an asynchronous request as soon as i change the view (back to the previous view using the navigation controller) the application crashes with a EXC_BAD_ACCESS on the main.m
This only happens while the request is being made.
Below is my code:
-(void)ProcessXML{
//Set url from string to url
NSURL *theurl = [NSURL URLWithString:#"http://twitter.com/statuses/user_timeline/2smssupport.xml"];
asirequest = [ASIHTTPRequest requestWithURL:theurl];
[asirequest setDelegate:self];
[asirequest startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
NSLog(#"Saving to Defaults");
NSData *responseData = [request responseData];
xmlSaved = responseData;
prefs = [NSUserDefaults standardUserDefaults];
[prefs setObject:responseData forKey:#"xmlDownload"];
rssParser = [[RssParser alloc] loadXMLbyURL:xmlSaved];
[self.tableView reloadData];
NSLog(#"%#",[prefs dataForKey:#"xmlDownload"]);
}
The Process XML method triggers the request and the then received data is processed in the RequestFinished.
There must be something i'm missing with the ASIHTTPRequest but i don't know what it is?
Thanks
This block of code should fix it:
-(void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[request clearDelegatesAndCancel]
}
If the view is being release then the delegate you set on the request is now invalid. Make sure you set the delegate to nil on the view dealloc and also stop the request.
The asirequest object isn't being retained anywhere, so it's being deallocated after ProcessXML returns.
NSZombieEnabled helps you a lot. You can tell which object is causing EXC_BAD_ACCESS.
Are you deallocating the ASIHTTPRequest object when you leave the current view? My guess is that the delegate methods are being called after your view controller has been released.
--
#Simon is right that you do need to set the delegate to nil. What I would do is:
Create an ASIHTTPRequest property in your class and set that property in your ProcessXML method. This simplifies memory management and ensures that the request object will stick around while you need it.
In both your dealloc method and your requestFinished methods, set the request delegate to nil and set self.request = nil;
At the very least, you should set the delegate to nil in your requestFinished method, but you need to remember to stop your request from running if you navigate away from this view controller before it returns, hence setting it to nil in the dealloc method as well.
// Create the request.
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.apple.com/"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// 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 {
// Inform the user that the connection failed.
}
Since we dont' "own" receivedData by calling retain on it, aren't we leaking memory?
When are you supposed to release connection and receivedData if at all?
1/ About the connection, we use the delegate pattern to handle the memory management of this. You alloc init and set the delegate in one method. And then when the connection callback you like:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[connection release];
}
or you can release the connection in any other delegate methods. This is one of reasons why they pass you back the connection. You will meet this delegate pattern a lot in iPhone like UIImagePickerController (just for another example), especially in networking problems when you have to wait until the network finish to release
2/ From the comment,
// Create the NSMutableData to hold the received data.
// receivedData is an instance variable declared elsewhere.
So, that is easy to answer, because receivedData is an instance variable, you should and can release it in dealloc method. Another choice for you is to declare a #property (nonatomic, retain) for it and then it will make sure no memory leak if you set the receivedData multiple times
This code owns both the connection and the data. The connection is created with alloc/init, requiring a release later, and the data is retained, so that too will require a release.
Our iPhone app code currently uses NSURLConnection sendSynchronousRequest and that works fine except we need more visibility into the connection progress and caching so we're moving to an async NSURLConnection.
What's the simplest way to wait for the async code to complete? Wrap it in a NSOperation/NSOperationQueue, performSelector..., or what?
Thanks.
I'm answering this in case anyone else bumps into the issue in the future. Apple's URLCache sample code is a fine example of how this is done. You can find it at:
iOS Developer Library - URLCache
As John points out in the comment above - don't block/wait - notify.
To use NSURLConnection asynchronously you supply a delegate when you init it in initWithRequest:delegate:. The delegate should implement the NSURLConnection delegate methods. NSURLConnection processing takes place on another thread but the delegate methods are called on the thread that started the asynchronous load operation for the associated NSURLConnection object.
Apart from notifications mentioned prior, a common approach is to have the class that needs to know about the URL load finishing set itself as a delegate of the class that's handling the URL callbacks. Then when the URL load is finished the delegate is called and told the load has completed.
Indeed, if you blocked the thread the connection would never go anywhere since it works on the same thread (yes, even if you are using the asynch methods).
I ran into this because our app used NSURLConnection sendSynchronousRequest in quite a few places where it made sense, like having some processing occurring on a background thread occasionally needing extra data to complete the processing. Something like this:
// do some processing
NSData * data = someCachedData;
if (data = nil) {
data = [NSURLConnection sendSynchronousRequest....]
someCachedData = data;
}
// Use data for further processing
If you have something like 3 different places in the same flow that do that, breaking it up into separate functions might not be desirable(or simply not doable if you have a large enough code base).
At some point, we needed to have a delegate for our connections(to do SSL certificate pinning) and I went trolling the internet for solutions and everything was of the form: "just use async and don't fight the framework!". Well, sendSynchronousRequest exists for a reason, this is how to reproduce it with an underlying async connection:
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse *__autoreleasing *)response error:(NSError *__autoreleasing *)error
{
static NSOperationQueue * requestsQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
requestsQueue = [[NSOperationQueue alloc] init];
requestsQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount;
});
NSCondition * waitLock = [NSCondition new];
[waitLock lock];
__block NSError * returnedError;
__block NSURLResponse * returnedResponse;
__block NSData * returnedData;
__block BOOL done = NO;
[NSURLConnection sendAsynchronousRequest:request
queue:requestsQueue
completionHandler:^(NSURLResponse * response, NSData * data, NSError * connectionError){
returnedError = connectionError;
returnedResponse = response;
returnedData = data;
[waitLock lock];
done = YES;
[waitLock signal];
[waitLock unlock];
}];
if (!done) {
[waitLock wait];
}
[waitLock unlock];
*response = returnedResponse;
*error = returnedError;
return returnedData;
}
Posted here in case anyone comes looking as I did.
Note that NSURLConnection sendAsynchrounousRequest can be replaced by whatever way you use to send an async request, like creating an NSURLConnection object with a delegate or something.