mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-24 20:00:37 +00:00
docs: 样式
This commit is contained in:
@@ -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);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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 输出了一堆日志,大体如下
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
其实我们可以给 Xcode 打个 `Runtime Issue Breakpoint` ,type 选择 `Main Thread Checker`, 在发生子线程操作 UI 的时候就会被系统检测到并触发断点,同时可以看到堆栈情况
|
||||
|
||||

|
||||
@@ -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` 来调用该方法,以达到对自定义类和方法的主线程调用监测。
|
||||

|
||||
|
||||
@@ -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 |
Reference in New Issue
Block a user