mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 04:17:17 +00:00
docs: 内容
This commit is contained in:
@@ -200,7 +200,7 @@
|
||||
**__CFRunLoopModeIsEmpty**
|
||||
|
||||
此函数的作用就是判断这个 Mode 下面有没有 source0、source1、timer,只要存在就说明当前 Mode 不是空的,同时看看这个 Mode 是不是属于当前的 RunLoop
|
||||
|
||||
|
||||
|
||||
|
||||
```objective-c
|
||||
@@ -768,3 +768,341 @@
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
- performSelector 是在 source0 上实现的
|
||||
- RunLoopTimer 外部接口设置的精度,精度大于0,则使用 dispatch_source_set_timer,精度小于0,则使用 mk_timer_arm
|
||||
- timer_source 使用 dispatch_timer_STRICT 创建,则系统会尽最大努力遵守设置的 leeway 值
|
||||
- NSTimer 不准的原因:底层 RunLoop Timer 底层使用的 timer 的精度不高(mk_timer);与 RunLoop 底层的调用机制有关系
|
||||
- 那么为什么存在 RunLoopTimer?意义是什么?应用场景
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
```c++
|
||||
// Data structure to hold TSD data, cleanup functions for each
|
||||
typedef struct __CFTSDTable {
|
||||
uint32_t destructorCount;
|
||||
uintptr_t data[CF_TSD_MAX_SLOTS];
|
||||
tsdDestructor destructors[CF_TSD_MAX_SLOTS];
|
||||
} __CFTSDTable;
|
||||
```
|
||||
|
||||
```c++
|
||||
// 主线程 RunLoop
|
||||
CFRunLoopRef CFRunLoopGetMain(void) {
|
||||
CHECK_FOR_FORK();
|
||||
// 局部静态变量
|
||||
static CFRunLoopRef __main = NULL; // no retain needed
|
||||
// 创建主线程对应的 RunLoop
|
||||
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
|
||||
return __main;
|
||||
}
|
||||
|
||||
// 子线程 RunLoop
|
||||
// 先从 __CFTSDTable 获取 RunLoop,如果有则 return,没有则调用 _CFRunLoopGet0,_CFRunLoopGet0 内部调用 __CFRunLoopCreate 创建 CFRunLoop,然后写入 __CFTSDTable
|
||||
CFRunLoopRef CFRunLoopGetCurrent(void) {
|
||||
CHECK_FOR_FORK();
|
||||
// __CFTSDTable
|
||||
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
|
||||
if (rl) return rl;
|
||||
// 没有则创建
|
||||
return _CFRunLoopGet0(pthread_self());
|
||||
}
|
||||
|
||||
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(_CFThreadRef t) {
|
||||
if (pthread_equal(t, kNilPthreadT)) {
|
||||
t = pthread_main_thread_np();
|
||||
}
|
||||
__CFLock(&loopsLock);
|
||||
if (!__CFRunLoops) {
|
||||
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
|
||||
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
|
||||
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
|
||||
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
|
||||
CFRelease(dict);
|
||||
}
|
||||
CFRelease(mainLoop);
|
||||
}
|
||||
CFRunLoopRef newLoop = NULL;
|
||||
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
|
||||
if (!loop) {
|
||||
newLoop = __CFRunLoopCreate(t);
|
||||
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
|
||||
loop = newLoop;
|
||||
}
|
||||
__CFUnlock(&loopsLock);
|
||||
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
|
||||
if (newLoop) { CFRelease(newLoop); }
|
||||
|
||||
if (pthread_equal(t, pthread_self())) {
|
||||
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
|
||||
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
|
||||
#if _POSIX_THREADS
|
||||
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
|
||||
#else
|
||||
_CFSetTSD(__CFTSDKeyRunLoopCntr, 0, &__CFFinalizeRunLoop);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return loop;
|
||||
}
|
||||
```
|
||||
|
||||
- 为什么只有当 RunLoop 中存在 Timer Sourcrs、Input Sources 时,才能保证 RunLoop 不退出?
|
||||
|
||||
RunLoop 本质就是一个有条件的 do...while 循环。__CFRunLoopModeIsEmpty 里面去判断 source0、source1、timers 不存在则 while 循环条件不满足,RunLoop 退出
|
||||
|
||||
```c++
|
||||
void CFRunLoopRun(void) { /* DOES CALLOUT */
|
||||
int32_t result;
|
||||
do {
|
||||
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
|
||||
CHECK_FOR_FORK();
|
||||
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
|
||||
}
|
||||
|
||||
// expects rl and rlm locked
|
||||
static Boolean __CFRunLoopModeIsEmpty(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopModeRef previousMode) {
|
||||
// ...
|
||||
if (NULL != rlm->_sources0 && 0 < CFSetGetCount(rlm->_sources0)) return false;
|
||||
if (NULL != rlm->_sources1 && 0 < CFSetGetCount(rlm->_sources1)) return false;
|
||||
if (NULL != rlm->_timers && 0 < CFArrayGetCount(rlm->_timers)) return false;
|
||||
// ...
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
|
||||
// ...
|
||||
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
|
||||
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
|
||||
Boolean did = false;
|
||||
if (currentMode) __CFRunLoopModeUnlock(currentMode);
|
||||
__CFRunLoopUnlock(rl);
|
||||
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
|
||||
}
|
||||
// ...
|
||||
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
|
||||
// ...
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
## 线程保活技术:
|
||||
|
||||
```objective-c
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
[self testKeepAliveThread];
|
||||
}
|
||||
|
||||
- (void)testKeepAliveThread
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
self.thread = (ThreadKeepLive *)[[NSThread alloc] initWithTarget:self selector:@selector(threadTask) object:nil];
|
||||
[self.thread setName:@"LongLiveThread"];
|
||||
[self.thread setQualityOfService:[NSThread mainThread].qualityOfService];
|
||||
[self.thread start];
|
||||
});
|
||||
}
|
||||
|
||||
- (void)threadTask
|
||||
{
|
||||
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
|
||||
NSLog(@"come");
|
||||
}];
|
||||
|
||||
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
|
||||
while (true) {
|
||||
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- come 会打印几次?1次,因为 RunLoop 跑起来之后,后面的代码不会执行,内部不断的 do...while
|
||||
|
||||
- NSRunLoop 常驻线程可以运行在 NSRunLoopCommonModes 下吗?
|
||||
|
||||
不可以。因为在跑的时候如果 modeName 等于 kCFRunLoopCommonModes 则直接 kCFRunLoopRunFinished,则 RunLoop 的 while 循环条件失败
|
||||
|
||||
```objective-c
|
||||
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
|
||||
CHECK_FOR_FORK();
|
||||
if (modeName == NULL || modeName == kCFRunLoopCommonModes || CFEqual(modeName, kCFRunLoopCommonModes)) {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
CFLog(kCFLogLevelError, CFSTR("invalid mode '%@' provided to CFRunLoopRunSpecific - break on _CFRunLoopError_RunCalledWithInvalidMode to debug. This message will only appear once per execution."), modeName);
|
||||
_CFRunLoopError_RunCalledWithInvalidMode();
|
||||
});
|
||||
return kCFRunLoopRunFinished;
|
||||
}
|
||||
// ...
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Mach Port 跨线程通信
|
||||
|
||||
1. Mach IPC 基于 Mach 内核实现进程间通讯。
|
||||
|
||||
2. Mach IPC 被抽象为3种操作:messages、ports、and port sets
|
||||
|
||||
3. Mach port 跨线程通信
|
||||
|
||||
线程 B 有个 port 在等待消息,具有消息接收权限,等待消息到来的时候会阻塞当前线程
|
||||
|
||||
线程 A 有个 port 在发送消息,具有发送消息权限,要把发送的消息包装成消息,通过消息队列传递,message 包括:header(目的地 port、size)、data。
|
||||
|
||||
线程 B 收到消息后,解除 block,线程继续向下运行
|
||||
|
||||

|
||||
|
||||
4. Mach port 如何进行跨线程通信?
|
||||
|
||||
线程开启一个 port,然后给 port 申请接收、发送的权限。mach_msg 是通信函数,在等待消息的时候不加 timeout 则会一直阻塞,等到消息到来
|
||||
|
||||

|
||||
|
||||
5. 在测试工作中 main.m 文件中打印当前的 RunLoop
|
||||
|
||||
```objective-c
|
||||
int main(int argc, char * argv[]) {
|
||||
NSString * appDelegateClassName;
|
||||
@autoreleasepool {
|
||||
appDelegateClassName = NSStringFromClass([AppDelegate class]);
|
||||
}
|
||||
NSLog(@"%@", NSRunLoop.currentRunLoop);
|
||||
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
|
||||
}
|
||||
|
||||
//
|
||||
2020-08-13 09:49:41.326621+0800 ***[52423:2383402] <CFRunLoop 0x6000015f0000 [0x7fff8062d750]>{wakeup port = 0x1103, stopped = false, ignoreWakeUps = true,
|
||||
current mode = (none),
|
||||
common modes = <CFBasicHash 0x6000027f1320 [0x7fff8062d750]>{type = mutable set, count = 1,
|
||||
entries =>
|
||||
2 : <CFString 0x7fff80640a20 [0x7fff8062d750]>{contents = "kCFRunLoopDefaultMode"}
|
||||
}
|
||||
,
|
||||
common mode items = (null),
|
||||
modes = <CFBasicHash 0x6000027f1290 [0x7fff8062d750]>{type = mutable set, count = 1,
|
||||
entries =>
|
||||
2 : <CFRunLoopMode 0x6000012fc0d0 [0x7fff8062d750]>{name = kCFRunLoopDefaultMode, port set = 0x1003, queue = 0x6000007f0100, source = 0x6000007f0280 (not fired), timer port = 0xe03,
|
||||
sources0 = (null),
|
||||
sources1 = (null),
|
||||
observers = (null),
|
||||
timers = (null),
|
||||
currently 618976181 (257342842892563) / soft deadline in: 1.84464867e+10 sec (@ -1) / hard deadline in: 1.84464867e+10 sec (@ -1)
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
可以看到 main.m 中,还没有 return 的时候当前 RunLoop 的内部结构中存在一个 **wakeup port** 的端口。查看 RunLoop 源代码 **wakeup port** 就是 mach port 的一种。
|
||||
|
||||
```c++
|
||||
typedef mach_port_t __CFPort;
|
||||
|
||||
struct __CFRunLoop {
|
||||
CFRuntimeBase _base;
|
||||
_CFRecursiveMutex _lock; /* locked for accessing mode list */
|
||||
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
|
||||
Boolean _unused;
|
||||
volatile _per_run_data *_perRunData; // reset for runs of the run loop
|
||||
_CFThreadRef _pthread;
|
||||
uint32_t _winthread;
|
||||
CFMutableSetRef _commonModes;
|
||||
CFMutableSetRef _commonModeItems;
|
||||
CFRunLoopModeRef _currentMode;
|
||||
CFMutableSetRef _modes;
|
||||
struct _block_item *_blocks_head;
|
||||
struct _block_item *_blocks_tail;
|
||||
CFAbsoluteTime _runTime;
|
||||
CFAbsoluteTime _sleepTime;
|
||||
CFTypeRef _counterpart;
|
||||
_Atomic(uint8_t) _fromTSD;
|
||||
CFLock_t _timerTSRLock;
|
||||
};
|
||||
```
|
||||
|
||||
```c++
|
||||
void CFRunLoopWakeUp(CFRunLoopRef rl) {
|
||||
// ...
|
||||
kern_return_t ret;
|
||||
/* We unconditionally try to send the message, since we don't want
|
||||
* to lose a wakeup, but the send may fail if there is already a
|
||||
* wakeup pending, since the queue length is 1. */
|
||||
ret = __CFSendTrivialMachMessage(rl->_wakeUpPort, 0, MACH_SEND_TIMEOUT, 0);
|
||||
// ...
|
||||
int ret;
|
||||
do {
|
||||
ret = eventfd_write(rl->_wakeUpPort, 1);
|
||||
} while (ret == -1 && errno == EINTR);
|
||||
// ...
|
||||
SetEvent(rl->_wakeUpPort);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
`CFRunLoopWakeUp(CFRunLoopGetCurrent());` 可以唤醒 RunLoop,函数的底层实现如上。核心实现就是 `__CFSendTrivialMachMessage` 函数。在iOS 中,除了 source1 可以自己唤醒 RunLoop 之外,其他的事件都需要用户手动唤醒 RunLoop 才可以。RunLoop 提供了专门的方法来实现这个功能。其核心部分就是调用 mach_msg 来向指定的 **_wakeUpPort** 端口发送消息,从而唤醒线程继续工作。
|
||||
|
||||
为什么 Source1 可以唤醒 RunLoop?因为 Source1 本质上就是针对 Mach Port 的封装
|
||||
|
||||
6. 做个实验检测 _wakeUpPort 端口
|
||||
|
||||
```objective-c
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
[self listenWakeUpPort];
|
||||
}
|
||||
|
||||
- (void)listenWakeUpPort
|
||||
{
|
||||
NSArray<NSString *> *array = [NSRunLoop.currentRunLoop.description componentsSeparatedByString:@"wakeup port = "];
|
||||
NSString *wakeupPort = [array.lastObject substringToIndex:[array.lastObject rangeOfString:@","].location];
|
||||
|
||||
dispatch_queue_t queue = dispatch_queue_create("com.test.wake_up_port_queue", DISPATCH_QUEUE_CONCURRENT);
|
||||
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, [self numberWithHexString:wakeupPort], 0, queue);
|
||||
dispatch_source_set_event_handler(source, ^{
|
||||
mach_port_t port = (mach_port_t)dispatch_source_get_handle(source);
|
||||
NSLog(@"%u--wakeUp", port);
|
||||
});
|
||||
dispatch_activate(source);
|
||||
}
|
||||
|
||||
- (NSInteger)numberWithHexString:(NSString *)hexString
|
||||
{
|
||||
const char *hexChar = [hexString cStringUsingEncoding:NSUTF8StringEncoding];
|
||||
int hexNumber;
|
||||
sscanf(hexChar, "%x", &hexNumber);
|
||||
return (NSInteger)hexNumber;
|
||||
}
|
||||
|
||||
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
|
||||
{
|
||||
CFRunLoopWakeUp(CFRunLoopGetCurrent());
|
||||
}
|
||||
```
|
||||
|
||||
可以看到每次在点击屏幕时调用 `CFRunLoopWakeUp` 尝试唤醒 RunLoop,然后监听 RunLoop 的 _wakeUpPort,都可以在回调中获取到消息。
|
||||
|
||||
7. RunLoop lifecycle
|
||||
|
||||

|
||||
|
||||
休眠时 RunLoop 被 mach_msg 阻塞,等到消息到来,可以是手动给 RunLoop 的 wakeUpPort 发消息 mach_msg,或者是 timer、Source1。然后继续走到 RunLoop run,不断循环
|
||||
|
||||
Timer 不准确的原因:1. RunLoop 底层对 Timer 实现的精度不高,所以需要指定 Timer 的精度;2. RunLoop 在 DoSource0、DoSource1 的时候耗时较多,则导致 Timer 下次执行会不准确
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user