Memory allocation growth on a method which fetches server data - ios

I have a method which fetches data from server. I'm calling the method several times inside a loop. The problem is when i use instrument to check memory allocation, i have increase in live memory in each of the method call. BTW I am using ARC.
-(NSArray*)callService
{
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL: serviceURL];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:requestData];
[request setTimeoutInterval:30.0];
NSURLResponse *response = NULL;
NSError *requestError = NULL;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&requestError];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSArray *array = [(NSArray*) [responseString JSONValue] autorelease];
responseString=nil;
return array;
}

I have a method which fetches data from server. I'm calling the method
several times inside a loop.
Don't. Call one, wait for the response and call the next one.
Edit 1:
A synchronous load is built on top of the asynchronous
loading code made available by the class. The calling thread is
blocked while the asynchronous loading system performs the URL load on
a thread spawned specifically for this load request. No special
threading or run loop configuration is necessary in the calling thread
in order to perform a synchronous load.
Because this call can potentially take several minutes to
fail (particularly when using a cellular network in iOS), you should
never call this function from the main thread of a GUI application.

If you have zombies on (scheme settings) ensure you turn them off while testing in Instruments for memory usage. The default setting for NSDeallocateZombies is NO, so zombies won't be deleted at all. So either set NSZombiesEnabled to NO (or the equivalent scheme checkbox) or leave in on but set NSDeallocateZombies to YES.

The problem was with SBJSON parser. Since i'm using iOS 5, i used NSJSONSerialization and it works like a charm.
NSError *e = nil;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData: responseData options: NSJSONReadingMutableContainers error: &e];
instead of the JSONValue

Related

IOS/Objective-C: Send Synchronous request for login

Hi I have been sending a login with asynchronous request (as it is commonly advised to use asynchronous where possible) but I now want to make it synchronous to better control when I receive the response.
Can someone suggest how to alter the asynchronous code below to synchronous?
Thanks for any suggestions:
NSURL *url = [NSURL URLWithString:#"http://~/login.php"];
NSMutableURLRequest *rq = [NSMutableURLRequest requestWithURL:url];
[rq setHTTPMethod:#"POST"];
NSData *jsonData = data;
[rq setHTTPBody:jsonData];
[rq setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[rq setValue:[NSString stringWithFormat:#"%ld", (long)[jsonData length]] forHTTPHeaderField:#"Content-Length"];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
//Want to change to a synchronous request
[NSURLConnection sendAsynchronousRequest:rq queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *rsp, NSData *data, NSError *err) {
if (err) {
NSLog(#"Error%#",err);
} else {
NSDictionary *jsonResults = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSNumber *idResponse = jsonResults[#"response"][#"userid"];
if (![idResponse isKindOfClass:[NSNull class]]) {
NSInteger userid = [idResponse integerValue];
}
}
dispatch_async(dispatch_get_main_queue(), ^{
//back in main queue to use results of login.
});
}
}];
}
I think you're out of luck.
Apple has deprecated just about all the methods in NSURLConnection. We're supposed to start using NSURLSession instead, and that is async only.
The take-away is "if you're using synchronous networking, you're doing it wrong."
I think you should probably bite the bullet and do that refactoring. What I do is to create my own methods that take a completion block, and the completion block in NSURLSession calls my methods completion block (from the main thread to make things simple.)
According to Apple, there is a Sync Request function called
+ sendSynchronousRequest:returningResponse:error:
But also, According to Apple, you should never use it from the main thread of a GUI application(explained in the following link)
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/index.html#//apple_ref/occ/clm/NSURLConnection/sendSynchronousRequest:returningResponse:error:
The Async you posted, did finally get back to the Main Thread when the call is finished using
dispatch_get_main_queue()
I would suggest to do some researches on how iOS manage multi-thread using GCD

returning a value from asynchronous call using semaphores

I need to use NSURLSession to make network calls. On the basis of certain things, after I receive the response, I need to return an NSError object.
I am using semaphores to make the asynchronous call behave synchronously.
The problem is, the err is set properly inside call, but as soon as semaphore ends (after
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
), the err becomes nil.
Please help
Code:
-(NSError*)loginWithEmail:(NSString*)email Password:(NSString*)password
{
NSError __block *err = NULL;
// preparing the URL of login
NSURL *Url = [NSURL URLWithString:urlString];
NSData *PostData = [Post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
// preparing the request object
NSMutableURLRequest *Request = [[NSMutableURLRequest alloc] init];
[Request setURL:Url];
[Request setHTTPMethod:#"POST"];
[Request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[Request setHTTPBody:PostData];
NSMutableDictionary __block *parsedData = NULL; // holds the data after it is parsed
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.TLSMinimumSupportedProtocol = kTLSProtocol11;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil];
NSURLSessionDataTask *task = [session dataTaskWithRequest:Request completionHandler:^(NSData *data, NSURLResponse *response1, NSError *err){
if(!data)
{
err = [NSError errorWithDomain:#"Connection Timeout" code:200 userInfo:nil];
}
else
{
NSString *formattedData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#", formattedData);
if([formattedData rangeOfString:#"<!DOCTYPE"].location != NSNotFound || [formattedData rangeOfString:#"<html"].location != NSNotFound)
{
loginSuccessful = NO;
//*errorr = [NSError errorWithDomain:#"Server Issue" code:201 userInfo:nil];
err = [NSError errorWithDomain:#"Server Issue" code:201 userInfo:nil];
}
else
{
parsedData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&err];
NSMutableDictionary *dict = [parsedData objectForKey:#"User"];
loginSuccessful = YES;
}
dispatch_semaphore_signal(semaphore);
}];
[task resume];
// but have the thread wait until the task is done
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return err;
}
Rob's answer tells you how to do it right, but not what mistake you made:
You have two variables named err, which are totally unrelated. It seems that you haven't turned on some important warnings, otherwise your code wouldn't even have compiled.
The parameter err that is passed to your completion block is the error from the URL request. You replace it without thinking with a timeout error - so the true error is now lost. Consider that timeout is not the only error.
But all the errors that you set only set the local variable err which was passed to you in the completion block; they never touch the variable err in the caller at all.
PS. Several serious errors in your JSON handling. JSON can come in UTF-16 or UTF-32, in which case formattedData will be nil and you incorrectly print "Server Issue". If the data isn't JSON there is no guarantee that it contains DOCTYPE or html, that test is absolute rubbish. Your user with the nickname JoeSmith will hate you.
Passing NSJSONReadingAllowFragments to NSJSONSerialization is nonsense. dict is not mutable; if you try to modify it your app will crash. You don't check that the parser returned a dictionary, you don't check that there is a value for the key "User", and you don't check that the value is a dictionary. That's lots of ways how your app can crash.
I would suggest cutting the Gordian knot: You should not use semaphores to make an asynchronous method behave synchronously. Adopt asynchronous patterns, e.g. use a completion handler:
- (void)loginWithEmail:(NSString *)email password:(NSString*)password completionHandler:(void (^ __nonnull)(NSDictionary *userDictionary, NSError *error))completionHandler
{
NSString *post = ...; // build your `post` here, making sure to percent-escape userid and password if this is x-www-form-urlencoded request
NSURL *url = [NSURL URLWithString:urlString];
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
// [request setValue:postLength forHTTPHeaderField:#"Content-Length"]; // not needed to set length ... this is done for you
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"]; // but it is best practice to set the `Content-Type`; use whatever `Content-Type` appropriate for your request
[request setValue:#"text/json" forHTTPHeaderField:#"Accept"]; // and it's also best practice to also inform server of what sort of response you'll accept
[request setHTTPBody:postData];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.TLSMinimumSupportedProtocol = kTLSProtocol11;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *err) {
if (!data) {
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(nil, [NSError errorWithDomain:#"Connection Timeout" code:200 userInfo:nil]);
});
} else {
NSError *parseError;
NSDictionary *parsedData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&parseError];
dispatch_async(dispatch_get_main_queue(), ^{
if (parsedData) {
NSDictionary *dict = parsedData[#"User"];
completionHandler(dict, nil);
} else {
completionHandler(nil, [NSError errorWithDomain:#"Server Issue" code:201 userInfo:nil]);
}
});
}
}];
[task resume];
}
And then call it like so:
[self loginWithEmail:userid password:password completionHandler:^(NSDictionary *userDictionary, NSError *error) {
if (error) {
// do whatever you want on error here
} else {
// successful, use `userDictionary` here
}
}];
// but don't do anything reliant on successful login here; put it inside the block above
Note:
I know you're going to object to restoring this back to asynchronous method, but it's a really bad idea to make this synchronous. First it's a horrible UX (the app will freeze and the user won't know if it's really doing something or whether it's dead) and if you're on a slow network you can have all sorts of problems (e.g. the watchdog process can kill your app if you do this at the wrong time).
So, keep this asynchronous. Ideally, show UIActivityIndicatorView before starting asynchronous login, and turn it off in the completionHandler. The completionHandler would also initiate the next step in the process (e.g. performSegueWithIdentifier).
I don't bother testing for HTML content; it is easier to just attempt parse JSON and see if it succeeds or not. You'll also capture a broader array of errors this way.
Personally, I wouldn't return my own error objects. I'd just go ahead and return the error objects the OS gave to me. That way, if the caller had to differentiate between different error codes (e.g. no connection vs server error), you could.
And if you use your own error codes, I'd suggest not varying the domain. The domain should cover a whole category of errors (e.g. perhaps one custom domain for all of your app's own internal errors), not vary from one error to another. It's not good practice to use the domain field for something like error messages. If you want something more descriptive in your NSError object, put the text of the error message inside the userInfo dictionary.
I might suggest method/variable names to conform to Cocoa naming conventions (e.g. classes start with uppercase letter, variables and method names and parameters start with lowercase letter).
There's no need to set Content-Length (that's done for you), but it is good practice to set Content-Type and Accept (though not necessary).
You need to let the compiler know that you will be modifying err. It needs some special handling to preserve that beyond the life of the block. Declare it with __block:
__block NSError *err = NULL;
See Blocks and Variables in Blocks Programming Topics for more details.

How do I figure out when the contents of NSData indicate that a webservice request has failed?

I'm trying to persist data from a microsoft web service call to a file when my app starts up. If the app is able to complete the webservice request, it's storing the data in an NSData object. Assuming the data has been successfully requested and stored, I want to execute certain code that I would NOT want to if the webservice is unsuccessful.
My webservice request is as follows:
NSMutableURLRequest *request1 = [[NSMutableURLRequest alloc]init];
[request1 setURL:[NSURL URLWithString:#"---URL---"]];
[request1 setHTTPMethod:#"GET"];
NSURLResponse *request1Response;
NSData *request1Handler = [NSURLConnection sendSynchronousRequest:request1 returningResponse:&request1Response error:nil];
NSString *request1Reply = [[NSString alloc]initWithBytes:[request1Handler bytes] length:[request1Handler length] encoding:NSASCIIStringEncoding];
NSData *data1 = [request1Reply dataUsingEncoding:NSUTF16StringEncoding];
So basically, the response is dropped into that data1 object.
When connected to the internet, the request executes fine. The code that follows is wrapped in a if(data1){ conditional to make sure that the webservice request is successful before executing it. The problem is that when I disconnect from the internet(and cut off access to that webservice), the code inside the conditional is still being executed.
I tried comparing data1 to nil, logging data1 to do a direct comparison of the contents, etc, but I gather that that data1 object isn't nil; it probably contains some sort of failure message that I have thus far been unable to access. What can I do in the conditional or in the webservice request itself to figure out when the request fails?
Create an NSError object and pass it as argument to the sendSynchronousRequest: method, then if there is network or another error, the err object will populated with error information hence it will not be nil. That means you can check if(!err)contiune else there is an error
check the code:
NSMutableURLRequest *request1 = [[NSMutableURLRequest alloc]init];
[request1 setURL:[NSURL URLWithString:#"http://www.google.com"]];
[request1 setHTTPMethod:#"GET"];
//object where error will be saved
NSError *err;
NSURLResponse *request1Response;
NSData *request1Handler = [NSURLConnection sendSynchronousRequest:request1 returningResponse:&request1Response error:&err];//pass the err here by reference
NSString *request1Reply = [[NSString alloc]initWithBytes:[request1Handler bytes] length:[request1Handler length] encoding:NSASCIIStringEncoding];
NSData *data1 = [request1Reply dataUsingEncoding:NSUTF16StringEncoding];
and then you can check here:
if (err){
NSLog(#"error: %#", err);
}
You should at least be checking for an error response by populating the error pointer parameter.
Ideally you should be using a different API which gives you access to more details such as the response HTTP status code which you should be using to determine what happened even if you did get something which looks like success.

Prevent initWithContentsOfURL: from using cache

I'm using this line to get the contents of a URL:
NSString *result=[[NSString alloc] initWithContentsOfURL:[NSURL URLWithString:URL]
encoding:NSUTF8StringEncoding
error:nil];
The problem is when there's a bad connection, it loads the contents from cache. Is there a way to avoid this behaviour? For example, clearing the cache or something.
First, it's not recommended to use initWithContentsOfURL:encoding:error to load data from a network resource.
Second, if you want to control caching behavior, you should be using an NSURLRequest. NSURLRequest allows you to customize the caching behavior of the request by setting the cachePolicy of the request. In your case, you want to use NSURLRequestReloadIgnoringLocalCacheData. An example of doing this synchronously using NSURLConnection would be:
NSString *result = nil;
NSData *data = nil;
NSURLResponse *response = nil;
NSURLError *error = nil;
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:20L];
data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (response != nil && [data length] > 0){
result = [NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
Note that this is a very naive implementation that does not check the HTTP status code returned, the mime-type of the response, or perform any error handling. It is also not a recommended practice to load network resources synchronously or to do so from the main thread. A better implementation would use sendAsynchronousRequest:completion: or NSURLSession.
However, it does demonstrate at a high level what you would need to do to answer your question: The NSURLRequest specifies that this request should never use the local cache, and the returned data is used to create an instance of NSString.
Simple cache-buster dummy parameter with random value added to URL should work.
And as #Josh-Caswell said, use NSURLRequest. Although in case of proxy servers, just using NSURLRequest may not help and you will still need cache-buster.

When accessing the web, what is the different between block and without block

I am using ASIHTTPRequest framework, in the document, what are the differences between the 2nd and 3rd example, in usage, advantage and disadvantage?
2nd example (Creating an asynchronous request):
- (IBAction)grabURLInBackground:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data
NSData *responseData = [request responseData];
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
}
3rd example (Using blocks)
- (IBAction)grabURLInBackground:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
__block ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data
NSData *responseData = [request responseData];
}];
[request setFailedBlock:^{
NSError *error = [request error];
}];
[request startAsynchronous];
}
The blocks in iOS are a part of Concurrent Programming
You use a block when you want to create units of work (that is, code segments) that can be passed around as though they are values. Blocks are usually used for writting a callbacks.
Usually, using blocks do not reflect in different applicatino behaviour. The syntactical difference is that, when using blocks you do not need to define a request delegate or implement delegate methods (such as -requestFinished: and -requestFailed:) for async requests.
One of the advantages is in accessing local method variables in completion block, bacause the function expression in block can reference and can preserve access to local variables (like variable url in your method -grabURLInBackground: or any other local variable defined in your method).
The second adventage is in using nested request calls. For example, you may need to perform a few requests in sequence, and without blocks you will need to define a separate delegate method callback for each request, which may result in reduced readability of your code.
Blocks allow you to write code at the point of invocation that is executed later in the context of the method implementation, which may be very usefull, when you get used to using them.
Some patterns to avoid when using blocks are mentioned in Apple Blocks Programming Topis

Resources