In the iPhone App am coding, I need to do several tasks in parallel :
PART 1 : All the time (even if the App is not active currently):
Fetch some data from a remote DB and persist it in local Sqlite
To do this, am firing a NSTimer in a separate queue in AppDelegate like this :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
...
self.queueForDbFetch = dispatch_queue_create("queueForDbFetch", NULL);
self.queueForDbFetchTimer = dispatch_queue_create("queueForDbFetchTimer", NULL);
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(getDbData:) name:kNotif_GetDbData object:nil];
dispatch_async(self.queueForDbFetchTimer, ^(void) {
self.timerDbNotifier = [NSTimer scheduledTimerWithTimeInterval:60.0
target:self selector:#selector(scheduleNotificationToFetchDbData)
userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timerDbNotifier forMode:NSDefaultRunLoopMode];
});
...
...
}
PART 2 :
And then, I need to asynchronously update the UI with the fetched data (which is from the local sqlite DB), which an doing with queues & timers (similar to the above) like this in a UIViewController class :
-(void) initializeThisView {
// Make sure the queues are created only once
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
self.queueForUiRefresh = dispatch_queue_create("queueForUiRefresh", NULL);
self.queueForUiRefreshTimer = dispatch_queue_create("queueForUiRefreshTimer", NULL);
});
[self scheduleUiDataRefresher];
}
-(void) scheduleUiDataRefresher {
dispatch_async(self.queueForUiRefreshTimer, ^(void) {
self.timerUiDataRefresh = [NSTimer scheduledTimerWithTimeInterval:60.0
target:self selector:#selector(loadUiData)
userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timerUiDataRefresh forMode:NSDefaultRunLoopMode];
});
}
-(void) loadUiData {
dispatch_async(self.queueForUiRefresh, ^(void) {
[self refreshWithUiData:dict];
});
}
THE ISSUE :
The NSTimer instances (in both Part 1 and part 2) get fired once, and that's it. They don't repeat.
1. will creating NSTimer to repeat in the main queue block other user interaction with the App?
2. Is there any issue (or better way) in my structuring of the activities?
Don't create the timers in the dispatch_async() call. As soon as the block you pass to dispatch_async() has finished running (and it finishes directly after creating the timer), all data belonging to it is freed. I'm surprised it doesn't crash. Also, when you use scheduledTimerWithTimeInterval:target:selector:userInfo:repeats, the timer will already be scheduled to the main runloop. The call to NSRunLoop:addTimer: is not necessary, it will either have no effect or you will have a conflict in scheduling the timer.
To answer your questions:
Create the timers without dispatch_async(), just call NSTimer:scheduledTimerWithTimeInterval: directly. Do not use NSRunLoop:addTimer (unless you know exactly why you want it that way). Then in the selector that is being called by your timers, use dispatch_async() to fire off your asynchronous tasks.
However, if you are sure that these tasks do not take long, you might as well avoid using dispatch_async() altogether.
Related
I'm kind of new to multithreading, and need some advice.
I'm using ARC in my code.
Problem : I've set up NSTimer in my app to fire every 1 second some method which creates and starts thread like this
//Create a new thread
mSomeThread = [[NSThread alloc] initWithTarget:self selector:#selector(someMethod) object:nil];
//start the thread
[mSomeThread start];
Where mSomeThread is an ivar
Let say the execution of mSomeThread takes more than 1 second, and the mSomeThread is allocated second time, i.e. according to ARC "rules" its released before be allocated one more time.
Does it mean that the first thread doesn't complete and and is forced to quite ?
An NSThread retains itself for the duration of its execution. There's no risk that resetting mSomeThread will cause a running thread to be terminated prematurely.
Yes. If you really need to keep reference to the current thread of execution for your someMethod then you need to wait for it to complete before you can actually start a new thread.
A quick way of doing this would be to add
while ([mSomeThread isExecuting]) {
sleep(1);
}
immediately after [mSomeThread start];.
By the way I'd rather re-implement NSThread and setup a repetitive NSTimer inside its main implementation.
Something like:
- main {
#autoreleasepool {
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(someMethod) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] run];
}
}
I have some class A. In this class i have a method,
which calls [self performSelectorInBackground:...]. And it starts downloading
some info from internet.
After i tap Home button, then enter the app again, this background method keeps working.
So, if i call this method again, i have bad_access, because background method is already working and i call it twice.
Can i stop performing selector in background of the class A? For example in my applicationDidEnterBackground?
Or can i check, if selector is performing or something?
I found couple things like
[[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget:a];
[NSObject cancelPreviousPerformRequestsWithTarget:a selector:#selector(startDownload) object:nil];
But they didn't work for me.
So
my objAppDelegate:
#inteface ObjAppDelegate
{
A *a;
}
#implementation ObjAppDelegate
{
-(void)applicationDidEnterBackground:(UIApplication *)application
{
//or it can be didBecomeActive..
//here. check if background task of class A is running, or just stop it ??
}
}
#implementation A
{
//some timer, or event, etc.
-(void)startDownload
{
[self performSelectorInBackground:#selector(runBackgroundTask) withObject:nil];
}
-(void)runBackgroundTask
{
//some network stuff..
}
}
i did it like this:
threadForDownload = [[NSThread alloc] initWithTarget:self selector:#selector(threadMain:) object:nil];
[threadForDownload start];
[self performSelector:#selector(startDownload) onThread:threadForDownload withObject:nil waitUntilDone:NO];
(void)threadMain:(id)data {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (YES) {
[runloop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[pool release];
}
In my startDownload method i look at activity indicator to check, whether
startDownload is already running..
-(void)startDownload
{
if (![[UIApplication sharedApplication] isNetworkActivityIndicatorVisible]) // flag..
{
//....
}
}
// I make visible networkActivityIndicator every time i start downloading
You can easily create a BOOL instance variable to determine whether background task is active.
BOOL isBackgroundTaskRunning;
Then in runBackgroundTask
if (isBackgroundTaskRunning) {
// already running
return;
}
isBackgroundTaskRunning = TRUE;
...
isBackgroundTaskRunning = FALSE;
Here's what to do:
the background task saves its thread to a property somewhere using NSThread currentThread
the background task periodically checks the thread's isCancelled property.
the main thread sends cancel to the thread object saved by the background thread in step 1.
On exit, the background thread sets the property to nil.
All of the operations on the property used to store the thread in have to be protected by #synchronized or equivalent to prevent the main thread from sending cancel to a deallocated thread object.
The background thread can't do IO operations that block for more than a short period of time. In particular, synchronous downloading of URLs using NSURLConnection is out. If you are using NSURLConnection, you'll want to move to the asynchronous methods and a run loop (arguably, in that case, you can do away with the background thread altogether). If you are using POSIX level IO, use poll() with a timeout.
I don't think that it would be save to force the interruption of a method. What you can do is to change the state of your object and check that state inside your method implementation to early return in case of a cancel (but don't forget to release allocated objects).
This is how NSOperationQueue works. From the documentation:
Cancelling an operation does not immediately force it to stop what it is doing. Although respecting the value returned by the isCancelled is expected of all operations, your code must explicitly check the value returned by this method and abort as needed.
Run the method in a background thread, and keep a record of the NSThread. Then later, you can just end the thread.
I have a task that runs periodically and it was originally designed to run on a separate run loop than the main runloop using NSThread and NSTimer.
What's the best way to adapt this to take advantage of GCD?
Current code:
-(void)initiateSomeTask
{
[NSThread detachNewThreadSelector:#selector(startTimerTask)
toTarget:self withObject:nil];
}
-(void)startTimerTask
{
// We won't get back the main runloop since we're on a new thread
NSRunLoop *myRunLoop = [NSRunLoop currentRunLoop];
NSPort *myPort = [NSMachPort port];
[myRunLoop addPort:myPort forMode:NSDefaultRunLoopMode];
NSTimer *myTimer = [NSTimer timerWithTimeInterval:10 /* seconds */
target:self selector:#selector(doMyTaskMethod)
userInfo:nil repeats:YES];
[myRunLoop addTimer:myTimer forMode:NSRunLoopCommonModes];
[myRunLoop run];
}
Is there anything I can do besides replace detachNewThreadSelector with dispatch_async?
You can replace the use of NSTimer with use of dispatch_source_create with DISPATCH_SOURCE_TYPE_TIMER. You won't need a run loop then.
Back in the original case, though, you don't really need to make a thread or use dispatch to run a timer. Kind of the point of run loops is that you don't need to make a thread to do something simple like a timer.
I need to guarantee that the same thread performs various actions at arbitrary times. First the thread needs to initialize a library, then I want the thread to sleep until work needs to be done and upon user input, I need to be able to pass selectors or blocks for execution.
How can I setup an NSRunLoop to sleep after initialization? After which, how do I signal the run loop to wake up and do something?
I've tried reading the Threading Programming Guide for iOS, but I'd like to avoid setting up classes as custom input classes and use something more lightweight like performSelector:onThread:
Can I set a timer to fire forever from now so the run loop doesn't end?
Here's essentially what I want in pseudo-code:
// Initialization Code...
do {
sleepUntilSignaled();
doWorkSentToThisThread();
while (!done);
Where I send the work to do as a performSelector:onThread: message. It would be even better if I could send the run loop a block like: ^{[someObj message]; [otherObj otherMsg];} but I'd be happy with performSelector since I'm pretty sure that's possible without much extra coding.
Thanks!
You have all the necessary pieces together in your question. You start your thread and have it run it’s runloop. If you need the thread to do something you can use performSelector:onThread: on the main thread to do it.
There is one thing with the runloop you have to be aware though: It won’t run unless it has an input source or a timer attached to it. Just attach a timer to the run loop that fires some time in the distant future and you’re all set.
// Initialization code here
[NSTimer scheduledTimerWithTimeInterval: FLT_MAX
target: self selector: #selector(doNothing:)
userInfo: nil repeats:YES];
NSRunLoop *rl = [NSRunLoop currentRunLoop];
do {
[rl runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} while (!done);
Using performSelector:onThread:withObject: you can also pass your block to the background thread. All you need to do is to write a method somewhere that takes an block as a parameter and runs it:
#interface NSThread (sendBlockToBackground)
- (void) performBlock: (void (^)())block;
#end
#implementation NSThread (sendBlockToBackground)
- (void) performBlock: (void (^)())block;
{
[self performSelector: #selector(runBlock:)
onThread: self withObject: block waitUntilDone: NO];
}
- (void) runBlock: (void (^)())block;
{
block();
}
#end
But maybe you should use a dispatch queue instead of all this. This requires less code and probably has less overhead also:
dispatch_queue_t myQueue = dispatch_queue_create( "net.example.product.queue", NULL );
dispatch_async( myQueue, ^{
// Initialization code here
} );
// Submit block:
dispatch_async( myQueue, ^{
[someObject someMethod: someParameter];
} );
A dispatch queue created using dispatch_queue_create is a serial queue - all blocks sent to it will be performed in the same order they arrived, one after another.
Consider using NSConditionLock. It is designed for tasks like this. Imagine that you have a queue with data. First thread adds data to queue, second thread waits for data and processes it.
id condLock = [[NSConditionLock alloc] initWithCondition:NO_DATA];
//First thread
while(true)
{
[condLock lock];
/* Add data to the queue. */
[condLock unlockWithCondition:HAS_DATA];
}
//Second thread
while (true)
{
[condLock lockWhenCondition:HAS_DATA];
/* Remove data from the queue. */
[condLock unlockWithCondition:(isEmpty ? NO_DATA : HAS_DATA)];
// Process the data locally.
}
I think you can use NSInvocationOperation with NSOperationQueue.
I've been using an NSTimer successfully, but am now having trouble with it. Undoubtably something stupid. Appreciate another set of eyes. Running the debugger, I see that applicationDidFinishLaunching is called, but trigger is never called.
-(void) trigger:(NSTimer *) theTimer{
NSLog(#"timer fired");
}
- (void)applicationDidFinishLaunching:(UIApplication *)application {
nst = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(trigger) userInfo:nil repeats:YES];
[window makeKeyAndVisible];
}
The selector must have the following signature:
- (void)timerFireMethod:(NSTimer*)theTimer
so you need
#selector(trigger:)
--edit--
Maybe you are doing this somewhere else, but in the code you included you do not actually start the timer. You have to add it to a NSRunLoop before it can trigger any events at all.
[[NSRunLoop currentRunLoop] addTimer:nst forMode:NSDefaultRunLoopMode];
If I read the examples correctly. I've only used the one the init method that automatically adds it to the current NSRunLoop. You really should look at the developer docs that someone included in the comments to my post.
Two things:
1) as others say, the method should have the following signature..
-(void) trigger:(NSTimer *) theTimer;
and you make the timer thus:
nst = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(trigger:) userInfo:nil repeats:YES];
2) merely creating the timer does not run it. As the documentation says:
You must add the new timer to a run
loop, using addTimer:forMode:. Then,
after seconds have elapsed, the timer
fires, invoking invocation. (If the
timer is configured to repeat, there
is no need to subsequently re-add the
timer to the run loop.)
Here's a piece of real functioning code that you can model after. The timer creation is the same as yours, but it also adds it to runloop the right way.
[[NSRunLoop currentRunLoop] addTimer:
[NSTimer timerWithTimeInterval:0.1
target:self
selector:#selector(someSelector:)
userInfo:nil
repeats:NO]
forMode:NSDefaultRunLoopMode];
The selector you're giving the timer, trigger, indicates that it should call a method that takes no parameter. Either change your timer-fired method to
- (void)trigger
{
// look at me, I don't take any parameters
NSLog(#"timer fired");
}
or change your initial timer call to use #selector(trigger:).
Your problem is due to the fact that timerWithTimeInterval:target:selector:userInfo:repeats: creates a timer but does not schedule it on the run loop, you have to do it yourself.
However, you may as well use this method which creates the timer and schedules it on the run loop: scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
I had a problem when starting timer in - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { not in main thread.
dispatch_async(dispatch_get_main_queue(), ^{
[self startScheduledTimer];
});