Progress bar hangs while deleting large files - ios

I have added the loading bar in below snippet.It’s worked fine which I have deleted small amount of data(say 30 to 150 mb). But the problem is when I deleted large size of data, the loading bar doesn't get loaded(approx 190mb).
dispatch_async(dispatch_get_main_queue(), ^{
[self addloading];
if(_isProgress)
return;
_lastDeleteItemIndexAsked = index;
NSInteger catalogue_id =[[[_currentData objectAtIndex:index%[_currentData count]] valueForKey:#"catalogue_id"] integerValue];
BOOL update_avilable = [[[online_date_array objectAtIndex:index%[_currentData count]] valueForKey:#"update_version"] boolValue];
if(update_avilable)
[[self GridViewDelegate] deletecatlogue_for_update:catalogue_id];
else
[[self GridViewDelegate] deletecatlogue:catalogue_id];
[online_date_array replaceObjectAtIndex:index%[online_date_array count] withObject:[NotificationHandler check_online_date_of_catalogue:catalogue_id]];
[_gmGridView reloadObjectAtIndex:index%[_currentData count] withAnimation:GMGridViewItemAnimationFade];
[self stopLoadingfor_delete];
});

Is it size or execution time?
I'm going to assume that self is a UI object. If it is, the block should be:
Controller* __weak weakSelf = self;
dispatch_async(queue, ^{
Controller* strongSelf = weakSelf;
if (strongSelf) {
...
}
else {
// self has been deallocated in the meantime.
}
});
Thus you may prefer a MVVC solution, where a view model does the hard work, not the view controller.

Related

Why action is perform multiple time after loading is finish and why it queuing all the events?

While loading when we click on back button or any cell of the table the action is called multiple times after loading is finish.Here the the code snippet that what i'm doing when I start the loading and stop the loading.
+(void)showLoader_OnView{
APP_DELEGATE.window.userInteractionEnabled = NO;
[MBProgressHUD showHUDAddedTo:APP_DELEGATE.window animated:YES];
}
To stop the loading:-
+(void)hideLoader {
APP_DELEGATE.window.userInteractionEnabled =YES;
[MBProgressHUD hideAllHUDsForView:APP_DELEGATE.window animated:YES];
}
please help me.
Update
actually i'm taking data from server. whenever user will go to next window then in viewWillAppear function i call a function which will hit the api to get the data.
-(void)performAutoSync
{
#try
{
if(self.shouldPerformAutoSync)//Necessary conditions to check the auto sync
{
[AppConstants showLoader_OnView]; //here i call the loader.
self.shouldPerformAutoSync = NO;
if(!self.isSyncing)
{
if(!syncBl)
{
syncBl = [[SyncBL alloc] init];
syncBl.delegate = self;
}
if(!syncDl)
syncDl = [[SyncDL alloc] init];
// [self saveModifiedDataForCurrentViewController];
[self delayToAutoSync];
NSMutableDictionary *dictMainData = [NSMutableDictionary new];
[dictMainData setObject:[syncDl fetchCompleteDataAndPrepareDictionary:YES] forKey:#"data"];//#"MainData"];
[syncBl performAutoSync:dictMainData];
}
}
}
#catch (NSException *exception) {
BILog(#"%#",exception);
}
}
Don't block the main thread.
Seeing that you invoke [AppConstants showLoader_OnView] from performAutoSync, and that showLoader_OnView in turn executes:
[MBProgressHUD showHUDAddedTo:APP_DELEGATE.window animated:YES]
I can only assume that performAutoSync is executed in the main thread. This, of course, blocks the UI until your operations are completed.
You should redesign so that you won't need all your state variables, globals, global calls, and take advantage of multi-threading.
Also, remove this, as it qualifies as a kludge;
APP_DELEGATE.window.userInteractionEnabled = NO

Pushing view controller within block not working

What is the correct way to fire methods within a completion block (if this is even recommended)? Right now, I have an IBAction that calls a method that downloads information with a completion block signifying if the info was retrieved successfully or not. If it was, I want to push a view controller that will display that information, but at the moment, nothing is happening. I'm guessing it has something to do with main thread, gcd, etc...
__weak YTTMSetupViewController *weakSelf = self;
[mc downloadJson:^(BOOL success) {
if(success){
NSLog(#"sucess. metric count - %i",(int)mc.collection.count);
//info was downloaded. Push new view controller with info
YTTMMetricTableViewController *mtvc = [self.storyboard instantiateViewControllerWithIdentifier:#"YTTMMetricTableViewController"];
mtvc.group = (WAGroup*)[[WAMetricCollection sharedInstance].collection lastObject];
mtvc.hidesBottomBarWhenPushed = YES;
[weakSelf.navigationController pushViewController:mtvc animated:YES];
}
else{
NSLog(#"failure");
//display failure UI
}
NSLog(#"end of downloading");
[HUD dismissAfterDelay:0.5f animated:YES];
}];
Not sure if this is the right way to do it, but it worked.
I added a method that will push the vc on the main thread as so:
[weakSelf performSelectorOnMainThread:#selector(pushDetail) withObject:nil waitUntilDone:YES];
Completed Code:
__weak YTTMSetupViewController *weakSelf = self;
[mc downloadJson:^(BOOL success) {
if(success){
NSLog(#"sucess. metric count - %i",(int)mc.collection.count);
//info was downloaded. Push new view controller with info
[weakSelf performSelectorOnMainThread:#selector(pushDetail) withObject:nil waitUntilDone:YES];
}
else{
NSLog(#"failure");
//display failure UI
}
NSLog(#"end of downloading");
}];
}
-(void)pushDetail{
__weak YTTMSetupViewController *weakSelf = self;
YTTMMetricTableViewController *mtvc = [self.storyboard instantiateViewControllerWithIdentifier:#"YTTMMetricTableViewController"];
mtvc.group = (WAGroup*)[[WAMetricCollection sharedInstance].collection lastObject];
mtvc.hidesBottomBarWhenPushed = YES;
[weakSelf.navigationController pushViewController:mtvc animated:YES];
}
You could simply try wrapping the call with a dispatch_asynch block...
__weak YTTMSetupViewController *weakSelf = self;
[mc downloadJson:^(BOOL success) {
if(success){
NSLog(#"sucess. metric count - %i",(int)mc.collection.count);
dispatch_async(dispatch_get_main_queue(), ^{
//info was downloaded. Push new view controller with info
YTTMMetricTableViewController *mtvc = [self.storyboard instantiateViewControllerWithIdentifier:#"YTTMMetricTableViewController"];
mtvc.group = (WAGroup*)[[WAMetricCollection sharedInstance].collection lastObject];
mtvc.hidesBottomBarWhenPushed = YES;
[weakSelf.navigationController pushViewController:mtvc animated:YES];
});
}
else{
NSLog(#"failure");
//display failure UI
}
NSLog(#"end of downloading");
[HUD dismissAfterDelay:0.5f animated:YES];
}];
All UI updates must be performed on the main thread. Personally I prefer to do this through GCD as it produces more readable code than performSelectorOnMainThread. However, there's nothing wrong with performSelectorOnMainThread aside from personal preference in the case of calling a single UI update on the main thread following the execution of some completion block. Do note that, whichever one you choose, you should be consistent with what you use to guarantee that blocks are enqueued in the order you specified.
Working code aside, however, the convention Apple's frameworks seem to use is to perform all completion blocks on the main thread unless a queue is specified as a method parameter, in which case the completion block should be performed on that queue. So in this case I would recommend you edit your download handler class's downloadJson method to automatically perform the completion block on the main queue.

Grand Central Dispatch and functions

I've been looking at this question to try to solve the problem I have here. The tl;dr is I want to use GCD to let me show a "Waiting" screen while I preform some tasks, then hide the screen when it's done. Right now, I have
- (void) doStuff
{
// Show wait on start
[self.waitScreen setHidden:NO];
dispatch_queue_t queue = dispatch_queue_create("com.myDomain.myApp",null);
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
// Double nesting the dispatches seems to allow me to do UI changes as part of 'Code to execute' below.
// If I do not double nest like this, the UI still freezes while it executes
dispatch_queue_t queue2 = dispatch_queue_create("com.myDomain.myApp",null);
dispatch_async(queue2, ^{
dispatch_async(dispatch_get_main_queue(), ^{
// Code to execute
{
//... Do my time consuming stuff here ...
// For testing purposes, I'm using
int i = 0;
while (i < 1000000000)
{
i++;
}
}
// Hide Wait Screen on End
[self.waitScreen setHidden:YES];
});
});
});
});
}
And this works just how I want. I'm calling [self doStuff] like so
- (IBAction) buttonTouchUpInside:(id)sender
{
[self doStuff];
}
- (void) doStuff
{
// ... code from first code block here ...
}
Everything to this point works perfectly. Now, I've discovered I will need to use this in a function call. So I need something like:
- (IBAction) buttonTouchUpInside:(id)sender
{
NSMutableString *string= [self doStuff];
// ... use 'string' to do other stuff ...
// For testing, I'm using
self.label.text = string;
}
- (NSMutableString *) doStuff
{
// ... code from first code block here ...
}
How do I need to change the syntax to be able to pass variables around with dispatch_async?
I looked at the Apple Docs and tried
- (IBAction) buttonTouchUpInside:(id)sender
{
NSMutableString *string= [self doStuff];
// ... use 'string' to do other stuff - shows 'nil' when I put breakpoints here ...
// For testing, I'm using
self.label.text = string;
}
- (NSMutableString *) doStuff
{
__block NSMutableString *string = [[NSMutableString alloc] initWithString:#"Initing"];
// Show wait on start
[self.waitScreen setHidden:NO];
dispatch_queue_t queue = dispatch_queue_create("com.myDomain.myApp",null);
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_queue_t queue = dispatch_queue_create("com.myDomain.myApp",null);
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
// Code to execute
{
int i = 0;
while (i < 1000000000)
{
i++;
}
[string setString:#"Hello World"];
}
// Hide Wait Screen on End
[self.waitScreen setHidden:YES];
});
});
});
});
return string;
}
But when I run this, label just shows Initing. I need it to show Hello World. None of the changes I make in the block are being passed through.
After looking at some other questions, this seems to be referred to as a "race condition". As I understand it, once it hits the dispatch_async, the code in the block starts running on a new thread, but the code outside of the block continues to run at the same time on the old thread. So it looks like the thread outside the block is getting to self.label.text = string before the thread running the block can get to [string setString:#"Hello World"];. How can I make the self.label.text = string line wait until [string setString:#"Hello World"]; finishes?
First of all your reasoning of double nesting is flawed. Not sure why it might have worked, but the correct way is to do some async work, and any time you want to update the ui wrap that code in a block on the main queue.
- (void) doStuff
{
// Show wait on start
[self.waitScreen setHidden:NO];
// queue should be a global variable, you don't want to create it every time you
// execute doStuff
dispatch_async(queue, ^{
// Code to execute
{
//... Do my time consuming stuff here ...
// For testing purposes, I'm using
int i = 0;
while (i < 1000000000)
{
i++;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
// Hide Wait Screen on End
[self.waitScreen setHidden:YES];
});
});
}
Since your queue is performing work asynchronously you can't simply return a value from doStuff without waiting, which will block the queue you call doStuff on again.
If you just want to set a value on a label, do that too in the block executed on the main queue, like hiding the wait screen.
Another common way to do things it to provide a callback block to execute as soon as work is finished.
- (void) doStuffWithCompletionBlock:(void(^)(NSString *))block
{
// again, a global variable for the queue
dispatch_async(queue, ^{
// do some work here that shouldn't block the UI
dispatch_async(dispatch_get_main_queue(), ^{
block(#"My result string");
});
});
}
- (void) myAction:(id)sender
{
__weak typeof(self) weakSelf = self;
[self doStuffWithCompletionBlock:^(NSString *result) {
weakSelf.label.text = result;
}];
}
Notice that I call the completion block on the main queue, this is a choice. You could leave that out, but then you would still have do all UI updates on the main queue later in the completion block itself.

Adding subViews on non-main Thread not displaying

I create a UIView contentView and display it. I then fetch data from server and create a bunch of subviews displaying the data. I am using MBProgressHUD to display while waiting on data.
if (datasetSubBar.panels == nil) {
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:datasetSubBar.filterListView animated:YES];
HUD.labelText = #"Creating Panels";
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[datasetSubBar createPanels];
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:datasetSubBar.filterListView animated:YES];
});
});
}
in createPanels method, i fetch the data then create the panels. The panel is created, added to filterListView (the content view) and then add the constraints:
for (int i = 0; i < panels.count; i++) {
NSLog(#"thread: %#", [NSThread currentThread]);
NSDate *startDate = [NSDate date];
DatasetFilterListPanelView *panel = [panels objectAtIndex:i];
[contentView addSubview:panel];
// add constraints to position each panel
}
these are run on a separate thread which is what I believe the issue is. The UI can only be updated on the main thread.
I tried adding:
dispatch_async(dispatch_get_main_queue(), ^{
[contentView addSubview:panel];
});
But that raises errors for the constraints (the constraints don't have reference to it since its in a different thread).
If I run createPanels on the main thread, the panels will display but it also locks up the UI until its complete.
Any ideas?
I'm not sure which constraints are violated, but you could also try:
[contentView performSelctorOnMainThread:#selector(addSubview) withObject:panel waitUntilDone:YES];
instead of
dispatch_async(dispatch_get_main_queue(), ^{
[contentView addSubview:panel];
});
Not sure whether this will help.

Wait for many asynchronous calls to perform callback

I want to synchronize some data with a web service. For each item I have to make a asynchronous call.
I want to have a completion block witch is called, when each item was synchronized. For each item I am able to perform a completion block. Now, I don't know a good way how to do it.
This is the interface:
-(void) synchronizeItemsOnComplete:(CompleteBlock) block {
NSArray* items = // get items
for (int i = 0, n = [items count]; i < n; i++) {
[self synchronizeItem:[items objectAtIndex:i] onComplete:^{
// What do do here?
}];
}
// And/or here?
}
-(void) synchronizeItemOnComplete:(CompleteBlock) block {
// do something
block();
}
How can I wait for the synchronization and then perform the block?
I tried something like this:
NSArray* items = // get items
__block int countOfItemsUntilDone = [items count];
for (int i = 0, n = countOfItemsUntilDone; i < n; i++) {
[self synchronizeItem:[items objectAtIndex:i] onComplete:^{
countOfItemsUntilDone--;
}];
}
dispatch_queue_t queue = dispatch_queue_create("wait for syncing", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
while (countOfItemsUntilDone > 0) {
usleep(1000); // wait a little bit
}
block();
});
dispatch_release(queue);
But I think this is a quite bad way. Any ideas?
Instead of spinning in a loop waiting for the counter to equal zero, check the counter value each time you decrement it, then fire an event when it reaches zero.
-(void) synchronizeItemsOnComplete:(CompleteBlock) block {
NSArray* items = // get items
__block NSUInteger remaining = [items count];
for (ItemClass* item in items) {
[self synchronizeItemImage:item onComplete:^{
--remaining;
if (remaining == 0) {
block();
}
}];
}
}
To explain why it feels wrong, there are two things you're doing here that you should do either never or rarely:
Using background queues. This is difficult and bug-prone. Don't do it without reading up a lot about writing concurrent code. You also only really need to do this if an operation blocks for a substantial amount of time (eg., to read a file from disk, or perform an intensive calculation). Don't assume you need to do it unless you have a good reason (eg., a measurable performance problem).
Spinning in a loop, checking a variable for changes and calling sleep. You should never do this.
Also, if you're looping over the elements in an array, the for ... in syntax is much nicer (and potentially more efficient) calling objectAtIndex: on each index.
Never check or decrement shared memory in different threads like this, it can cause races. Use a dispatch group to do what you're doing.
dispatch_queue_t myBGQueue;
dispatch_group_t itemsGroup = dispatch_group_create();
for (ItemClass *item in items) {
dispatch_group_async(itemsGroup, myBGQueue, ^{
[self synchronizeItemImage:item];
});
}
/* execution will sleep here until all the blocks added in the `for` complete */
dispatch_group_wait(itemsGroup, DISPATCH_TIME_FOREVER);
dispatch_release(itemsGroup);
You can use these to use synchronously.
GCD and this
performSelector:waitUntilDone:YES

Resources