I have a view controller with a three segmented button at the top, and below it a table view. When the user clicks a button, a URL is called, and the appropriate data is loaded into the cells. Then, the appropriate images are loaded in the background and the tableview is updated. The issue lies when the user clicks a different button AND the images from the last set of data haven't finished loading. Initially I prevented the user from clicking a different button, until all of the first data was done loading. But this was really slow. Then I tried waiting until all the images were done being loaded before updating the tableview, but that is also pretty slow. Is there a better way of doing this besides making three tableviews? Below is how I get images.
- (void) getImageLinks:(int)number{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[imageLinks objectAtIndex:number]];
[request setAllHTTPHeaderFields:#{#"User-Agent": #"Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25"}];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if([data length] > 0){
UIImage *image = [UIImage imageWithData:data];
if(image){
[images addObject:image];
dispatch_async(dispatch_get_main_queue(), ^{
if(number<[imageLinks count]-1){
[self getImageLinks:number+1];
}else{
isRefreshing = NO;
_segmentedControl.userInteractionEnabled = YES;
NSLog(#"end refreshing");
[_tableView reloadData];
[refreshControl endRefreshing];
}
});
}else{
NSLog(#"NO IMAGE");
}
}
}];
You haven't used background Queue for Requesting data. Just Call the request to the URL in Background queue and save the Image data. Then You can Synchronously call the Image to update to ImageView in Main queue using dispatch_sync() method.
Related
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();
});}];
i have a UITableView which shows (doctor image , doctor Name)
In the cellForRowAtIndexPath function iam fetching the images as follow:
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
dispatch_async(dispatch_get_main_queue(), ^{
updateCell.DRImage.image = [[UIImage alloc] initWithData:data];
}];
The Problem is: when i'm scrolling down fast , the images start loading , and almost 100 images being loaded , then when i click on row to show the doctor details , i can't get the data from the server until all the images finish loading (until all async requests finish).
i'm using the following code to get the doctor details:
SoapCall *Obj = [[SoapCall alloc] init];
NSString* Url=[[NSString alloc] initWithFormat:#"%#/doctorsDetails.php?i=%#" , ServerUrl ,DoctorDataFromTableView.Doctor_ID];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
[Obj CallUrl:Url OnCompletion:^(NSMutableArray * Results , NSError * Error){
dispatch_async(dispatch_get_main_queue(), ^{
Nviews.text =[[Results objectAtIndex:0] objectForKey:#"numViews"];
NComments.text =[[Results objectAtIndex:0] objectForKey:#"totComments"];
});
}];
Note: SoapCall is a custom class i made to make an Async request to the server
my question is :
Is there is any way to cancel loading the images???
Or can i load the Doctor details in different thread ???
i found a great project which is "SDWebImage"
https://github.com/rs/SDWebImage
it's using NSoperationQueue and NSOperation in a very advanced way
This project saved a lot of time for me
I have a sample application with a UITableViewController.
As in the facebook newsfeed, the app is supposed to download a first time X news, and then fetch news progressively as the user scroll.
Here is my implementation :
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
if (indexPath.row == self.newsList.count-PADDLE_BEFORE_FETCHING && !cantFetchMore)
if (!fetching){
fetching = YES;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[self fetchNews];
});
}
}
(the idea is to start fetching additional news when we reach the N-PADDLE_BEFORE_FETCHING cell, only if we can still fetch some - see below- and if fetching is still not currently running)
and then the implementation of fetchNews :
-(void)fetchNews{
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *url = [NSString stringWithFormat:#"%#%#%#%#%d%#",HOSTNAME,GET_NEWS,[defaults objectForKey:#"oAuthToken"],#"&limit=",FETCH_SIZE_NEWS,[NSString stringWithFormat:#"&offset=%d",self.newsList.count]];
NSURLRequest *request =[[NSURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
#if DEVELOPMENT_MODE
NSLog(#"News : %#",JSON);
NSLog(#"Response : %#\n Request : %#",response,request);
#endif
//NSLog(#"Number of news fetched : %d",((NSArray*)JSON[#"data"]).count);
for (NSDictionary *d in JSON[#"data"]){
News *new = [[News alloc] initWithDictionary:d];
[self.newsList addObject:new];
new = nil;
}
if ((((NSArray*)JSON[#"data"]).count)%FETCH_SIZE_NEWS !=0) cantFetchMore = YES;
//NSLog(#"%d cantFetch",cantFetchMore);
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[self.tableView reloadData];
fetching = NO;
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
NSLog(#"Request error : %# %# %#",request,error, JSON);
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
fetching = NO;
}];
[operation start];
}
This will fetch FETCH_SIZE_NEWS additional news from the server starting at the good offset which is the current size of the newsList array.
Also, if the count of fetched news % FETCH_SIZE_NEWS is different from 0, that means that we cannot fetch additional news (which will prevent from calling the webservice while scrolling the UITableView).
My issue is that when the fetching is done (exactly when I see the activity wheel running in the status bar), it blocks the GUI, and I cant continue to scroll down from the n-PADDLE_BEFORE_FETCHING cells to the n cells, or even scroll up to the previously loaded cells.
I don't really understand why as AFNetworking is supposed to run asynchronously.
Any ideas?
Thanks,
The for-loop in the completion block is running on the main thread and may be causing the slow-down. Try sending that code to another thread/queue.
As stated above buy Guy Kogus, the processing within the complete block of the AFNetworking operation was adding a bit a freezer on the main thread while scrolling.
Just added
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{//processing block}
Within the complete block (notably the foreach loop) and this is far better.
Thanks
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'm using UIImageView+AFNetworking:setImageWithURLRequest to load images into table view cells. It's working great and I tried using AFNetworkActivityIndicatorManager as in the example below to display the activity indicator while the images are loading.
[[AFNetworkActivityIndicatorManager sharedManager] incrementActivityCount];
[imageView setImageWithURLRequest:request placeholderImage:[UIImage imageNamed:placeholder]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
[[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
}
];
The problem I've run into is when scrolling quickly through the table view AFNetworking cancels certain requests and the AFNetworkActivityIndicatorManager never gets decremented. Canceling requests does keep things fast and appears to be by design:
"As table view cells are recycled, for instance, setting a new image URL will automatically cancel the previous request." https://github.com/AFNetworking/AFNetworking/wiki/Introduction-to-AFNetworking
Is there a way to detect the cancellation so I can decrement the AFNetworkActivityIndicatorManager or is there a better way to make sure the requests and activity indicator stay in sync?
Thanks!
You shouldn't need to do this all you need to do is call:
AFNetworkActivityIndicatorManager.sharedManager.enabled = YES;
in you AppDelegate application:didFinishLaunchingWithOptions: and then AFNetworking looks after hiding and showing the indicator as and when network connections start and stop.