又一编辑:
要回答评论的问题:
使用什么 IPC 机制在线程之间传递信息?共享内存?插座?马赫消息?
NSThread 在内部存储对主线程的引用,通过该引用,您可以获得对该线程的 NSRunloop 的引用。NSRunloop 内部是一个链表,通过将 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 方法(因此它需要目标对象的方法的字符串名称)(因此它需要对它的引用)。
不完全是实现,但非常接近它:
Cocoa 中的每个线程都有一个 NSRunLoop(它总是在那里,你永远不需要为线程创建)。PerformSelectorOnMainThread 创建一个像这样的 NSTimer 对象,它只触发一次并且触发时间已经位于过去(因此它需要立即触发),然后获取主线程的 NSRunLoop 并在那里添加计时器对象。一旦主线程空闲,它会在其 Runloop 中搜索下一个要处理的事件(如果没有要处理的内容,则进入睡眠状态,并在添加事件后立即再次唤醒)并执行它。调度调用时主线程正忙,在这种情况下,它会在完成当前任务后立即处理计时器事件,或者此时它正在休眠,在这种情况下,它将通过添加事件来唤醒并立即处理。
了解 Apple最有可能如何做到这一点的一个很好的来源(没有人可以肯定地说,毕竟它是封闭的源代码)是 GNUStep。由于 GCC 可以处理 Objective-C(它不仅仅是 Apple 提供的扩展,甚至标准的 GCC 也可以处理),然而,没有 Apple 提供的所有基本类的 Obj-C 是相当无用的,GNU 社区试图重新- 实现您在 Mac 上使用的最常见的 Obj-C 类,它们的实现是开源的。
在这里您可以下载最近的源包。
将其解压缩并查看 NSThread、NSObject 和 NSTimer 的实现以了解详细信息。我猜 Apple 的做法并没有太大的不同,我可能可以使用 gdb 来证明这一点,但为什么他们的做法与这种方法有很大不同呢?这是一种非常有效的聪明方法:)