docs: Electron

This commit is contained in:
杭城小刘
2020-05-04 02:51:19 +08:00
parent 79f10acba2
commit 8dbcff87ed
42 changed files with 851 additions and 33 deletions

View File

@@ -1,10 +1,12 @@
# KVC && KVO
## 基本用法
### 字典快速赋值
KVC 可以将字典里面和 model 同名的 property 进行快速赋值 **setValuesForKeysWithDictionary**
## 一、基本用法-字典快速赋值
KVC 可以将字典里面和 model 同名的 property 进行快速赋值 `setValuesForKeysWithDictionary`
```objective-c
//前提model 中的各个 property 必须和 NSDictionary 中的属性一致
@@ -22,7 +24,8 @@ KVC 可以将字典里面和 model 同名的 property 进行快速赋值 **setVa
运行上面的代码,代码不崩溃,只不过在输出值的时候输出了 null
- 情况二:在 NSDictionary 中存在某个值,但是在 model 里面没有值
运行后编译成功,但是代码奔溃掉。原因是 KVC 。所以我们只需要实现这么一个方法。甚至不需要写函数体部分
```
@@ -62,7 +65,8 @@ NSMutableArray *hobbies = [_person mutableArrayValueForKeyPath:@"hobbies"];
```
- 情况五: 注册依赖键.
KVO 可以观察属性的二级属性对象的所有属性变化。说人话就是“假如 Person 类有个 Dog 类Dog 类有 name、fur、weight 等属性,我们给 Person 的 Dog 属性观察,假如 Dog 的任何属性变化是Person 的观察者对象都可以拿到当前的变化值。我们只需要在 Person 中写下面的方法即可”
```
@@ -88,7 +92,9 @@ self.person.dog.weight = 50;
```
## KVO 的本质
## 二、 KVO 的本质
kVO 是Objective-C 对观察者模式的实现。也是 Cocoa Binding 的基础。
@@ -96,16 +102,14 @@ kVO 是Objective-C 对观察者模式的实现。也是 Cocoa Binding 的基础
1. KVO 观察者和属性被观察的对象之间不是强引用的关系
2. KVO 的触发分为`自动触发模式`和`手动触发模式`2种。通常我们使用的都是自动通知注册观察者之后当条件触发的时候会自动调用`-(void)observeValueForKeyPath...`

如果需要实现手动通知,我们需要使用下面的方法
2. KVO 的触发分为`自动触发模式`和`手动触发模式`2种。通常我们使用的都是自动通知注册观察者之后当条件触发的时候会自动调用 `-(void)observeValueForKeyPath`。如果需要实现手动通知,我们需要使用下面的方法
```
```Objective-c
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
return NO;
}
```
3. 若类有实例变量 NSString *_foo 调用 setValue:forKey: 是以 foo 还是 _foo 作为 key
3. 若类有实例变量 NSString *_foo 调用 setValue:forKey: 是以 foo 还是 _foo 作为 key
都可以
@@ -120,8 +124,7 @@ kVO 是Objective-C 对观察者模式的实现。也是 Cocoa Binding 的基础
### 实现机制
## 三、 实现机制
> Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ...
@@ -149,7 +152,8 @@ Apple 文档告诉我们:被观察对象的 `isa指针` 会指向一个中间
关于分类与子类的关系可以看看我之前的 [文章](1.50.md).
### 模拟实现系统的 KVO
## 四、 模拟实现系统的 KVO
1. 创建被观察对象的子类
2. 重写观察对象属性的 set 方法,同时调用 `willChangeValueForKey、didChangeValueForKey`
@@ -246,11 +250,12 @@ void setName (id self, SEL _cmd, NSString *name) {
```
### KVO 的缺陷
KVO 虽然很强大,你只能重写 `-observeValueForKeyPath:ofObject:change:context: ` 来获得通知,想要提供自定义的 selector ,不行;想要传入一个 block没门儿。感觉如果加入 block 就更棒了。
KVO 的缺陷:
### KVO 的改装
KVO 虽然很强大,你只能重写 `-observeValueForKeyPath:ofObject:change:context: ` 来获得通知,想要提供自定义的 selector ,不行;想要传入一个 block 没门儿。感觉如果加入 block 就更棒了。
KVO 的改装:
看到官方的做法并不是很方便使用,我们看到无数的优秀框架都支持 block 特性,比如 AFNetworking ,所以我们可以将系统的 KVO 改装成支持 block。

View File

@@ -644,7 +644,7 @@ RISC 架构要求软件来指定各个操作步骤。比如上面的乘法,指
#### 2. 获取线程信息
#### 2. 获取线程信息<a name="threadInfo"></a>
讲完了区别来讲下如何做 CPU 使用率的监控
- 开启定时器,按照设定的周期不断执行下面的逻辑
@@ -1857,11 +1857,11 @@ iOS 网络框架层级关系如下:
iOS 网络现状是由4层组成的最底层的 BSD Sockets、SecureTransport次级底层是 CFNetwork、NSURLSession、NSURLConnection、WebView 是用 Objective-C 实现的,且调用 CFNetwork应用层框架 AFNetworking 基于 NSURLSession、NSURLConnection 实现。
目前业界对于网络监控主要有2种一种是通过 NSURLProtocol 监控、一种是通过 Hook 来监控。
目前业界对于网络监控主要有2种一种是通过 NSURLProtocol 监控、一种是通过 Hook 来监控。下面介绍几种办法来监控网络请求,各有优缺点。
#### 2.1 NSURLProtocol 监控 App 网络请求<a name="network-2.1"></a>
#### 2.1 方案一:NSURLProtocol 监控 App 网络请求<a name="network-2.1"></a>
NSURLProtocol 作为上层接口,使用较为简单,但 NSURLProtocol 属于 URL Loading System 体系中。应用协议的支持程度有限,支持 FTP、HTTP、HTTPS 等几个应用层协议,对于其他的协议则无法监控,存在一定的局限性。如果监控底层网络库 CFNetwork 则没有这个限制。
@@ -2341,7 +2341,7 @@ API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0))
#### 2.2 骚操作篇 <a name="network-2.2"></a>
#### 2.2 方案二NSURLProtocol 监控 App 网络请求之黑魔法篇 <a name="network-2.2"></a>
文章上面 [2.1 ](#network-2.1)分析到了 NSURLSessionTaskMetrics 由于兼容性问题,对于网络监控来说似乎不太完美,但是自后在搜资料的时候看到了一篇[文章](https://www.jianshu.com/p/1c34147030d1)。文章在分析 WebView 的网络监控的时候分析 Webkit 源码的时候发现了下面代码
@@ -2433,7 +2433,11 @@ NSURLSession 在 iOS9 之前使用 `_setCollectsTimingData:` 就可以使用 Tim
#### 2.3 Hook
#### 2.3 方案三:Hook
iOS 中 hook 技术有2类一种是 NSProxy一种是 method swizzlingisa swizzling
##### 2.3.1 方法一
写 SDK 肯定不可能手动侵入业务代码(你没那个权限提交到线上代码 😂),所以不管是 APM 还是无痕埋点都是通过 Hook 的方式。
@@ -2548,6 +2552,42 @@ void printResponseData (CFDataRef responseData) {
我们知道 NSURLSession、NSURLConnection、CFNetwork 的使用都需要调用一堆方法进行设置然后需要设置代理对象,实现代理方法。所以针对这种情况进行监控首先想到的是使用 runtime hook 掉方法层级。但是针对设置的代理对象的代理方法没办法 hook因为不知道代理对象是哪个类。所以想办法可以 hook 设置代理对象这个步骤,将代理对象替换成我们设计好的某个类,然后让这个类去实现 NSURLConnection、NSURLSession、CFNetwork 相关的代理方法。然后在这些方法的内部都去调用一下原代理对象的方法实现。所以我们的需求得以满足,我们在相应的方法里面可以拿到监控数据,比如请求开始时间、结束时间、状态码、内容大小等。
NSURLSession、NSURLConnection hook 如下。
![NSURLSession Hook](./../assets/2020-04-13-NSURLSessionHook.jpeg)
![NSURLConnection Hook](./../assets/2020-04-13-NSURLConnectionHook.jpeg)
业界有 APM 针对 CFNetwork 的方案,整理描述下:
CFNetwork 是 c 语言实现的,要对 c 代码进行 hook 需要使用 Dynamic Loader Hook 库 - [fishhook](https://github.com/facebook/fishhook)。
> **Dynamic Loader**dyld通过更新 **Mach-O** 文件中保存的指针的方法来绑定符号。借用它可以在 **Runtime** 修改 **C** 函数调用的函数指针。**fishhook** 的实现原理:遍历 `__DATA segment` 里面 `__nl_symbol_ptr` 、`__la_symbol_ptr` 两个 section 里面的符号,通过 Indirect Symbol Table、Symbol Table 和 String Table 的配合,找到自己要替换的函数,达到 hook 的目的。
> /* Returns the number of bytes read, or -1 if an error occurs preventing any
>
> bytes from being read, or 0 if the stream's end was encountered.
>
> It is an error to try and read from a stream that hasn't been opened first.
>
> This call will block until at least one byte is available; it will NOT block
>
> until the entire buffer can be filled. To avoid blocking, either poll using
>
> CFReadStreamHasBytesAvailable() or use the run loop and listen for the
>
> kCFStreamEventHasBytesAvailable event for notification of data available. */
>
> CF_EXPORT
>
> CFIndex CFReadStreamRead(CFReadStreamRef **_Null_unspecified** stream, UInt8 * **_Null_unspecified** buffer, CFIndex bufferLength);
CFNetwork 使用 CFReadStreamRef 来传递数据,使用回调函数的形式来接受服务器的响应。当回调函数受到
具体步骤及其关键代码如下,以 NSURLConnection 举例
- 因为要 Hook 挺多地方,所以写一个 method swizzling 的工具类
@@ -2791,6 +2831,10 @@ void printResponseData (CFDataRef responseData) {
这样下来就是可以监控到网络信息了,然后将数据交给数据上报 SDK按照下发的数据上报策略去上报数据。
##### 2.3.2 方法二
其实,针对上述的需求还有另一种方法一样可以达到目的,那就是 **isa swizzling**。
顺道说一句,上面针对 NSURLConnection、NSURLSession、NSInputStream 代理对象的 hook 之后,利用 NSProxy 实现代理对象方法的转发,有另一种方法可以实现,那就是 **isa swizzling**。
- Method swizzling 原理
@@ -2835,13 +2879,93 @@ void printResponseData (CFDataRef responseData) {
![isa swizzling](./../assets/2020-04-13-isaSwizzling.png)
我们来分析一下为什么修改 `isa` 可以实现目的呢?
1. 写 APM 监控的人没办法确定业务代码
2. 不可能为了方便监控 APM写某些类让业务线开发者别使用系统 NSURLSession、NSURLConnection 类
想想 KVO 的实现原理?结合上面的图
- 创建监控对象子类
- 重写子类中属性的 getter、seeter
- 将监控对象的 isa 指针指向新创建的子类
- 在子类的 getter、setter 中拦截值的变化,通知监控对象值的变化
- 监控完之后将监控对象的 isa 还原回去
按照这个思路,我们也可以对 NSURLConnection、NSURLSession 的 load 方法中动态创建子类,在子类中重写方法,比如 `- (**nullable** **instancetype**)initWithRequest:(NSURLRequest *)request delegate:(**nullable** **id**)delegate startImmediately:(**BOOL**)startImmediately;` ,然后将 NSURLSession、NSURLConnection 的 isa 指向动态创建的子类。在这些方法处理完之后还原本身的 isa 指针。
不过 isa swizzling 针对的还是 method swizzling代理对象不确定还是需要 NSProxy 进行动态处理。
#### 2.4 监控 App 常见网络请求<a name="categoryNameRules"></a>
至于如何修改 isa我写一个简单的 Demo 来模拟 KVO
```objective-c
- (void)lbpKVO_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context {
//生成自定义的名称
NSString *className = NSStringFromClass(self.class);
NSString *currentClassName = [@"LBPKVONotifying_" stringByAppendingString:className];
//1. runtime 生成类
Class myclass = objc_allocateClassPair(self.class, [currentClassName UTF8String], 0);
// 生成后不能马上使用,必须先注册
objc_registerClassPair(myclass);
//2. 重写 setter 方法
class_addMethod(myclass,@selector(say) , (IMP)say, "v@:@");
// class_addMethod(myclass,@selector(setName:) , (IMP)setName, "v@:@");
//3. 修改 isa
object_setClass(self, myclass);
//4. 将观察者保存到当前对象里面
objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_ASSIGN);
//5. 将传递的上下文绑定到当前对象里面
objc_setAssociatedObject(self, "context", (__bridge id _Nullable)(context), OBJC_ASSOCIATION_RETAIN);
}
void say(id self, SEL _cmd)
{
// 调用父类方法一
struct objc_super superclass = {self, [self superclass]};
((void(*)(struct objc_super *,SEL))objc_msgSendSuper)(&superclass,@selector(say));
NSLog(@"%s", __func__);
// 调用父类方法二
// Class class = [self class];
// object_setClass(self, class_getSuperclass(class));
// objc_msgSend(self, @selector(say));
}
void setName (id self, SEL _cmd, NSString *name) {
NSLog(@"come here");
//先切换到当前类的父类,然后发送消息 setName然后切换当前子类
//1. 切换到父类
Class class = [self class];
object_setClass(self, class_getSuperclass(class));
//2. 调用父类的 setName 方法
objc_msgSend(self, @selector(setName:), name);
//3. 调用观察
id observer = objc_getAssociatedObject(self, "observer");
id context = objc_getAssociatedObject(self, "context");
if (observer) {
objc_msgSend(observer, @selector(observeValueForKeyPath:ofObject:change:context:), @"name", self, @{@"new": name, @"kind": @1 } , context);
}
//4. 改回子类
object_setClass(self, class);
}
@end
```
#### 2.4 方案四:监控 App 常见网络请求
本着成本的原因,由于现在大多数的项目的网络能力都是通过 [AFNetworking](https://github.com/AFNetworking/AFNetworking) 完成的,所以本文的网络监控可以快速完成。
@@ -2913,16 +3037,130 @@ self.didCompleteObserver = [[NSNotificationCenter defaultCenter] addObserverForN
#### 2.5 iOS 流量监控
## 六、 Crash 监控
简易版本。
## 六、 电量消耗
移动设备上电量一直是比较敏感的问题,如果用户在某款 App 的时候发现耗电量严重、手机发热严重,那么用户很大可能会马上卸载这款 App。所以需要在开发阶段关心耗电量问题。
一般来说遇到耗电量较大,我们立马会想到是不是使用了定位、是不是使用了频繁网络请求、是不是不断循环做某件事情?
开发阶段基本没啥问题,我们可以结合 `Instrucments` 里的 `Energy Log` 工具来定位问题。但是线上问题就需要代码去监控耗电量,可以作为 APM 的能力之一。
### 1. 如何获取电量
在 iOS 中,`IOKit` 是一个私有框架,用来获取硬件和设备的详细信息,也是硬件和内核服务通信的底层框架。所以我们可以通过 `IOKit `来获取硬件信息,从而获取到电量信息。步骤如下:
- 首先在苹果开放源代码 opensource 中找到 [IOPowerSources.h](https://opensource.apple.com/source/IOKitUser/IOKitUser-647.6/ps.subproj/IOPowerSources.h.auto.html)、[IOPSKeys.h](https://opensource.apple.com/source/IOKitUser/IOKitUser-647.6/ps.subproj/IOPSKeys.h)。在 Xcode 的 `Package Contents` 里面找到 `IOKit.framework`。 路径为 `/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/IOKit.framework`
- 然后将 IOPowerSources.h、IOPSKeys.h、IOKit.framework 导入项目工程
- 设置 UIDevice 的 batteryMonitoringEnabled 为 true
- 获取到的耗电量精确度为 1%
```objective-c
- (double)fetchBatteryCostUsage
{
// returns a blob of power source information in an opaque CFTypeRef
CFTypeRef blob = IOPSCopyPowerSourcesInfo();
// returns a CFArray of power source handles, each of type CFTypeRef
CFArrayRef sources = IOPSCopyPowerSourcesList(blob);
CFDictionaryRef pSource = NULL;
const void *psValue;
// returns the number of values currently in an array
int numOfSources = CFArrayGetCount(sources);
// error in CFArrayGetCount
if (numOfSources == 0) {
NSLog(@"Error in CFArrayGetCount");
return -1.0f;
}
// calculating the remaining energy
for (int i=0; i<numOfSources; i++) {
// returns a CFDictionary with readable information about the specific power source
pSource = IOPSGetPowerSourceDescription(blob, CFArrayGetValueAtIndex(sources, i));
if (!pSource) {
NSLog(@"Error in IOPSGetPowerSourceDescription");
return -1.0f;
}
psValue = (CFStringRef) CFDictionaryGetValue(pSource, CFSTR(kIOPSNameKey));
int curCapacity = 0;
int maxCapacity = 0;
double percentage;
psValue = CFDictionaryGetValue(pSource, CFSTR(kIOPSCurrentCapacityKey));
CFNumberGetValue((CFNumberRef)psValue, kCFNumberSInt32Type, &curCapacity);
psValue = CFDictionaryGetValue(pSource, CFSTR(kIOPSMaxCapacityKey));
CFNumberGetValue((CFNumberRef)psValue, kCFNumberSInt32Type, &maxCapacity);
percentage = ((double) curCapacity / (double) maxCapacity * 100.0f);
NSLog(@"curCapacity : %d / maxCapacity: %d , percentage: %.1f ", curCapacity, maxCapacity, percentage);
return percentage;
}
return -1.0f;
}
```
### 2. 定位问题
通常我们通过 Instrucments 里的 Energy Log 解决了很多问题后App 上线了,线上的耗电量解决就需要使用 APM 来解决了。耗电地方可能是二方库、三方库,也可能是某个同事的代码。
思路是:在检测到耗电后,先找到有问题的线程,然后堆栈 dump还原案发现场。
在上面部分我们知道了线程信息的结构, `thread_basic_info` 中有个记录 CPU 使用率百分比的字段 `cpu_usage`。所以我们可以通过遍历当前线程,判断哪个线程的 CPU 使用率较高,从而找出有问题的线程。然后再 dump 堆栈,从而定位到发生耗电量的代码。详细请看 [3.2](#threadInfo) 部分。
### 3. 开发阶段针对电量消耗我们能做什么
CPU 密集运算是耗电量主要原因。所以我们对 CPU 的使用需要精打细算。尽量避免让 CPU 做无用功。对于大量数据的复杂运算可以借助服务器的能力、GPU 的能力。如果方案设计必须是在 CPU 上完成数据的运算,则可以利用 GCD 技术,使用 `dispatch_block_create_with_qos_class(<#dispatch_block_flags_t flags#>, dispatch_qos_class_t qos_class, <#int relative_priority#>, <#^(void)block#>)()` 并指定 队列的 qos 为 `QOS_CLASS_UTILITY`。将任务提交到这个队列的 block 中,在 QOS_CLASS_UTILITY 模式下,系统针对大量数据的计算,做了电量优化
除了 CPU 大量运算I/O 操作也是耗电主要原因。业界常见方案都是将「碎片化的数据写入磁盘存储」这个操作延后先在内存中聚合吗然后再进行磁盘存储。碎片化数据先聚合在内存中进行存储的机制iOS 提供 `NSCache` 这个对象。
NSCache 是线程安全的NSCache 会在达到达预设的缓存空间的条件时清理缓存,此时会触发 `- (**void**)cache:(NSCache *)cache willEvictObject:(**id**)obj;` 方法回调,在该方法内部对数据进行 I/O 操作,达到将聚合的数据 I/O 延后的目的。I/O 次数少了,对电量的消耗也就减少了。
NSCache 的使用可以查看 SDWebImage 这个图片加载框架。在图片读取缓存处理时没直接读取硬盘文件I/O而是使用系统的 NSCache。
```objective-c
- (nullable UIImage *)imageFromMemoryCacheForKey:(nullable NSString *)key {
return [self.memoryCache objectForKey:key];
}
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
[self.memoryCache setObject:diskImage forKey:key cost:cost];
}
return diskImage;
}
```
可以看到主要逻辑是先从磁盘中读取图片,如果配置允许开启内存缓存,则将图片保存到 NSCache 中,使用的时候也是从 NSCache 中读取图片。NSCache 的 `totalCostLimit、countLimit` 属性,
`- (void)setObject:(ObjectType)obj forKey:(KeyType)key cost:(NSUInteger)g;` 方法用来设置缓存条件。所以我们写磁盘、内存的文件操作时可以借鉴该策略,以优化耗电量。
## 七、 Crash 监控
对于奔溃
*base +offset= 你imp
## 参考资料
@@ -2942,3 +3180,4 @@ self.didCompleteObserver = [[NSNotificationCenter defaultCenter] addObserverForN
- [iOS堆栈信息解析函数地址与符号关联](https://www.jianshu.com/p/df5b08330afd)
- [Apple-CFNetwork Programming Guide](https://developer.apple.com/library/archive/documentation/Networking/Conceptual/CFNetwork/Introduction/Introduction.html#//apple_ref/doc/uid/TP30001132-CH1-DontLinkElementID_30)

6
Chapter1 - iOS/1.80.md Normal file
View File

@@ -0,0 +1,6 @@
# 打造一个通用、可配置的数据上报 SDK
1. https://mp.weixin.qq.com/s?__biz=MzI4MTY5NTk4Ng==&mid=2247489516&amp;idx=1&amp;sn=97c8b0fd84e5fc5b214539c135919884&source=41#wechat_redirect
2.

View File

@@ -4,6 +4,10 @@
## 一、 NSURLProtocol 是什么
> An NSURLProtocol object handles the loading of protocol-specific URL data. The NSURLProtocol class itself is an abstract class that provides the infrastructure for processing URLs with a specific URL scheme. You create subclasses for any custom protocols or URL schemes that your app supports.
![URL Loading System](./../assets/2020-04-18-URL-loading-system.png)
NSURLProtocol 是 Foundation 框架中 URL Loading System 的一部分。它可以让开发者可以在不修改应用内原始请求代码的情况下,去改变 URL 加载的全部细节。换句话说NSURLProtocol 是一个被 Apple 默许的中间人攻击。
虽然 NSURLProtocol 叫“Protocol”却不是协议而是一个抽象类。
@@ -324,7 +328,38 @@ didCancelAuthenticationChallenge:challenge];
## 五、 补充内容
## 五、 读源码,学习 NSURLProtocol
iOS 中网络测试框架 [ OHHTTPStubs](https://github.com/AliSoftware/OHHTTPStubs)的实现就是利用了 NSURLProtocol 实现的。
几个主要类及其功能HTTPStubsProtocol 拦截网络请求HTTPStubs 单例管理 HTTPStubsDescriptor 实例对象HTTPStubsResponse 伪造 HTTP 请求。
HTTPStubsProtocol 继承自 NSURLProtocol可以在 HTTP 请求发送之前对 request 进行过滤处理
```objective-c
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
BOOL found = ([HTTPStubs.sharedInstance firstStubPassingTestForRequest:request] != nil);
if (!found && HTTPStubs.sharedInstance.onStubMissingBlock) {
HTTPStubs.sharedInstance.onStubMissingBlock(request);
}
return found;
}
```
`firstStubPassingTestForRequest` 方法内部会判断请求是否需要被当前对象处理
紧接着开始发送网络请求。实际上在 `- (void)startLoading` 方法中可以用任何网络能力去完成请求,比如 NSURLSession、NSURLConnection、AFNetworking 或其他网络框架。OHHTTPStubs 的做法是获取 request、client 对象。如果 HTTPStubs 单例中包含 `onStubActivationBlock` 对象,则执行该 block然后利用 responseBlock 对象返回一个 HTTPStubsResponse 响应对象。
[参考资料](https://github.com/draveness/analyze/blob/master/contents/OHHTTPStubs/如何进行%20HTTP%20MockiOS.md)
## 六、 补充内容
### 1. 使用 NSURLSession 时的注意事项

View File

@@ -83,7 +83,7 @@
* [77、iOS 打包系统构建加速](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.77.md)
* [78、App 上架包预检](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.78.md)
* [79、深入理解各种锁](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.79.md)
* [80、打造功能强大的数据上报组件](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.80.md)
* [80、打造一个通用、可配置的数据上报 SDK](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.80.md)
* [81、__asm__ 重命名符号](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.81.md)
* [82、runtime](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.82.md)
* [83、NSURLProtocol 应用场景](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter1%20-%20iOS/1.83.md)