Why my application crash on applicationDidEnterBackground? - iphone

I have a static method called writeToServer that is called when application enter in background mode.
in my AppDelegate.m:
- (void)applicationDidEnterBackground:(UIApplication *) application {
[LogZone writeToServer];
NSLog(#"Log sended to server. Done.");
}
in my LogZone.m:
+ (void) writeToServer {
NSString *qStr = [[NSString alloc]
initWithFormat:#"%#?ip=%#&uid=%#&platform=%#&model=%#&lat=%#&lon=%#",
LOG_SERVER_URL,
_LOG_IP, _LOG_UID, _LOG_PLAT, _LOG_MOD, _LOG_LAT, _LOG_LON];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:qStr]];
[request setHTTPMethod: #"POST"];
[NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
}
Uppercase vars are static strings created in this way:
.h
extern NSString* _LOG_UID;
.m
NSString* _LOG_UID = #"-1";
When I enter in background mode it crash with this "classic" error:
* -[CFString respondsToSelector:]: message sent to deallocated instance
0x6a4c800
But why?
I don't release anything!=!
What's wrong?

But why?
I don't release anything!
Sure, but do you retain the right objects?
Retaining at the right time is every bit as important as not releasing at the wrong time...

Why not just use the following:
NSString *qStr = [NSString stringWithFormat:#"%#?ip=%#&uid=%#&platform=%#&model=%#&lat=%#&lon=%#", LOG_SERVER_URL, _LOG_IP, _LOG_UID, _LOG_PLAT, _LOG_MOD, _LOG_LAT, _LOG_LON];

Related

Data gets deallocated before use with ASIHTTPRequest asynchronous mode

I'm using ASIHTTPRequest in asynchronous mode in a function called from my viewDidLoad()
NSURL *url = [NSURL URLWithString:#"http://somewebsite.com/data.txt"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
This is my requestFinished() where I do some text replacement and populate an NSArray with the data I've received from the website. It's just text, one data item per line.
The NSArray (and alloc / init) in viedDidLoad().
- (void)requestFinished:(ASIHTTPRequest *)request
{
NSString *sTemp = [request responseString];
sTemp = [sTemp stringByReplacingOccurrencesOfString:#"\n" withString:#","];
arrayTidalData = [sTemp componentsSeparatedByString:#","];
NSLog(#"Loaded %d items into the array",[arrayTidalData count]);
[tableTideTimes reloadData];
}
NSLog reports 127 data items.
Now, I use the NSArray data to populate a UITableView.
But, in cellForRowAtIndexPath() when I attempt to access the NSArray (arrayTidalData), for instance by doing a count, I get the following:
TideTimes[14696:b303] * -[__NSArrayM count]: message sent to deallocated instance 0x4e6c6a0
(I turned on NSZOMBIEEnabled = YES to get this data)
It seems the NSArray has been deallocated before I can use it. I also tried populating an NSString with the data in requestFinish() but got the same result.
Am I missing something really simple or am I doing something terribly wrong?
(It's my first time with ASIHTTPRequest)
Replace
arrayTidalData = [sTemp componentsSeparatedByString:#","];
with
arrayTidalData = [[sTemp componentsSeparatedByString:#","] retain];
It is because componentsSeparatedByString: returns autoreleased object. So it is released after method requestFinished: ends working.
And don't forget to release at the end of work (for example, dealloc).

Ampersand in POST request causing havoc

I have a simple POST coming from my iphone app. Its working fine, except passing an ampersand causes the backend to break - it's almost like its treating it like a GET request (ampersands seperate the variable names). Do I need to do some kind of encoding first? Here is the code:
NSString *content = [[NSString alloc] initWithFormat:#"data=%#&email=%#", str, emailAddress.text];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://www.myurl.com/myscript.php"]];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:[content dataUsingEncoding:NSISOLatin1StringEncoding]];
// generates an autoreleased NSURLConnection
[NSURLConnection connectionWithRequest:request delegate:self];
I had this issue in iOS7 and the accepted answer didn't work at all (actually, that is my standard when sending data to the backend). The ampersand was breaking in the backend side, so I had to replace the & by %26. The backend was being done in python and the code was legacy and was using ASI.
Essentially I have done the following:
NSString *dataContent = [NSString stringWithFormat:#"text=%#",
[json stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
dataContent = [dataContent stringByReplacingOccurrencesOfString:#"&"
withString:#"%26"];
ByAddingPercent....... will not work as & is a valid URL character.
I needed to send a JSON with & in it, it is the same idea though;
NSString *post = [NSString stringWithFormat:#"JSON=%#", (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)jsonString, NULL, CFSTR(":/?#[]#!$&’()*+,;="), kCFStringEncodingUTF8))];
"jsonString" towards the end is what is converted.
Edit: As stringByAddingPercentEscapesUsingEncoding: should be used to encode parts of the query, not the whole one, you should be using another method instead.
Unfortunately, Foundation doesn't provide such a method, so you need to reach to CoreFoundation:
- (NSString *)stringByURLEncodingString:(NSString *)string {
return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(
kCFAllocatorDefault,
(__bridge CFStringRef)string,
NULL, // or (__bridge CFStringRef)(#"[].")
(__bridge CFStringRef)(#":/?&=;+!##$()',*"),
kCFStringEncodingUTF8
);
}
You can use
- stringByAddingPercentEscapesUsingEncoding:
In your case it will look like this:
NSString * content = [[NSString alloc] initWithFormat:#"data=%#&email=%#", [str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding], [emailAddress.text stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
You can do this in this way, too:
NSString *dataStr = [str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *emailStr = [emailAddress.text stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSString *content = [[NSString alloc] initWithFormat:#"data=%#&email=%#", dataStr, emailStr];
I'm not sure if this will work in your case, but you could try %38 to try and encode the ampersand.

dispatch_async memory problems in iOS

I have a loop of about 2000+ items I need to go through:
for (NSDictionary* dict in array) {
NSString *fileName = [NSString stringWithFormat:#"%#/%#_lg.jpg?t=",manufacturerID, [[dict valueForKey:#"ItemID"] stringByReplacingOccurrencesOfString:#" " withString:#"%20"]];
NSString *savePath = [documentsPath stringByAppendingPathComponent:fileName];
NSURL *url = [NSURL URLWithString: [[NSString stringWithFormat:kProductImagesURL, fileName]stringByAppendingString:lastImagesSyncDate]];
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_async(aQueue, ^{
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
int statusCode = [request responseStatusCode];
if (statusCode==200) {
NSData *responseData = [request responseData];
[responseData writeToFile:[savePath stringByReplacingOccurrencesOfString:#"%20" withString:#" "] atomically:YES];
}
}
});
}
This works great and my main thread is not blocked but my memory goes through the roof - how do I get it to be released? Once the queue is empty it drops but I need it to clear out as it is going along.
Although you are running the code in the background you are running all of the code in the background at the same time. As fast you are able to loop through the array you are creating a new ASIHttpRequest that will be trying to download and save data at the same time. You may want to move your loop inside of the dispatch_async, or use an NSOperation that does the same thing but limit the max concurrent operations on the NSOperationQueue. If you move the loop inside of dispatch_async to do one at a time remember to create an NSAutoreleasePool locally and drain it periodically.
Instead of getting an autoreleased ASIHTTPRequest , try to alloc ,init , release one.
Haven't tried it , but think about alternating async and sync calls (to the same thread, not the main one) , like having 20 async requests followed by one sync.. this trick could help.

Objective-C Check for downloaded file size

I'm creating an app which downloads a .zip file from S3 server.
All works fine. Now I want to be able to interrupt the current download. If I could save the current size (bytes) of the file, I would be able to send a new request with a range header for the other part of the file.
Problem lies in the fact that I cannot determine the size of the 'already' downloaded content, because I can only see the file in my directory when the download is completed. So if I interrupt, there isn't a partial file saved.
At this time I use the following code for this:
-(void) downloadFile:(NSMutableArray*)paramArray withDict:(NSMutableDictionary*)options
{
NSLog(#"DOWNLOAD THREAD STARTED");
NSString * sourceUrl = [paramArray objectAtIndex:0];
NSString * fileName = [paramArray objectAtIndex:1];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *newFilePath = [documentsDirectory stringByAppendingString:fileName];
NSError *error=[[[NSError alloc]init] autorelease];
NSURLConnection *fileURL = [NSData dataWithContentsOfURL: [NSURL URLWithString:sourceUrl]];
BOOL response = [fileURL writeToFile:newFilePath options:NSDataWritingFileProtectionNone error:&error];
if (response == TRUE)
{
NSLog(#"DOWNLOAD COMPLETED");
[self performSelectorOnMainThread:#selector(downloadComplete:withDict:) withObject:paramArray waitUntilDone:YES];
}
else
{
NSLog(#"Something went wrong while downloading file.");
NSString *callback = [NSString stringWithFormat:#"downloadInterrupted('%#');",fileName];
[webView stringByEvaluatingJavaScriptFromString:callback];
}
[pool drain];
}
AsiHTTP isn't an option because there are issues with the PhoneGap I'm using.
A better idea is to download the file asynchronously. This has several advantages: The most important one is that your user interface stays responsive. The user can go on using your application while it is downloading and waiting for the data. If the data you are downloading is absolutely essential for the application, display some sort of loading indicator.
You can easily start the asynchronous download via
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:sourceUrl]];
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
Now, how do I get the downloades data in an NSData object? You implement the following delegate methods for self:
-connection:didReceiveData:
-connection:didFailWithError:
-connectionDidFinishLoading:
The idea is that you are notified whenever some data drops in through your connection or anything important else happens (success or failure for exmple). So you are going to declare a temporary NSMutableData object as an instance variable (say downloadData) and write to it until the download is complete. Do not forget to initialize the empty object and declare a property as well!
-connection:didReceiveData: is called whenever some sort of data (that is, a part of your downloaded file) arrives. So you are going to append it to your temporary object like this:
-(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.downloadData appendData:data];
}
Once the download has finished (successfully), the next delegate method is called:
-(void) connectionDidFinishLoading:(NSURLConnection *)connection {
//do whatever you need to do with the data in self.downloadData
}
If the downloads fails, -connection:didFailWithError: is called. You can then save the temporary object, get its size and resume the download later. [self.downloadData length]; gets you the size in bytes of the data in your object.
You are going to have to use a lower level api.
time to read up on unix socket programming. http://www.fortunecity.com/skyscraper/arpanet/6/cc.htm would be a good start.
It really won't be too hard. honest.
I recommend you to build a method that save data chunk every 1, 2 MB or maybe less in order to resume properly your download and avoid memory crash.
This because if you get an error in your transfer maybe your file could be result corrupted.
Anyway send a range HTML header is pretty simple
NSFileHandle *fileHandler = [NSFileHandle fileHandleForReadingAtPath:dataPreviouslySavedPath];
[fileHandler seekToEndOfFile];
unsigned long long int range = [fileHandler offsetInFile];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:downloadURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
[request setValue:[NSString stringWithFormat:#"bytes=%lli-", range] forHTTPHeaderField:#"Range"];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
Hope this help you.

iphone: NSMutableURLRequest returns strange characters for MS Word style apostrophe

We are pulling content off our website using XML/NSMutableURLRequest and sometimes it pulls through the "curly" style apostrophe and quotes, ’ rather than '. NSMutableURLRequest seems to hate these and turns them into the strange \U00e2\U0080\U0099 string.
Is there something that I can to do prevent this? I am using the GET method, so should I be somehow telling it to use UTF-8? Or, am I missing something?
UIApplication* app = [UIApplication sharedApplication];
app.networkActivityIndicatorVisible = YES;
NSString *urlStr = [NSString stringWithFormat:#"%#",url];
NSURL *serviceUrl = [NSURL URLWithString:urlStr];
NSMutableURLRequest *serviceRequest = [NSMutableURLRequest requestWithURL:serviceUrl];
[serviceRequest setHTTPMethod:#"GET"];
NSURLResponse *serviceResponse;
NSError *serviceError;
app.networkActivityIndicatorVisible = NO;
return [NSURLConnection sendSynchronousRequest:serviceRequest returningResponse:&serviceResponse error:&serviceError];
NSURLConnection returns an NSData response. You can take that NSData response and turn it into a string. Then take this string, turn it back into a NSData object, properly UTF-8 encoding it along the way, and feed it to NSXMLParser.
Example: (Assuming response is the NSData response from your request)
// long variable names for descriptive purposes
NSString* xmlDataAsAString = [[[NSString alloc] initWithData:response] autorelease];
NSData* toFeedToXMLParser = [xmDataAsAString dataUsingEncoding:NSUTF8StringEncoding];
NSXMLParser* parser = [[[NSXMLParser alloc] initWithData:toFeedToXMLParser] autorelease];
// now utilize parser...
I would suggest replacing those characters using stringByReplacingCharactersInRange:withString: to replace the unwanted strings.
NSString *currentTitle = #"Some string with a bunch of stuff in it.";
//Create a new range for each character.
NSRange rangeOfDash = [currentTitle rangeOfString:#"character to replace"];
NSString *location = (rangeOfDash.location != NSNotFound) ? [currentTitle substringToIndex:rangeOfDash.location] : nil;
if(location){
currentTitle = [[currentTitle stringByReplacingOccurrencesOfString:location withString:#""] mutableCopy];
}
I've done this in the past to handle the same problem you describe.
Try using the stringByReplacingPercentEscapesUsingEncoding: