ios - Passing on data to another method - same viewController - objective-c

Please see this code as an example:
-(void)generateConnection
{
NSString *urlString = #"";
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
NSLog(#"%#", connection);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSString *partialData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[_jsonContentForText appendString:partialData];
NSLog(#"%#", partialData);
}
Really for lack knowing what it is called, but after searching - a lot - I am unable to out what this process is called.
I need to know how -(void)connection: gets the information it needs from the previous method. I don't know if this is called passing on methods to another method, but I need to create another one, so I need to know where I can learn this.
Please spare me the lines like: You need to read up on objective-c and the general stuff, because those answers do not cover the specific need I have.
I do not mind reading up on it, as long as I know what I am reading explains this. However if in any kind hearts it is easy to explain, I will not reject that offer:-)

You need to read up on Delegates and Protocols. When you create the NSURLConnection and set self as the delegate, your new NSURLConnection object is told to query self (the object that created it) to do things like find out if authentication is supported, or what to do when it receives some data. For the NSURLConnectionDelegate specifically, if your delegate implements the methods defined in NSURLConnectionDelegate Protocol, they'll be called.
Some good sources to read up on Delegates and Protocols are:
Working with Protocols(Apple)
Delegates and Data Sources(Apple)
The Basics of Protocols and Delegates(Mobile Developer Tips)

Related

How can I pause the program until nsurlconnection complete?

I know that people have asked this but I have not found satisfactory answers. I have one method that I send all my URLRequests through. I return the response of the request as a string when the method completes. I have recently added ssl to my program. This means that I can no longer use a synchronous request because I need to take advantage of the didReceiveAuthenticationChallenge function as my credentials are currently self-signing. The program needs the response from the URL in order to continue so there is not harm in waiting for the response. However, I cannot seem to find a way to just hold the code up and continue once completed. I can alert the original function that called to request function but I would like the program to pick up right after that call. And it has unique code below such calls so I cannot specialize the connectionDidFinishLoading: function because each method who calls this is different.
How can I pause the program so I can return the nsdata from the connection to the methods that called it?
Here is some pseudo-code to show you what I mean:
- (void) login:(NSString *)username :(NSString *)password {
NSString *str = [NSString stringWithFormat:%#"%#:::%#",username,password];
NSURL *url = [NSURL urlWithString:#"https://blahblahblah"];
NSString *result = [self connectToUrl:str:url];
if ([result isEqualToString:#"valid"]) {
//this would be more complex in here
NSLog(#"hooray");
} else {
NSLog(#"bummer");
}
}
- (NSString *)connectToUrl:(NSURL *)url :(NSString *)str {
NSData *FileData = [str dataUsingEncoding: NSUTF8StringEncoding];
NSMutableData *data = [[NSMutableData alloc] initWithCapacity:100];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:#"POST"];
//set up the rest of the request...
...
connection = [NSURLConnection connectionWithRequest:request delegate:self];
[connection start];
//WOULD LIKE TO PAUSE HERE UNTIL COMPLETE! THEN CONTINUE
// received data is assigned in didReceiveData: method
return [[[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding] autorelease];
}
But alas, I cannot do this because I cannot make the final line wait until the connection is complete... Please help me!
Very appreciative!
R
iOS and OS X and much of the Cocoa/Cocoa touch frameworks are built on an event model. You don't pause your app. That's not the proper approach. You need to start the connection and then move on. When the connection completes, you act on that event.
In other words, your login method can't sit and wait for the result. It should start the connection and return.
When you get the result of the connection you call some method to process the login result.
Making use of blocks can make things like this easier but there are other ways. You just need to stop thinking about such things in a linear fashion. Dealing with asynchronous processing requires a different approach.

Unit Testing an NSURLConnection Delegate: Faking a Connection

I'm writing a weather application and I've created a sort of weather model that does all the calculations, fetching of data, etc. Before I created the ViewControllers, I wanted to write some unit tests for my model to ensure that everything was working properly (the weather is being fetched in the expected format, weather is refreshing correctly, etc.).
Now I would love to unit test with confidence and test for equality like:
STAssertEquals([[testableModel weatherDictionary]objectForKey:#"current_conditions"], #"Sunny", #"The weather should be sunny.");
...but alas, mother nature changes so fast. Also, (and correct me on this), I don't think I can connect to the Internet while I'm unit testing(...?) (Either way, it doesn't particularly matter.)
So, I searched on my most favorite website and I found this question: how to unit test a NSURLConnection Delegate?
It was quite informative, but now I've got a question. When I call a method that invokes an NSURLConnection delegate method, such as:
[myBeautifulWeatherModel getTheWeather];
...how do I feed the model (i.e., myBeautifulWeatherModel, which is implementing the NSURLConnection delegate methods) data? The model is going to do some JSON parsing when it receives its data in this delegate method:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
My first thought was to take the JSON data that Wunderground sends back and just change some of the keys so that they match expected data and feed that in. But the question is HOW would I feed in that response?
I understand that NSURLConnection is going to call 3 required delegate methods. I feel like I need to "fake out" the following:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
But again, I'm a little confused as to how I feed it in and get this model to think its connected to the Internet.
I think you're trying to test too broadly. What you want to test here are 2 things: 1) when the NSURLConnectionDelegate callbacks are invoked, they correctly write the data somewhere useful, and 2) given some stored data when the connection completes, it gets stored in your model appropriately. So something like this:
-(void)testShouldAppendData {
expect([connectionDelegate data]).to.beNil();
NSString *response = #"1";
NSData *responseData = [input dataUsingEncoding:NSUTF8StringEncoding];
[connectionDelegate connection:connection didReceiveData:responseData];
NSString *stringFromResponseData = [[[NSString alloc] initWithData:[connectionDelegate data] encoding:NSUTF8StringEncoding] autorelease];
expect(stringFromResponseData).to.equal(#"1");
[connectionDelegate connection:connection didReceiveData:responseData];
stringFromResponseData = [[[NSString alloc] initWithData:[connectionDelegate data] encoding:NSUTF8StringEncoding] autorelease];
expect(stringFromResponseData).to.equal(#"11");
}
and for the data format:
-(void)testShouldUpdateWeatherModel {
NSString *response = #"{\"current_conditions\":\"sunny\"}}";
NSMutableData *responseBytes = [NSMutableData dataWithData:[response dataUsingEncoding:NSUTF8StringEncoding]];
[connectionDelegate setData:responseBytes];
[connectionDelegate connectionDidFinishLoading:nil];
expect([[connectionDelegate weatherDictionary] objectForKey:#"current_conditions"]).to.equal(#"sunny");
}

How to handle different requests using connectionDidFinishLoading in the same delegate?

Whenever I do a curl call using the below code:
NSURL *url = [NSURL URLWithString:requestURL];
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:30];
if (connectionInProgress) {
[connectionInProgress cancel];
}
connectionInProgress = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:YES];
connectionDidFinishLoading is my final destination where I can manipulate the response data and call my next methods to continue with the app . If I hard-code some specific tasks like
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:xmlData];
[parser setDelegate:self];
[parser parse];
[someLabel setText:parsedTextFromXMLData];
}
If I need to do another curl call to a different address, wouldn't someLabel setText always get re-set again? Is there a way to make this delegate function behave differently on each curl call? (btw, is connectionDidFinishLoading usually the right place to put the next step of codes?) If so then wouldn't it always get called again by the next curl call?
Have a look at this S.O. post for a recipe concerning NSURLConnection and multiple requests.The suggestion is doing something like this:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if (connection == firstConnection) {
// do something
}
else if (connection == secondConnection) {
// do something else
}
}
EDIT: the idea here is that connectionDidFinishLoading is a method of your own delegate (so you write it). In the delegate, you store the address of each connection you create; then, when the connection comes back with the data, you tell which connection it is by comparing its address to the one you stored in the delegate. -END EDIT
Another option you have is using the ASIHTTPRequest framework, which offers a request-based (as opposed to connection-based) delegation mechanism, so each request has got a delegate object to handle the result; or, in other words, the delegate receives a reference to the request, so you can easily tell which request result you are handling.
ASIHTTPRequest offers a bunch of advantages over NSURLConnection. You can read about them in this S.O. post.
There're 2 options to do this:
you can implement a separate class, that will be responsible for handling NSURLConnection delegate stuff and create a separate instance for each request
you can use NSObject key-value methods on NSURLConnection instance for setting up some tag, that will be checked in connectionDidFinishLoading: method
For me, option 1 will be a better approach

Why release the NSURLConnection instance in this statement?

I read this in a book.
-(IBAction) updateTweets
{
tweetsView.text = #"";
[tweetsData release];
tweetsData = [[NSMutableData alloc] init];
NSURL *url = [NSURL URLWithString:#"http://twitter.com/statuses/public_timeline.xml" ];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: url];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection release];
[request release];
[activityIndicator startAnimating];
}
In this statement,is that correct to release the "connection" instance at that time? After releasing it which means this NSURLConnection instance will be destroyed since it's reference count is 0 ,how are we going to make this connection operation work after release this instance? THANKS.
I thought we should release it in one of the following callback methods,right?
connectionDidFinishLoading:
connection:didFailWithError:
It's actually fine to release it there, once the connection is sent out via initWithRequest, the only thing that matters is that the delegate exists or I believe the response will just be silently lost.
From what I can tell, the only reason to not release it there is if you want to call [connection cancel] at some point in one of the delegate functions, in which case it would be best to do what you suggest and release it in BOTH connectionDidFinishLoading and didFailWithError since only one of them will be called (right?).
Edit: For a more thorough answer, NSURLConnection initWithRequest is an asynchronous request. So it actually spawns it's own thread (but calls the delegate functions on the thread that called initWithRequest). So basically, on the thread that calls initWithRequest you are actually done with the connection object and you can release it. All the while it's doing stuff on some other thread that you don't need to be concerned with.
Also I should note that if you do release it there, make sure you DON'T release it in the finish/fail methods, because it won't be a valid object.

NSURLConnection leak?

i have set up a nsurl which grabs the data from http.
when i run instrument, it says i have a leak NSFNetwork object.
and how do i release theConnection in (void)ButtonClicked? or it will be release later on?
- (void)ButtonClicked {
NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:KmlUrl]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:20.0f];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
// receivedData is declared as a method instance elsewhere
NSMutableData *receivedData = [[NSMutableData data] retain];
[self setKMLdata:receivedData];
} else {
// inform the user that the download could not be made
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// append the new data to the receivedData
// receivedData is declared as a method instance elsewhere
[KMLdata appendData:data];
NSLog(#"didReceiveData");
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// release the connection, and the data object
[connection release];
[KMLdata release];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
// release the connection, and the data object
[connection release];
// receivedData is declared as a method instance elsewhere
[KMLdata release];
}
I finally found the answer for this.
The error in the above code (which by the way is the near-exact sample from the SDK docs) is not in the memory management code. Autorelease is one option, manual release is another. Regardless of how you handle your NSURLConnection object, you get leaks using NSURLConnection.
First up, here is the solution. Just copy these 3 lines of code directly into connectionDidFinishLoading, didFailWithError and anywhere else you release the NSURLConnection object.
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
[sharedCache release];
Credit to mpramodjain on http://forums.macrumors.com/showthread.php?t=573253 for the code.
The problem seems to be this – the SDK caches the requests and replies on the iPhone. Even it seems if your NSMutableURLRequest cachePolicy is set to not load the reply from the cache.
The silly thing is that it seems to cache a lot of data by default. I'm transmitting a lot of data (split into multiple connections) and started to get memory warnings, and finally my App died.
The docs we need are in NSURLCache (not NSURLConnection), they state:
NSURLCache implements the caching of
responses to URL load requests by
mapping NSURLRequest objects to
NSCachedURLResponse objects. It is a
composite of an in-memory and an
on-disk cache.
Methods are provided to manipulate the
sizes of each of these caches as well
as to control the path on disk to use
for persistent storage of cache data.
Those three lines have the effect of nuking the cache totally. After adding them to my App (GPS Log), my #living object count remains steady.
Hello have you test this delegate method ?
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
return nil;
}
You can manage the cache more precisely.
"reset" NSURLCache *sharedCache can cause problems on other part of your code ?
This is a common question and is solved by the magic of [object autorelease]. In your code this would be as follows:
NSURLConnection *theConnection = [[[NSURLConnection alloc] initWithRequest:theRequest delegate:self] autorelease];
In this way, the object is automatically added to the "autorelease pool" and dealloc'd at the start of the next run loop after it is no longer referenced.
Hope that helps
Edit: Also, I don't see why you're needing to call -retain on your receivedData variable.
I am using the static method/autoreleased approach and it appears to work fine:
[NSURLConnection connectionWithRequest:theRequest delegate:self];
This way you don't even have to worry about releasing in the delegate callbacks. It turns out that the retain count of the connection is actually 2 (not 1) after it is alloc'd in the examples above, which changes the way I thought about this memory "leak."
#rpetrich I actually don't think you need to worry about the delegate being released before the connection is released. The connection retains it's delegate and the connection itself is actually retained by some sort of open connections queue. I wrote a blog post on my experiments with NSURLConnection on my blog:
"Potential leak of object" with NSURLConnection