HTTP request in For loop, Low Memory and Crashed - ios

I need to send a large amount of HTTP request in a for loop, and once I finish a task, I need to update progress on main thread.
float __block progress = 0.0f;
float __block tempValue = 0.0f;
dispatch_queue_t operationQueue = dispatch_queue_create("Operation Data", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int uploadCount = 0; uploadCount < mutableArray.count; uploadCount++) {
dispatch_barrier_async(operationQueue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
DDLogInfo(#"add Task.");
[MPRestAPI copyMediaToAlbum:mutableArray[uploadCount] targetAlbum:albumName success:^(BOOL bSuccess){
if (bSuccess) {
dispatch_async(dispatch_get_main_queue(), ^{
progress = (float)(uploadCount+1)/(float)mutableArray.count;
if(uploadCount+1 == mutableArray.count){
progressHUD.progress = 1.0;
[progressHUD hide:YES];
[self dismissViewControllerAnimated:NO completion:nil];
}else if(progress > tempValue) {
progressHUD.progress = progress;
tempValue = progress + 0.01;
}
});
}
}failure:^(NSError* error){
if(uploadCount == mutableArray.count -1){
[progressHUD hide:YES];
[self dismissViewControllerAnimated:NO completion:nil];
}
}];
});
dispatch_semaphore_signal(semaphore);
}
Then there comes low memory, and it crashed. and mutableArray.count can be 5000,so I wonder what should I do to fix this problem?

Related

For loop with ASync and then wait for it to complete (Obj-C)

So let's assume I have the following code
-(void)doSomething{
[self expensiveMethod];
[self otherMethod]; //Depends on above method to have finished
}
-(void)expensiveMethod{
for(int i = 0; i<[someArray count]; i++{
[self costlyOperation:someArray[i]];
}
}
Ideally I want [self costlyOperation] to spin off other threads so that each one is done as close to parallel (of course I realize this isn't exactly possible). Once the [self costlyOperation] has been done on each array object I'd like it to return so that [self otherMethod can take advantage of the processing.
You can use default queue to run tasks in the background using dispatch async.
EDIT
You can group async tasks for parallel asyc execution. You might have to tweak it a bit as per your requirement.
-(void)doSomething{
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
[self expensiveMethod:^{
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates
[self otherMethod]; //Depends on above method to have finished
});
}];
});
}
-(void)expensiveMethod:(void (^)(void))callbackBlock {
dispatch_group_t group = dispatch_group_create();
for(int i = 0; i<[someArray count]; i++) {
__block int index = i;
dispatch_group_async(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
[self costlyOperation:someArray[index]];
});
}
dispatch_group_notify(group,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^ {
if (callbackBlock) {
callbackBlock();
}
});
}

Problems on updating UI from async callback

I have the following code :
#property (weak, nonatomic) IBOutlet UIView *loadingView;
-(void) hideLoadingScreen{
self.loadingViewRect = self.loadingView.frame;
[self.loadingView setHidden:YES];
[self.loadingView setFrame:CGRectMake(0,0,0,0)];
}
- (void)viewDidAppear:(BOOL)animated{
[self.apiClient checkLoggedInWithCallback:^(int status){
if(status == ONLINE_LOGGED_IN){
[self.dbAPI isPullSynchronized:^(BOOL isPullSynced){
if(isPullSynced){
self.data = [self.dbAPI getTodayVisits];
[[self tableView] reloadData];
[self hideLoadingScreen];
} else {
[self.apiClient getTodayVisits:^(NSArray* visits){
[self.dbAPI insertTodayVisits:visits withCallback:^(int status){
self.data = [self.dbAPI getTodayVisits];
[[self tableView] reloadData];
}];
}];
}
}];
} else if(status == ONLINE_NOT_LOGGED_IN || status == ONLINE_INVALID_TOKEN || status == OFFLINE_NOT_LOGGED_IN) {
//Redirect to login
} else {
//Get from Local DB
self.data = [self.dbAPI getTodayVisits];
[[self tableView] reloadData];
}
}];
}
The hideLoadingScreen method won't execute (I mean it gets executed, but the UI doesn't update).
I tried all things to make it work (including dispatching [self hideLoadingScreen] to the main thread via GCD and performSelectorOnMainThread / making a __block BOOL variable isLoading and sleeping on the main thread till that variable was changed etc.). I also called the hideLoadingView method on the viewDidAppear method and it works, but I want it to hide when the callback is executed. Unfortunately, I couldn't find a solution by searching on stackoverflow, neither on google (I must say I tried all the solutions I found).
L.E. I logged self.loadingView as Rob Napier suggested
New code:
-(void) hideLoadingScreen{
self.loadingViewRect = self.loadingView.frame;
NSLog(#"hideLoadingScreen before: %#",self.loadingView);
[self.loadingView setHidden:YES];
[self.loadingView setFrame:CGRectMake(0,0,0,0)];
NSLog(#"hideLoadingScreen after: %#",self.loadingView);
}
- (void)viewDidAppear:(BOOL)animated{
NSLog(#"%#",self.loadingView);
[self.apiClient checkLoggedInWithCallback:^(int status){
if(status == ONLINE_LOGGED_IN){
[self.dbAPI isPullSynchronized:^(BOOL isPullSynced){
if(isPullSynced){
dispatch_async(dispatch_get_main_queue(), ^{
self.data = [self.dbAPI getTodayVisits];
[[self tableView] reloadData];
NSLog(#"async before: %#",self.loadingView);
[self hideLoadingScreen];
NSLog(#"async after: %#",self.loadingView);
});
} else {
[self.apiClient getTodayVisits:^(NSArray* visits){
[self.dbAPI insertTodayVisits:visits withCallback:^(int status){
self.data = [self.dbAPI getTodayVisits];
[[self tableView] reloadData];
}];
}];
}
}];
} else if(status == ONLINE_NOT_LOGGED_IN || status == ONLINE_INVALID_TOKEN || status == OFFLINE_NOT_LOGGED_IN) {
//Redirect to login
} else {
//Get from Local DB
self.data = [self.dbAPI getTodayVisits];
[[self tableView] reloadData];
}
}];
}
Logs:
2016-01-08 16:22:25.973 sunwaves.reporting[4566:282042] async before: <UIView: 0x7fa681e745d0; frame = (0 0; 1024 650); autoresize = RM+H+BM; layer = <CALayer: 0x7fa681e291c0>>
2016-01-08 16:22:25.973 sunwaves.reporting[4566:282042] hideLoadingScreen before: <UIView: 0x7fa681e745d0; frame = (0 0; 1024 650); autoresize = RM+H+BM; layer = <CALayer: 0x7fa681e291c0>>
2016-01-08 16:22:25.974 sunwaves.reporting[4566:282042] hideLoadingScreen after: <UIView: 0x7fa681e745d0; frame = (0 0; 0 0); hidden = YES; autoresize = RM+H+BM; layer = <CALayer: 0x7fa681e291c0>>
2016-01-08 16:22:25.974 sunwaves.reporting[4566:282042] async after: <UIView: 0x7fa681e745d0; frame = (0 0; 0 0); hidden = YES; autoresize = RM+H+BM; layer = <CALayer: 0x7fa681e291c0>>
The majority of UIKit calls must be made on the main queue. You should use dispatch_async(dispatch_get_main_queue(),... to do this.
This includes your calls to reloadData at a minimum.
You assignments to self.data are also likely not thread-safe (unless you've done something special in the setter). So those need to be on the main queue.
And of course your calls to hideLoadingScreen.
I assume that most of these blocks execute off the main queue, so that means putting in dispatch_async in several places.
- (void)viewDidAppear:(BOOL)animated{
[self.apiClient checkLoggedInWithCallback:^(int status){
if(status == ONLINE_LOGGED_IN){
[self.dbAPI isPullSynchronized:^(BOOL isPullSynced){
if(isPullSynced){
dispatch_async(dispatch_get_main_queue(), ^{
self.data = [self.dbAPI getTodayVisits];
[[self tableView] reloadData];
[self hideLoadingScreen];
});
} else {
[self.apiClient getTodayVisits:^(NSArray* visits){
[self.dbAPI insertTodayVisits:visits withCallback:^(int status){
dispatch_async(dispatch_get_main_queue(), ^{
self.data = [self.dbAPI getTodayVisits];
[[self tableView] reloadData];
});
}];
}];
}
}];
} else if(status == ONLINE_NOT_LOGGED_IN || status == ONLINE_INVALID_TOKEN || status == OFFLINE_NOT_LOGGED_IN) {
//Redirect to login
} else {
//Get from Local DB
dispatch_async(dispatch_get_main_queue(), ^{
self.data = [self.dbAPI getTodayVisits];
[[self tableView] reloadData];
});
}
}];
}

Multiple serial queues, UI not updating

I'm trying to run some intensive processes serially, with multiple serial queues. The code is working, however my UI update doesn't occur, even though the method is called.
Here is the code that runs several processes serially.
- (void)importProcess {
dispatch_queue_t serialQueue = dispatch_queue_create("com.cyt.importprocessqueue", DISPATCH_QUEUE_SERIAL);
NSLog(#"checking image sizes");
__block NSMutableArray *assets;
dispatch_sync(serialQueue, ^() {
assets = [self checkImageSizes];
});
dispatch_sync(serialQueue, ^() {
[self appendLogToTextView:[NSString stringWithFormat:#"%i screenshot(s) ignored due to invalid size.",(int)(self.assets.count-assets.count)]];
if(assets.count==0) {
[self showNoRunesFoundAlert];
}
else {
[self appendLogToTextView:#"Preparing to process screenshots..."];
self.assets = [NSArray arrayWithArray:assets];
}
});
NSLog(#"processing uploads");
dispatch_sync(serialQueue, ^() {
[self processUploads];
});
NSLog(#"recognizing images");
dispatch_sync(serialQueue, ^() {
[self recognizeImages];
});
NSLog(#"recognizing images");
dispatch_sync(serialQueue, ^() {
[self processRuneText];
});
//dispatch_sync(dispatch_get_main_queue(), ^ {
//});
}
Within checkImageSizes, I have another serial queue:
- (NSMutableArray *)checkImageSizes {
dispatch_queue_t serialQueue = dispatch_queue_create("com.cyt.checkimagesizequeue", DISPATCH_QUEUE_SERIAL);
NSMutableArray *assets = [NSMutableArray new];
for(int i=0;i<self.assets.count;i++) {
dispatch_sync(serialQueue, ^{
PHAsset *asset = (PHAsset *)self.assets[i];
if(asset.pixelWidth==self.screenSize.width && asset.pixelHeight==self.screenSize.height) {
[assets addObject:asset];
NSString *logText = [NSString stringWithFormat:#"Screenshot %i/%i size is OK.",i+1,(int)self.assets.count];
[self performSelectorOnMainThread:#selector(appendLogToTextView:) withObject:logText waitUntilDone:YES];
}
else {
[self appendLogToTextView:[NSString stringWithFormat:#"ERROR: Screenshot %i/%i has invalid size. Skipping...",i+1,(int)self.assets.count]];
}
});
}
return assets;
}
The appendLogToTextView method is supposed to update the UI. Here is that code:
- (void)appendLogToTextView:(NSString *)newText {
dispatch_block_t block = ^ {
self.logText = [NSString stringWithFormat:#"%#\n%#", self.logText, newText];
NSString *textViewText = [self.logText substringFromIndex:1];
[UIView setAnimationsEnabled:NO];
if(IOS9) {
[self.textView scrollRangeToVisible:NSMakeRange(0,[self.textView.text length])];
self.textView.scrollEnabled = NO;
self.textView.text = textViewText;
self.textView.scrollEnabled = YES;
}
else {
self.textView.text = textViewText;
NSRange range = NSMakeRange(self.textView.text.length - 1, 1);
[self.textView scrollRangeToVisible:range];
}
[UIView setAnimationsEnabled:YES];
};
if ([NSThread isMainThread]) {
block();
}
else {
dispatch_sync(dispatch_get_main_queue(), block);
}
}
As you can see, I have tried calling appendLogToTextView both directly and using performSelectorOnMainThread. However, I'm not getting any updates to my text view, even though I confirm that the method is being called properly.
Interestingly, the UI updating works properly when I only use a single serial queue and use iteration counts to call the next method, as shown in the code below (_serialQueue is defined in viewDidLoad). However, I do not believe that implementation is good practice, as I'm wrapping serial queues within serial queues.
- (void)checkImageSizes {
NSMutableArray *assets = [NSMutableArray new];
for(int i=0;i<self.assets.count;i++) {
dispatch_async(_serialQueue, ^{
PHAsset *asset = (PHAsset *)self.assets[i];
if(asset.pixelWidth==self.screenSize.width && asset.pixelHeight==self.screenSize.height) {
[assets addObject:asset];
[self appendLogToTextView:[NSString stringWithFormat:#"Screenshot %i/%i size is OK.",i+1,(int)self.assets.count]];
}
else {
[self appendLogToTextView:[NSString stringWithFormat:#"ERROR: Screenshot %i/%i has invalid size. Skipping...",i+1,(int)self.assets.count]];
}
//request images
if(i==self.assets.count-1) {
[self appendLogToTextView:[NSString stringWithFormat:#"%i screenshot(s) ignored due to invalid size.",(int)(self.assets.count-assets.count)]];
if(assets.count==0) {
[self showNoRunesFoundAlert];
}
else {
[self appendLogToTextView:#"Preparing to process screenshots..."];
self.assets = [NSArray arrayWithArray:assets];
[self processUploads];
}
}
});
}
}
What am I not understanding about serial queues that is causing the UI updates in this version of the code to work, but my attempt at a "cleaner" implementation to fail?
In the end, I just ended up using dispatch groups and completion blocks in order to solve this problem.

updating UIProgressBar progress during loop

I can't find a clear answer on how to update the progress of a UIProgressbar whilst iterating a loop e.g. :
for (int i=0;i<items.count;i++) {
Object *new = [Object new];
new.xxx = #"";
new...
...
float progress = (i+1) / (float)items.count;
progressBar.progress = progress;
}
[self save];
how can I update the UI on a seperate thread?
Run the loop on a background thread, and update the progress bar on the main thread:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
for (int i=0;i<items.count;i++) {
Object *new = [Object new];
new.xxx = #"";
new...
...
float progress = (i+1) / (float)items.count;
dispatch_async(dispatch_get_main_queue(), ^{
progressBar.progress = progress;
});
}
[self save];
});

NSDictionary data does not pass to another ViewController

I need to pass 2 NSDictionary from a ViewController to another ViewController, My App some times pass the data ok, but, another times, data don't reach to second ViewController.
I need consult a web service twice, that web service response is a JSON, this JSON (2) is what I need pass to other ViewController
The call to web service is made here (in dispatch_async):
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));
dispatch_queue_t queue = dispatch_queue_create("com.micrologica.modules", nil);
dispatch_async(queue, ^{
urlConsultarDisponibilidad = [NSString stringWithFormat:connectionUrl getAvailableModuleListAPI, fechaDiaUno, idLocal, idServicioUno, idProfesionalUno, idServicioDos, idProfesionalDos];
[FunctionsAmano setConnectionAndRequest:urlConsultarDisponibilidad completion:^(NSDictionary *dataResponse) {
NSLog(#"response dia 1: %#", dataResponse);
if(dataResponse) {
NSString *resp = [NSString stringWithFormat:#"%#", [dataResponse objectForKey:#"resp"]];
if([resp isEqualToString:#"1"]) {
NSLog(#"almost finish 1");
modulesDiaUno = [dataResponse objectForKey:#"data"];
}
dispatch_after(popTime, queue, ^(void){
finishedDiaUno = YES;
NSLog(#"finished 1");
});
}
}];
});
dispatch_async(queue, ^{
urlConsultarDisponibilidad = [NSString stringWithFormat:connectionUrl getAvailableModuleListAPI, fechaDiaDos, idLocal, idServicioUno, idProfesionalUno, idServicioDos, idProfesionalDos];
[FunctionsAmano setConnectionAndRequest:urlConsultarDisponibilidad completion:^(NSDictionary *dataResponse) {
NSLog(#"response dia 2: %#", dataResponse);
if(dataResponse) {
NSString *resp = [NSString stringWithFormat:#"%#", [dataResponse objectForKey:#"resp"]];
if([resp isEqualToString:#"1"]) {
NSLog(#"almost finish 2");
modulesDiaDos = [dataResponse objectForKey:#"data"];
}
dispatch_after(popTime, queue, ^(void){
finishedDiaDos = YES;
NSLog(#"finished 2");
});
}
}];
});
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
BOOL exito = YES;
int segundos = 0;
/* Si la consulta se demora mas de 60 segundos, se interrumpe e informa al usuario de un problema */
while (!finishedDiaUno && !finishedDiaDos) {
[NSThread sleepForTimeInterval:1];
if(segundos >= 60) {
exito = NO;
break;
}
segundos++;
}
if(exito) {
HorariosViewController *horariosView = [self.storyboard instantiateViewControllerWithIdentifier:#"ModulesView"];
horariosView.modulesDiaUno = self.modulesDiaUno;
horariosView.modulesDiaDos = self.modulesDiaDos;
double delayInSeconds = 4.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self presentViewController:horariosView animated:NO completion:nil];
});
}
});
});
This code is working fine, but, some times the response (dataResponse) comes after I make change of ViewController, I don't know why changes ViewController if the dataResponse not came.
How to you see, I instantiate of the second ViewController, I set the data, and change the ViewController on a dispatch_after (4.0 seconds).
Why I change the ViewController in a dispatch_after? because, if I don't implement the dispatch_after, the NSDictionarys ALWAYS comes empty!, In this way, the NSDictionarys some times comes empty and some times comes ok.
Can you tell me why is wrong with my code?
PS: the dictionaries appear empties in the other ViewController (has no element, but is not null).
Here is a advice: don't block your main thread, don't call sleep on main thread.
It a concurrency problem , the method setConnectionAndRequest:urlConsultarDisponibilidad should be asynchronous and the UI code may execute before the two responses. I think it can be fixed like this:
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));
dispatch_queue_t queue = dispatch_queue_create("com.micrologica.modules", nil);
dispatch_async(queue, ^{
urlConsultarDisponibilidad = [NSString stringWithFormat:connectionUrl getAvailableModuleListAPI, fechaDiaUno, idLocal, idServicioUno, idProfesionalUno, idServicioDos, idProfesionalDos];
[FunctionsAmano setConnectionAndRequest:urlConsultarDisponibilidad completion:^(NSDictionary *dataResponse) {
NSLog(#"response dia 1: %#", dataResponse);
if(dataResponse) {
NSString *resp = [NSString stringWithFormat:#"%#", [dataResponse objectForKey:#"resp"]];
if([resp isEqualToString:#"1"]) {
NSLog(#"almost finish 1");
modulesDiaUno = [dataResponse objectForKey:#"data"];
}
dispatch_after(popTime, queue, ^(void){
finishedDiaUno = YES;
NSLog(#"finished 1");
if (finishedDiaDos) {
dispatch_async(dispatch_get_main_queue(), ^{
HorariosViewController *horariosView = [self.storyboard instantiateViewControllerWithIdentifier:#"ModulesView"];
horariosView.modulesDiaUno = self.modulesDiaUno;
horariosView.modulesDiaDos = self.modulesDiaDos;
[self presentViewController:horariosView animated:NO completion:nil];
});
}
});
}
}];
});
dispatch_async(queue, ^{
urlConsultarDisponibilidad = [NSString stringWithFormat:connectionUrl getAvailableModuleListAPI, fechaDiaDos, idLocal, idServicioUno, idProfesionalUno, idServicioDos, idProfesionalDos];
[FunctionsAmano setConnectionAndRequest:urlConsultarDisponibilidad completion:^(NSDictionary *dataResponse) {
NSLog(#"response dia 2: %#", dataResponse);
if(dataResponse) {
NSString *resp = [NSString stringWithFormat:#"%#", [dataResponse objectForKey:#"resp"]];
if([resp isEqualToString:#"1"]) {
NSLog(#"almost finish 2");
modulesDiaDos = [dataResponse objectForKey:#"data"];
}
dispatch_after(popTime, queue, ^(void){
finishedDiaDos = YES;
NSLog(#"finished 2");
if (finishedDiaUno) {
dispatch_async(dispatch_get_main_queue(), ^{
HorariosViewController *horariosView = [self.storyboard instantiateViewControllerWithIdentifier:#"ModulesView"];
horariosView.modulesDiaUno = self.modulesDiaUno;
horariosView.modulesDiaDos = self.modulesDiaDos;
[self presentViewController:horariosView animated:NO completion:nil];
});
}
});
}
}];
});

Resources