mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-24 20:00:37 +00:00
update: 动态库、静态库的编译链接细节
This commit is contained in:
@@ -35,7 +35,7 @@
|
||||
|
||||
先附上一张总结的非常棒的RunLoop图
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2019-05-09-RunLoop-review.png" style="zoom:30%" />
|
||||
<img src="./../assets/2019-05-09-RunLoop-review.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ struct __CFRunLoopMode {
|
||||
|
||||
Demo:
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/NSRunloopRoundWithCFRunloop.png" style="zoom:30%" />
|
||||
<img src="./../assets/NSRunloopRoundWithCFRunloop.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
@@ -170,24 +170,24 @@ RunLoop 在各个 Mode 下做事情,其实就是在处理某个 Mode 中的 So
|
||||
|
||||
Source0:
|
||||
|
||||
- 屏幕触摸事件处理(非基于 Port 的事件),对应需要手动触发的事件,对应官方文档 Input Source 中的 Custom 和 `performSelector:onThread` 事件源。
|
||||
|
||||
- `performSelector:onThread:`
|
||||
|
||||
- 数组
|
||||
|
||||
Demo:给屏幕点击事件加断点,查看堆栈可以看到是 Source0 触发的。
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TouchActionByRunLoopSource0.png" style="zoom:25%" />
|
||||
- 处理开发者主动提交的任务或应用内部逻辑。
|
||||
- `performSelector:onThread:`
|
||||
- `dispatch_async`到主线程的任务(最终封装为Source0)。
|
||||
- 屏幕触摸事件处理(非基于 Port 的事件),对应需要手动触发的事件,对应官方文档 Input Source 中的 Custom 和 `performSelector:onThread` 事件源。UIKit控件的事件处理(如按钮点击后的回调)。
|
||||
|
||||
|
||||
Demo1:给屏幕点击事件加断点,查看堆栈可以看到是 Source0 触发的。
|
||||
|
||||
<img src="./../assets/TouchActionByRunLoopSource0.png" style="zoom:25%" />
|
||||
|
||||
Demo2: `- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait` 该 API 的底层就是:向目标线程的 RunLoop 添加一个 Source0 事件,标记后唤醒线程执行对应的事件。
|
||||
|
||||
<img src="./../assets/PerformThreadTaskByRunLoopSource0.png" style="zoom:25%" />
|
||||
|
||||
Source1:
|
||||
|
||||
- 基于 Port 的线程间通信,可以主动唤醒 RunLoop
|
||||
|
||||
- 系统事件捕捉(比如屏幕触摸事件,Source1捕捉后,派发给 Source0 处理)
|
||||
|
||||
- 用户触摸屏幕时,系统通过 Mach Port 将事件传递到应用主线程的 RunLoop。RunLoop 被 Source1 唤醒后,将事件分发给 Source0处理具体的 UI 响应逻辑(如 `hitTest:withEvent:` 和响应链)。
|
||||
- 字典。`{machport : 1}`
|
||||
|
||||
Timers:
|
||||
@@ -221,7 +221,7 @@ CFRunLoopSourceRef 事件源(输入源)
|
||||
|
||||
### 一对多的关系
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ThreadRunLoopModeStructure.png" style="zoom:70%" />
|
||||
<img src="./../assets/ThreadRunLoopModeStructure.png" style="zoom:70%" />
|
||||
|
||||
|
||||
|
||||
@@ -434,7 +434,7 @@ CFRelease(obersver);
|
||||
*/
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
上个实验是在主线程对 RunLoop 进行的监听,但是由于是主线程是由系统创建的,所以系统也创建了对应的主 RunLoop,所以我们看不到 RunLoop 创建的状态,为了模拟完整的状态,我们开启子线程,在子线程中模拟
|
||||
|
||||
@@ -510,7 +510,7 @@ CFRelease(obersver);
|
||||
|
||||
### 运行原概要
|
||||
|
||||

|
||||

|
||||
|
||||
- 图上左上角的 Input source 是早期 RunLoop 的分法,现在分法为:Source0 和 Source1。
|
||||
- Source0:非基于 port 的,用户主动触发的事件。
|
||||
@@ -527,7 +527,7 @@ CFRelease(obersver);
|
||||
|
||||
但是如何直到系统是运行 RunLoop 的哪个函数?给 viewDidLoad 设置断点,在 lldb 模式输入 `bt` 查看堆栈
|
||||
|
||||

|
||||

|
||||
|
||||
查看 CF 中 `CFRunLoop.c`源码。方法比较复杂,做了精简摘要
|
||||
|
||||
@@ -1137,7 +1137,7 @@ RunLoop 内部几个核心的动作:`__CFRunLoopDoObservers`、`__CFRunLoopSer
|
||||
|
||||
上面结合源码看了 Runloop 是怎么运行的。下面通过图片看看 RunLoop 各个状态运行切换的完整流程。
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RunLoop-SourceCode.png" style="zoom:30%" />
|
||||
<img src="./../assets/RunLoop-SourceCode.png" style="zoom:40%" />
|
||||
|
||||
Demo:
|
||||
|
||||
@@ -1151,7 +1151,7 @@ Demo:
|
||||
|
||||
2. 上面8>2,Runloop 处理 GCD Async To Main Quque
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RunLoopPerformTaskFromGCDMainQueue.png" style="zoom:25%" />
|
||||
<img src="./../assets/RunLoopPerformTaskFromGCDMainQueue.png" style="zoom:25%" />
|
||||
|
||||
|
||||
|
||||
@@ -1165,7 +1165,7 @@ Demo:
|
||||
|
||||
可以在 App 运行过程中,点击 Xcode 左下角的 debug 暂停按钮,可以看到 App 堆栈存在系统调用
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RunLoopSleepSystemCall.png" style="zoom:30%" />
|
||||
<img src="./../assets/RunLoopSleepSystemCall.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
@@ -1887,10 +1887,72 @@ main 方法内其实就是在执行 _selector。也就是在 NSThread 的初始
|
||||
|
||||
2. `[[NSRunLoop currentRunLoop] run]` api 换掉。查看系统说明,底层其实就是一个无限循环,循环内部不断调用 `runMode:beforeDate:`。下面也有建议,建议我们想销毁 RunLoop,可以替换 API,比如设置一个变量,标记是否需要结束 RunLoop
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RunLoop-RunIssue.png" style="zoom:45%" />
|
||||
<img src="./../assets/RunLoop-RunIssue.png" style="zoom:45%" />
|
||||
|
||||
改进代码如下
|
||||
|
||||
```objective-c
|
||||
__weak ViewController *weakself = self;
|
||||
self.thread = [[LifeThread alloc] initWithBlock:^{
|
||||
NSLog(@"RunLoop Start");
|
||||
[[NSRunLoop currentRunLoop] addPort:[[NSMachPort alloc] init] forMode:NSDefaultRunLoopMode];
|
||||
// tips2:
|
||||
while (!weakself.needStopThread) {
|
||||
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
|
||||
}
|
||||
NSLog(@"RunLoop Stop");
|
||||
}];
|
||||
[self.thread start];
|
||||
|
||||
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
|
||||
if(!self.thread) return;
|
||||
[self performSelector:@selector(threadTask) onThread:self.thread withObject:nil waitUntilDone:NO];
|
||||
}
|
||||
#pragma mark - 线程相关
|
||||
- (void)threadTask {
|
||||
NSLog(@"线程任务 %@", [NSThread currentThread]);
|
||||
}
|
||||
|
||||
- (void)stopThread {
|
||||
if(!self.thread) return;
|
||||
// tips1:
|
||||
[self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:YES];
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
self.needStopThread = YES;
|
||||
CFRunLoopStop(CFRunLoopGetCurrent());
|
||||
self.thread = nil;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
NSLog(@"%s", __func__);
|
||||
[self stopThread];
|
||||
}
|
||||
|
||||
@end
|
||||
```
|
||||
|
||||
但上面的代码还是存在问题:
|
||||
|
||||
- 如果 `stop` 方法内部的 **`waitUntilDone` 为 NO,则可能会出现 Crash**。因为该参数代表后续逻辑代表会不会等该 selector 执行完毕。因为为 NO,所以 ViewController 执行 dealloc 了,所以 `dealloc` 方法和 Thread 内部的 block 同时进行,不能确保在 block 内部执行的时候 dealloc 有没有执行完,访问 weakSelf.isStoped 可能会出现坏内存访问,则会 crash
|
||||
|
||||
解决方案:把 waitUntilDone 改为 YES
|
||||
|
||||
- 但发现还存在问题:页面返回后,线程还是在执行打印任务。
|
||||
|
||||
断点发现,在 NSThread 的 block 里,while 条件中 weakself 已经为 nil 了。但 self.thread 还存在,且 block 里的逻辑,while 条件取反后条件成立,则继续调用 `[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];` RunLoop 持续运行
|
||||
|
||||
解决方案:不能在 block 里添加 `__strong typeof(weakself) strongSelf = weakself;` 否则造成循环引用(`VC -> thread -> block -> self(VC)`),页面消失,thread 无法释放。
|
||||
|
||||
正确的做法是在 while 条件中增加 weakself 是否为 nil 的判断。
|
||||
|
||||
可能有些人会产生疑问:为什么 waitUntilDone 改为 YES,dealloc 也就没执行完毕,为什么 weak 指针指向的 weakself 就为 nil 了?
|
||||
|
||||
`weak` 指针的特性是:**当对象开始销毁时(即 `dealloc` 被调用时),所有指向它的 `weak` 指针会立即被置为 `nil`**。这一行为发生在 `dealloc` 方法执行之前,而不是之后。
|
||||
|
||||
继续优化版本:
|
||||
|
||||
```objective-c
|
||||
__weak ViewController *weakself = self;
|
||||
self.thread = [[LifeThread alloc] initWithBlock:^{
|
||||
@@ -1933,20 +1995,18 @@ self.thread = [[LifeThread alloc] initWithBlock:^{
|
||||
|
||||
效果如下:
|
||||
|
||||
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/RunLoopThreadKeepLive.png" style="zoom:30%" />
|
||||
<img src="./../assets/RunLoopThreadKeepLive.png" style="zoom:30%" />
|
||||
|
||||
|
||||
|
||||
注意:
|
||||
|
||||
- 如果 `stop` 方法内部的 `waitUntilDone` 为 NO,则会出现 Crash。因为该参数代表后续代表会不会等该 selector 执行完毕。因为为 NO,所以 ViewController 执行 dealloc 了,所以 `dealloc` 方法和 Thread 内部的 block 同时进行,不能确保在 block 内部执行的时候 dealloc 有没有执行完,访问 weakSelf.isStoped 可能会 crash
|
||||
|
||||
- 线程的 RunLoop 结束了,线程也无法执行任务了,所以需要给线程对象设置为 nil。同时任务派发的地方也需要判断线程是否存在,否则会 crash
|
||||
|
||||
- NSRunLoop 常驻线程可以运行在 NSRunLoopCommonModes 下吗?
|
||||
|
||||
|
||||
不可以。因为在跑的时候如果 modeName 等于 kCFRunLoopCommonModes 则直接 kCFRunLoopRunFinished,则 RunLoop 的 while 循环条件失败
|
||||
|
||||
|
||||
```objective-c
|
||||
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
|
||||
CHECK_FOR_FORK();
|
||||
|
||||
Reference in New Issue
Block a user