Low-level details of the implementation of performSelectorOnMainThread:

后端 未结 4 1498
情歌与酒
情歌与酒 2021-02-06 07:13

Was wondering if anyone knows, or has pointers to good documentation that discusses, the low-level implementation details of Cocoa\'s \'performSelectorOnMainThread:\' method.

相关标签:
4条回答
  • 2021-02-06 07:33

    Yes, it does use Mach ports. What happens is this:

    1. A block of data encapsulating the perform info (the target object, the selector, the optional object argument to the selector, etc.) is enqueued in the thread's run loop info. This is done using @synchronized, which ultimately uses pthread_mutex_lock.
    2. CFRunLoopSourceSignal is called to signal that the source is ready to fire.
    3. CFRunLoopWakeUp is called to let the main thread's run loop know it's time to wake up. This is done using mach_msg.

    From the Apple docs:

    Version 1 sources are managed by the run loop and kernel. These sources use Mach ports to signal when the sources are ready to fire. A source is automatically signaled by the kernel when a message arrives on the source’s Mach port. The contents of the message are given to the source to process when the source is fired. The run loop sources for CFMachPort and CFMessagePort are currently implemented as version 1 sources.

    I'm looking at a stack trace right now, and this is what it shows:

    0 mach_msg
    1 CFRunLoopWakeUp
    2 -[NSThread _nq:]
    3 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:modes:]
    4 -[NSObject(NSThreadPerformAdditions) performSelectorOnMainThread:withObject:waitUntilDone:]
    

    Set a breakpoint on mach_msg and you'll be able to confirm it.

    0 讨论(0)
  • 2021-02-06 07:35

    One More Edit:

    To answer the question of the comment:

    what IPC mechanism is being used to pass info between threads? Shared memory? Sockets? Mach messaging?

    NSThread stores internally a reference to the main thread and via that reference you can get a reference to the NSRunloop of that thread. A NSRunloop internally is a linked list and by adding a NSTimer object to the runloop, a new linked list element is created and added to the list. So you could say it's shared memory, the linked list, that actually belongs to the main thread, is simply modified from within a different thread. There are mutexes/locks (possibly even NSLock objects) that will make sure editing the linked list is thread-safe.

    Pseudo code:

    // Main Thread
    
    for (;;) {
        lock(runloop->runloopLock);
        task = NULL;
        do {
            task = getNextTask(runloop);
            if (!task) {
                // function below unlocks the lock and
                // atomically sends thread to sleep.
                // If thread is woken up again, it will
                // get the lock again before continuing
                // running. See "man pthread_cond_wait"
                // as an example function that works
                // this way
                wait_for_notification(runloop->newTasks, runloop->runloopLock);
            }
        } while (!task);
        unlock(runloop->runloopLock);
        processTask(task);
    }
    
    
    // Other thread, perform selector on main thread
    // selector is char *, containing the selector
    // object is void *, reference to object
    
    timer = createTimerInPast(selector, object);
    runloop = getRunloopOfMainThread();
    lock(runloop->runloopLock);
    addTask(runloop, timer);
    wake_all_sleeping(runloop->newTasks);
    unlock(runloop->runloopLock);
    

    Of course this is oversimplified, most details are hidden between functions here. E.g. getNextTask will only return a timer, if the timer should have fired already. If the fire date for every timer is still in the future and there is no other event to process (like a keyboard, mouse event from UI or a sent notification), it would return NULL.


    I'm still not sure what the question is. A selector is nothing more than a C string containing the name of a method being called. Every method is a normal C function and there exists a string table, containing the method names as strings and function pointers. That are the very basics how Objective-C actually works.

    As I wrote below, a NSTimer object is created that gets a pointer to the target object and a pointer to a C string containing the method name and when the timer fires, it finds the right C method to call by using the string table (hence it needs the string name of the method) of the target object (hence it needs a reference to it).

    Not exactly the implementation, but pretty close to it:

    Every thread in Cocoa has a NSRunLoop (it's always there, you never need to create on for a thread). PerformSelectorOnMainThread creates a NSTimer object like this, one that fires only once and where the time to fire is already located in the past (so it needs firing immediately), then gets the NSRunLoop of the main thread and adds the timer object there. As soon as the main thread goes idle, it searches for the next event in its Runloop to process (or goes to sleep if there is nothing to process and being woken up again as soon as an event is added) and performs it. Either the main thread is busy when you schedule the call, in which case it will process the timer event as soon as it has finished its current task or it is sleeping at the moment, in which case it will be woken up by adding the event and processes it immediately.

    A good source to look up how Apple is most likely doing it (nobody can say for sure, as after all its closed source) is GNUStep. Since the GCC can handle Objective-C (it's not just an extension only Apple ships, even the standard GCC can handle it), however, having Obj-C without all the basic classes Apple ships is rather useless, the GNU community tried to re-implement the most common Obj-C classes you use on Mac and their implementation is OpenSource.

    Here you can download a recent source package.

    Unpack that and have a look at the implementation of NSThread, NSObject and NSTimer for details. I guess Apple is not doing it much different, I could probably prove it using gdb, but why would they do it much different than that approach? It's a clever approach that works very well :)

    0 讨论(0)
  • 2021-02-06 07:42

    As Mecki said, a more general mechanism that could be used to implement -performSelectorOn… is NSTimer.

    NSTimer is toll-free bridged to CFRunLoopTimer. An implementation of CFRunLoopTimer – although not necessarily the one actually used for normal processes in OS X – can be found in CFLite (open-source subset of CoreFoundation; package CF-476.14 in the Darwin 9.4 source code. (CF-476.15, corresponding to OS X 10.5.5, is not yet available.)

    0 讨论(0)
  • 2021-02-06 07:49

    The documentation for NSObject's performSelectorOnMainThread:withObject:waitUntilDone: method says:

    This method queues the message on the run loop of the main thread using the default run loop modes—that is, the modes associated with the NSRunLoopCommonModes constant. As part of its normal run loop processing, the main thread dequeues the message (assuming it is running in one of the default run loop modes) and invokes the desired method.

    0 讨论(0)
提交回复
热议问题