Download image asynchronously without blocking UI - ios

I need to download an image asynchronously without blocking the UI in an iOS app. While downloading the image, a 'waiting' UIView must be shown for 3 seconds at least. I would like to implement a solution that does not block UI management (even if in the current implementation no user operations are offered while the image download is in progress).
The current solution is:
- main thread: dispatch_async + block to download the image (described in thread_2);
- main thread: sleep for three seconds;
- main thread: P (wait) on a semaphore S;
- main thread: read data or error message set by thread_2, then behave accordingly.
- thread_2: download the image, set data or error flag/msg according to the download result;
- thread_2: V (signal) on the semaphore S.
There are other solutions, for example based on NSNotification, but this one seems the best for respecting the 3-seconds delay.
My question is: when the main thread is sleeping (or when it is waiting on the semaphore), is the UI frozen? If it is, which solution would be the best one?
What do you think of this second one:
- main thread: dispatch_async + block to download the image (described in thread_2);
- main thread: dispath_async thread_3
- thread_2: as above
- thread_3: sleep three seconds, P on semaphore S;
- thread_3: read data or error message set by thread_2, prepare everything, then behave accordingly using the main_queue.

This is a way working with multiple threads and with specific delay
double delayInSeconds =3;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
//Entering a specific thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^{
//Wait delayInSeconds and this thread will dispatch
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
//Do your main thread operations here
});
});

More or less you can do this:
dispatch_async(your_download_queue, ^{
dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
//download image
dispatch_after(timer, dispatch_get_main_queue(), ^{
//update ui with your image
});
});

Related

NSTimer is not calling in background method

I got stuck in a problem. I'm trying to run a timer in a method which itself is in background resulting my timer is not initiating. I got to know some where that timer can't be initialized in background so is there any way to do this?
You can perform the operation on main thread.
[self performSelectorOnMainThread:#selector(initTimer) withObject:nil waitUntilDone:NO];
and then
- (void)initTimer {
// Init your timer here.
}
You can try Background Fetch in ios7 to run code on background
Try this :
// provide value as required. Time here is 3 sec
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// do your task
});
As pointed out by #Bryan Chen, you can schedule a method to be run on the current thread using the following code:
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC);
dispatch_after(when, dispatch_get_current_queue(), ^(void) {
[self myTimedMethod];
});
You cannot use NSTimer in a background thread unless you are maintaining a run loop on that thread.

Delay via GCD vs. sleep()

I have the method in which I need to make a 5 sec delay every time I call it. Firstly I make it with sleep(5); - it worked excellent but I believe - it's not obj-c way, so I tried to write it with GCD help. The first call of this procedure make a delay about 5 sec, but other calls in this queue are going one after one without delay. How to solve this problem?
- (void) buyItemAtUUID:(NSString*)UUID
{
dispatch_barrier_async(dataManagerQueue, ^{
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dataManagerQueue, ^(void){
NSInteger path = [self indexFromObjectUUID:UUID];
if (path != NSNotFound)
{
NSMutableDictionary *item = [[_items objectAtIndex:path] mutableCopy];
NSNumber *value = [NSNumber numberWithFloat:[[item objectForKey:#"Quantity"] floatValue] - 1.0];
}
});
});
}
The first call of this procedure make a delay about 5 sec, but other
calls in this queue are going one after one without delay.
That's usually desired behavior. The reason you shouldn't call sleep() is that it'll block the thread, preventing anything else in that thread from happening. If you block the main thread, your device will appear to freeze, which isn't a very nice user experience.
GCD provides a nice dispatch_group_wait() function that lets you make one set of tasks wait for some other group to finish. Take a look at Waiting on Groups of Queued Tasks for an example.
dispatch_barrier_async only stops blocks added to the concurrent queue after this block from running. The blocks that were already queued are not prevented from running.

While Loop Delay

I'm making a while loop and I'm trying to delay it.
while (mode == 1)
{
[self performSelector:#selector(on) withObject:nil afterDelay:slider.value];
NSLog(#"on");
[self performSelector:#selector(off) withObject:nil afterDelay:slider.value];
NSLog(#"off");
}
But even though I'm setting the slider to 10 seconds it goes on and off very fast.
Also my app black screens and I only see the status bar and my nslog but that might have to do with something else.
performSelector:withObject:afterDelay: does not wait for the selector to finish. This means that as soon as you are telling selector A to run after a certain time, it immediately goes on and tells selector B to run after a certain time. There are plenty of options to fix this:
If you would like to stick with selectors, you could use performSelector:onThread:withObject:waitUntilDone:. Make sure you don't use the main thread though or you will experience UI freezes.
[NSThread waitForInterval] is another option but, like the previous option, will freeze the UI unless you are calling the entire while loop on a different thread. I am surprised this has been mentioned so much without people noting this important factor.
GCD is another option. It doesn't wait for it to finish so you shouldn't experience major UI freezes.
dispatch_time_t dispatchTime = dispatch_time(DISPATCH_TIME_NOW, slider.value * NSEC_PER_SEC);
dispatch_after(dispatchTime, dispatch_get_main_queue(), ^(void){
[self on];
dispatch_after(dispatchTime, dispatch_get_main_queue(), ^(void){
[self off];
})
});
Another option is to keep what you are doing now and make selector B run after slider.value*2. If you think of this in a mathematically way, it makes sense:
A) 0-1-2-3-4-5-6-7-8-9-10
B) 0-1-2-3-4-5-6-7-8-9-10-1-2-3-4-5-6-7-8-9-10
This is what you're telling your app:
"Turn on something in slider.value seconds from now"
"Turn off something in slider.value seconds from now"
Do you see a problem with that?
double delayInSeconds = slider.value;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self off];
//or
[self on];
});
This performs whatever inside the block after given time! :)
The while loop doesn't wait for the delayed actions to complete, it simply schedules them. The problem is that you're scheduling all the events in a very quick loop that then runs the events one after the other just as quickly, all 10 seconds later.
[self performSelector:#selector(on) withObject:nil afterDelay:slider.value];
In this statement afterDelay doesn't mean that it will pause for some second.
It means that method "on" will execute after some second. But execution will not wait to finish execution of method"on" and go to next statement.
If you want to make only some delay than you can use following statement :
[NSThread sleepForTimeInterval:2.0];
This will wait for 2 second. But It may not be good idea because it will freeze your UI.
I hope this will help you.
All the best.....
If you want to turn it on immediately and turn it off the specified delay:
[self on];
NSLog(#"on");
[self performSelector:#selector(off) withObject:nil afterDelay:slider.value];
NSLog(#"off");
If you want to turn it on after the specified delay, then turn it off again 10 seconds after that:
[self performSelector:#selector(on) withObject:nil afterDelay:slider.value];
NSLog(#"on");
[self performSelector:#selector(off) withObject:nil afterDelay:slider.value + 10];
NSLog(#"off");
try to use [NSThread sleepForTimeInterval:1.0f]; it will stop your thread based on the time u are providing

Proper way to delay while allowing the run loop to continue

I have a need to delay for a certain amount of time and yet allow other things on the same runloop to keep running. I have been using the following code to do this:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
This seems to do exactly what I want, except that sometimes the function returns immediately without waiting the desired time (1 second).
Can anyone let me know what could cause this? And what is the proper way to wait while allowing the run loop to run?
NOTE: I want to delay in a manner similar to sleep(), such that after the delay I am back in the same execution stream as before.
You should use GCD and dispatch_after for that. It is much more recent and efficient (and thread-safe and all), and very easy to use.
There is even a code snippet embedded in Xcode, so that if you start typing dispatch_after it will suggest the snippet and if you validate it will write the prepared 2-3 lines for you in your code :)
int64_t delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
<#code to be executed on the main queue after delay#>
});
Use an NSTimer to fire off a call to some method after a certain delay.
Have you tried performSelector:withObject:afterDelay:?
From the Apple documentation
Invokes a method of the receiver on the current thread using the default mode after a delay.
I had a similar issue and this is my solution. Hope it works for others as well.
__block bool dispatched = false;
while ( put your loop condition here )
{
if (dispatched)
{
// We want to relinquish control if we are already dispatched on this iteration.
[ [ NSRunLoop currentRunLoop ] runMode: NSDefaultRunLoopMode beforeDate:[ NSDate date ] ];
continue;
}
// mark that a dispatch is being scheduled
dispatched = true;
int64_t delayInNanoSeconds = (int64_t) (0.1 * (float) NSEC_PER_SEC);
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, delayInNanoSeconds);
dispatch_after(delayTime, dispatch_get_main_queue(), ^() {
// Do your loop stuff here
// and now ready for the next dispatch
dispatched = false;
} );
} // end of while

Multiple dispatch_after can not work well

It is easy to delay executing something like this
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
<#code to be executed on the main queue after delay#>
});
But it will make above code fail to execute if putting another longer delay like
double delayInSeconds2 = 3.0;
dispatch_time_t popTime2 = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds2 * NSEC_PER_SEC);
dispatch_after(popTime2, dispatch_get_main_queue(), ^(void){
<#code to be executed on the main queue after delay#>
});
Why just execute the longer one instead of both ? Or am I totally wrong ?
it is a queue, but the position depend on the future fire time instead of enqueue time.
if second one is 1 second, it will be insert before the first one.
if you want them executed concurrently (time slice for single core or real concurrency for multi-core cpu), put them on different queue.

Resources