docs: 样式

This commit is contained in:
LiuBinPeng
2022-02-07 19:29:16 +08:00
parent 50bc85248b
commit d3b2102dc9
4 changed files with 104 additions and 182 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -142,22 +142,28 @@ self.shouldStopRun = NO;
0x1 和 0xa0 是十六进制的数对应十进制为1和160。 0x1 和 0xa0 是十六进制的数对应十进制为1和160。
## RunLoop 空闲时做一些任务
```
- (void)print
参考文章: {
NSLog(@"test");
http://www.cocoachina.com/ios/20180515/23380.html }
http://www.cocoachina.com/ios/20170417/19075.html - (void)viewDidLoad
{
https://www.jianshu.com/p/4c38d16a29f1 CFRunLoopActivity flags = kCFRunLoopBeforeWaiting;
CFRunLoopObserverRef runloopObserver = CFRunLoopObserverCreateWithHandler(
https://www.cnblogs.com/kenshincui/p/6823841.html kCFAllocatorDefault, flags, YES, 0,
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
https://juejin.im/entry/599c13bc6fb9a0248926a77d [self performSelector:@selector(print)
onThread:[NSThread mainThread]
https://blog.ibireme.com/2015/05/18/runloop/ withObject:nil
waitUntilDone:NO
modes:@[ NSDefaultRunLoopMode ]];
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), runloopObserver, kCFRunLoopDefaultMode);
}
```

View File

@@ -2701,7 +2701,6 @@ CFNetwork 使用 CFReadStreamRef 来传递数据,使用回调函数的形式
@implementation NetworkDelegateProxy @implementation NetworkDelegateProxy
#pragma mark - life cycle #pragma mark - life cycle
+ (instancetype)sharedInstance { + (instancetype)sharedInstance {
static NetworkDelegateProxy *_sharedInstance = nil; static NetworkDelegateProxy *_sharedInstance = nil;
@@ -4797,7 +4796,7 @@ static int getReportCount()
{ {
KSLOG_ERROR("Could not open directory %s", g_reportsPath); KSLOG_ERROR("Could not open directory %s", g_reportsPath);
goto done; goto done;
} }APM 能力中为 Crash 模块设置一个启动器。启动器内部设置 KSCrash 的初始化工作,以及触发 Crash 时候监控所需数据的组装。比如SESSION_ID、App 启动时间、App 名称、崩溃时间、App 版本号、当前页面信息等基础信息。
struct dirent* ent; struct dirent* ent;
while((ent = readdir(dir)) != NULL) while((ent = readdir(dir)) != NULL)
{ {
@@ -5528,9 +5527,7 @@ parseJSError(line, column);
* Objective-C methods!!! * Objective-C methods!!!
*/ */
@property(atomic,readwrite,assign) KSReportWriteCallback onCrash; @property(atomic,readwrite,assign) KSReportWriteCallback onCrash;
```
```objective-c
+ (instancetype)sharedInstance + (instancetype)sharedInstance
{ {
static APMCrashMonitor *_sharedManager = nil; static APMCrashMonitor *_sharedManager = nil;
@@ -5540,189 +5537,115 @@ parseJSError(line, column);
}); });
return _sharedManager; return _sharedManager;
} }
```
#pragma mark - public Method #pragma mark - public Method
- (void)startMonitor {
- (void)startMonitor     APMMLog(@"crash monitor started");
{
APMMLog(@"crash monitor started");
#ifdef DEBUG #ifdef DEBUG
BOOL _trackingCrashOnDebug = [APMMonitorConfig sharedInstance].trackingCrashOnDebug;     BOOL _trackingCrashOnDebug = [APMMonitorConfig sharedInstance].trackingCrashOnDebug;
if (_trackingCrashOnDebug) {     if (_trackingCrashOnDebug) {
        [self installKSCrash];
[self installKSCrash];     }
}
#else #else
[self installKSCrash];     [self installKSCrash];
#endif #endif
} }
#pragma mark - private method #pragma mark - private method
static void onCrash(const KSCrashReportWriter* writer) static void onCrash(const KSCrashReportWriter* writer) {
{     NSString *sessionId = [NSString stringWithFormat:@""%@"", ***]];
NSString *sessionId = [NSString stringWithFormat:@"\"%@\"", ***]];     writer->addJSONElement(writer, "SESSION_ID", [sessionId UTF8String], true);
writer->addJSONElement(writer, "SESSION_ID", [sessionId UTF8String], true);     NSString *appLaunchTime = ***;
    writer->addJSONElement(writer, "USER_APP_START_DATE", [[NSString stringWithFormat:@""%@"", appLaunchTime] UTF8String], true);
NSString *appLaunchTime = ***;     // ...
writer->addJSONElement(writer, "USER_APP_START_DATE", [[NSString stringWithFormat:@"\"%@\"", appLaunchTime] UTF8String], true);
// ...
} }
- (void)installKSCrash -(void)installKSCrash {
{     [[APMCrashInstallation sharedInstance] install];
[[APMCrashInstallation sharedInstance] install];     [[APMCrashInstallation sharedInstance] sendAllReportsWithCompletion:nil];
[[APMCrashInstallation sharedInstance] sendAllReportsWithCompletion:nil];     [APMCrashInstallation sharedInstance].onCrash = onCrash;
[APMCrashInstallation sharedInstance].onCrash = onCrash;     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{        _isCanAddCrashCount = NO;
    });
_isCanAddCrashCount = NO;
});
} }
``` ```
在 `installKSCrash` 方法中调用了 `[[APMCrashInstallation sharedInstance] sendAllReportsWithCompletion: nil]`,内部实现如下 在 `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<KSCrashReportFilter> 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];
}
``` ```
-(void) sendAllReportsWithCompletion:(KSCrashReportFilterCompletion) onCompletion {
方法内部将 `KSCrashInstallation` 的 `sink` 赋值给 `KSCrash` 对象。 内部还是调用了 `KSCrash` 的 `sendAllReportsWithCompletion` 方法,实现如下     NSArray* reports = [self allReports];
    KSLOG_INFO(@"Sending %d crash reports", [reports count]);
```objective-c     [self sendReports:reports onCompletion:^(NSArray* filteredReports, BOOL completed, NSError* error) {
        KSLOG_DEBUG(@"Process finished with completion: %d", completed);
- (void) sendAllReportsWithCompletion:(KSCrashReportFilterCompletion) onCompletion         if(error != nil) {
{     KSLOG_ERROR(@"Failed to send reports: %@", error);
NSArray* reports = [self allReports];      }
    if((self.deleteBehaviorAfterSendAll == KSCDeleteOnSucess && completed) ||
KSLOG_INFO(@"Sending %d crash reports", [reports count]);     self.deleteBehaviorAfterSendAll == KSCDeleteAlways){
     kscrash_deleteAllReports();
[self sendReports:reports     }
     kscrash_callCompletion(onCompletion, filteredReports, completed, error);
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:`,如下所示 该方法内部调用了对象方法 `sendReports: onCompletion:`,如下所示
```objective-c ```
- (void) sendReports:(NSArray*) reports onCompletion:(KSCrashReportFilterCompletion) onCompletion
- (void) sendReports:(NSArray*) reports onCompletion:(KSCrashReportFilterCompletion) onCompletion
{ {
if([reports count] == 0) if([reports count] == 0)
{ {
kscrash_callCompletion(onCompletion, reports, YES, nil);
return;
}
kscrash_callCompletion(onCompletion, reports, YES, nil); if(self.sink == nil)
return; {
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)
if(self.sink == nil) {
{ kscrash_callCompletion(onCompletion, filteredReports, completed, error);
}];
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` 方法的返回值。内部实现如下 方法内部的 `[self.sink filterReports: onCompletion: ]` 实现其实就是 `APMCrashInstallation` 中设置的 `sink` getter 方法,内部返回了 `APMCrashReporterSink` 对象的 `defaultCrashReportFilterSetAppleFmt` 方法的返回值。内部实现如下
```objective-c ```
- (id <KSCrashReportFilter>) defaultCrashReportFilterSetAppleFmt
- (id <KSCrashReportFilter>) defaultCrashReportFilterSetAppleFmt
{ {
return [KSCrashReportFilterPipeline filterWithFilters: return [KSCrashReportFilterPipeline filterWithFilters:
[KSCrashReportFilterAppleFmt filterWithReportStyle:KSAppleReportStyleSymbolicatedSideBySide],
[APMCrashReportFilterAppleFmt filterWithReportStyle:KSAppleReportStyleSymbolicatedSideBySide], [KSCrashReportFilterStringToData filter],
self, [KSCrashReportFilterGZipCompress filterWithCompressionLevel:-1],
nil]; self,
nil];
} }
``` ```
可以看到这个函数内部设置了多个 **filters**,其中一个就是 **self**,也就是 `APMCrashReporterSink` 对象,所以上面的 ` [self.sink filterReports: onCompletion:]` ,也就是调用 `APMCrashReporterSink` 内的数据处理方法。完了之后通过 `kscrash_callCompletion(onCompletion, reports, YES, nil);` 告诉 `KSCrash` 本地保存的 Crash 日志已经处理完毕,可以删除了。 可以看到这个函数内部设置了多个 **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);
}
``` ```
- (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 文件夹中的日志。 至此,概括下 KSCrash 做的事情,提供各种 crash 的监控能力,在 crash 后将进程信息、基本信息、异常信息、线程信息等用 c 高效转换为 json 写入文件App 下次启动后读取本地的 crash 文件夹中的 crash 日志,让开发者可以自定义 key、value 然后去上报日志到 APM 系统,然后删除本地 crash 文件夹中的日志。
```
-
### 4. 符号化 ### 4. 符号化
@@ -6340,12 +6263,8 @@ Crash log 统一入库 Kibana 时是没有符号化的,所以需要符号化
可能有些人一直没有遇到过因为在子线程操作 UI导致在开发阶段 Xcode console 输出了一堆日志,大体如下 可能有些人一直没有遇到过因为在子线程操作 UI导致在开发阶段 Xcode console 输出了一堆日志,大体如下
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2022-0204-SubThreadUIXcode1@2x.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2022-0204-SubThreadUIXcode1@2x.png)
其实我们可以给 Xcode 打个 `Runtime Issue Breakpoint` type 选择 `Main Thread Checker` 在发生子线程操作 UI 的时候就会被系统检测到并触发断点,同时可以看到堆栈情况 其实我们可以给 Xcode 打个 `Runtime Issue Breakpoint` type 选择 `Main Thread Checker` 在发生子线程操作 UI 的时候就会被系统检测到并触发断点,同时可以看到堆栈情况
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2022-0204-SubThreadUISymbolBreakpoints@2x.png) ![](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 调用,最后可以通过堆栈打印出来,就可以帮助定位到具体问题。 另外在监控到子线程调用 UI 调用时,在 Xcode 环境下,会将调用栈输出到控制台,经过测试,`libMainThreadChecker.dylib` 使用的是进行输出的,由于 NSLog 是将信息输出到 `STDERR`中,我们可以通过 `NSPipe` 与 `dup2` 将 `STDERR` 输出拦截,通过对信息的文案的判断,进而获取监测到的 UI 调用,最后可以通过堆栈打印出来,就可以帮助定位到具体问题。
`libMainThreadChecker.dylib` 库具有局限性,仅仅对系统提供的一些特定类的特定 API 在子线程调用会被监控到(例如 UIKit 框架中 UIView 类)。 `libMainThreadChecker.dylib` 库具有局限性,仅仅对系统提供的一些特定类的特定 API 在子线程调用会被监控到(例如 UIKit 框架中 UIView 类)。
但是某些类有些 API 我们也不希望在子线程被调用,这时候 `libMainThreadChecker.dylib`是无法满足的。 但是某些类有些 API 我们也不希望在子线程被调用,这时候 `libMainThreadChecker.dylib`是无法满足的。
对 `libMainThreadChecker.dylib` 库的汇编代码研究,发现 `libMainThreadChecker.dylib` 是通过内部 `__main_thread_add_check_for_selector` 这个方法来进行类和方法的注册的。所以如果我们同样可以通过 `dlsym` 来调用该方法,以达到对自定义类和方法的主线程调用监测。 对 `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) ![](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 官方文档,这里不做展开。 对 [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) 具体可以参考这个 [Demo](https://github.com/FantasticLBP/MainThreadChecker)
## 九、 APM 小结 ## 九、 APM 小结

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB