Since 2 yrs I have been trying different ways to find the solution of app crash while click back button.
My application scenario:
In a tableview contoller I have to load list of users, On view did load I call getData(Asyncronous download) API method to load data. At the time of data download, If user press back button my application gets crash due to null value objects. That says all of my variable memory deallocated.
To overcome this problem, I used some loading indicator which lock UIScreen untill data download.
Questions:
Is there any alternatives to prevent crash, UIScreen Lock
Other applications use Activity Indicator in Menu bar without UIScreen Lock. How they are doing?
Need help to recover this issue
Here is my sample code to download data :
Below code doesnt crash app. But it download data even I cancel operations on dealloc
viewDidLoad:
ShowNetworkActivityIndicator();
_processQueue = [[NSOperationQueue alloc] init];
_processQueue.maxConcurrentOperationCount = 4;
_processQueue.name = #"Events Processing";
[self loadData];
loadData:
-(void)loadData
{
[_processQueue addOperationWithBlock: ^ {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[[NSURL alloc] initWithString:#"https://restcountries.eu/rest/v1/all"]];
NSURLResponse *response;
NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
NSString *data=[[NSString alloc]initWithData:urlData encoding:NSUTF8StringEncoding];
NSDictionary *search = [NSJSONSerialization JSONObjectWithData:[data dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil];
[[NSOperationQueue mainQueue] addOperationWithBlock: ^ {
_countryListArray=[search mutableCopy];
[self.tableViewSample reloadData];
HideNetworkActivityIndicator();
}];
}];
}
I tried cancelAllOperations in dealloc:
[_processQueue setSuspended:YES];
[_processQueue cancelAllOperations];
Can you try inserting the reload data is dispatch_async(dispatch_get_main(),void (^){}); callback , main thread , I think the reload happening in the background thread is crashing the app.
[[NSOperationQueue mainQueue] addOperationWithBlock: ^ { _countryListArray=[search mutableCopy]; dispatch_async(dispatch_get_main_queue(), ^{
[self.tableViewSample reloadData];
HideNetworkActivityIndicator();
});}];
Related
I am learning How to parse JSON and load it in tableview while my internet is working the data is get parsed and loaded in table correctly but when i turn off my internet and refresh my table view my UI get stuck on same screen with previous JSON data and crashes after some time
following is how i implemented it..
on my table view controller i define this method which is getting called on refresh button click.
- (IBAction) reloadJasonData:(id)sender
{
NSMutableArray * jsonArray= [DownloadJsonData getJsonArray];
if(! jsonArray)
{
UIAlertView * errorAlert = [[UIAlertView alloc]initWithTitle:#"Error!!" message:#" Please Check the Internet connection" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[errorAlert show];
}
else
{
NSSortDescriptor * sortDescripter = [[NSSortDescriptor alloc]initWithKey:#"location" ascending:YES];
NSArray * discriptorArray = #[sortDescripter];
self.sortedJsonArray = [jsonArray sortedArrayUsingDescriptors:discriptorArray];
self.arrayOfLinks = [self.sortedJsonArray valueForKey:#"link"];
self.arrayOfLocations = [self.sortedJsonArray valueForKey:#"location"];
self.arrayOfDates = [self.sortedJsonArray valueForKey:#"date_time"];
NSLog(#"Count of cities in fetch data: %d",[self.sortedJsonArray count]);
[self.tableView reloadData];
}
}
in other class i have define class method to download JSON data
+(NSMutableArray *) getJsonArray
{
NSError * error;
NSData * data = [NSData dataWithContentsOfURL:httpURL];
NSMutableArray * json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
return json;
}
the error message shows only when my internet connection is not active and i click on refresh button, but once the data is displayed into table and i shout down my internet and click on refresh button again my UI get stuck..
I hope i have explained my problem in well manner so can any body help me to figure it out where did i messed it up. Thanks in advance.
I think you are accessing your data from internet using main thread that's why your UI get stuck. You should access any remote data using secondary thread.
Write this code inside your getJsonArray class
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:httpURL];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (!error && data!=nil)
//call a delegate to send the data to your page from where it was called
else
//show alert that an error has occurred during downloading data from net.
}];
Then use delegate to tell the main page that the data has been downloaded and then there on delegate method reload your tableView in main thread.
I'm using synchronous requests for the first time and would love some help. (The code I'm writing is solely for my own use, and given its purposes synchronous requests are not a problem.)
The code gets data from a web page in a series, manipulates the data, moves on to the next page in the series, manipulates THAT data, and so on. I'm using a synchronous request because I need the connection to finish loading and the data to be manipulated before the function loops to the next page.
Here's my looping code:
-(NSData *)myMethod {
NSString *string;
NSData *data;
for (int x = 1; x<100; x++) {
string = [NSString stringWithFormat:#"http://www.blahblah.com/%d",(x)];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:string]];
NSURLResponse *response = nil;
NSError *error = nil;
data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
}
return data;
}
When I was using connectionWithRequest, I just put the code to manipulate the data in connectionDidFinishLoading and it worked fine. But with sendSynchronousRequest, even though NSLog shows that the loop code is looping, the code in connectionDidFinishLoading never runs.
How can I fix this?
(Or am I taking the wrong approach completely?)
Here's how to take #nhgrif's good advice to perform asynch and preserve all of the results.
- (void)doRequest:(NSInteger)requestIndex gatheringResultsIn:(NSMutableArray *)array completion:(void (^)(void))completion {
if (requestIndex < 100) {
NSString *string = [NSString stringWithFormat:#"http://www.blahblah.com/%d",(requestIndex)];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:string]];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (data) [array addObject:data];
[self doRequest:requestIndex+1 gatheringResultsIn:array completion: completion];
}];
} else {
completion();
}
}
This will run 100 requests indexed 0..99 placing the results in a mutable array. Call it like this:
NSMutableArray *results = [NSMutableArray array];
[self doRequest:0 gatheringResultsIn:results completion:^{
NSLog(#"100 NSData objects should be here: %#", results);
}];
connectionDidFinishLoading is an NSURLConnection delegate method for when you've sent asynchronous requests. Normally, you'd implement this method to get the data that loaded, but you don't need to do this, as it's returned synchronously and assigned to your data variable.
I will note however, you are definitely taking a poor approach here.
First of all, if you'd use asynchronous requests here, you could query all 100 URLs as basically the same time and let them return in their own time.
But what's more problematic is what actually happens with your code.
We create a URL, send the synchronous request, and when it finishes, assign the return to data.
... then we loop. And do this 99 times. 99 times we make this synchronous request (to a different URL each time) and overwrite the data that the previous request loaded. And after the 100th time, we exit the loop and return the data we downloaded in the final request.
Hi My problem is that i am getting a response from a web service and when i parse it and add to table and reload the table view it is not refreshing . Although if i call the [table reload] in keyboard is shown it gets updated there. Could someone tell me if im missing anything
This is what i am trying to do
- (void) longPoll {
//create an autorelease pool for the thread
#autoreleasepool {
NSLog(#"polling");
VSAppDelegate *var = (VSAppDelegate*)[[UIApplication sharedApplication] delegate];
//compose the request
NSError* error = nil;
NSHTTPURLResponse* response = nil;
//send the request (will block until a response comes back)
NSData* responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"polling response is %d",response.statusCode);
//pass the response on to the handler (can also check for errors here, if you want)
[self performSelectorOnMainThread:#selector(dataReceived:) withObject:responseData waitUntilDone:YES];
}
[self performSelectorInBackground:#selector(longPoll) withObject: nil];
}
- (void) startPoll {
[self performSelectorInBackground:#selector(longPoll) withObject: nil];
}
- (void) dataReceived: (NSData*) theData
{
//process the response here
NSError *error = nil;
NSLog(#"polling data is %#",[[NSString alloc] initWithData:theData encoding:NSUTF8StringEncoding]);
NSLog(#"polling data is %#",[[theData base64EncodedString]base64DecodedString]);
NSDictionary *notifDic= [NSJSONSerialization JSONObjectWithData:theData options:kNilOptions error:&error];
//VSViewControllerSplit *split = [[VSViewControllerSplit alloc]init];
[self RecieveFunction:notifDic];
}
try it
dispatch_async(dispatch_get_main_queue(), ^{
[tablrView reloaddata];
});
The dataReceived method doesn't appear to be calling reloadData. I'll assume that RecieveFunction method does, though, but you should confirm that. It's hard to say without seeing RecieveFunction.
The more fundamental issue would appear to be that dataReceived method is creating a new instance of VSViewControllerSplit, calling its RecieveFunction method, and then letting this new VSViewControllerSplit instance fall out of scope (and if using ARC, get deallocated unless you pushed to it, presented it, etc.). You presumably don't want to create a new VSViewControllerSplit every time longPoll calls dataReceived, but rather just reference the existing instance.
I have an application that retrieves json (employees workschedules) from a web service using AFNetworking and displays them in a table view.
I have my webservice class that takes care of doing the request and once it is done, it stores these data into coredata (I have an another issue here, being that I use magicalRecord and the data does not persist, and I don't understand why) and then calls back its delegate (my tableViewController) telling it it's done, so this can load the workschedules into the cells.
WebServiceClient.m
NSURL *url = [NSURL URLWithString:stringUrl];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
{
NSArray *workSchedules = [[[NSSet alloc] initWithArray:JSON] allObjects];
NSManagedObjectContext *context = [NSManagedObjectContext MR_contextForCurrentThread];
Workschedule *workscheduleEntity = nil;
NSError *error = nil;
for (NSDictionary *web_workschedule in workSchedules)
{//Inside this method I create other entities that will hydrate my workschedule entity, and it is done using the MR_CreateInContext
workscheduleEntity = [Workschedule workScheduleFromJSONDictionary:web_workschedule withError:&error];
[context MR_save];
}
if([self.delegate respondsToSelector:#selector(workSchedules)]){
[self.delegate workSchedules];
}
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
LOG_ERROR(2,#"Received an HTTTP %d:", response.statusCode);
LOG_ERROR(2,#"The error was: %#", error);
if([self.delegate respondsToSelector:#selector(workSchedules:)]){
[self.delegate workSchedules:nil];//return error
}}];
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
[operationQueue addOperation:operation];
}
PendingWorkscheduleViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
[self.webServiceClient getMockedWorkSchedulesForEmployee:[NSNumber numberWithInt:1]];
[self workSchedules];
}
-(void)workSchedules
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"pending == YES"];
NSArray *pendingWorkSchedules = [Workschedule MR_findAllWithPredicate:predicate];
self.pendingWorkSchedules = pendingWorkSchedules;
[self.tableView reloadData];
}
My problem is that when i run this while the request is processed the UI is unresponsive (it's a very brief time, but if the request were to increase...) so that if i load the table view and right away try to scroll or click the back button, it just ignores it as it is "frozen". This behavior is on my iphone 4s. On the simulator this works fine and I can't wrap my head around why is that. I tried to call the "[self.webServiceClient getMockedWorkSchedulesForEmployee:[NSNumber numberWithInt:1]];" in a queue using GCD, I tried using performSelectorInBackground: WithObject: etc but still the same (even though with this last method it seemed a little more efficient, but it's an impression and only on the simulator, no changes on the device).
As far as magicalRecord goes I will make separate question.
I would appreciate your help.
Fixed it. The problem is that the success block run on the main thread! (which I did not understand). I just used GCD in the success block with a background queue for processing the data and the main queue to store this data in core data.
As far as magical record issue, i needed to save "nestedContext".
Cheers everyone.
I am developing an iPhone app that gathers data from 3 separate feeds. In applicationDidFinishLaunching and applicationWillEnterForeground and Do the following:
[self emptySchedule];
[self populateSchedule];
[self emptyPlayers];
[self populatePlayers];
[self emptyNews];
[self populateNews];
The empty methods simply remove info from core data, and the populate methods add info back to core data by calling various web json/xml feeds. It seems to do this very fast; but was wondering if this is the preferred method for keeping information up to date in the app.
EDIT:
Just to give some context, here are a couple methods used for empty/populate:
Since this is mostly asynchronous will it affect application launch time?
- (void) emptySchedule
{
NSFetchRequest * allEvents = [[NSFetchRequest alloc] init];
[allEvents setEntity:[NSEntityDescription entityForName:#"Event" inManagedObjectContext:self.managedObjectContext]];
[allEvents setIncludesPropertyValues:NO]; //only fetch the managedObjectID
NSError * error = nil;
NSArray * events = [self.managedObjectContext executeFetchRequest:allEvents error:&error];
//error handling goes here
for (NSManagedObject * event in events) {
[self.managedObjectContext deleteObject:event];
}
NSError *saveError = nil;
[self.managedObjectContext save:&saveError];
}
-(void)populateSchedule
{
NSURL *url = [NSURL URLWithString:SCHEDULE_FEED_URL];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id schedule)
{
for (NSDictionary *campEvent in schedule)
{
Event *event = nil;
event = [NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:self.managedObjectContext];
event.eventName = [campEvent valueForKeyPath:#"eventName"];
event.ticketsRequired = [campEvent valueForKeyPath:#"ticketsRequired"];
event.location = [campEvent valueForKeyPath:#"location"];
event.practiceStart = [NSDate dateWithTimeIntervalSince1970:[[campEvent valueForKeyPath:#"practiceStart"] doubleValue]];
event.practiceEnd = [NSDate dateWithTimeIntervalSince1970:[[campEvent valueForKeyPath:#"practiceEnd"] doubleValue]];
}
NSError *saveError = nil;
//Save inserts
[self.managedObjectContext save:&saveError];
//Notify other objects of this
[[NSNotificationCenter defaultCenter] postNotificationName:#"populateSchedule" object:nil];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Error" message:#"Error Retrieving Data. Please try again later." delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alert show];
}];
[operation start];
}
I'll try to answer based on my personal experience. Maybe someone else could have a different opinion on it.
In your case the syncing is performed only in phases of the application lifecycle.
So, I would add a third. When the user asks it. But it strictly depends on the nature of your application. Another way could be to set up a background thread that periodically wakes up and asks the server to send it new data. This could be more complex than the manually syncing.
About these two ways I would perfom import operation in specific background threads. You could set up your own operations (NSOperation class is there also for this type of task) and do stuff there or use new iOS 5 Queue Core Data API.
If you haven't done already (the background importing) do it also for your actual methods (I think in this case you could just unify the pairs empty/populate). This mechanism allows you to boost application startup and say to the user what is going on without freeze the UI:"Hello, I'm fetching data from the server! Please wait".
Edit
About the code you added it's ok for me. Only two considerations.
First, if the deletion is performed in the main thread, it could block the main thread if you have a lot of entries to remove. In this case, the UI could be not responsive. Anyway you have done a good job setting setIncludesPropertyValues to NO.
About the other snippet, I guess only the data download is perfomed in an asynchronous fashion. The completion handler is performed in the main thread (you can check for example with BOOL isMainThread = [NSThread isMainThread]) and so the core data object creation and its relative saving. Also in this case if you have a lot of data the main thread could be blocked.
Anyway, if you have done some tests and the app doesn't take too long to startup, you could just remain with your code. If you start to see some sort of latency, maybe you could do Core Data operations in backgrounds.
With no iOS 5 API the save call could (I say could since you can save chuncks of data and not the whole one) take time to be performed (in particular when you have a lot of objects to store in your core data file). Starting form iOS 5 you could take advantage of new type of NSManagedObjectContext (queue concurrency type) and parent-child context. Furthermore you can avoid to write the entire Core Data stack and use the UIManagedDocument class. By means of both the save can be performed in a concurrent queue without blocking the main thread.
Hope that helps.