I'm trying to figure out how NSRunLoop works.
So there are a few delayed tasks and I want to perform them in a few seconds using NSRunLoop. And I want to create NSRunLoop manually. How am I supposed to do this?
NSRunLoop *loop = [NSRunLoop currentRunLoop];
//create delayed tasks
[object performSelector:NSSelectorFromString(#"firstMethod") withObject:firstArgument afterDelay:5.0];
[object performSelector:NSSelectorFromString(#"secondMethod") withObject:secondArgument afterDelay:3.0];
//and here I must run a loop
while(flag&&[loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
And also I have somehow to stop this loop. As you see I'm totally confused and lots of Apple's documentation and topics here didn't help me.
To the Q and the comments:
Of course, they are never performed. -performSelector:… is attached to the thread's current run loop. This run loop is never reached, because your program get stuck in your private run loop.
Moreover, it is simply not possible, to attach a run loop to a thread:
Your application cannot either create or explicitly manage NSRunLoop objects. Each NSThread object, including the application’s main thread, has an NSRunLoop object automatically created for it as needed. If you need to access the current thread’s run loop, you do so with the class method currentRunLoop.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSRunLoop_Class/index.html
Related
I created a simple singleton and run method in it:
- (void)run {
static int times = 0;
NSLog(#"times = %d", times++);
[self performSelector:#selector(run) withObject:nil afterDelay:MIN_DELAY];
}
But it doesn't work properly. It is executed only once.
But if I replace performSelector:withObject:afterDelay: with performSelector: then it will be called a lot of times (but I need a delay between calls).
So why method performSelector:withObject:afterDelay: doesn't work? And can I use this method at all?
Calls to -performSelector:withObject:afterDelay: require a run loop. Console applications do not, by default, pass control into the run loop ever. For more info, search for NSRunLoop.
From the docs:
This method registers with the runloop of its current context, and depends on that runloop being run on a regular basis to perform correctly.
You have no runloop. Ipso facto, this method does not perform correctly for you.
(Creating and starting a runloop is one of the things that calling UIApplicationMain does, but of course you are never calling it.)
I'm getting to know the NS/Objective-C model of concurrency. Say I have a command line tool that does something like this:
#include "myLibrary.h"
void callback(void* parameter){
cout<<"callback called.\n";
//some logic...
}
int main(int argc, char* argv[]){
myLibraryInit(callback);
std::string s;
while(true){
cin>>s;
myLibrarysResponseTo(s);
}
}
In my library, I'd like to be able to have two responses. One which starts a repeating timer and one which stops it. The timer should call the callback supplied to the library by myLibraryInit.
I've used NSTimers before in iPhone/iPad apps, and I think the problem stems from the different paradigm command line tools have. The main thread goes into main and never finishes it until the program is finished. This means it's not free to run the main run loop, which is what gets the timer going. I think. So how do I make an NSTimer work in this context?
The other thing is that Apple NSTimer documentation says I need to invalidate an NSTimer on the same thread it was installed. I don't know how to figure out what thread I was on when I installed the timer, and then keep track of it (and ensure it stays alive) until I want to invalidate the timer. I'm not sure if I'm just missing an obvious mapping between threads and dispatch queues, run loops, or something else. I am using core bluetooth and I initialize a central manager like so:
_centralManager=[[CBCentralManager alloc]
initWithDelegate: self
queue: dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
];
so a callback may be triggered from here. If the callback includes some logic to call the library function that stops the timer, I can't guarantee from which thread came the invalidate. So how do I properly invalidate the timer?
I found this question but it doesn't allow a main to happen at the same time as the run loop that that the timer is on.
I hope I gave enough context. Thanks in advance for your replies.
You must call dispatch_main() or run an NSRunLoop in the main thread if any of the system frameworks [that use GCD or asynchronous operations] are to work correctly.
This can be as simple as calling [[NSRunLoop currentRunLoop] run]; at the end of your main() function (just make sure you schedule the kickoff work first as that method never returns).
In my application at some point I have a bunch of messages scheduled using performSelector.
Under some conditions, while handling an UI action, I need to wait for all the currently scheduled selectors to fire.
I could place my code in another method and schedule it using performSelector:target:argument:order:modes: with order value high enough to be sure it will fire last, but there are reasons why I think that would make an ugly solution.
So I send [[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]] and it seems to work just like I need it to.
Still, I'm not sure if that is a legitimate way to let the run loop roll for just one iteration. Also, are there any potential troubles associated with this approach?
Okay, answering my own question.
First of all it's a duplicate (also, this).
Next, generally, sending [[NSRunLoop currentRunLoop] runUntilDate:[NSDate date]] from within the same run loop is a bad idea:
In theory, autorelease pool will get drained that way. In practice, I've not been able to make my app crash by using objects allocated pre-runUntilDate (under ARC), but better not to risk anyway.
If somehow another action gets dequeued during that runUntilDate it might cause some unexpected side effects.
TL;DR I should do myself a favor and replace that piece of code with something more deterministic.
I've run into an while trying to launch an NSTask from inside an NSOperation.
Here's a very simple app I've put together to showcase the problem. When you click the button, an NSOperation is queued. It sets up an NSRunLoop, and calls a method which invokes an NSTask. The task is really simple- it just launches /bin/sleep for two seconds (enough to easily see the spinner when things are working properly).
The app works as advertised, however if you change line 23 of TaskPerformer.m to an autorelease, (sorry, I'm a new poster so I can't link directly) or comment it out entirely (thus leaking the NSTask object), the NSOperation's thread will never exit. Its main runloop seems to be blocking on something.
Now, the problem here is twofold. First off, I don't understand why my thread is blocking, but moreover, if I turn on garbage collection for this application, the same behavior manifests itself. Because there's no way for me to manually release the NSTask, the thread blocks no matter what.
If someone could tell me what's going on, I'd be eternally grateful!
I see a couple different issues in the sample project you posted. In TaskPerformer.m you have:
[task waitUntilExit];
[task launch];
The waitUntilExit call is meant to be called after launching the task, and will simply block and do nothing until the task has completed. If you only care about waiting until the task has finished, and not about getting output from it or anything, then you should be able to just call launch followed by waitUntilExit, and not have to bother messing with the run loop at all.
If you do want to get output from the task though, then you'll want to get its standardOutput, which by default should return you an instance of NSFileHandle. You can then call readDataOfLength: or readDataToEndOfFile, both of which will block and return data from the process when data is available.
Since this entire thing is going to be done in a background thread anyway, it's OK that these methods block, but you wouldn't want to do the same thing on the main thread, since that would lock up the interface for the user until the task was complete. If you did this on the main thread, you'd want to use NSFileHandle's readInBackgroundAndNotify and friends. For a background thread though, using NSRunLoop shouldn't really be necessary.
I figured out what was going on here. Turns out my call to [runLoop run] was setting up the loop and running it indefinitely. It was never falling down into the while (!done) loop. Turns out that after calling run, an NSRunLoop will run until it has no more inputs. Calling release on my NSTask led to exactly this scenario, so (quite by accident) my runloop exited.
The solution was to remove [runLoop run] and simply rely on my own while loop. Hope this helps somebody else!
how to use how to use NSRunLoop in objective-C and wait for some variable to change value ?
Thanks
We would not normally use a NSRunLoop in production to wait for a variable to change. One could use a callback.
However, in unit test code we do have the following:
NSDate *twoSecondsFromNow = [NSDate dateWithTimeIntervalSinceNow:2.0];
while (!callBackInvoked && !errorHasOccured && runCount-- && [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:twoSecondsFromNow]) {
twoSecondsFromNow = [NSDate dateWithTimeIntervalSinceNow:2.0];
}
The code waits until either our callback is invoked, an error occured or the number of 2 second periods we've waited for has occurred. We use this to test delegates that make callbacks.
As I said I would not do this in production code.
You would generally not use NSRunLoop directly in your code.
You would for example create GUI application which already has NSRunLoop in it (use predefined application code templates in Xcode).
It depends what the variable is that is supposed to change, you might have it somewhere inside your 'Model' object and it will be changed by some even like data arriving online, or linked to GUI object and user performed action.
If it is button you would setup
handlers to invoke action.
If it is variable you would setup
KVC/KVO to detect change and call
handler.
And so on, Cocoa will handle the glue code for you, you just need to setup appropriate handling to perform action.
There's not enough details in your question, I would suggest having a look at some basic tutorial at Apple's site for developers to see what is available in Cocoa.