RunLoopObserver and RunLoopActivity

2k views Asked by At

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()
            }
        }
    }
}
1

There are 1 answers

0
par On BEST ANSWER

You are creating a runloop observer and asking to be notified when the runloop either starts operation (its enter event) or exits. Runloop activities are documented here.

enter events are delivered when a runloop is started by CFRunLoopRun() or similar. If you manually create a runloop, add an enter observer to it, then call CFRunLoopRun() on your new runloop, you will receive an enter event at that time. If you later call CFRunLoopStop() on your runloop, you will receive an exit event.

When you add an enter observer to an already running runloop, you will receive an enter 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 an exit 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.

  1. 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.

  2. 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.

  3. 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 a DispatchQueue call as I mentioned above in #2.