performSelectorOnMainThread的低级执行细节:

时间:2008-09-29 16:06:29

标签: objective-c cocoa

想知道是否有人知道或者指向讨论Cocoa的'performSelectorOnMainThread:'方法的低级实现细节的好文档。

我最好的猜测,我认为可能非常接近,就是它使用马赫端口或它们之上的抽象来提供线程内通信,将选择器信息作为马赫消息的一部分传递。

右?错误?谢谢!

更新09:39 AMPST

谢谢Evan DiBiase和Mecki的答案,但要澄清一下:我理解运行循环中会发生什么,但我正在寻找答案的是; “其中是排队的方法? 是如何将选择器信息传递到队列中的?”寻找超过Apple的文档信息:我读过'em

更新14:21PST

Chris Hanson在评论中提出了一个很好的观点:我的目标不是学习底层机制,以便在我自己的代码中利用它们。相反,我只是对更好地概念性地理解另一个线程执行代码的过程感兴趣。正如我所说的,我自己的研究让我相信它利用了IPC的消息传递来在线程之间传递选择器信息,但我特别在寻找关于正在发生的事情的具体信息,所以我可以肯定我正确理解 。谢谢!

更新03/06/09

我已经开启了这个问题的赏金,因为我真的希望看到它的回答,但是如果你想收集请确保你阅读一切,包括所有当前提出的答案,评论这些答案和我原来的问题,以及我上面发布的更新文本。我正在寻找performSelectorOnMainThread:等使用的机制的最低级细节,正如我之前提到的,我怀疑它与Mach端口有关,但我'我真的很想知道。除非我确认给出的答案是正确的,否则不会获得赏金。谢谢大家!

4 个答案:

答案 0 :(得分:10)

是的,确实使用了Mach端口。会发生什么:

  1. 封装执行信息的数据块(目标对象,选择器,选择器的可选对象参数等)在线程的运行循环信息中排队。这是使用@synchronized完成的,最终使用pthread_mutex_lock
  2. 调用CFRunLoopSourceSignal表示源已准备好开火。
  3. 调用CFRunLoopWakeUp让主线程的运行循环知道是时候唤醒。这是使用mach_msg完成的。
  4. 来自Apple文档:

      

    版本1源由运行循环和内核管理。这些信号源使用马赫端口在信号源准备好发射时发出信号。当消息到达源的Mach端口时,内核会自动发信号通知源。当触发源时,消息的内容将被提供给要处理的源。 CFMachPort和CFMessagePort的运行循环源目前实现为版本1源。

    我现在正在查看堆栈跟踪,这就是它显示的内容:

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

    在mach_msg上设置断点,您将能够确认它。

答案 1 :(得分:2)

documentation for NSObject's performSelectorOnMainThread:withObject:waitUntilDone: method说:

  

此方法使用默认的运行循环模式(即与NSRunLoopCommonModes常量关联的模式)在主线程的运行循环上对消息进行排队。作为正常运行循环处理的一部分,主线程使消息出列(假设它在一个默认的运行循环模式下运行)并调用所需的方法。

答案 2 :(得分:2)

再一次编辑:

回答评论的问题:

  

正在使用什么IPC机制   在线程之间传递信息?共享   记忆?套接字?马赫消息?

NSThread在内部存储对主线程的引用,通过该引用,您可以获得对该线程的NSRunloop的引用。 NSRunloop内部是链接列表,通过向runloop添加NSTimer对象,将创建一个新的链接列表元素并将其添加到列表中。所以你可以说它的共享内存,实际上属于主线程的链表,只是在不同的线程中修改。有互斥/锁(甚至可能是NSLock对象),这将确保编辑链表是线程安全的。

伪代码:

// 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);

当然这是过于简单的,大多数细节隐藏在这里的功能之间。例如。 getNextTask只返回一个计时器,如果计时器已经被解雇了。如果每个计时器的开火日期仍在将来并且没有其他事件要处理(例如键盘,来自UI的鼠标事件或发送的通知),它将返回NULL。


我仍然不确定问题是什么。 选择器只不过是一个包含被调用方法名称的C字符串。每个方法都是一个普通的C函数,并且存在一个字符串表,其中包含方法名称作为字符串和函数指针。这就是Objective-C实际工作的基础。

正如我在下面写的,创建了一个NSTimer对象,它获取一个指向目标对象的指针和一个指向包含方法名称的C字符串的指针,当计时器触发时,它通过使用字符串找到要调用的正确C方法table(因此它需要方法的字符串名称)(因此它需要对它的引用)。

不完全是实现,但非常接近它:

Cocoa中的每个线程都有一个NSRunLoop(它总是在那里,你永远不需要为一个线程创建)。 PerformSelectorOnMainThread创建一个像this这样的NSTimer对象,一个只触发一次并且过去已经定位的时间(因此需要立即触发),然后获取主线程的NSRunLoop并添加定时器对象那里。一旦主线程空闲,它就会在其Runloop中搜索下一个要处理的事件(如果没有任何事情要处理,则会进入休眠状态,并在添加事件后再次唤醒并执行它。当您安排呼叫时,主线程正忙,在这种情况下,它将在完成当前任务后立即处理计时器事件,或者此时它正在休眠,在这种情况下,它将通过添加事件来唤醒并立即处理它。

一个很好的资源来查找Apple最有可能做到这一点(没有人可以肯定地说,因为它毕竟是关闭源代码的)是GNUStep。由于GCC可以处理Objective-C(它不仅仅是Apple扩展,只有标准GCC可以处理它),然而,如果没有Apple运送的所有基本类的Obj-C相当无用,那么GNU社区试图重新使用它 - 实现你在Mac上使用的最常见的Obj-C类,它们的实现是OpenSource。

Here您可以下载最近的源包。

解压缩并详细了解NSThread,NSObject和NSTimer的实现。我猜Apple没有做太多不同,我可能用gdb来证明它,但为什么他们会采用与那种方法有很大不同呢?这是一个非常有效的聪明方法:)

答案 3 :(得分:0)

正如Mecki所说,可用于实施-performSelectorOn…的更一般机制是NSTimer

NSTimer免费桥接到CFRunLoopTimerCFRunLoopTimer的实现 - 虽然不一定是OS X中正常进程实际使用的实现 - 可以在CFLite(CoreFoundation的开源子集; Darwin 9.4 source code中的包CF-476.14)中找到。(CF与OS X 10.5.5相对应的-476.15尚不可用。)