In the code that I show below, I have created a thread that creates random numbers between 0 to 15, and it stops when it comes out 3, changing the end parameter. After I added a run loop observer (that "observe" the end parameter) to the run loop of the main thread. As you can see, both the run loop observer and my thread, sleep for 1 second before printing, so I expect that in the console, observer's print and the print of my thread is alternate. Which is not the case. I believe, if I understand it, it would depend on CFrunloopActivity
parameter and its possible combinations.
Does anyone can explain the operation of this parameter? If yes, there is a combination to have alternating prints? If you can not have alternating prints, how does the observer work inside the run loop of the main thread ?
Thank you
This is the code :
class ViewController: UIViewController {
var end = false
override func viewDidLoad() {
super.viewDidLoad()
//my thread
performSelector(inBackground: #selector(rununtil3(thread:)), with: Thread.current)
//the observer
let runLoopObserver = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFRunLoopActivity.entry.rawValue | CFRunLoopActivity.exit.rawValue , true , 0 , {
(observer: CFRunLoopObserver?, activity: CFRunLoopActivity) -> Void in
Thread.sleep(until: Date(timeIntervalSinceNow: 1))
print("+++ is main?: \(Thread.isMainThread)")
if self.end == true {
//print the end of my thread and remove the observer from main run loop
print("end of own thread")
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, CFRunLoopMode.commonModes)
return
}
//CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, CFRunLoopMode.commonModes)
})
//add observer to main run loop
CFRunLoopAddObserver(CFRunLoopGetCurrent(), runLoopObserver, CFRunLoopMode.commonModes)
print("Out of observer")
}
func rununtil3(thread : Thread) {
print("main?: \(thread.isMainThread) is run : \(thread.isExecuting)")
while true {
let ran = Int (arc4random() % 15 )
Thread.sleep(until: Date(timeIntervalSinceNow: 1))
print("\(ran) . main is run : \(thread.isExecuting)")
if ran == 3 {
end = true
Thread.exit()
}
}
}
}
You are creating a runloop observer and asking to be notified when the runloop either starts operation (its
enter
event) orexit
s. Runloop activities are documented here.enter
events are delivered when a runloop is started byCFRunLoopRun()
or similar. If you manually create a runloop, add anenter
observer to it, then callCFRunLoopRun()
on your new runloop, you will receive anenter
event at that time. If you later callCFRunLoopStop()
on your runloop, you will receive anexit
event.When you add an
enter
observer to an already running runloop, you will receive anenter
event. This is to keep your observer state consistent with the actual state of the runloop.In your code, you create a runloop observer then attach it to the main thread's runloop (aka the "Main Runloop").
The OS creates the Main Runloop automatically for you at program start and automatically calls
CFRunLoopRun()
on it.CFRunLoopStop()
is never called, so the main runloop effectively runs forever.Since the main runloop is already running, you receive an
enter
event. Since the main runloop does not stop, you never see anexit
event.It's important to note that a runloop observer is bound to the specific runloop you add it to, not to the lifecycle of some arbitrary background thread or property (i.e. your
end
property is not the thing being observed).To your second question about how to get threads to alternate, that's a question with a very broad answer and it depends highly on what you want to do. I won't attempt to answer all of that here, only give you some ideas.
You might be best served by not creating a background thread at all, and instead adding a timer to the main runloop that fires every second. Then you'll get periodic behavior.
If you really want to use a background thread, then you should read a good operating systems book on thread communication and synchronization. A common thing to do on iOS/OS X is to use a background thread, then use something like
DispatchQueue.main.async { }
to signal back to the main thread that your processing is complete. There are many examples of how to do this if you search a bit.You might also read about thread synchronization using semaphores or condition variables. One thing you definitely do NOT want to do is call
Thread.sleep()
on the main thread as you are doing in your observer callback. If you wait on the main thread too long the operating system will kill your app. It's better to keep background threads totally independent of the main thread and then call back onto the main thread using aDispatchQueue
call as I mentioned above in #2.