How to use the background thread in Objective-C? - ios

I am trying to run a while loop when I push the button, but I can not push the button because the while loop blocks the UI.
Is there a background thread where I can run the while loop and also push the UIButton?

Personally, I'd run a HUD activity indicator over the top of the UI and then run your loop in the background.
//Start the HUD here
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Run your loop here
dispatch_async(dispatch_get_main_queue(), ^(void) {
//stop your HUD here
//This is run on the main thread
});
});

Try this
dispatch_async(dispatch_get_main_queue(), ^{
// your code
});
Once it dispatches, you will not have full control over the operation.
If you want to take the control of the operation. Use
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
// Background work
}];

Way 1 :
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Write your code here
});
Way 2 :
If you use performSelectorInBackground:withObject: to spawn a new thread.
Way 3 :
[NSThread detachNewThreadSelector:#selector(yourMethod:) toTarget:self withObject:nil];

There are many options for you, Grand Central Despatch is a good option, or you could use a NSTimer to trigger an event in the background every x milliseconds which may also work for you.
dispatch_async(dispatch_get_main_queue(), ^{
// your code
});
Or
NSTimer *refreshTimer;
refreshTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(YourMethodHere) userInfo:nil repeats:YES];

You can use dispatch_async for this purpose.You have to run the loop in the background thread, while the UI updates must be done in the main thread.Here is a link

Related

Background task stopping when device locked?

I have a timer running when the device enters the background as I want to keep a check on a small amount of data in my service. I am using the following code in the applicationDidEnterBackground method in app delegate
UIApplication *app = [UIApplication sharedApplication];
//create new uiBackgroundTask
__block UIBackgroundTaskIdentifier bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
[app endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
//and create new timer with async call:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//run function methodRunAfterBackground
NSString *server = [variableStore sharedGlobalData].server;
NSLog(#"%#",server);
if([server isEqual:#"_DEV"]){
arrivalsTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:#selector(getArrivals) userInfo:nil repeats:YES];
}
else {
arrivalsTimer = [NSTimer scheduledTimerWithTimeInterval:300 target:self selector:#selector(getArrivals) userInfo:nil repeats:YES];
}
[[NSRunLoop currentRunLoop] addTimer:arrivalsTimer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
This works absolutely fine, until the device auto-locks and then the timer stops ticking. Any suggestions on how to stop this from happening? The default live time is 5 minutes so the majority of devices will be locked long before this even ticks once.
Thanks
A couple of observations:
As Mr. H points out, beginBackgroundTaskWithExpirationHandler only gives you 30 seconds (previously 3 minutes) in contemporary iOS versions. So an attempt to fire a timer in five minutes won't work.
You can use [[UIApplication sharedApplication] backgroundTimeRemaining] to inquire as to how much time you have left.
When the device locks, the background tasks continue. You should not see the app terminating. If the user manually terminates the app through the "double tap the home button and swipe up on task switcher screen", that will kill the background tasks, but not simply locking the device.
A few comments on timers:
The code is adding timer to background queue. That's generally not necessary. Just because the app is in a background state, you can still continue to use the main run loop for timers and the like.
So, just call scheduledTimerWithTimeInterval from the main thread and you're done. There's no point in using up a GCD worker thread with a run loop unless absolutely necessary (and even then, I might create my own thread and add a run loop to that).
By the way, if it's absolutely necessary to schedule timer on some background dispatch queue, it's probably easier to use dispatch timer instead. It eliminates the run loop requirement entirely.
BTW, it's not appropriate to use scheduledTimerWithTimeInterval with addTimer. You call scheduledTimerWithTimeInterval to create a timer and add it to the current run loop. You use timerWithTimeInterval and then call addTimer if you want to add it to another run loop.

Slow Network - DidSelectRowAtIndexPath delayed

I have a table that is refreshing itself every two seconds. It works great on the simulator and on my wifi. But once I switch to the cellular network (or any slow network), I cannot select the rows reliably.
Sometimes when I click a row it will work after 8 seconds. Sometimes never.
I thought my refresh function was causing the delay but I printed the time at the beginning and end of the function and it only takes 2 milliseconds.
Has anyone had a similar slow network issue? Any tips on what might be the cause of the hang-up?
My refresh function is called in viewDidLoad:
//Set timer to call refresh function every two seconds
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:#selector(updateMethod) userInfo:nil repeats:YES];
My updateMethod is:
- (void) updateMethod
{
[columnArray removeAllObjects];
[self getColumnData];
[homeTable reloadData];
}
getColumnData calls a website and puts data in the columnArray
You must not perform network operations on the main queue. You can create an NSOperationQueue to move the network logic to a background queue and only perform the UI update in the main queue when the network operation ends.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = #"Data request queue";
[queue addOperationWithBlock:^{
[self getColumnData];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[homeTable reloadData];
}];
}];

Timer in background mode

As we know there are some limitations for applications which running in background mode.For example, the NSTimer doesn't work. I tried to write a "Timer" like this which can work in background mode.
-(UIBackgroundTaskIdentifier)startTimerWithInterval:(NSTimeInterval)interval run:(void (^)())runBlock complete:(void (^)())completeBlock
{
NSTimeInterval delay_in_seconds = interval;
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, delay_in_seconds * NSEC_PER_SEC);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// ensure the app stays awake long enough to complete the task when switching apps
UIBackgroundTaskIdentifier taskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
completeBlock();
}];
NSLog(#"remain task time = %f,taskId = %d",[UIApplication sharedApplication].backgroundTimeRemaining,taskIdentifier);
dispatch_after(delay, queue, ^{
// perform your background tasks here. It's a block, so variables available in the calling method can be referenced here.
runBlock();
// now dispatch a new block on the main thread, to update our UI
dispatch_async(dispatch_get_main_queue(), ^{
completeBlock();
[[UIApplication sharedApplication] endBackgroundTask:taskIdentifier];
});
});
return taskIdentifier;
}
I called this function like this:
-(void)fire
{
self.taskIdentifier = [self startTimerWithInterval:10
run:^{
NSLog(#"timer!");
[self fire];
}
complete:^{
NSLog(#"Finished");
}];
}
This timer works perfect except that there is one problem. The longest period for background task is 10 minute.(Please refer to the NSLog in startTimerWithInterval).
If there any way to make my timer work more than 10 minutes? By the way, my application is a BLE application, I set UIBackgroundModes to bluetooth-central already.

NSTimer requiring me to add it to a runloop

I am wondering if someone can explain why dispatching back to the main queue and creating a repeating NSTimer I am having to add it to RUN LOOP for it too fire? Even when using performselectorOnMainThread I still have to add it to a RUN LOOP to get it to fire.
Below is an example of my question:
#define queue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#define mainqueue dispatch_get_main_queue()
- (void)someMethodBeginCalled
{
dispatch_async(queue, ^{
int x = 0;
dispatch_async(mainqueue, ^(void){
if([_delegate respondsToSelector:#selector(complete:)])
[_delegate complete:nil];
});
});
}
- (void)compelete:(id)object
{
[self startTimer];
//[self performSelectorOnMainThread:#selector(startTimer) withObject:nil waitUntilDone:NO];
}
- (void)startTimer
{
NSTimer timer = [NSTimer timerWithTimeInterval:3 target:self selector:#selector(callsomethingelse) userInfo:nil repeats:YES];
//NSDefaultRunLoopMode
[[NSRunLoop currentRunLoop] addTimer:_busTimer forMode:NSRunLoopCommonModes];
}
EDIT:
I believe I worded this question very poorly. I would like to know why [[NSRunLoop currentRunLoop] addTimer:_busTimer forMode:NSRunLoopCommonModes]; is necessary in startTimer if I call someMethodBeginCalled. If I don't include that line, the timer doesn't fire.
If I call startTimer from viewDidLoad for example, I can remove the NSRunLoop line and the timer will fire every 60 seconds.
And here's how to add an NSTimer to a runloop:
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
You could always use this method instead:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:#selector(getBusLocation) userInfo:nil repeats:YES];
This will save you a line, as it will add it to the run loop automatically.
Because, as the docs say:
Timers work in conjunction with run loops. To use a timer effectively,
you should be aware of how run loops operate—see NSRunLoop and
Threading Programming Guide. Note in particular that run loops retain
their timers, so you can release a timer after you have added it to a
run loop.
It is a design decision that Apple made when they wrote the code for NSTimer (and I'm sure they had good reason to do so) and there is nothing we can do to get around it. Is it really that burdensome?
Like #sosborn said, NSTimers depend on NSRunLoops, and since GCD queues create threads that don't have run loops, NSTimer doesn't play well with GCD.
Check out this other StackOverflow question on the matter: Is it safe to schedule and invalidate NSTimers on a GCD serial queue?
To solve that problem, I implemented MSWeakTimer: https://github.com/mindsnacks/MSWeakTimer (and had the implementation checked by a libdispatch engineer at the last WWDC!)
Timer method won't be called since GCD queues create threads that don't have run loops
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(#"Timer method from GCD main queue");
}];
});
However when dispatched on main queue the timer method will be called as it will get added to main threads run loop.
dispatch_async(dispatch_get_main_queue(), ^{
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(#"Timer method from GCD main queue");
}];
});
Adding the timer to the runloop didn't work in my case. I had to create the timer on the main thread. I was doing this thread creation in a MultipeerConnectivity delegate.
dispatch_async(dispatch_get_main_queue(), ^{
self.timer = [NSTimer scheduledTimerWithTimeInterval:self.interval invocation: self.invocation repeats:YES];
});

Even using nsthread interface freezes

I have to perform a timer using a NSThread as I need to download text data from the web, and without that, in 3G connection it freezes the UI while downloading. So I've used a NSThread but it still freezes for a while and I don't know how to solve this....
Here's the code I'm using to perform the timer:
- (void)viewDidLoad{
[NSThread detachNewThreadSelector:#selector(onTimerK2) toTarget:self withObject:nil];
}
- (void)onTimerK2{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
timer = [NSTimer scheduledTimerWithTimeInterval:15 target:self selector:#selector(onTimerKY2) userInfo:nil repeats:YES];
[pool release];
}
- (void)onTimerKY2{
NSLog(#"working");
}
You're detaching a new thread to call onTimerK2, which then immediately calls a method back on the main thread, which will of course freeze your interface.
Edit
You should be doing any long-running work not on the main thread (either yourself, or by using the asynchronous nature of NSURLConnection as mentioned elsewhere),and then updating your UI by calling selectors on the main thread as this activity progresses.
Having said that, you may have more success with the following changes/reordering of your code:
- (void)viewDidLoad {
timer = [NSTimer scheduledTimerWithTimeInterval:15
target:self
selector:#selector(onTimerK2)
userInfo:nil
repeats:YES];
}
- (void)onTimerK2{
[NSThread detachNewThreadSelector:#selector(onTimerKY2)
toTarget:self
withObject:nil];
}
- (void)onTimerKY2{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"working");
[pool release];
}
It's not very clear how you are trying to solve the UI freeze problem by using timer. But if your UI is freezing due to downloading then you can try asynchronous loading instead of using timer or detaching another thread.
EDIT: Unless you configure a run loop for secondary thread, timer is not going to work from that thread. Check the run loop management in threading programming guide. This can be a far difficult work than to use asynchronous connection.

Resources