I have been working on this issue for a long time, and I am unsure how to proceed trying to solve it.
I have a simple ASIHTTPRequest. The code is posted below.
The app always works when I first run it. I have a table view which I can pull to refresh, which initiates the ASIHTTPRequest, and I can refresh as many times as I like with out problem. I can send the app to the background and bring it back and everything works fine. But if I leave the app for a number of hours and come back, sometimes I will start getting a request timed out error. Once this occurs, the error will repeat every time I try to refresh and I am never able to connect again without actually shutting down the app and restarting it.
I do not believe the problem is with my url, because it can be stuck on one device while fine on another device. I have never been able to get the time out error on the simulator.
I can imagine why I might get the time out error once, but I can't understand why once it starts, the error never stops. I really have no idea where to look to try to fix this, or how I could go about debugging it.
It might be relevant to note that I am currently using TestFlightLive, GoogleAnalytics and Urban Airship in my app. Perhaps one of these libraries is causing a problem with my networking behaviour?
Here is the code:
- (void)getData
{
NSURL *url = [NSURL URLWithString:#"http://www.mysite.com/appname/latestData.py?callback=?"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setTimeOutSeconds:20.0];
[request startAsynchronous];
}
- (void)requestFinished:(ASIHTTPRequest *)request
{
NSData *responseData = [request responseData];
DLog(#"requestFinished entered");
NSString *dataString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
// Update the data model
if (dataString != nil)
{
SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
NSDictionary *dataDictionary = [jsonParser objectWithString:dataString error:NULL];
[self updateDataModel:dataDictionary];
}
[self refreshIsFinished];
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
// inform the user
ELog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil
message:NSLocalizedString(#"CONNECTION_FAILED_MESSAGE",nil)
delegate:nil
cancelButtonTitle:NSLocalizedString(#"CLOSE",nil)
otherButtonTitles:nil];
[alertView show];
[self updateUI];
[self refreshIsFinished];
}
Related
I built a simple app which gets the reports from HockeyApp. However when I run the app with the memory leak instruments, it showed there was a memory leak when I perform the getReport action. I couldn't understand all the information shown in the instrument.
Here is the button action method which causes the memory leak:
- (IBAction)getReports:(id)sender {
//initialize url that is going to be fetched.
NSURL *url = [NSURL URLWithString:#"https://rink.hockeyapp.net/api/2/apps/APP_ID/crash_reasons"];
//initialize a request from url
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request addValue:tokenReceived forHTTPHeaderField:#"X-HockeyAppToken"];
[request setHTTPMethod:#"GET"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
//initialize a connection from request
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
self.getReportConnection = connection;
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData*)data{
if (connection==getReportConnection) {
[self.receivedData appendData:data];
NSLog(#"data is %#",data);
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSError *e = nil;
NSData *jsonData = [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *JSON = [NSJSONSerialization JSONObjectWithData:jsonData options: NSJSONReadingMutableContainers error: &e];
NSLog(#"login json is %#",JSON);
NSLog(#"reason json is %#",JSON[#"reason"]);
[JSON[#"crash_reasons"] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[reportArray addObject:obj[#"reason"]];
NSLog(#"index = %lu, Object For title Key = %#", (unsigned long)idx, obj[#"reason"]);
}];
NSError *error = nil;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:jsonData
options:kNilOptions error:&error];
if (error != nil) {
NSLog(#"Error parsing JSON.");
}
else {
NSLog(#"Array: %#,array count is %d", jsonArray,jsonArray.count);
}
// [reportArray addObject:[jsonArray objectAtIndex:0]];
if (JSON!=NULL) {
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"Reports succesfully retrieved" message:#"" delegate:self cancelButtonTitle:#"Ok" otherButtonTitles: nil];
[alert show];
}
}
}
// This method receives the error report in case of connection is not made to server.
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
UIAlertView *errorAlert=[[UIAlertView alloc]initWithTitle:#"Wrong Login" message:nil delegate:self cancelButtonTitle:#"ok" otherButtonTitles: nil];
[errorAlert show];
NSLog(#"error is %#",error);
}
// This method is used to process the data after connection has made successfully.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
}
I see that the memory leak occurs just before the alert view appears in the didRecieveData method.
Here is the screenshot of the memory leak instrument showing memory leak:
I couldn't understand which part of the code causes the memory leak. Can anyone tell me how to identify the part of code that causes the memory leak with the leak instrument?
Edit: When I run the app on the simulator, the instrument didnt show any memory leak:
Here is the screenshot:
Again When I run the app on device, the instrument showed me the memory leak:
I looked into the leaks section and I found NSmutableArray is causing the leak:
I used only one NSMutableArray in my code. I declared it in .h file:
#property (nonatomic,strong) NSMutableArray *reportArray;
and allocated it in viewDidLoad:
reportArray=[[NSMutableArray alloc]init];
and loaded it in didRecieveData:
[reportArray addObject:obj[#"reason"]];
Stacktrace snapshots:
Try this:
reportArray = [[[NSMutableArray alloc] init] autorelease];
in your connectionDidFinishLoading: and connection:didFailWithError: methods set
reportArray = nil
and finally in Project > Build Phases > Compile Sources add -fno-objc-arc as compiler flag for this file (edited, sorry). Then click Product menu >Analyze (command + shift + B) again and check if the memory leak still occurs.
It is possible it is Apple's leak -- sure looks like it is coming from UIAlertView / UIAlertConnection. You might try implementing the alert using UIAlertConnection and see if it goes away -- Apple may not have tested the back-compatible UIAlertView implementation as much.
It won't show up in leaks, but be aware that NSURLConnection retains its delegate, and in your case you have your delegate retaining the NSURLConnection it looks like. That should be a retain loop if I'm not mistaken. Be sure to break it (nil out the delegate, or nil out the connection on your controller) when the connection finishes or fails.
I am performing JSON POST request by clicking UIButton and after the submission, segue perform can be done. So, After submitting values, I cannot perform POST request anymore. It shows status code 200 and response is OK. But, data is not reflected in the Backend. Here is my code:
(IBAction)transitsurveydone:(id)sender {
if([tempArray count]!=0){
/* alert= [[UIAlertView alloc] initWithTitle:#"Survey Submission"
message:#"Please Enter your location"
delegate:self
cancelButtonTitle:#"Modify"
otherButtonTitles:#"Submit",nil];
alert.alertViewStyle = UIAlertViewStylePlainTextInput;
alert.tag=2;
[alert show];*/
NSLog(#"Caption array is %# %#",captionArray,tempArray);
NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:#"myURL"]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
NSMutableDictionary *postDict = [[NSMutableDictionary alloc]init];
NSMutableDictionary *d=[[NSMutableDictionary alloc] initWithObjectsAndKeys:#10,#"\"Bus\"", nil];
NSMutableArray *m=[[NSMutableArray alloc] init];
NSString *str1,*str2,*str3,*str4;
// Checking the format
if(tempArray.count==1){
for (int x=0; x<1; x++) {
str1=[NSString stringWithFormat:#"\"%#\"",[captionArray objectAtIndex:x]];
[d setObject:[tempArray objectAtIndex:x] forKey:str1];
}
[request setHTTPBody:[[NSString stringWithFormat: #"{\n \"instance\" : %#,\n \"response_method\": \"web\",\n \"routes\": [\n {%#:%#}\n ]\n}",randomString,str1,[d objectForKey:str1] ]dataUsingEncoding:NSUTF8StringEncoding]];
}
NSLog(#"%#:%#",str1,[d objectForKey:str1]);
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
// Handle error...
return;
}
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSLog(#"Response HTTP Status code: %ld\n", (long)[(NSHTTPURLResponse *)response statusCode]);
NSLog(#"Response HTTP Headers:\n%#\n", [(NSHTTPURLResponse *)response allHeaderFields]);
}
NSString* body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Response Body:\n%#\n", body);
}];
[task resume];
NSHTTPURLResponse *response = nil;
NSError *error = nil;
NSData *respData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"~~~~~ Status code: %d", [response statusCode]);
if([response statusCode]==200){
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:#"Transit Survey submitted" delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
alert.transform = CGAffineTransformMakeTranslation( 10, 740);
[alert show];
[self performSelector:#selector(dismissAlert:) withObject:alert afterDelay:1.0f];
submitteddonthide=NO;
}
else{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:#"Transit Survey Submission Failed" delegate:self cancelButtonTitle:nil otherButtonTitles:nil];
alert.transform = CGAffineTransformMakeTranslation( 10, 740);
[alert show];
[self performSelector:#selector(dismissAlert:) withObject:alert afterDelay:1.0f];
}
if([prefs integerForKey:#"humandone"]==1){
[self performSegueWithIdentifier:#"transittohuman" sender:nil];
}
else{
[self performSegueWithIdentifier:#"gobackfromtransit" sender:nil];
}
}
`
The above code is in IBAction
Your code looks fine and there is no any issues.
If this issue is happens all the time, add "Advanced Rest Client" add-on to chrome browser and test your server URL passing respective input values. If this process also can't be able to update values on your backend there should be some issue on your backend.
A couple of thoughts:
You are using status code to determine whether the server inserted the data correctly or not. You should actually have your web service build an affirmative response (in JSON, would be great) that says whether data was successfully inserted or not. You should not be relying solely on the web server's request response code.
I would observe the request with something like Charles and make sure it looks OK.
You're building a JSON request manually, which is very fragile. I would suggest using Charles to observe the request, and copy and paste it into http://jsonlint.com and make sure it's OK.
Even better, use NSJSONSerialization which is more robust and easier to use.
Also, you're sending this request twice. Lose this second request (you shouldn't do synchronous requests, anyway) and put all of your confirmation logic inside the session's completion handler block.
Yes. After struggling a bit, Clearing cookies helped me a lot :-
Here is a chunk of code, which is pretty much simple
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSArray *cookies = [cookieStorage cookiesForURL:[NSURL URLWithString:urlString]];
for (NSHTTPCookie *cookie in cookies) {
NSLog(#"Deleting cookie for domain: %#", [cookie domain]);
[cookieStorage deleteCookie:cookie];
}
Thank you Mr. reddys and Rob for the suggestions
So just like the question suggests, I'm trying not to freeze up the UI after the user sends some data to the server. In my case, they're possibly sending a lot of data and server side I have to do a foreach loop for multiple queries.
While all that is happening, I don't want the user to wait for a response so I'm dismissing the modal VC after "Send" is clicked. The data still gets inserted into the database but what if there's an error? Right now I'm showing a UIAlertView after the modal VC is dismissed but I get a bad access error. What's the best way of showing an error?
- (void)send:(id)sender{
if ([[Data sharedInstance].someData objectForKey:#"time"] != NULL) {
[self dismissViewControllerAnimated:YES completion:^(){
NSMutableDictionary *paramDic = [NSMutableDictionary new];
[paramDic setValue:[[Data sharedInstance].someData objectForKey:#"oneArray"] forKeyPath:#"oneArray"];
[paramDic setValue:[NSArray arrayWithObjects:[[[Data sharedInstance].someData objectForKey:#"two"] valueForKey:#"Name"], [[[Data sharedInstance].someData objectForKey:#"two"] valueForKey:#"State"], [[[Data sharedInstance].someData objectForKey:#"two"] valueForKey:#"Country"], nil] forKeyPath:#"twoArray"];
[paramDic setValue:[[[Data sharedInstance].someData objectForKey:#"three"] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKeyPath:#"three"];
[paramDic setValue:[[NSUserDefaults standardUserDefaults] valueForKey:#"username"] forKey:#"username"];
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:paramDic options:NSJSONWritingPrettyPrinted error:nil];
NSURL *url = [NSURL URLWithString:#"http://localhost/myapp/handleData.php"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
NSString *length = [NSString stringWithFormat:#"%d", jsonData.length];
[request setValue:length forHTTPHeaderField:#"Content-Length"];
[request setValue:#"json" forHTTPHeaderField:#"Data-Type"];
[request setHTTPBody:jsonData];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
if (![httpResponse statusCode] == 200 || ![[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding] isEqualToString:#"success"]) {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Error" message:#"Problem on the server. Please try again later." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alert show];
}
}];
}];
}
That's how I'm doing it now...what's a better way?
The above answer is valuable. But I think this is what causes the problem.
Change:
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Error" message:#"Problem on the server. Please try again later." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
To:
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Error" message:#"Problem on the server. Please try again later." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
Hope this helps.
I'm thinking the issue has to do with your nested completion blocks. Because you place your web service call in the modal dismissal completion block, by the time it finishes and starts to execute your URL completion block, the class/controller in charge of that block has already been forgotten/destroyed/deallocated/whatever. At the very least, the view no longer exists where you're trying to present your alert view. You've listed self as the delegate of that alert view, but self doesn't exist anymore.
Try this first: instead of trying to display your alert on a no-longer-in-memory view from a background thread (big no-no even if the view still existed), try posting a notification to the main thread that you can pick up elsewhere in your app (such as your root view controller) and present the alert from there:
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
if (![httpResponse statusCode] == 200 || ![[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding] isEqualToString:#"success"]) {
dispatch_async(dispatch_get_main_queue(),^{
// observe this notification in root view controller or somewhere
// that you know will be in memory when it fires
[[NSNotificationCenter defaultCenter] postNotificationName:#"ErrorAlert"
object:nil
userInfo:imageDict];
});
}
}];
If that still doesn't work because the app has discarded your completion block when the view was dismissed, you will need to create a separate class that is in charge of sending this web service request instead of doing it all directly in the completion block. But still, remember to present your alert on the main thread :)
I have read all detail about stripe integration in iOS app from here. And run the sample app of stripe by downloading from here.
On running this app, I am getting following error when I am testing on iPhone Simulator.
Following method is calling after token received::
- (void)hasError:(NSError *)error
{
UIAlertView *message = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Error", #"Error")
message:[error localizedDescription]
delegate:nil
cancelButtonTitle:NSLocalizedString(#"OK", #"OK")
otherButtonTitles:nil];
[message show];
}
//--Called after sucessfully token received ::
- (void)hasToken:(STPToken *)token
{
NSLog(#"Received token %#", token.tokenId);
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"https://example.com"]];
request.HTTPMethod = #"POST";
NSString *body = [NSString stringWithFormat:#"stripeToken=%#", token.tokenId];
request.HTTPBody = [body dataUsingEncoding:NSUTF8StringEncoding];
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
[MBProgressHUD hideHUDForView:self.view animated:YES];
if (error) {
[self hasError:error];
} else {
NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"\nStr is :::%#",str);
NSLog(#"\n\nResponse is :::%#",response);
[self.navigationController popViewControllerAnimated:YES];
}
}];
}
Please tell me how to resolve this problem and make a credit card payment on iOS app because in this sample code, there is information about payment.
You are using example.com. Replace it first with your own server address.
For charging a card in Stripe you need a server module. Here is detail explanation of how to charge your card via server module. If you are using parse as backend you can always use cloudcode for this purpose.
Here is raywenderlich's explanation of making payment system with python backend.
I have searched for awhile and tried many things, but I cannot get this error to go away. I have a table that is refreshed by a pull down method. I also use Apple's Reachability to determine internet connectivity. When I run my application WITH internet, and then while the app is running, shut the internet off, my app crashes trying to display a UIAlertView. Although, when I start my app WITHOUT internet and then turn on internet while the app is running and turn it back off, the app does not crash and everything works as it should. Any help would be greatly appreciated.
The error occurs on the [message show] line.
Edited code:
- (void)loadCallList {
NSURL *theURL = [NSURL URLWithString:#"http://www.wccca.com/PITS/"];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:theURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10.0];
NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
if (theConnection) {
NSLog(#"Reachable");
self.headerHeight = 45.0;
NSData *data = [[NSData alloc] initWithContentsOfURL:theURL];
xpathParser = [[TFHpple alloc] initWithHTMLData:data];
NSArray *elements = [xpathParser searchWithXPathQuery:#"//input[#id='hidXMLID']//#value"];
if (elements.count >= 1) {
TFHppleElement *element = [elements objectAtIndex:0];
TFHppleElement *child = [element.children objectAtIndex:0];
NSString *idValue = [child content];
NSString *idwithxml = [idValue stringByAppendingFormat:#".xml"];
NSString *url = #"http://www.wccca.com/PITS/xml/fire_data_";
NSString *finalurl = [url stringByAppendingString:idwithxml];
xmlParser = [[XMLParser alloc] loadXMLByURL:finalurl];
[callsTableView reloadData];
}
}
else {
NSLog(#"Not Reachable");
self.headerHeight = 0.0;
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"No Internet Connection"
message:#"Please check your internet connection and pull down to refresh."
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[message show];
[message release];
}
[pull finishedLoading];
}
I tested the following method using sendAsynchronousRequest:queue:completionHandler: as the method to download data from the URL. It seems to work fine, I can get the else clause to fire, either by messing up the URL or making the timeout interval .1 seconds.
NSURL *theURL = [NSURL URLWithString:#"http://www.wccca.com/PITS/"];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:theURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:5];
[NSURLConnection sendAsynchronousRequest:theRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error == nil) {
//do whatever with the data. I converted it to a string for testing purposes.
NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#",text);
}else{
NSLog(#"%#",error.localizedDescription);
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"No Internet Connection" message:#"Please check your internet connection and pull down to refresh." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message show];
}
}];
You have two problems:
You shouldn't be using Reachability to determine whether there is an internet connection. Reachability is only useful to determine when an offline connection comes back online. Really. Making the networking calls is sometimes enough to power up the radios and connect so if you try to pre-check whether there is an internet connection the radios will remain off.
The second problem is that no synchronous networking calls can be made on the main thread. This includes Reachability and [NSData dataWithContentsOfURL:xxx]. Make them on a secondary thread using Grand Central Dispatch, NSOperationQueues, NSURLConnections, or NSThreads. Otherwise your application can lock up if the network isn't in good shape and the system watchdog timer will kill your app.