The main
method of your NSThread subclass is everything that runs on that thread. You cannot interrupt it to run other code without the main
method's cooperation.
What you really should do is throw out that entire loop and replace it with NSRunLoop and NSTimer.
- NSRunLoop keeps the thread alive as long as there's something it will need to do, but also sleeps the thread until it needs to do something.
- NSTimer does something on a repeating interval as long as it's scheduled on a run loop.
You need your thread to do two things:
- send the MyThread object a
heartBeats
message (you're doing this)
- send the view controller a
someMethod
message (this is what you asked about)
For the latter, you need one additional thing: A run loop source.
So, clear out your main
method and replace it with the following:
- Get the current NSRunLoop and store it in a local variable.
- Create an NSTimer with a 10-second interval, whose target is
self
and selector is heartBeats
. (Slightly cleaner version: Have another method that takes an NSTimer *but ignores it, so your timer calls that method and that method calls heartBeats
. The proper way to set up a timer is to give it a method that expects to be called with a timer, but, in practice, giving it a method that takes no arguments works, too.)
- If you didn't create the timer using
scheduledTimerWith…
, add it to the run loop. (The scheduledTimerWith…
methods do this for you.)
- Create a run loop source and add it to the run loop.
- Call
[myRunLoop run]
.
Step 4 bears explaining:
Core Foundation (but not Foundation; I don't know why) has something called “run loop sources”, which are custom objects that can be added to a run loop and will call something once signaled.
Sounds like what you want, to call your view controller method!
First, in the view controller, change myThread
from a local variable in viewDidLoad
to an instance variable. Rename it _myThread
to make that clear.
Next, add a delegate
property to MyThread
. This should be weak
and have type id <MyThreadDelegate>
. You'll also need to define a MyThreadDelegate
protocol, which should have one method taking no arguments and returning nothing (void
).
You should now be able to set _myThread.delegate = self
from the view controller, and implement in the view controller the method that you declared in the protocol. The view controller is now the delegate of its MyThread.
In -[MyThread main]
, create a version-0 CFRunLoopSource. The Create function takes a “context” structure, containing, among other things, the version (0), an “info” pointer (set this to self
, i.e., the MyThread) and a Perform callback (a function, which will be called with the info
pointer as its only argument).
In your perform callback, you'll need to do something like this:
MyThread *self = (__bridge MyThread *)info;
[self fireDelegateMessage];
In MyThread, implement that fireDelegateMessage
method. In there, send self.delegate
the message you declared in your protocol.
Next, add a public method to MyThread (i.e., declare it in MyThread.h as well as implementing it in MyThread.m) named something like “requestDelegateMessage”. In this method, call CFRunLoopSourceSignal
on the run loop source. (The documentation for that function suggests that you also need to call CFRunLoopWakeUp
on the thread's CFRunLoop
. Try it without first.)
Lastly, when the view controller wants someMethod
to be called on that thread, call [_myThread requestDelegateMessage]
.
So:
- the view controller calls
requestDelegateMessage
requestDelegateMessage
signals the run loop source (and wakes up the run loop, if that is needed)
- the run loop source calls the perform callback on the MyThread's thread
- the perform callback calls
fireDelegateMessage
on the MyThread's thread
fireDelegateMessage
calls the view controller's implementation of the delegate method on the MyThread's thread
- the view controller calls
someMethod
on the MyThread's thread