diff --git a/.DS_Store b/.DS_Store index 822ab33..e642cb9 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Chapter1 - iOS/1.40.md b/Chapter1 - iOS/1.40.md index 4768189..a79ff32 100644 --- a/Chapter1 - iOS/1.40.md +++ b/Chapter1 - iOS/1.40.md @@ -142,22 +142,28 @@ self.shouldStopRun = NO; 0x1 和 0xa0 是十六进制的数,对应十进制为1和160。 - - - -参考文章: - -http://www.cocoachina.com/ios/20180515/23380.html - -http://www.cocoachina.com/ios/20170417/19075.html - -https://www.jianshu.com/p/4c38d16a29f1 - -https://www.cnblogs.com/kenshincui/p/6823841.html - -https://juejin.im/entry/599c13bc6fb9a0248926a77d - -https://blog.ibireme.com/2015/05/18/runloop/ +## RunLoop 空闲时做一些任务 +``` +- (void)print +{ + NSLog(@"test"); +} + +- (void)viewDidLoad +{ + CFRunLoopActivity flags = kCFRunLoopBeforeWaiting; + CFRunLoopObserverRef runloopObserver = CFRunLoopObserverCreateWithHandler( + kCFAllocatorDefault, flags, YES, 0, + ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { + [self performSelector:@selector(print) + onThread:[NSThread mainThread] + withObject:nil + waitUntilDone:NO + modes:@[ NSDefaultRunLoopMode ]]; + }); + CFRunLoopAddObserver(CFRunLoopGetCurrent(), runloopObserver, kCFRunLoopDefaultMode); +} +``` diff --git a/Chapter1 - iOS/1.74.md b/Chapter1 - iOS/1.74.md index 3c8fd4d..ea5d8ba 100644 --- a/Chapter1 - iOS/1.74.md +++ b/Chapter1 - iOS/1.74.md @@ -2697,11 +2697,10 @@ CFNetwork 使用 CFReadStreamRef 来传递数据,使用回调函数的形式 @end ``` - + @implementation NetworkDelegateProxy - + #pragma mark - life cycle - + (instancetype)sharedInstance { static NetworkDelegateProxy *_sharedInstance = nil; @@ -2715,7 +2714,7 @@ CFNetwork 使用 CFReadStreamRef 来传递数据,使用回调函数的形式 return _sharedInstance; } - + #pragma mark - public Method + (instancetype)setProxyForObject:(id)originalTarget withNewDelegate:(id)newDelegate @@ -4797,7 +4796,7 @@ static int getReportCount() { KSLOG_ERROR("Could not open directory %s", g_reportsPath); goto done; - } + }APM 能力中为 Crash 模块设置一个启动器。启动器内部设置 KSCrash 的初始化工作,以及触发 Crash 时候监控所需数据的组装。比如:SESSION_ID、App 启动时间、App 名称、崩溃时间、App 版本号、当前页面信息等基础信息。 struct dirent* ent; while((ent = readdir(dir)) != NULL) { @@ -5528,9 +5527,7 @@ parseJSError(line, column); * Objective-C methods!!! */ @property(atomic,readwrite,assign) KSReportWriteCallback onCrash; - ``` - ```objective-c + (instancetype)sharedInstance { static APMCrashMonitor *_sharedManager = nil; @@ -5540,189 +5537,115 @@ parseJSError(line, column); }); return _sharedManager; } - ``` - #pragma mark - public Method - -- (void)startMonitor - { - APMMLog(@"crash monitor started"); - - #ifdef DEBUG - BOOL _trackingCrashOnDebug = [APMMonitorConfig sharedInstance].trackingCrashOnDebug; - if (_trackingCrashOnDebug) { - - [self installKSCrash]; - - } - #else - [self installKSCrash]; - #endif + - (void)startMonitor { +     APMMLog(@"crash monitor started"); + #ifdef DEBUG +     BOOL _trackingCrashOnDebug = [APMMonitorConfig sharedInstance].trackingCrashOnDebug; +     if (_trackingCrashOnDebug) { +         [self installKSCrash]; +     } + #else +     [self installKSCrash]; + #endif } #pragma mark - private method - static void onCrash(const KSCrashReportWriter* writer) - { - NSString *sessionId = [NSString stringWithFormat:@"\"%@\"", ***]]; - writer->addJSONElement(writer, "SESSION_ID", [sessionId UTF8String], true); - - NSString *appLaunchTime = ***; - writer->addJSONElement(writer, "USER_APP_START_DATE", [[NSString stringWithFormat:@"\"%@\"", appLaunchTime] UTF8String], true); - // ... + static void onCrash(const KSCrashReportWriter* writer) { +     NSString *sessionId = [NSString stringWithFormat:@""%@"", ***]]; +     writer->addJSONElement(writer, "SESSION_ID", [sessionId UTF8String], true); +     NSString *appLaunchTime = ***; +     writer->addJSONElement(writer, "USER_APP_START_DATE", [[NSString stringWithFormat:@""%@"", appLaunchTime] UTF8String], true); +     // ... } - -- (void)installKSCrash - { - [[APMCrashInstallation sharedInstance] install]; - [[APMCrashInstallation sharedInstance] sendAllReportsWithCompletion:nil]; - [APMCrashInstallation sharedInstance].onCrash = onCrash; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - _isCanAddCrashCount = NO; - - }); + -(void)installKSCrash { +     [[APMCrashInstallation sharedInstance] install]; +     [[APMCrashInstallation sharedInstance] sendAllReportsWithCompletion:nil]; +     [APMCrashInstallation sharedInstance].onCrash = onCrash; +     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ +        _isCanAddCrashCount = NO; +     }); } ``` 在 `installKSCrash` 方法中调用了 `[[APMCrashInstallation sharedInstance] sendAllReportsWithCompletion: nil]`,内部实现如下 - ```objective-c - -- (void) sendAllReportsWithCompletion:(KSCrashReportFilterCompletion) onCompletion - { - NSError* error = [self validateProperties]; - if(error != nil) - { - - if(onCompletion != nil) - { - onCompletion(nil, NO, error); - } - return; - - } - - id sink = [self sink]; - if(sink == nil) - { - - onCompletion(nil, NO, [NSError errorWithDomain:[[self class] description] - code:0 - description:@"Sink was nil (subclasses must implement method \"sink\")"]); - return; - - } - - sink = [KSCrashReportFilterPipeline filterWithFilters:self.prependedFilters, sink, nil]; - - KSCrash* handler = [KSCrash sharedInstance]; - handler.sink = sink; - [handler sendAllReportsWithCompletion:onCompletion]; - } ``` - - 方法内部将 `KSCrashInstallation` 的 `sink` 赋值给 `KSCrash` 对象。 内部还是调用了 `KSCrash` 的 `sendAllReportsWithCompletion` 方法,实现如下 - - ```objective-c - -- (void) sendAllReportsWithCompletion:(KSCrashReportFilterCompletion) onCompletion - { - NSArray* reports = [self allReports]; - - KSLOG_INFO(@"Sending %d crash reports", [reports count]); - - [self sendReports:reports - - onCompletion:^(NSArray* filteredReports, BOOL completed, NSError* error) - - { - - KSLOG_DEBUG(@"Process finished with completion: %d", completed); - if(error != nil) - { - KSLOG_ERROR(@"Failed to send reports: %@", error); - } - if((self.deleteBehaviorAfterSendAll == KSCDeleteOnSucess && completed) || - self.deleteBehaviorAfterSendAll == KSCDeleteAlways) - { - kscrash_deleteAllReports(); - } - kscrash_callCompletion(onCompletion, filteredReports, completed, error); - - }]; + -(void) sendAllReportsWithCompletion:(KSCrashReportFilterCompletion) onCompletion { +     NSArray* reports = [self allReports]; +     KSLOG_INFO(@"Sending %d crash reports", [reports count]); +     [self sendReports:reports onCompletion:^(NSArray* filteredReports, BOOL completed, NSError* error) { +         KSLOG_DEBUG(@"Process finished with completion: %d", completed); +         if(error != nil) { +     KSLOG_ERROR(@"Failed to send reports: %@", error); +      } +     if((self.deleteBehaviorAfterSendAll == KSCDeleteOnSucess && completed) || +     self.deleteBehaviorAfterSendAll == KSCDeleteAlways){ +      kscrash_deleteAllReports(); +     } +      kscrash_callCompletion(onCompletion, filteredReports, completed, error); +     }]; } ``` 该方法内部调用了对象方法 `sendReports: onCompletion:`,如下所示 - ```objective-c - -- (void) sendReports:(NSArray*) reports onCompletion:(KSCrashReportFilterCompletion) onCompletion + ``` + - (void) sendReports:(NSArray*) reports onCompletion:(KSCrashReportFilterCompletion) onCompletion { - if([reports count] == 0) - { - - kscrash_callCompletion(onCompletion, reports, YES, nil); - return; - - } - - if(self.sink == nil) - { - - kscrash_callCompletion(onCompletion, reports, NO, - [NSError errorWithDomain:[[self class] description] - code:0 - description:@"No sink set. Crash reports not sent."]); - return; - - } - - [self.sink filterReports:reports - - onCompletion:^(NSArray* filteredReports, BOOL completed, NSError* error) - - { - - kscrash_callCompletion(onCompletion, filteredReports, completed, error); - - }]; + if([reports count] == 0) + { + kscrash_callCompletion(onCompletion, reports, YES, nil); + return; + } + + if(self.sink == nil) + { + kscrash_callCompletion(onCompletion, reports, NO, + [NSError errorWithDomain:[[self class] description] + code:0 + description:@"No sink set. Crash reports not sent."]); + return; + } + + [self.sink filterReports:reports + onCompletion:^(NSArray* filteredReports, BOOL completed, NSError* error) + { + kscrash_callCompletion(onCompletion, filteredReports, completed, error); + }]; } ``` 方法内部的 `[self.sink filterReports: onCompletion: ]` 实现其实就是 `APMCrashInstallation` 中设置的 `sink` getter 方法,内部返回了 `APMCrashReporterSink` 对象的 `defaultCrashReportFilterSetAppleFmt` 方法的返回值。内部实现如下 - ```objective-c - -- (id ) defaultCrashReportFilterSetAppleFmt + ``` + - (id ) defaultCrashReportFilterSetAppleFmt { - return [KSCrashReportFilterPipeline filterWithFilters: - - [APMCrashReportFilterAppleFmt filterWithReportStyle:KSAppleReportStyleSymbolicatedSideBySide], - self, - nil]; - + return [KSCrashReportFilterPipeline filterWithFilters: + [KSCrashReportFilterAppleFmt filterWithReportStyle:KSAppleReportStyleSymbolicatedSideBySide], + [KSCrashReportFilterStringToData filter], + [KSCrashReportFilterGZipCompress filterWithCompressionLevel:-1], + self, + nil]; } ``` - 可以看到这个函数内部设置了多个 **filters**,其中一个就是 **self**,也就是 `APMCrashReporterSink` 对象,所以上面的 ` [self.sink filterReports: onCompletion:]` ,也就是调用 `APMCrashReporterSink` 内的数据处理方法。完了之后通过 `kscrash_callCompletion(onCompletion, reports, YES, nil);` 告诉 `KSCrash` 本地保存的 Crash 日志已经处理完毕,可以删除了。 - - ```objective-c - -- (void)filterReports:(NSArray *)reports onCompletion:(KSCrashReportFilterCompletion)onCompletion - { - for (NSDictionary *report in reports) { - - // 处理 Crash 数据,将数据交给统一的数据上报组件处理... - - } - kscrash_callCompletion(onCompletion, reports, YES, nil); - } + 可以看到这个函数内部设置了多个 **filters**,其中一个就是 **self**,也就是 `APMCrashReporterSink` 对象,所以上面的 `[self.sink filterReports: onCompletion:]` ,也就是调用 `APMCrashReporterSink` 内的数据处理方法。完了之后通过 `kscrash_callCompletion(onCompletion, reports, YES, nil);` 告诉 `KSCrash` 本地保存的 Crash 日志已经处理完毕,可以删除了。 ``` + - (void) filterReports:(NSArray*) reports + onCompletion:(KSCrashReportFilterCompletion) onCompletion + { + // 处理 Crash 数据,将数据交给统一的数据上报组件处理... + kscrash_callCompletion(onCompletion, filteredReports, YES, nil); + } + ``` + 至此,概括下 KSCrash 做的事情,提供各种 crash 的监控能力,在 crash 后将进程信息、基本信息、异常信息、线程信息等用 c 高效转换为 json 写入文件,App 下次启动后读取本地的 crash 文件夹中的 crash 日志,让开发者可以自定义 key、value 然后去上报日志到 APM 系统,然后删除本地 crash 文件夹中的日志。 - ``` + +- ### 4. 符号化 @@ -6340,12 +6263,8 @@ Crash log 统一入库 Kibana 时是没有符号化的,所以需要符号化 可能有些人一直没有遇到过因为在子线程操作 UI,导致在开发阶段 Xcode console 输出了一堆日志,大体如下 - - ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2022-0204-SubThreadUIXcode1@2x.png) - - 其实我们可以给 Xcode 打个 `Runtime Issue Breakpoint` ,type 选择 `Main Thread Checker`, 在发生子线程操作 UI 的时候就会被系统检测到并触发断点,同时可以看到堆栈情况 ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2022-0204-SubThreadUISymbolBreakpoints@2x.png) @@ -6362,11 +6281,9 @@ Crash log 统一入库 Kibana 时是没有符号化的,所以需要符号化 另外在监控到子线程调用 UI 调用时,在 Xcode 环境下,会将调用栈输出到控制台,经过测试,`libMainThreadChecker.dylib` 使用的是进行输出的,由于 NSLog 是将信息输出到 `STDERR`中,我们可以通过 `NSPipe` 与 `dup2` 将 `STDERR` 输出拦截,通过对信息的文案的判断,进而获取监测到的 UI 调用,最后可以通过堆栈打印出来,就可以帮助定位到具体问题。 - `libMainThreadChecker.dylib` 库具有局限性,仅仅对系统提供的一些特定类的特定 API 在子线程调用会被监控到(例如 UIKit 框架中 UIView 类)。 但是某些类有些 API 我们也不希望在子线程被调用,这时候 `libMainThreadChecker.dylib`是无法满足的。 - 对 `libMainThreadChecker.dylib` 库的汇编代码研究,发现 `libMainThreadChecker.dylib` 是通过内部 `__main_thread_add_check_for_selector` 这个方法来进行类和方法的注册的。所以如果我们同样可以通过 `dlsym` 来调用该方法,以达到对自定义类和方法的主线程调用监测。 ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2022-0204-SubThreadUIMonitor@2x.PNG) @@ -6374,7 +6291,6 @@ Crash log 统一入库 Kibana 时是没有符号化的,所以需要符号化 对 [dlopen](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dlopen.3.html)、[dlsym](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dlsym.3.html) 陌生的小伙伴可以直接看 Apple 官方文档,这里不做展开。 - 具体可以参考这个 [Demo](https://github.com/FantasticLBP/MainThreadChecker) ## 九、 APM 小结 diff --git a/assets/2022-0204-SubThreadUIMonitor@2x.PNG b/assets/2022-0204-SubThreadUIMonitor@2x.PNG index 63493de..d17504a 100644 Binary files a/assets/2022-0204-SubThreadUIMonitor@2x.PNG and b/assets/2022-0204-SubThreadUIMonitor@2x.PNG differ