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。
参考文章:
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);
}
```

View File

@@ -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<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];
}
```
方法内部将 `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 <KSCrashReportFilter>) defaultCrashReportFilterSetAppleFmt
```
- (id <KSCrashReportFilter>) 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 小结

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB