I'm debugging a crash reported as:
Exception Type: SIGSEGV
Exception Codes: SEGV_ACCERR
The crash is happening on the line that does numberOfFails++.
The app uses ASIHTTP. I personally much prefer using NSURLConnection. I'd never automatically repeat a request for NSURLConnection if it failed because I've never seen it fail when it shouldn't. I'd rather just give the UI a refresh button or show a UIAlertView with a button to try again or something like that.
Anyway, to cooperate with other team members, I'm looking to fix this issue without replacing ASIHTTP with NSURLConnection for now.
The request is being started with something like:
- (void)getResources:(CLLocation *)location withQuery:(NSString *)query {
NSURL *url = [NSURL URLWithString:[NSString stringWithString:#"https://example.com/"]];
self.resourcesAPIRequest = [ASIFormDataRequest requestWithURL:url];
[resourcesAPIRequest setPostValue:[Model instance].oauth_token forKey:#"oauth_token"];
[resourcesAPIRequest setPostValue:[[NSNumber numberWithDouble:location.coordinate.latitude] stringValue] forKey:#"latitude"];
[resourcesAPIRequest setPostValue:[[NSNumber numberWithDouble:location.coordinate.longitude] stringValue] forKey:#"longitude"];
[resourcesAPIRequest setPostValue:query forKey:#"query"];
[resourcesAPIRequest setDelegate:self];
[resourcesAPIRequest setDidFinishSelector:#selector(resourcesAPIReturned:)];
resourcesAPIRequest.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:NSStringFromSelector(#selector(getResources:withQuery:)), #"repeatSelector", location, #"argument1", query, #"argument2", nil];
[resourcesAPIRequest startAsynchronous];
}
One thing I noticed is : <ASIHTTPRequestDelegate> is not in the header file, but this callback method is still being called OK:
#define maximumNumberOfFails 50
- (void)requestFailed:(ASIFormDataRequest *)request {
static int numberOfFails = 0;
if (numberOfFails < maximumNumberOfFails) {
[NSThread sleepForTimeInterval:sleepTimeInSeconds];
if ([request.userInfo objectForKey:#"argument2"]) {
[self performSelector:NSSelectorFromString([request.userInfo objectForKey:#"repeatSelector"]) withObject:[request.userInfo objectForKey:#"argument1"] withObject:[request.userInfo objectForKey:#"argument2"]];
} else if ([request.userInfo objectForKey:#"argument1"]) {
[self performSelector:NSSelectorFromString([request.userInfo objectForKey:#"repeatSelector"]) withObject:[request.userInfo objectForKey:#"argument1"]];
} else {
[self performSelector:NSSelectorFromString([request.userInfo objectForKey:#"repeatSelector"])];
}
numberOfFails++;
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Problem" message:#"There was a problem connecting to the servers. Please make sure you have an Internet connection." delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alert show];
[alert release];
numberOfFails = 0;
}
}
Also, I think that static int numberOfFails should be static NSUInteger numberOfFails. And, I noticed that the request is started with startAsynchronous. Is static int numberOfFails atomic? That may be why we're getting the error SEGV_ACCERR (Invalid permissions for mapped object).
Thoughts?
The problem likely has nothing to do with your static variable.
Does requestFailed: execute on the main thread, or in a background thread?
If it's on a background thread, you'll need to use performSelectorOnMainThread:withObject:.
If it's on the main thread, you may need to take a pass through the runloop before executing a new HTTP request. To do that, use performSelector:withObject:afterDelay:, and pass '0.0' as the delay.
You'll notice that both those methods allow only a single method parameter. Typically, you pass your parameters in a NSDictionary rather than try to parse out the number of parameters beforehand, like you're doing.
Related
I am using following code to get results from server
NSString *queryString = #"MyString"
NSString *response = [NSString stringWithContentsOfURL:[NSURL URLWithString:queryString] encoding:NSUTF8StringEncoding error:&err];
NSLog(#"%#",response);
if (err != nil)
{
UIAlertView *alert = [[UIAlertView alloc]initWithTitle: #"Error"
message: #"An error has occurred. Kindly check your internet connection"
delegate: self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alert show];
[indicator stopAnimating];
}
else
{
//BLABLA
}
The problem with this code is ,If server shows lag and it takes lets say 3 seconds to get this response
NSString *response = [NSString stringWithContentsOfURL:[NSURL URLWithString:queryString]
For 3 seconds my iPhone screen is jammed. How can i make it run in background so that it won't slow down or jam the mobile
Regards
What you are doing is sending HTTP request from the main thread. that will jam up the UI as you said. You need to spawn a background thread and make a request to your server, when the response comes back then you need to update the UI from the main thread. This is a common pattern in UI coding.
__block__ NSString *response;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//your server url and request. data comes back in this background thread
response; = [NSString stringWithContentsOfURL:[NSURL URLWithString:queryString] encoding:NSUTF8StringEncoding error:&err];
dispatch_async(dispatch_get_main_queue(), ^{
//update main thread here.
NSLog(#"%#",response);
if (err != nil)
{
UIAlertView *alert = [[UIAlertView alloc]initWithTitle: #"Error"
message: #"An error has occurred."
delegate: self
cancelButtonTitle:#"Ok"
otherButtonTitles:nil];
[alert show];
[indicator stopAnimating];
}
});
});
You can also use performSelectorInBackground:withObject: to spawn a new thread, then the performed selector is responsible for setting up the new thread's autorelease pool, run loop and other configuration details – see "Using NSObject to Spawn a Thread" in Apple's Threading Programming Guide.
You'd probably be better off using Grand Central Dispatch as I posted above. GCD is a newer technology, and is more efficient in terms of memory overhead and lines of code.
You could use ASIHTTPRequest which is my favorite for getting and sending information.
I've inherited a project that uses of ASIHttpRequest for all network communication. I am unclear as to which specific version we're using. All I can tell is that, from the .h files, the oldest creation date on a particular file is 17/08/10 (ASIDataDecompressor).
We're using completion and failure blocks. For some reason, the failure block is often triggered, which should only really happen if the server fails to respond. Our logs look sane, and we haven't received any notifications (Airbrake) that there were server problems around the time the errors occur, so for now I'm moving forward with the assumption that our server is fine and it's the app that is the culprit.
I decided to run the app through Instruments (Leaks) and was astonished to see that when I force a request to fail, ~27 leaks are created immediately. I'm don't know how to get around Instruments all that well, so I'm not really sure what to do with the information now that I have it.
I figured I'd post my code to see if there's anything glaring.
In viewDidLoad, this code is executed
[[MyAPI sharedAPI] getAllHighlights:pageNumber:perPage onSuccess:^(NSString *receivedString,NSString *responseCode) {
[self getResults:receivedString];
if(![responseCode isEqualToString:#"Success"]) {
[self hideProgressView];
appDelegate.isDiscover_RefreshTime=YES;
[[MyAPI sharedAPI] showAlert:responseCode];
} else {
NSString *strLogEvent=#"Discover_Highlights_Loaded Page_";
strLogEvent=[strLogEvent stringByAppendingFormat:#"%i",intPageNumber];
[FlurryAnalytics logEvent:strLogEvent timed:YES];
}
} onFail:^(ASIFormDataRequest *request) {
NSDictionary *parameters = [[MyAPI sharedAPI] prepareFailedRequestData:request file:#"Discover" method:_cmd];
[FlurryAnalytics logEvent:#"Unable_to_Connect_to_Server" withParameters:parameters timed:true];
[self hideProgressView];
appDelegate.isDiscover_RefreshTime=YES;
[[AfarAPI sharedAPI] showAlert:#"Unable to Connect to Server."];
[tblHighlightsGrid reloadData];
[tblListHighlights reloadData];
}];
These typedefs have been defined at the top of API Singleton:
typedef void (^ASIBasicBlockWrapper)(NSString *responseString,NSString *responseCode);
typedef void (^ASIBasicBlockWrapperFail)(ASIFormDataRequest *request);
MyAPISingleton#getAllHighlights...
- (void)getAllHighlights:(NSString *)pageNumber:(NSString *)perPage onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2{
NSString *access_token= [[NSUserDefaults standardUserDefaults] objectForKey:#"access_token"];
NSString *url = [baseURL stringByAppendingFormat:AFAR_GET_ALL_HIGHLIGHTS_ENDPOINT, pageNumber,perPage];
if (access_token) { url = [url stringByAppendingFormat:ACCESS_TOKEN, access_token]; }
__block ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:url]];
[request setRequestMethod:#"GET"];
[request setDelegate:self];
[self executeAsynchronousRequest:request onSuccess:cb1 onFail:cb2];
}
And finally, MyAPI#executeAsynchronousRequest:
- (void) executeAsynchronousRequest:(ASIFormDataRequest *)request onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2
{
[request setCompletionBlock:^{
int statusCode = [request responseStatusCode];
NSString *statusMessage = [self statusErrorMessage:statusCode];
cb1([request responseString],statusMessage);
}];
[request setFailedBlock:^{
cb2(request);
}];
[request startAsynchronous];
}
Does anything stand out as to why 27 leaks are created?
I figured this out.
The ASIHttpRequest Documentation is very clear about the fact that you need to designate your request object with the __block storage mechanism:
Note the use of the __block qualifier when we declare the request, this is important! It tells the block not to retain the request, which is important in preventing a retain-cycle, since the request will always retain the block.
In getAllHighlights(), I'm doing that, but then I'm sending my request object as an argument to another method (executeAsyncRequest). The __block storage type can only be declared on local variables, so in the method signature, request is just typed to a normal ASIFormDataRequest, and so it seems as though it loses its __block status.
The trick is to cast (I'm not sure if that's technically accurate) the argument before using it in a block.
Here's my leak free implementation of executeAsyncRequest:
- (void) executeAsyncRequest:(ASIFormDataRequest *)request onSuccess:(ASIBasicBlockWrapper)cb1 onFail:(ASIBasicBlockWrapperFail)cb2
{
// this is the important part. now we just need to make sure
// to use blockSafeRequest _inside_ our blocks
__block ASIFormDataRequest *blockSafeRequest = request;
[request setCompletionBlock: ^{
int statusCode = [blockSafeRequest responseStatusCode];
NSString *statusMessage = [self statusErrorMessage:statusCode];
cb1([blockSafeRequest responseString],statusMessage);
}];
[request setFailedBlock: ^{
cb2(blockSafeRequest);
}];
[request startAsynchronous];
}
First of all the questions are failry simiple.. if you just want to see what they are skip to the bottom of this post and you will see them in bold.. for more detail then you can read the rest of this post...
I am just trying to iron out my NSURLConnection so that its working smoothly and I understand this properly. There is a profound lack of example/tutorials for Asynchronous connections on the internet or not any that I can find that explaine what is going on with any level of depth other than getting the connection up and running which after working on it seems pretty simple. Hopefully this question can full the void that I feel is out there for other users.
So, in my .h file i have imported the foundations headers and declared the methods required for the received or lack of received data (errors etc).
.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h> //add foundations
//.. other headers can be imported here
#interface MyViewController: UITableViewController {
//Im not setting any delegates to access the methods because Is all happening in the same
//place so I just use the key word 'self' when accessing the methods declared below
//I'm not sure if this is the best thing to do but I wasn't able to get my head around declaring the delegate or how it would help me with the way I have set up my request etc.
}
- (IBAction)setRequestString:(NSString *)string; //this method sets the request and connection methods
//these methods receive the response from my async nsurlconnection
- (void)receivedData:(NSData *)data;
- (void)emptyReply;
- (void)timedOut;
- (void)downloadError:(NSError *)error;
So thats my header file.. pretty simple not much explaining needed.
.m
//call setRequestString from some other method attached to a button click or something
[self setRequestString:#"rss.xml"];
//..
- (IBAction)setRequestString:(NSString *)string
{
//Set database address
NSMutableString *databaseURL = [[NSMutableString alloc] initWithString:#"http:www.comicbookresources/feeds/"]; // address not real jsut example
//append the string coming in to the end of the databaseURL
[databaseURL appendString:string];
//prepare NSURL with newly created string
NSURL *url = [NSURL URLWithString:databaseURL];
//AsynchronousRequest to grab the data
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if ([data length] > 0 && error == nil){
[self receivedData:data];
}else if ([data length] == 0 && error == nil){
[self emptyReply];
}else if (error != nil && error.code == NSURLErrorTimedOut){ //used this NSURLErrorTimedOut from foundation error responses
[self timedOut];
}else if (error != nil){
[self downloadError:error];
}
}];
}
now set up the methods that were initialized in the .h file and called in the if statement above
- (void)receivedData:(NSData *)data
{
NSString* newStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#", newStr); //logs recived data
//now you can see what data is coming in on your log
//do what you want with your data here (i.e. start parsing methods
}
- (void)emptyReply
{
//not sure what to do here yet?
}
- (void)timedOut
{
//also not sure what to do here yet?
}
- (void)downloadError:(NSError *)error
{
NSLog(#"%#", error);
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Error!" message:#"A connection failure occurred." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[errorAlert show];
}
Cool so that pretty much the basics of what I have done right there.. now the questions I have are as follows.
Question one:
Where I call NSURLConnection like so
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
What is happening here what is the ^ for is that executing that whole block (including the if statements) on a different thread or something? because it looks alot like grand central dispatch formatting but slightly different.
Question two:
what should I be doing inside emptyReply & timedOut methods?
Question three:
How would I incorporate caching into this? I would like to cache the responses I get back from different requests. i.e. with my setRequestString you will see there is a string input parameter, so i can request different rss feeds with the same method.. I need to figure out how to cache these responses into individual caches.. but im not sure where to start with it.
Finally
If you have made it this far, thank you very much for reading my question. Hopefully with your responses we can get a pretty nice solution going here.. that other people can use for themselves and pick and choose the bits and peices they need that works for there own solution..
Anyway thank you very much for reading and I look forward to your replies.. even if they are just refrences to tutorials or examples you think might help me.. anything is good I just want to fully understand whats going on and whats a good solution.
Read about blocks in Apple documentation. Its new. Or you can read here
You can show errors such as request timed out etc. You don't really have to handle them separately than the error one unless you have special logic.
Try this for caching
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:TIMEOUT_INTERVAL];
Guys, I was retrieving an XML response from a .php script on my server using the following code:
NSString *xmlString = [NSString stringWithContentsOfURL:[NSURL URLWithString:urlString]
encoding:NSUTF8StringEncoding error:&error];
and
//tried also: if(error)
if(!xmlString)
{
NSString * errorString = [NSString stringWithFormat:#"Unable to download xml data (Error code %i )", [error code]];
UIAlertView * errorAlert = [[UIAlertView alloc] initWithTitle:#"Error loading content" message:errorString delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[errorAlert show];
[errorAlert release];
}
//*** now I have a touchXML xPath query code that extracts what I need from XML
My .php script wasn't working today and my iApp was freezing without any alerts, errors or notifications? I thought that the code above will handle errors but it doesn't???
A) Then what type of error does it catch?
Then I remembered that i I should try working with NSURLConnection and its delegate methods to catch any error that occurs.
//Inside viewDidLoad method
NSMutableData *responseData = [[NSMutableData alloc] init];
NSURL *baseURL = [[NSURL URLWithString:self.chosenDrawRss] retain];
NSURLRequest *request = [NSURLRequest requestWithURL:baseURL];
[[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
NSString *xmlString = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding];
The problem I had using this approach was that app ran the Connection in the background and continued executing the rest of the code found in viewDidLoad, so I had to reorganize my code a bit by moving it to another method that I call from delegate method connectionDidFinishLoading. The problem I have is that my delegate methods are not called. Well, all except didFailWithError: if I try to load URL that doesn't exist.
UPDATE:
delegate methods are called but it takes one minute or so for delegate method to be called and until alert message pops out...
B) Could I use stringWithContentsOfURL and still have an alert to the user if anything happens?
C) If not, then I need help with setting up NSURLConnection approach? I mean, what I'm missing here is why aren't my delegate methods called?
I truly hope my questions make sense :D
L
If your delegate method is called in didFailWithError then i suppose it will even be called in
connectionDidFinishLoading. Check by enabling a breakpoint in the delegate methods and track the flow. It may have happened the function returns before you call the delegate.
NSString *xmlString = [[NSString alloc] initWithData:self.responseData encoding:NSUTF8StringEncoding]
should be called in your delegate connectionDidFinishLoading method and in
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[responseData appendData:data];
}
I dont see why the alert should pop up after a minute unless you are doing some processing before displaying it.
I have follwowing peace of code in which I have specified the leaky line . As I am new to iPhone developement I can't understand what actually is wrong with that line . please have a look at that line and tell me .
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
//take away
//NSURL *url1 = [[NSURL alloc] initWithString:#"http://url/Service.asmx/takeAwayList"];
NSURL *url1 = [[NSURL alloc] initWithString:[NSString stringWithFormat:#"%#/Service.asmx/takeAwayList",serviceURL]];
NSMutableURLRequest* request1=[NSMutableURLRequest requestWithURL:url1];
[request1 setHTTPMethod:#"POST"];
[request1 setTimeoutInterval:10];
//*****the leaky line***********************///
NSData *data2=[[NSURLConnection sendSynchronousRequest:request1 returningResponse:nil error:nil] autorelease];
if(data2 == nil)
{
UIAlertView* alert = [[UIAlertView alloc]initWithTitle:#"Alert" message:#"The network is not available.\n Please check the Internet connection." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
[alert release];
}
else
{
NSXMLParser *xmlParser1 = [[NSXMLParser alloc] initWithData:data2];
//Initialize the delegate.
TakeAwayParser *takeAwayParser = [[TakeAwayParser alloc] initTakeAwayParser];
//Set delegate
[xmlParser1 setDelegate:takeAwayParser];
//Start parsing the XML file.
#try {
BOOL success = [xmlParser1 parse];
if(success)
NSLog(#"No Errors");
else
NSLog(#"Error Error Error!!!");
}
#catch (NSException * e) {
NSLog(#"Exception in parsing %# %#",[e name], [e reason]);
}
[takeAwayParser release];
[xmlParser1 release];
}
//[request1 release];
// [response1 release];
//
[url1 release];
// [data2 release];
//new arrivals
//[data2 release];
[pool release];
I had issues with this as well in my Large project. After working with an Apple engineer on trying to locate the leaks, he finally asked the main Apple dev team behind NSURLConnection. They basically said that there is an internal cache that is not clearable at all in NSURLConnection and it was a known issue.
So I set out looking for alternatives. I found ASIHTTPConnection (link below) which works off of CFNetwork. It is designed to be a drop-in replacement for NSURLConnection, plus a bunch of other awesome goodies like downloading to disk instead of memory, download resuming, progress bar callbacks etc..
I have used it in all my projects and have never had any issues or complaints. An, in answer to your question, this is how I got rid of those memory leaks.
http://allseeing-i.com/ASIHTTPRequest/
This line isn't leaking, you shouldn't even being autoreleasing it.
Do yourself a favor and read the Memory Management Guide in Apple's developer docs, commented out releases in your code do not bode well.
EDIT: Hrm I take that back your code is completely fine except for that one line. Are you sure that it's leaking? It's returning an object with a retain count of 0, so you autoreleasing it should be causing trouble because it already has a retain count of 0.