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];
});
Related
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();
}
});
}
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.
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?
I have downloaded images and saved it to a GCD, updated the count of images and performed a post notification in queue. What is happening now is that it does not register that I have downloaded the images. Sometimes I am missing some images, and I think it is because I have missed something in my GCD logic.
Here is my code:
for (NSString *i in items)
{
[[RequestAPI sharedInstance]downloadImage:i completion:^(AFHTTPRequestOperation *operation, UIImage *image, NSError *error) {
//1. here main thread I receive images and go to BG
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//2. here I save image on disk and get path
NSString *path = [ImageManager saveImageToDisk:image toEntity:entity withparams:#{#"save" : #"lala"}];
__block NSMutableDictionary *attachments = [NSMutableDictionary dictionary];
__block NSMutableArray *photoPaths = [NSMutableArray array];
dispatch_async(dispatch_get_main_queue(), ^{
//3. here I load entity and dictionary from it with NSKeyedUnarchiver from CD and set to it image path
if (entity.attachments)
{
attachments = [NSKeyedUnarchiver unarchiveObjectWithData:entity.attachments];
if (attachments[type])
{
photoPaths = attachments[type];
}
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//4. here I check all images equality ti themselves in entity
BOOL haveDublicate = NO;
NSData *i = [ImageManager imageDataFromPath:path];
NSArray *photoImages = [ImageManager imageDatasFromPaths:photoPaths];
for (NSData *saved in photoImages)
{
if ([saved isEqualToData: i])
{
haveDublicate = YES;
}
}
if (!photoPaths)
{
photoPaths = [NSMutableArray array];
}
dispatch_async(dispatch_get_main_queue(), ^{
//5. and finally if all ok I save image path, change load counter and post notification
if (path.length
&& ![photoPaths containsObject:path]
&& !haveDublicate
)
{
[photoPaths addObject:path];
[savedLinks setObject:photoPaths forKey:type];
entity.attachments = [NSKeyedArchiver archivedDataWithRootObject:savedLinks];
[self saveContext];
}
[RequestAPI sharedInstance].downloadsCount -= 1;
[[NSNotificationCenter defaultCenter]postNotificationName:kReloadFeedData object:nil];
});
});
});
});
}];
I think here I need process 1-2-3-4-5 to get desired result. Am I right or how can I do this queuing?
I'm trying to use the RACSignal class's interval method of ReactiveCocoa.
The following code works every second after 1 seconds.
But I want it works immediately and every second.
What's the best way?
#weakify(self);
[[[RACSignal interval:1.0] takeUntilBlock:^BOOL(id x) {
return [AClass count] == 0;
}] subscribeNext:^(id x) {
dispatch_async(dispatch_get_main_queue(), ^{
#strongify(self);
NSUInteger count = [AClass count];
self.title = [NSString stringWithFormat:#"%u", count];
});
} completed:^{
dispatch_async(dispatch_get_main_queue(), ^{
#strongify(self);
self.title = #"";
});
}];
I believe you're looking for -startWith:.
[[[RACSignal interval:1] startWith:NSDate.date] takeUntilBlock:^(id _) { // ...