开源信息

coobjc 是由手淘架构团队推出的能在 iOS 上使用的协程开发框架,所以大家在了解coobjc之前必须先了解什么是协程,说到协程必定让人想起和它相关的两个概念进程线程,
这里不会对这两个概念进行长篇大论,概括地讲对操作系统来说,线程是最小的执行单元,进程是最小的资源管理单元。进程负责开拓各个资源,线程共享这些资源,每个线程有单独的堆栈空间。并且无论进程还是线程,都是由操作系统所管理的。
而协程是用户态的微线程,协程不是被操作系统内核所管理,而完全是由程序所控制。

它的好处有哪些呢?

首先它可以以同步的方式写异步逻辑,可以避免“回调地域”现象,在性能方面调度性能更快,协程本身不需要进行内核级线程的切换,调度性能快,即使创建上万个协程也毫无压力。协程的使用以帮助开发减少锁、信号量的滥用。
对于经常进行高并发处理的服务端协程是很实用的。但是这不意味着移动端就不需要协程了。就iOS开发而言Objective C中基于Block 的异步编程回调也会照成比较常见的“回调地域”。如果具备同步方式编写异步代码可以极大地改善我们代码的逻辑结构,并且由于协程的引入也可以减少因为锁的使用带来的性能损耗。

整体架构

下面是coobjc的整体架构图:

底层是协程的核心,包括堆栈切换管理,协程调度,协程之间channel通信实现
中间层是协程的封装,提供了 async/await, generator 和 Actor的支持
顶层是对系统库的一个扩展,是对FoundationUIKit IO及其他耗时操作的封装

coobjec中每个协程都是可以暂停和恢复的,并且每个协程都分配一个单独的内存区域用于存储它的调用栈,它有四个状态分别是:READYRUNNINGSUSPENDDEAD,并且在运行过程中可以多次在RUNNINGSUSPEND状态之间进行切换。
下面是协程进行yield和resume操作的示意图:

coobjc内部使用一个调度器来负责用户所有协程的调度,它实际上是通过一个协程队列来进行管理,调度器会不断从队列中取出协程去执行。一旦队列中没有协程可以执行的时候,会切换到线程执行。所以当我们需要执行某个协程的时候,我们只需要将协程添加到某个线程的调度器队列中就可以了。
调度器负责执行它。

话不多说我们来看下coobjc的具体用法:

coobjc 的使用

1 创建协程

coobjc可以在当前线程或者指定的queue创建协程,在哪里创建就会在哪个线程进行调度,当然还可以在指定的队列创建和运行协程。

co_launch(^{
//默认在当前线程进行调度
});

co_launch_onqueue(q, ^{
//在指定的队列中运行的协程
});

2 取消协程

在coobjc中协程是可取消的,ObjC不建议使用异常,所以coobjc中不使用异常来取消协程。要取消协程可以调用CCOCoroutine的cancel方法。通过co_isCancelled宏来查询当前协程是否被取消了。

CCOCoroutine *co = co_launch(^{
val++;
co_delay(1.0);
if(co_isCancelled()){
//Do some cleaning operations
return;
}
val++;
});

[co cancel];

在调用cancel后,协程的内部代码将不再继续执行,协程的内存将会被释放。

3. COPromise用法

await主要用于避免回调地狱现象,有时候我们会遇到请求两个不相关的数据接口后,将最终的数据组合后作为最终数据返回。如果不借助await的话,只能分别串行请求后最后将数据组合,这样会显得很低效。借助协程可以同时并行执行然后将数据合并。

  1. 定义一个COPromise,在COPromise中通过fulfill 返回成功的结果,通过reject 返回失败的结果。返回的错误信息可以通过co_getError来获取。

fulfill

- (COPromise *)testPromiseFullfill {
COPromise *promise = [COPromise promise];
dispatch_async(dispatch_get_main_queue(), ^{
[promise fulfill:@"Hello coobjc!"];
});
return promise;
}

co_launch(^{
id result = await([self testPromiseFullfill]);
NSLog(@"result = %@",result);
});

reject

- (COPromise *)testPromiseReject {
COPromise *promise = [COPromise promise];
dispatch_async(dispatch_get_main_queue(), ^{
[promise reject:[NSError errorWithDomain:@"COPromise" code:-100 userInfo:nil]];
});
return promise;
}

co_launch(^{
id result = await([self testPromiseReject]);
NSError *error = co_getError();
if(error) {
NSLog(@"Error = %@",error);
} else {
NSLog(@"result = %@",result);
}
});

fullfill && reject

- (COPromise *)testPromiseRejectFullfile {
return [COPromise promise:^(COPromiseFulfill _Nonnull fullfill, COPromiseReject _Nonnull reject) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
fullfill(@"Say Hello To Coobjc");
});
}];
}

co_launch(^{
id res = await([self testPromiseRejectFullfile]);
NSLog(@"result = %@",res);
});

cancel

- (COPromise *)testPromiseCancel {
COPromise *promise = [COPromise promise];
dispatch_async(dispatch_get_main_queue(), ^{
[promise cancel];
});
return promise;
}

co_launch(^{
COPromise *promise = [self testPromiseCancel];
[promise onCancel:^(COPromise * _Nonnull promise) {
NSLog(@"isCancel");
}];

[promise catch:^(NSError * _Nonnull error) {
NSLog(@"NSError");
}];
id res = await(promise);
if(!res) {
NSError *error = co_getError();
BOOL isPromiseCancel = [COPromise isPromiseCancelled:error];
NSLog(@"isPromiseCancel %ld",isPromiseCancel);
return;
}
NSLog(@"result = %@",res);
});

then

co_launch(^{
COPromise *promise = [self testPromiseFullfill];
COPromise *thenPromise = [promise then:^id _Nullable(id _Nullable value) {
return [self testPromiseThen];
}];

id res = await(thenPromise);
if(!res) {
NSError *error = co_getError();
BOOL isPromiseCancel = [COPromise isPromiseCancelled:error];
return;
}
NSLog(@"result = %@",res);
});

批量等待

co_launch(^{
NSArray *batchFullfile =
batch_await(@[
[self testPromiseFullfill01],
[self testPromiseFullfill02],
[self testPromiseFullfill03],
]);

if(batchFullfile && batchFullfile.count) {
NSLog(@"%@",batchFullfile);
}
});

我们来看下COPromise的一些关键属性和方法:

关键属性:

/**
Tell the promise is pending or not.
*/
@property(nonatomic, readonly) BOOL isPending;

/**
Tell the promise is fulfilled or not.
*/
@property(nonatomic, readonly) BOOL isFulfilled;

/**
Tell the promise is rejected or not.
*/
@property(nonatomic, readonly) BOOL isRejected;

/**
If fulfilled, value store into this property.
*/
@property(nonatomic, readonly, nullable) Value value;

/**
If reject, error store into this property
*/
@property(nonatomic, readonly, nullable) NSError *error;

关键构造方法:

/**
Create a promise without constructor. Which means, you should control when the job begins.

@return The `COPromise` instance
*/
+ (instancetype)promise;
/**
Create a promise with constructor. the job begans when someone observing on it.

@param constructor the constructor block.
@return The `COPromise` instance
*/
+ (instancetype)promise:(COPromiseConstructor)constructor;
/**
Create a promise with constructor. the job begans when someone observing on it.

@param constructor the constructor block.
@param queue the dispatch_queue_t that the job run.
@return The `COPromise` instance
*/
+ (instancetype)promise:(COPromiseConstructor)constructor onQueue:(dispatch_queue_t _Nullable )queue;

关键方法:

/**
Fulfill the promise with a return value.

@param value the value fulfilled.
*/
- (void)fulfill:(nullable Value)value;

/**
Reject the promise with a error

@param error the error.
*/
- (void)reject:(NSError * _Nullable)error;

/**
Cancel the job.

@discussion If you want a `COPromise` be cancellable, you must make the job cancel in `onCancel:`.
*/
- (void)cancel;
/**
Set the onCancelBlock.

@param onCancelBlock will execute on the promise cancelled.
*/
- (void)onCancel:(COPromiseOnCancelBlock _Nullable )onCancelBlock;

/**
Chained observe the promise fulfilled.

@param work the observer worker.
@return The chained promise instance.
*/
- (COPromise *)then:(COPromiseThenWorkBlock)work;

/**
Observe the promises rejected.

@param reject the reject dealing worker.
@return The chained promise instance.
*/
- (COPromise *)catch:(COPromiseCatchWorkBlock)reject;

/**
Tell if the error is promise cancelled error

@param error the error object
@return is cancellled error.
*/
+ (BOOL)isPromiseCancelled:(NSError *)error;

4.COProgressPromise用法

static COProgressPromise* progressDownloadFileFromUrl(NSString *url){
COProgressPromise *promise = [COProgressPromise promise];
[NSURLSession sharedSession].configuration.requestCachePolicy = NSURLRequestReloadIgnoringCacheData;
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:url] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
[promise reject:error];
}
else{
[promise fulfill:data];
}
}];
[task resume];
if (@available(iOS 11.0, *)) {
[promise setupWithProgress:task.progress];
} else {
// Fallback on earlier versions
NSProgress *progress = [NSProgress progressWithTotalUnitCount:10];
[promise setupWithProgress:progress];
dispatch_source_t timer = nil;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), 0.1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
if (progress.completedUnitCount < progress.totalUnitCount) {
progress.completedUnitCount += 1;
}
else{
dispatch_source_cancel(timer);
}
});
dispatch_resume(timer);
[promise onCancel:^(COPromise * _Nonnull promise) {
dispatch_source_cancel(timer);
}];
}
return promise;
}

co_launch(^{
int progressCount = 0;
COProgressPromise *promise = progressDownloadFileFromUrl(@"http://img17.3lian.com/d/file/201701/17/9a0d018ba683b9cbdcc5a7267b90891c.jpg");
for(id p in promise){
double v = [p doubleValue];
NSLog(@"current progress: %f", (float)v);
progressCount++;
}
NSData *data = await(promise);
});

上述的这些co_launch都可以在任何子线程中运行。

5. COChan用法

COChan 主要用于协程之间进行数据传输,它有阻塞和非阻塞两种方式收发数据,针对channel,可以有无缓存类型,有缓存类型,以及无限缓存类型,下面会进行详细介绍。

和Promise类似的用法:

- (COChan<id> *)co_fetchSomething {
COChan *chan = [COChan chan];
dispatch_async(_someQueue, ^{
// fetch result operations
...
[chan send_nonblock:result];
});
return chan;
}

// calling in a coroutine.
co_launch(^{
id ret = await([self co_fetchSomething]);
});

作为生产者消费者的用法:

COChan *chan = [COChan chanWithBuffCount:10];
co_launch(^{
for (int i = 0; i < 10; i++) {
[chan send_nonblock:@(i)];
}
});

co_launch(^{
//NSArray *data = [chan receiveAll];
//NSLog(@"%@",data);

NSArray *dataWithCount = [chan receiveWithCount:3];
NSLog(@"%@",dataWithCount);
});

当COChan容量为0的时候,发送数据是阻塞的,只有在有人接收数据后才会执行send之后的代码

COChan *chan = [COChan chanWithBuffCount:0];
__block NSInteger step = 0;
co_launch(^{
step = 1;//@1
[chan send:@111]; //代码会停在这里直到receive_nonblock消费数据后才继续往下执行 //@2
step = 2;//@6
});

co_launch(^{
step = 3;//@3
id value = [chan receive_nonblock];//@4
step = 4;//@5
});

上面数字代表的是代码的执行顺序。

当将COChan容量改为非0的时候执行顺序如下:

COChan *chan = [COChan chanWithBuffCount:2];
__block NSInteger step = 0;
co_launch(^{
step = 1;//@1
[chan send:@111]; //代码不会阻塞 //@2
step = 2;//@3
});

co_launch(^{
step = 3;//@4
id value = [chan receive_nonblock];//@5
step = 4;//@6
});

如果COChan中没有数据的时候调用receive将会阻塞:

__block NSInteger step = 0;
COChan *chan = [COChan chan];
co_launch(^{
step = 1;//@1
id value = [chan receive];//@2
step = 2;//@6
});

co_launch(^{
step = 3;//@3
[chan send:@111];//@4
step = 4;//@5
});

也就是send方法会往COChan中通道缓存中添加消息,如果满的话则会阻塞,直到数据被receive之后,才会继续执行。

receive会不断从通道缓存中获取数据,如果数据被取完后就阻塞,直到有数据send到缓存的时候,才会继续执行。这是很典型的生产者消费者模型。
send_nonblock会完缓存通道送数据后立刻返回,如果缓存满了就会丢弃数据,不会阻塞。看下coobjc给出的两个测试用例:

it(@"send non blocking will not block the coroutine.", ^{
__block NSInteger step = 0;
COChan *chan = [COChan chanWithBuffCount:1];
co_launch(^{
step = 1;
[chan send_nonblock:@111];
expect(step).to.equal(1);
step = 2;
});

co_launch(^{
expect(step).to.equal(2);
step = 3;
id value = [chan receive_nonblock];
expect(step).to.equal(3);
expect(value).to.equal(@111);
});
waitUntil(^(DoneCallback done) {
dispatch_async(dispatch_get_main_queue(), ^{
expect(step).to.equal(3);
done();
});
});
});

it(@"Channel buff is full, send non blocking will abandon the value.", ^{
__block NSInteger step = 0;

COChan *chan = [COChan chan];
co_launch(^{
step = 1;
[chan send_nonblock:@111];
expect(step).to.equal(1);
step = 2;
});
co_launch(^{
expect(step).to.equal(2);
step = 3;
id value = [chan receive_nonblock];
expect(step).to.equal(3);
expect(value).to.equal(nil);
});
waitUntil(^(DoneCallback done) {
dispatch_async(dispatch_get_main_queue(), ^{
expect(step).to.equal(3);
done();
});
});
});

我们看下接收被阻塞的例子:

it(@"receive can block muti coroutine.", ^{
__block NSInteger step = 0;

COChan *chan = [COChan chanWithBuffCount:1];

co_launch(^{
step = 1;
id value = [chan receive];
expect(step).to.equal(7);
expect(value).to.equal(@111);
step = 2;
});

co_launch(^{
expect(step).to.equal(1);
step = 3;
id value = [chan receive];
expect(step).to.equal(2);
expect(value).to.equal(@222);
step = 4;
});

co_launch(^{
expect(step).to.equal(3);
step = 5;
[chan send:@111];
expect(step).to.equal(5);

step = 6;
[chan send:@222];
expect(step).to.equal(6);
step = 7;
});
waitUntil(^(DoneCallback done) {
dispatch_async(dispatch_get_main_queue(), ^{
expect(step).to.equal(4);
done();
});
});
});

上面的例子中缓存通道容量都是指定的,还可以使用可扩展的通道expandableChan:

it(@"expandableChan will not abandon values.", ^{
__block NSInteger receiveCount = 0;
__block NSInteger receiveValue = 0;
__block NSInteger sendCount = 0;
COChan *chan = [COChan expandableChan];
for (int i = 0; i < 2000; i++) {
co_launch(^{
[chan send:@(i)];
sendCount++;
});
}

for (int i = 0; i < 2000; i++) {
co_launch(^{
id value = [chan receive];
receiveCount++;
receiveValue+=[value integerValue];
});
}
waitUntilTimeout(100, ^(DoneCallback done) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
expect(receiveCount).to.equal(2000);
expect(receiveValue).to.equal(1999000);
expect(sendCount).to.equal(2000);
done();
});
});
});

在进入下一个主题之前我们先回顾下OCChannel接口:

构造方法:

/**
容量为0的消息通道
*/
+ (instancetype)chan;

/**
指定容量的消息通道
*/
+ (instancetype)chanWithBuffCount:(int32_t)buffCount;

/**
创建一个可扩展的通道,通道的缓存大小是可以扩展的,这样send就不会阻塞当前进程,并且发送的数据将不会丢失
*/
+ (instancetype)expandableChan;

往通道发送数据:

/**
发送一个数据到通道上,这个方法会在缓存已满,并且没有接收者的情况下阻塞当前协程
*/
- (void)send:(Value _Nullable )val;

/**
和上面一致,只不过会在协程取消的时候调用cancelBlock
*/
- (void)send:(Value _Nullable )val onCancel:(COChanOnCancelBlock _Nullable)cancelBlock;

/**
非阻塞式得向通道发送数据:
如果有接收者则发送
如果没有接收者,但是缓存还没满则存在缓存中
如果没有接收者,并且缓存已经满了,则丢弃数据
*/
- (void)send_nonblock:(Value _Nullable )val;

接收数据

/**
阻塞式从通道中获取数据,这个方法会一直阻塞,直到有人往通道上发送数据为止
*/
- (Value _Nullable )receive;

/**
和上面一致,只不过会在协程取消的时候调用cancelBlock
*/
- (Value _Nullable )receiveWithOnCancel:(COChanOnCancelBlock _Nullable)cancelBlock;

/**
非阻塞式接收数据
如果缓存中有数据则从缓存中直接取数据后返回
如果缓存是空的,但是刚好有人发送了数据,则直接接收
如果缓存是空的,但是没人发送数据,这时候返回nil
*/
- (Value _Nullable)receive_nonblock;

/**
阻塞接收通道的所有数据
1. 如果通道没有数据,那么会阻塞等待直到有一个数据为止
2. 如果通道内有数据则返回所有的值
3. 如果发送了nil,那么接送到数据将会是 [NSNull null],因此需要注意检测返回数组中的类型
*/
- (NSArray<Value> * _Nonnull)receiveAll;

/**
阻塞式接收指定数量的数据,如果通道缓存中没有足够的数据那么就会一直阻塞。
如果发送了nil,那么接送到数据将会是 [NSNull null],因此需要注意检测返回数组中的类型
*/
- (NSArray<Value> * _Nonnull)receiveWithCount:(NSUInteger)count;

6. COActor用法

Actor 的概念来自于 Erlang ,在 AKKA 中,可以认为一个 Actor 就是一个容器,用以存储状态、行为、Mailbox 以及子 Actor 与 Supervisor 策略。Actor 之间并不直接通信,而是通过 Mail 来互通有无

* Mailbox: 用于存储消息的队列
* Isolated State: actor的状态以及内部变量等。
* message: 消息,类似于方法调用
Actor模型有两个特点:
1. 在单个线程中的每个Actor顺序处理发送给它的消息
2. 不同的Actors同时并行运行

Actor的创建:

co_actor(^(COActorChan *chan) {
val = 1;
XCTAssert(chan != nil);
});

co_actor_onqueue(get_test_queue(), ^(COActorChan *chan) {
val1 = 1;
XCTAssert(chan != nil);
});
it(@"next in chan", ^{
__block int val = 0;
COActor* actor = co_actor(^(COActorChan *chan) {
int tmpVal = 0;
COActorMessage *message = nil;
while((message = [chan next])){
if([message intType] == 1){
tmpVal++;
}
else if([message intType] == -1){
tmpVal--;
}
else if([message intType] == 2){
//返回数据
message.complete(@(tmpVal));
}
}
});
co_launch(^{
[actor sendMessage:@(1)];
[actor sendMessage:@(1)];
[actor sendMessage:@(1)];
[actor sendMessage:@(1)];
[actor sendMessage:@(1)];
[actor sendMessage:@(1)];

[actor sendMessage:@(-1)];

COActorCompletable *completable = [actor sendMessage:@(2)];
id result = await(completable);
val = [result intValue];
[actor cancel];
});

waitUntil(^(DoneCallback done) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
XCTAssert(val == 5);
done();
});
});
});


it(@"error example", ^{
COActor *actor = co_actor_onqueue(get_test_queue(), ^(COActorChan *channel) {
for(COActorMessage *message in channel){
message.complete(await(test_promise()));
}
});
co_launch(^{
id value = await([actor sendMessage:@"test"]);
NSError *error = co_getError();
XCTAssert(error.code == 100);
});

waitUntilTimeout(3, ^(DoneCallback done) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
done();
});
});

});

7. COTuple用法

COPromise<COTuple*>*
cotest_loadContentFromFile(NSString *filePath){
return [COPromise promise:^(COPromiseFullfill _Nonnull resolve, COPromiseReject _Nonnull reject) {
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSData *data = [[NSData alloc] initWithContentsOfFile:filePath];
resolve(co_tuple(filePath, data, nil));
}
else{
NSError *error = [NSError errorWithDomain:@"fileNotFound" code:-1 userInfo:nil];
resolve(co_tuple(filePath, nil, error));
}
}];
}

co_launch(^{
NSString *tmpFilePath = nil;
NSData *data = nil;
NSError *error = nil;
co_unpack(&tmpFilePath, &data, &error) = await(cotest_loadContentFromFile(filePath));
XCTAssert([tmpFilePath isEqualToString:filePath], @"file path is wrong");
XCTAssert(data.length > 0, @"data is wrong");
XCTAssert(error == nil, @"error is wrong");
});

8. 协程内延迟

co_delay 可以暂停所在的协程但是协程所在的线程不会停止运行,线程内部的其他协程也不会暂停,co_delay只能在协程内部运行,如果在外部执行的话将会抛出异常。

co_launch(^{
NSTimeInterval begin = [[NSDate date] timeIntervalSince1970];
co_delay(3);
realDuration = [[NSDate date] timeIntervalSince1970] - begin;
});

9. cokit简介

cokit 为常用的一些原生方法提供了一些常用的分类供我们使用,主要涉及到文件网络的IO

  • NSDictionary+Coroutine.h:主要用于将NSDictionary从文件中读取和写入文件。
  • NSArray+Coroutine.h:主要用于读取NSPropertyList中的数组。
  • NSString+Coroutine.h: 用于字符串从文件中读取和写入文件。
  • NSData+Coroutine.h:主要用于将NSData数据从文件中读取和写入文件。
  • NSFileManager+Coroutine.hw: 用于文件的操作
  • NSJSONSerialization+Coroutine.hw: 用于JSON的序列化操作
  • NSKeyedArchiver+Coroutine.h:用于归档类操作
  • NSURLConnection+Coroutine.h,NSURLSession+Coroutine.h:用于网络请求操作
  • NSUserDefaults+Coroutine.h:用于NSUserDefaults存储操作
  • UIImage+Coroutine.h:用于获取图片操作

源码信息

源码地址

fishhook是真正意义上精炼的代码,不管是接口还是总体代码量都十分简单,但是背后的原理以及它所具备的功能却是十分强大的。最早听说fishhook的时候,只知道它是用于hook的一个开源库,以为和Aspect同种类型所以没把它列到开源代码解析的列表中,
后面出于好奇看了下它居然是用于hook C函数,所以赶紧把它补上了,由于涉及底层的内容比较多,如果不是很清楚大家可以看后面推荐的文章,这里只做简单介绍。

那么上面叨叨了那么多,到底什么是fishhook呢,我们看下官方的解释:

fishhook is a very simple library that enables dynamically rebinding symbols in Mach-O binaries running on iOS in the simulator and on device.

上面的意思就是fishhook是一个用于动态修改 Mach-O 二进制文件里面symbols的一个开源库。fishhook 的强大之处在于它可以 hook 系统的静态 C 函数。我们接下来看下它是怎么做到的:

原理介绍

使用情景

我们先来看下fishhook对外暴露的内容,就两大类:一个rebinding结构体,用于封装对应的绑定信息,两个接口进行对C函数进行hook:

struct rebinding {
const char *name; // 目标符号名
void *replacement; // 用于替换的函数地址
void **replaced; // 用来存放原来的地址值
};

FISHHOOK_VISIBILITY
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);

FISHHOOK_VISIBILITY
int rebind_symbols_image(void *header,
intptr_t slide,
struct rebinding rebindings[],
size_t rebindings_nel);

rebind_symbols和rebind_symbols_image都用于符号重绑定,区别在于前者操作的对象是所有镜像,后者操作的对象是某个指定的镜像

我们先来看一个官方的例子:

#import <dlfcn.h>

#import <UIKit/UIKit.h>

#import "AppDelegate.h"
//1. 引入 fishhook.h
#import "fishhook.h"

//2. 声明与原函数签名相同的函数指针:
static int (*orig_close)(int);
static int (*orig_open)(const char *, int, ...);

//3. 实现用于替换的方法
int my_close(int fd) {
printf("Calling real close(%d)\n", fd);
//4.执行被hook的方法
return orig_close(fd);
}

//3. 实现用于替换的方法
int my_open(const char *path, int oflag, ...) {
va_list ap = {0};
mode_t mode = 0;

if ((oflag & O_CREAT) != 0) {
// mode only applies to O_CREAT
va_start(ap, oflag);
mode = va_arg(ap, int);
va_end(ap);
printf("Calling real open('%s', %d, %d)\n", path, oflag, mode);
//4.执行被hook的方法
return orig_open(path, oflag, mode);
} else {
printf("Calling real open('%s', %d)\n", path, oflag);
//4.执行被hook的方法
return orig_open(path, oflag, mode);
}
}

int main(int argc, char * argv[])
{
@autoreleasepool {

rebind_symbols((struct rebinding[2]){{"close", my_close, (void *)&orig_close}/*初始化rebinding 结构体*/, {"open", my_open, (void *)&orig_open/*初始化rebinding 结构体*/}}, 2 /*rebind个数*/);

// Open our own binary and print out first 4 bytes (which is the same
// for all Mach-O binaries on a given architecture)
int fd = open(argv[0], O_RDONLY);
uint32_t magic_number = 0;
read(fd, &magic_number, 4);
printf("Mach-O Magic Number: %x \n", magic_number);
close(fd);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

经过上面替换后代码中的open,close都会替换为my_open,my_close。所以运行这段代码会输出:

Calling real open('/var/mobile/Applications/161DA598-5B83-41F5-8A44-675491AF6A2C/Test.app/Test', 0)
Mach-O Magic Number: feedface
Calling real close(3)

原理介绍

要完全弄懂fishhook需要知道Mach-O,dyld,编译过程,动态链接,静态链接的过程,这里如果展开讲的话会涉及很大的一块内容,所以这个阶段先不展开细讲,后面会开一个底层系列到时候再回过头来详细得看下fishhood的源码。

我们这里只简单介绍下fishhook的大致原理:

我们知道现在很多代码都是由多个模块构成的(比如我们的一个项目都是由多个.m/.h文件对构成,这里的每个.m/.h文件都相当于一个编译模块)这样有利于复用,并且由于这些模块是可以单独编译的,所以如果只是单个模块发生改变,只要单独编译改变的模块,然后在与其他模块进行链接就可以了。而从源代码到可执行文件需要通过对每个模块的源代码进行编译,生成一个个目标文件,然后再通过
链接器将这些中间文件链接起来,形成一个最终的可行性文件Mach O,在单个模块中C函数在编译时就确定了函数指针的地址偏移量(Offset),这个偏移量在编译好的可执行文件中是固定的,而可执行文件每次被重新装载到内存中时被系统分配的起始地址是不断变化的。运行中的静态函数指针地址其实就等于上述 Offset + Mach0 文件在内存中的首地址.
而在链接阶段主要负责符号决议和重定位。之所以需要链接阶段是由于存在模块间依赖,不论是依赖方法还是依赖变量,本质上都是模块间的符号引用。

但是C函数的指针地址是相对固定且不可修改的。所以内部非动态链接库中的C函数fishhook是hook不了的,它只能用于Mach-O外部的函数。对于这些动态链接库是不会被编译到Mach O 文件的,而是在动态链接时才去重新绑定。

苹果采用了PIC(Position-independent code)技术来完成代码的动态加载特性:
编译时在 Mach-O 文件 _DATA 段的符号表中为每一个被引用的系统 C 函数建立一个指针(8字节的数据,放的全是0),这个指针用于动态绑定时重定位到共享库中的函数实现。在运行时当系统 C 函数被第一次调用时会动态绑定一次,然后将 Mach-O 中的 _DATA 段符号表中对应的指针,指向其在共享库中的实际内存地址,也就是会将我们需要替换的C函数在这个共享库中实际的内存地址保存到 Mach-O 中的 _DATA 段符号表中。
经过上面的一串操作我们在调用这个方法的时候都会读取_DATA中的这个地址。我们上面提到内部非动态链接库中的代码fishhook不能hook是因为这些地址值是存放在TEXT session中,而这部分数据是只读的。而动态链接库里面的最终符号地址存在可读写的 __DATA segment 的某个 section 中,fishhook 的实现原理就是通过修改这些 section 内容,进而实现符号的 rebind。

嗯,大致就是这样,讲得太细节容易把自己都陷进去,这样刚好能够理解。最近正好正在看底层的原理,打算放在后面整体梳理清楚后,再补一篇。赶紧逃。^ V ^

较好的文章

开源库信息

objection 是一个iOS / MacOS X 平台上的一个基于注释的依赖注入库,能够支持类方式,协议方式,实例对象方式,名称方式绑定,如果接触过Android开发大家可能会比较熟悉Butterknife,两者的功能是一致的,确切地说两者在很多特性上也都存在很多共同点:

我们来看下objection有哪些特性:

* "Annotation" Based Dependency Injection
* Seamless support for integrating custom and external dependencies
Custom Object Providers
Meta Class Bindings
Protocol Bindings
Instance Bindings
Named Bindings
* Lazily instantiates dependencies
* Eager Singletons
* Initializer Support
Default and custom arguments

objection用法

要熟悉objection的用法可以从objection的测试用例中进行了解,我们在进行源码分析的时候先从这些测试用例入手了解下objection的各个用法:

1.注入器的创建

当我们需要一个对象的时候我们都是通过JSObjectionInjector来获取,我们可以创建多个JSObjectionInjector:

JSObjectionInjector *injector = [JSObjection createInjector];

也可以创建一个JSObjectionInjector 并通过setDefaultInjector来将它作为默认的注入器:

JSObjectionInjector *injector = [JSObjection createInjector];
[JSObjection setDefaultInjector:injector];

这种情况下任何地方就可以通过****[JSObjection defaultInjector]****来获取这个默认的注入器了。

它支持getObject以及下标获取两类方式:

[injector getObject:AObject.class];
injector[AObject.class];
[injector objectForKeyedSubscript:AObject.class];

2.注册一个对象

objection_register(XXXX)
objection_register_singleton(XXXX)

objection 可以自动完成对象的注册,但是我们也可以显式地对某个对象进行注册,objection中对象的注入有上面两种方式,最后一个是注册一个单例对象使用到的。个人建议如果不是注册单例就不用显式注册了。
objection_register和objection_register_singleton的区别是如果一旦有某个类被标记为objection_register_singleton,那么任何地方从注册器取出来的对象都是同一个。而注册成非单例的对象每次创建的时候都会返回一个新的对象。

还需要注意一点是这里的单例是针对单个注入器的,不同的注入器中返回的则是不同的对象,这点需要十分注意。

will not return the same instance per injector if object is a singleton

@interface Engine : NSObject
@end

@implementation Engine
objection_register(Engine)
@end

@interface UnregisteredCar : NSObject
@property(nonatomic, strong) Engine *engine;
@end

@implementation UnregisteredCar
objection_requires(@"engine")
@synthesize engine;
@end
it(@"correctly builds a registered object", ^{
id engine = [[JSObjection defaultInjector] getObject:[Engine class]];
assertThat(engine, isNot(nilValue()));
});

it(@"will auto register a class if it is not explicitly registered", ^{
UnregisteredCar *unregisteredCar = [[JSObjection defaultInjector] getObject:[UnregisteredCar class]];
assertThat(unregisteredCar, is(notNilValue()));
assertThat(unregisteredCar.engine, is(notNilValue()));
});

每次注入的时候都会执行awakeFromObjection

@implementation Car
objection_register(Car)

@synthesize engine, brakes, awake;

- (void)awakeFromObjection {
awake = YES;
}
it(@"calls awakeFromObjection when injecting dependencies into properties of an existing instance", ^{
Car *car = [[Car alloc] init];
[[JSObjection defaultInjector] injectDependencies:car];
assertThatBool([car awake], isTrue());
assertThatBool([car.engine awake], isTrue());
});

3. 属性注入

如果我们需要的对象里面有其他的对象那么就需要objection_requires和objection_requires_sel指定当前类中哪些子属性需要注入了,直接看例子

@interface Car : NSObject {
Engine *engine;
Brakes *brakes;
}
@property(nonatomic, strong) Engine *engine;
@property(nonatomic, strong) Brakes *brakes;
@end

@implementation Car
objection_register(Car)
@synthesize engine, brakes, awake;

objection_requires(@"engine", @"brakes")
@end
it(@"correctly builds and object with dependencies", ^{
Car *car = [[JSObjection defaultInjector] getObject:[Car class]];
assertThat(car, isNot(nilValue()));
assertThat(car.engine, isNot(nilValue()));
assertThat(car.engine, is(instanceOf([Engine class])));
assertThat(car.brakes, isNot(nilValue()));
assertThat(car.brakes, is(instanceOf([Brakes class])));
});

@interface UnstoppableCar : NSObject
@property(nonatomic, strong) Engine *engine;
@end

@implementation UnstoppableCar
objection_requires_sel(@selector(engine))
@end
it(@"correctly builds objects with selector dependencies", ^{
UnstoppableCar *car = [[JSObjection defaultInjector] getObject:[UnstoppableCar class]];
assertThat(car.engine, is(instanceOf([Engine class])));
});

这里个人建议使用objection_requires_sel,避免使用objection_requires这种以字符串作为参数的方式。

4. 指定构造方法获取对象

默认情况下getObject使用的是对象默认的构造方法来创建出注入的对象,但是一般情况下我们会使用不同的构造方法来创建对象,这种情况下我们要怎么处理呢?方法有两种:

方式一:
通过objection_initializer_sel

objection_initializer_sel(@selector(initWithfirstName:secondName:))

然后代码中可以通过如下几种方式的任意一种给构造方法指定参数:

JSObjectionInjector *injector = [JSObjection defaultInjector];
JSObjectFactory *factory = [injector getObject:[JSObjectFactory class]];


//self.dog = [factory getObjectWithArgs:[IDLDog class], @"Piter", @"Pop",nil];
//self.dog = [injector getObjectWithArgs:[IDLDog class], @"Piter", @"Pop", nil];
//self.dog = [injector getObject:[IDLDog class] argumentList:@[@"Piter", @"Pop"]];

方法二是通过在getObject的时候指定初始化方法:

IDLCar *car = [[IDLCar alloc] initWithName:@"GTM"];
self.dog = [injector getObject:[IDLDog class] initializer:@selector(initWithCar:) argumentList:@[car]];

这种方式就不用objection_initializer_sel来注解了。

个人比较偏向使用第二种方式,同时注意这里的参数类型可以是自定义的类型。

5. JSObjectFactory

JSObjectFactory和JSObjectionInjector处理的事情是一样的,实际上它内部持有JSObjectionInjector。所以这里在这块不做过多介绍。

6. JSObjectionModule && JSObjectionProvider

JSObjectionModule 和 JSObjectionProvider 为我们提供了一个数据集中绑定的场所。

我们先来看下JSObjectionModule的用法:

@interface MyModule : JSObjectionModule {
BOOL _instrumentInvalidEagerSingleton;
BOOL _instrumentInvalidMetaClass;
}

@property(weak, nonatomic, readonly) Engine *engine;
@property(weak, nonatomic, readonly) id<GearBox> gearBox;
@property(nonatomic, assign) BOOL instrumentInvalidEagerSingleton;
@property (nonatomic, assign) BOOL instrumentInvalidMetaClass;

- (id)initWithEngine:(Engine *)engine andGearBox:(id<GearBox>)gearBox;
@end

重写configure方法:

@implementation MyModule
@synthesize engine = _engine;
@synthesize gearBox = _gearBox;
@synthesize instrumentInvalidEagerSingleton=_instrumentInvalidEagerSingleton;
@synthesize instrumentInvalidMetaClass = _instrumentInvalidMetaClass;

- (id)initWithEngine:(Engine *)engine andGearBox:(id<GearBox>)gearBox {
if ((self = [super init])) {
_engine = engine;
_gearBox = gearBox;
}
return self;
}

- (void)configure {

[self bind:_engine toClass:[Engine class]];
[self bind:_gearBox toProtocol:@protocol(GearBox)];
[self bindClass:[VisaCCProcessor class] toProtocol:@protocol(CreditCardProcessor)];
[self bindClass:[VisaCCProcessor class] toClass:[BaseCreditCardProcessor class]];


if (self.instrumentInvalidMetaClass) {
[self bindMetaClass:(id)@"sneaky" toProtocol:@protocol(MetaCar)];
} else {
[self bindMetaClass:[Car class] toProtocol:@protocol(MetaCar)];
}

if (self.instrumentInvalidEagerSingleton) {
[self registerEagerSingleton:[Car class]];
} else {
[self registerEagerSingleton:[EagerSingleton class]];
}
}

首先自定义的Module需要继承JSObjectionModule,并且重写configure方法,在configure方法中可以通过bind bindClass绑定,一个对象或者类可以绑定到Class和Protocol。

-bind:-bindClass: 的区别是:前者通过注入器会获取同一个实例,就是你所绑定的实例,后者每次通过注入器获取的对象都是不同的,因为绑定的是类。

通过Model方式和不通过Model方式的区别是在创建Injector的时候,使用model的时候需要在创建Injector的时候指定需要的Model:

module = [[MyModule alloc] initWithEngine:engine andGearBox:gearBox];    
JSObjectionInjector *injector = [JSObjection createInjector:module];
[JSObjection setDefaultInjector:injector];

但是一般建议放在load方法中

+(void)load{
JSObjectionInjector* injector = [JSObjection defaultInjector];
injector = injector ? : [JSObjection createInjector];
injector = [injector withModule:[self.class new]];
[JSObjection setDefaultInjector:injector];
}

6.0 Eager Singleton

一般Singleton会在使用的时候进行注入,但是被注册为Eager Singleton会在一开始的时候就进行注入:

@interface EagerSingleton : NSObject
@end

@implementation EagerSingleton
objection_register_singleton(EagerSingleton)
- (void)awakeFromObjection {
gEagerSingletonHook = YES;
}
@end
[self registerEagerSingleton:[EagerSingleton class]];

这里需要注意的是必须是使用objection_register_singleton注册为单例的类才能注册为Eager Singleton

6.1 使用name区分同一类型的绑定

当你绑定相同的 class/protocol 时,之前的绑定会被覆盖,如果不希望被覆盖,可以使用 name 进行区分:

[self bind:_rightHeadlight toClass:[Headlight class] named:@"RightHeadlight"];
[self bindClass:[HIDHeadlight class] toClass:[Headlight class] named:@"LeftHeadlight"];

接下来就可以在注解绑定中这样用:

@implementation ShinyCar
objection_register(ShinyCar)
objection_requires_names((@{@"LeftHeadlight":@"leftHeadlight", @"RightHeadlight":@"rightHeadlight"}))
objection_requires(@"foglight")
@synthesize leftHeadlight, rightHeadlight, foglight;
@end

还可以在手动获取的时候这样用

- (id)getObject:(id)classOrProtocol named:(NSString*)name;

6.2 需要介入实例创建过程的情景

如果需要介入实例的构建过程,可以使用 block 回调来配置你的实例对象:

- (void)configure {
[self bindBlock:^(JSObjectionInjector *context) {
Car *car = nil;
if (_instrumentNilBlock) {
car = [context getObject:[SixSpeedCar class]];
}
else {
car = [context getObject:[FiveSpeedCar class]];
car.engine = (id)myEngine;
}
return (id)car;
} toClass:[Car class]];
}

6.3 需要外部参数来创建实例的情景

如果需要外部的一些参数来创建该实例的时候可以使用Provider:

定义一个Provider遵循JSObjectionProvider协议,重写对应的方法:

@interface DemoObjectProvider : NSObject <JSObjectionProvider>
@end

@implementation DemoObjectProvider
-(id)provide:(JSObjectionInjector *)context arguments:(NSArray *)arguments{
AObject* ob = AObject.new;
NSString* name = arguments.firstObject;
ob.bj = [[BObject alloc] initWithName:name];
return ob;
}
@end

在Model中绑定Provider

-(void)configure {
[self bindProvider:[[DemoObjectProvider alloc] init] toClass:AObject.class];
}

那么参数怎么传进来呢:

JSObjectionInjector* injector = [JSObjection defaultInjector];
AObject* ob1 = [injector getObject:AObject.class argumentList:@[@"linxiaohai"]];

6.4 指定绑定的作用域

这个一般用在给单例绑定降级的时候使用

@implementation Car
objection_register(Car)
@synthesize engine, brakes, awake;
- (void)awakeFromObjection {
awake = YES;
}
objection_requires(@"engine", @"brakes")
@end
@implementation VisaCCProcessor
objection_register_singleton(VisaCCProcessor)
objection_initializer(initWithCreditCardNumber:, @"Default")
objection_requires(@"validator")

@synthesize validator = _validator;
@synthesize CCNumber;

- (id)initWithCreditCardNumber:(NSString *)aCCNumber {
if ((self = [super init])) {
self.CCNumber = aCCNumber;
}
return self;
}
- (void)processNumber:(NSString *)number {
[super processNumber:number];
}

@end
@implementation ScopeModule

- (void)configure {
[self bindClass:[VisaCCProcessor class] inScope:JSObjectionScopeNormal];
[self bindClass:[Car class] inScope:JSObjectionScopeSingleton];
[self registerEagerSingleton:[Car class]];
}
@end

测试用例:

it(@"can bind a class in singleton scope", ^{
assertThat(injector[[Car class]], is(sameInstance(injector[[Car class]])));
});

it(@"can bind a class in a normal scope", ^{
assertThat(injector[[VisaCCProcessor class]], isNot(sameInstance(injector[[VisaCCProcessor class]])));
});

还可以在绑定Provider/Block的时候指定作用域:

@implementation ProviderScopeModule
- (void)configure {
[self bindProvider:[[CarProvider alloc] init] toClass:[Car class] inScope:JSObjectionScopeSingleton];
[self bindProvider:[[GearBoxProvider alloc] init] toProtocol:@protocol(GearBox) inScope:JSObjectionScopeNormal];
}
@end


it(@"can bind a provider in singleton scope", ^{
assertThat(injector[[Car class]], is(sameInstance(injector[[Car class]])));
});

it(@"can bind a provider in a normal scope", ^{
assertThat(injector[@protocol(GearBox)], isNot(sameInstance(injector[@protocol(GearBox)])));
});
@implementation BlockScopeModule

- (void)configure {
[self bindBlock:^(JSObjectionInjector *context) {
Car *car = [[Car alloc] init];
return (id)car;
} toClass:[Car class] inScope:JSObjectionScopeSingleton];

[self bindBlock:^(JSObjectionInjector *context) {
return [[AfterMarketGearBox alloc] init];
} toProtocol:@protocol(GearBox) inScope:JSObjectionScopeNormal];
}

@end

6.4 指定多个模块

// 启用默认的注入器
JSObjectionInjector* injector = [JSObjection defaultInjector];
injector = injector ? : [JSObjection createInjector];

// 添加自定义的模块
injector = [injector withModule:MyModule.new];

// 添加更多的模块
injector = [injector withModules:MyModule1.new, MyModule2.new, nil];
[JSObjection setDefaultInjector:injector];
-withModule:方法加入的模块,会将之前相同的绑定覆盖,如果你想更换甭定、作用域等等,就可以使用该方法加入新的绑定。
既然能够加入,那么对应的,withoutModuleOfType: 将会移除之前的模块。

源码解析

objection 源码不算复杂,整个思路还是蛮清晰的,下面是我重新对它目录结构进行组织后的结果:

Entry/
JSObjectionEntry.h
JSObjectionProviderEntry.h
JSObjectionInjectorEntry.h
JSObjectionBindingEntry.h
Module/
JSObjectionModule.h
Factory/
JSObjectFactory.h
Injector/
JSObjectionInjector.h
Utils/
JSObjectionUtils.h

上面是几个重要的模块:

  • Entry是一系列用于创建对象的入口
  • Module用于绑定数据的地方,为Injector提供对象数据源
  • Injector用于从对象源中取出依赖对象
  • Factory 其实是对Injector的一个封装
  • Utils 是整个代码底层的一个支持

1. Injector的创建

在使用Objection进行注入的时候需要先调用:

+ (JSObjectionInjector *)createInjector:(JSObjectionModule *)module;

来创建Injector

+ (JSObjectionInjector *)createInjector:(JSObjectionModule *)module {
pthread_mutex_lock(&gObjectionMutex);
@try {
return [[JSObjectionInjector alloc] initWithContext:gObjectionContext andModule:module];
}
@finally {
pthread_mutex_unlock(&gObjectionMutex);
}
return nil;
}

createInjector方法中会通过JSObjectionInjector 的 initWithContext:andModule:module来创建一个JSObjectionInjector返回,这里的Context在JSObjection的initialize方法中就完成了初始化。
一般我们会将createInjector创建的JSObjectionInjector 设置为默认的gGlobalInjector这样就可以在任何地方使用了。
那么传入的Context用来做什么用的?我们继续往下看:

- (instancetype)initWithContext:(NSDictionary *)theGlobalContext andModule:(JSObjectionModule *)theModule {
if ((self = [self initWithContext:theGlobalContext])) {
[self configureModule:theModule];
[self initializeEagerSingletons];
}
return self;
}

- (instancetype)initWithContext:(NSDictionary *)theGlobalContext {
if ((self = [super init])) {
_globalContext = theGlobalContext;
_context = [[NSMutableDictionary alloc] init];
_modules = [[NSMutableArray alloc] init];
[self configureDefaultModule];
[self initializeEagerSingletons];
}
return self;
}

- (void)configureDefaultModule {
//这里绑定有JSObjectFactory
__JSObjectionInjectorDefaultModule *module = [[__JSObjectionInjectorDefaultModule alloc] initWithInjector:self];
//将__JSObjectionInjectorDefaultModule添加到_modules,调用__JSObjectionInjectorDefaultModule的configure方法(__JSObjectionInjectorDefaultModule没有重写configure)。
//将__JSObjectionInjectorDefaultModule中的eagerSingletons合并到_eagerSingletons
//将__JSObjectionInjectorDefaultModule中的bindings添加到_context
[self configureModule:module];
}

- (void)initializeEagerSingletons {
for (NSString *eagerSingletonKey in _eagerSingletons) {
id entry = [_context objectForKey:eagerSingletonKey] ?: [_globalContext objectForKey:eagerSingletonKey];
if ([entry lifeCycle] == JSObjectionScopeSingleton) {
//创建eagerSingletonKey对象
[self getObject:NSClassFromString(eagerSingletonKey)];
} else {
@throw [NSException exceptionWithName:@"JSObjectionException"
reason:[NSString stringWithFormat:@"Unable to initialize eager singleton for the class '%@' because it was never registered as a singleton", eagerSingletonKey]
userInfo:nil];
}
}
}

- (void)configureModule:(JSObjectionModule *)module {
//将model添加到_modules
[_modules addObject:module];
//调用module的configure执行绑定
[module configure];
//将当前的model的eagerSingletons合并到_eagerSingletons
NSSet *mergedSet = [module.eagerSingletons setByAddingObjectsFromSet:_eagerSingletons];
_eagerSingletons = mergedSet;
//将当前model的module.bindings添加到_context
[_context addEntriesFromDictionary:module.bindings];
}

JSObjectionInjector初始化方法中有几个比较重要的对象_globalContext,_context,_modules
_globalContext用于存储全局的绑定信息,_context存储的是当前injector的绑定信息,_modules 存储的是该injector所包含的JSObjectionModule。

在初始化JSObjectionInjector的阶段,主要完成如下工作:

1. 将_JSObjectionInjectorDefaultModule以及外部注入的module添加到_modules,并调用configure 执行绑定
2. 将_JSObjectionInjectorDefaultModule 以及外部注入的module中的eagerSingletons 添加到_eagerSingletons
3. 将module中的绑定信息添加到_context
4. 完成EagerSingletons的初始化

2. JSObjectionModule

JSObjectionModule中有两个对象_bindings,_eagerSingletons 一个用于存放所有的绑定信息,后面用于存放Eager Singletons的。

JSObjectionModule中的所有注册绑定信息都存放在_bindings,大致归纳了下无非就只有如下几种:

- (void)bindMetaClass:(Class)metaClass toProtocol:(Protocol *)aProtocol
- (void)bind:(id)instance toProtocol:(Protocol *)aProtocol named:(NSString *)name
- (void)bind:(id)instance toClass:(Class)aClass named:(NSString *)name
- (void)bindClass:(Class)aClass toClass:(Class)toClass inScope:(JSObjectionScope)scope named:(NSString*)name
- (void)bindClass:(Class)aClass toProtocol:(Protocol *)aProtocol inScope:(JSObjectionScope)scope named:(NSString*)name
- (void)bindProvider:(id <JSObjectionProvider>)provider toClass:(Class)aClass inScope:(JSObjectionScope)scope named:(NSString *)name
- (void)bindProvider:(id <JSObjectionProvider>)provider toProtocol:(Protocol *)aProtocol inScope:(JSObjectionScope)scope named:(NSString *)name
- (void)bindBlock:(id (^)(JSObjectionInjector *context))block toClass:(Class)aClass inScope:(JSObjectionScope)scope named:(NSString *)name
- (void)bindBlock:(id (^)(JSObjectionInjector *context))block toProtocol:(Protocol *)aProtocol inScope:(JSObjectionScope)scope named:(NSString *)name

绑定实例对象,绑定类,绑定Provider,绑定block,绑定MetaClass。 绑定的目标对象主要有两类一个是Class 一个是Protocol。

_bindings的key有两类如果目标对象是Class则key通过方法:

- (NSString *)classKey:(Class)aClass withName:(NSString*)name {
return [NSString stringWithFormat:@"%@%@%@", NSStringFromClass(aClass), name ? @":" : @"", name ? name : @""];
}

比如class名为IDLDog name为animalDomain,那么key就是@”IDLDog:animalDomain”

如果目标对象是Protocol则key通过方法:

- (NSString *)protocolKey:(Protocol *)aProtocol withName:(NSString*)name{
return [NSString stringWithFormat:@"<%@>%@%@", NSStringFromProtocol(aProtocol), name ? @":" : @"", name ? name : @""];
}

比如Protocol名为IDLDogProtocol name为animalDomain,那么key就是@”:animalDomain”

而value值是用于生成对象的Entry

bindMetaClass ---> JSObjectionBindingEntry
bind ---> JSObjectionBindingEntry
bindClass ---> __JSClassProvider ---> JSObjectionProviderEntry
bindProvider ---> JSObjectionProviderEntry
bindBlock ---> JSObjectionProviderEntry

3. getObject 向依赖注入器中获取对象

- (id)getObject:(id)classOrProtocol named:(NSString*)name initializer:(SEL)selector argumentList:(NSArray *)argumentList {
@synchronized(self) {

//获取key
if (!classOrProtocol) {
return nil;
}
NSString *key = nil;
BOOL isClass = class_isMetaClass(object_getClass(classOrProtocol));
if (isClass) {
key = NSStringFromClass(classOrProtocol);
} else {
key = [NSString stringWithFormat:@"<%@>", NSStringFromProtocol(classOrProtocol)];
}

if (name) {
key = [NSString stringWithFormat:@"%@:%@",key,name];
}

//从_context中获取对应的Entry
id<JSObjectionEntry> injectorEntry = [_context objectForKey:key];
injectorEntry.injector = self;
if (!injectorEntry) {
//如果没有JSObjectionEntry 则 从_globalContext中获取
id<JSObjectionEntry> entry = [_globalContext objectForKey:key];
if (entry) {
injectorEntry = [[entry class] entryWithEntry:entry];
injectorEntry.injector = self;
[_context setObject:injectorEntry forKey:key];
} else if(isClass) {
injectorEntry = [JSObjectionInjectorEntry entryWithClass:classOrProtocol scope:JSObjectionScopeNormal];
injectorEntry.injector = self;
[_context setObject:injectorEntry forKey:key];
}
}
if (classOrProtocol && injectorEntry) {
if ([injectorEntry respondsToSelector:@selector(extractObject:initializer:)]) {
//调用extractObject获取对象
return [injectorEntry extractObject:argumentList initializer:selector];
}
return [injectorEntry extractObject:argumentList];
}
return nil;
}
return nil;
}

getObject方法比较简单,就是通过classOrProtocol,name 来构建出key。先从_context中获取如果没有则从_globalContext中获取对应的JSObjectionEntry。然后通过extractObject:initializer:或者extractObject:来获取。

4. Entrys 构建对象

我们上面归纳了bind方法与JSObjectionEntry之间的对应关系:

bindMetaClass ---> JSObjectionBindingEntry
bind ---> JSObjectionBindingEntry
bindClass ---> __JSClassProvider ---> JSObjectionProviderEntry
bindProvider ---> JSObjectionProviderEntry
bindBlock ---> JSObjectionProviderEntry

JSObjectionBindingEntry

@implementation JSObjectionBindingEntry
- (instancetype)initWithObject:(id)theObject {
if ((self = [super init])) {
_instance = theObject;
}
return self;
}

- (id)extractObject:(NSArray *)arguments {
return _instance;
}

- (JSObjectionScope)lifeCycle {
return JSObjectionScopeSingleton;
}

- (void)dealloc {
_instance = nil;
}
@end

JSObjectionBindingEntry 中用于绑定已经创建好的对象,extractObject方法实际上返回的就是最初传入的_instance

JSObjectionProviderEntry

@implementation JSObjectionProviderEntry
@synthesize lifeCycle = _lifeCycle;
- (id)initWithProvider:(id<JSObjectionProvider>)theProvider lifeCycle:(JSObjectionScope)theLifeCycle {
if ((self = [super init])) {
_provider = theProvider;
_lifeCycle = theLifeCycle;
_storageCache = nil;
}

return self;
}

- (id)initWithBlock:(id(^)(JSObjectionInjector *context))theBlock lifeCycle:(JSObjectionScope)theLifeCycle {
if ((self = [super init])) {
_block = [theBlock copy];
_lifeCycle = theLifeCycle;
_storageCache = nil;
}

return self;
}

- (id)extractObject:(NSArray *)arguments {
if (self.lifeCycle == JSObjectionScopeNormal || !_storageCache) {
return [self buildObject:arguments];
}
return _storageCache;
}

- (void)dealloc {
_storageCache = nil;
}

- (id)buildObject:(NSArray *)arguments {
id objectUnderConstruction = nil;
if (_block) {
objectUnderConstruction = _block(self.injector);
}
else {
objectUnderConstruction = [_provider provide:self.injector arguments:arguments];
}
if (self.lifeCycle == JSObjectionScopeSingleton) {
_storageCache = objectUnderConstruction;
}
return objectUnderConstruction;
}

@end

JSObjectionProviderEntry 有两种构造方法,一种是通过Provider,一种是通过Block,JSObjectionProviderEntry 内部会缓存一个_storageCache,在生命周期为JSObjectionScopeSingleton
的情况下extractObject会使用_storageCache 否则会在buildObject中通过_block或者_provider创建创建对象返回。

5.宏定义

5.1 注册依赖

#define objection_register(value)			\
+ (void)initialize { \
if (self == [value class]) { \
[JSObjection registerClass:[value class] scope: JSObjectionScopeNormal]; \
} \
}

#define objection_register_singleton(value) \
+ (void)initialize { \
if (self == [value class]) { \
[JSObjection registerClass:[value class] scope: JSObjectionScopeSingleton]; \
} \
}

我们上面提到了注册依赖可以通过objection_register以及objection_register_singleton前者用于注册正常的类,后者用于注册单例,但是底层都是通过:

JSObjection registerClass:scope:

来注册的只不过作用域参数不一样罢了。

+ (void)registerClass:(Class)theClass scope:(JSObjectionScope)scope {
pthread_mutex_lock(&gObjectionMutex);
//...
if (theClass && [gObjectionContext objectForKey:NSStringFromClass(theClass)] == nil) {
[gObjectionContext setObject:[JSObjectionInjectorEntry entryWithClass:theClass scope:scope] forKey:NSStringFromClass(theClass)];
}
pthread_mutex_unlock(&gObjectionMutex);
}

这种情况下的Entry和上面的就不大一样了它是JSObjectionInjectorEntry。被添加到gObjectionContext。

@implementation JSObjectionInjectorEntry

//...

#pragma mark - Instance Methods

//.....

- (instancetype) extractObject:(NSArray *)arguments initializer:(SEL)initializer {
if (self.lifeCycle == JSObjectionScopeNormal || !_storageCache) {
return [self buildObject:arguments initializer: initializer];
}
return _storageCache;
}
//...
#pragma mark - Private Methods

- (id)buildObject:(NSArray *)arguments initializer: (SEL) initializer {

id objectUnderConstruction = nil;
if(initializer != nil) {
objectUnderConstruction = JSObjectionUtils.buildObjectWithInitializer(self.classEntry, initializer, arguments);
} else if ([self.classEntry respondsToSelector:@selector(objectionInitializer)]) {
objectUnderConstruction = JSObjectionUtils.buildObjectWithInitializer(self.classEntry, [self initializerForObject], [self argumentsForObject:arguments]);
} else {
objectUnderConstruction = [[self.classEntry alloc] init];
}

if (self.lifeCycle == JSObjectionScopeSingleton) {
_storageCache = objectUnderConstruction;
}

JSObjectionUtils.injectDependenciesIntoProperties(self.injector, self.classEntry, objectUnderConstruction);

return objectUnderConstruction;
}

- (SEL)initializerForObject {
return NSSelectorFromString([[self.classEntry objectionInitializer] objectForKey:JSObjectionInitializerKey]);
}

- (NSArray *)argumentsForObject:(NSArray *)givenArguments {
return givenArguments.count > 0 ? givenArguments : [[self.classEntry objectionInitializer] objectForKey:JSObjectionDefaultArgumentsKey];
}


#pragma mark - Class Methods

+ (id)entryWithClass:(Class)theClass scope:(JSObjectionScope)theLifeCycle {
return [[JSObjectionInjectorEntry alloc] initWithClass:theClass lifeCycle:theLifeCycle];
}

+ (id)entryWithEntry:(JSObjectionInjectorEntry *)entry {
return [[JSObjectionInjectorEntry alloc] initWithClass:entry.classEntry lifeCycle:entry.lifeCycle];
}
@end

上面最关键的代码在buildObject中如果没有指定参数则调用:

objectUnderConstruction = [[self.classEntry alloc] init]; 

也就是走默认构造方法。

如果有指定构造方法则走下面方法:

JSObjectionUtils.buildObjectWithInitializer(self.classEntry, initializer, arguments);

当然还可以实现objectionInitializer方法来指定对应的构造方法。

JSObjectionUtils.buildObjectWithInitializer 方法很简单就是通过消息分发机制调用构造方法:

static id BuildObjectWithInitializer(Class klass, SEL initializer, NSArray *arguments) {
NSMethodSignature *signature = [klass methodSignatureForSelector:initializer];
__autoreleasing id instance = nil;
BOOL isClassMethod = signature != nil && initializer != @selector(init);

if (!isClassMethod) {
instance = [klass alloc];
signature = [klass instanceMethodSignatureForSelector:initializer];
}

if (signature) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:isClassMethod ? klass : instance];
[invocation setSelector:initializer];
for (int i = 0; i < arguments.count; i++) {
__unsafe_unretained id argument = [arguments objectAtIndex:i];
[invocation setArgument:&argument atIndex:i + 2];
}
[invocation invoke];
[invocation getReturnValue:&instance];
return instance;
} else {
@throw [NSException exceptionWithName:JSObjectionException reason:[NSString stringWithFormat:@"Could not find initializer '%@' on %@", NSStringFromSelector(initializer), NSStringFromClass(klass)] userInfo:nil];
}
return nil;
}

构造完需要的对象后还需要通过JSObjectionUtils.injectDependenciesIntoProperties 来使用依赖对属性进行注入:

static void InjectDependenciesIntoProperties(JSObjectionInjector *injector, Class klass, id object) {

//如果当前有通过objectionRequires指定需要注入的属性
if ([klass respondsToSelector:@selector(objectionRequires)]) {
NSSet *properties = [klass objectionRequires];
NSMutableDictionary *propertiesDictionary = [NSMutableDictionary dictionaryWithCapacity:properties.count];
for (NSString *propertyName in properties) {
JSObjectionPropertyInfo propertyInfo;
id desiredClassOrProtocol;
_getPropertyInfo(klass, propertyName, &propertyInfo, &desiredClassOrProtocol);
id theObject = [injector getObject:desiredClassOrProtocol];
_validateObjectReturnedFromInjector(&theObject, propertyInfo, desiredClassOrProtocol, propertyName);
[propertiesDictionary setObject:theObject forKey:propertyName];
}

[object setValuesForKeysWithDictionary:propertiesDictionary];
}
//如果当前有通过objectionRequiresNames指定需要注入的属性
if ([klass respondsToSelector:@selector(objectionRequiresNames)]) {
NSDictionary *namedProperties = [klass objectionRequiresNames];
NSMutableDictionary *propertiesDictionary = [NSMutableDictionary dictionaryWithCapacity:namedProperties.count];
for (NSString *namedPropertyKey in [namedProperties allKeys]) {
NSString* propertyName = [namedProperties valueForKey:namedPropertyKey];
JSObjectionPropertyInfo propertyInfo;
id desiredClassOrProtocol;
_getPropertyInfo(klass, propertyName, &propertyInfo, &desiredClassOrProtocol);
id theObject = [injector getObject:desiredClassOrProtocol named:namedPropertyKey];
_validateObjectReturnedFromInjector(&theObject, propertyInfo, desiredClassOrProtocol, propertyName);
[propertiesDictionary setObject:theObject forKey:propertyName];
}
// 添加属性
[object setValuesForKeysWithDictionary:propertiesDictionary];
}
//调用awakeFromObjection
if ([object respondsToSelector:@selector(awakeFromObjection)]) {
[object awakeFromObjection];
}
}

5.2 属性依赖

#define objection_requires(args...) \
+ (NSSet *)objectionRequires { \
NSSet *requirements = [NSSet setWithObjects: args, nil]; \
return JSObjectionUtils.buildDependenciesForClass(self, requirements); \
}

#define objection_requires_sel(args...) \
+ (NSSet *)objectionRequires { \
SEL selectors[] = {args}; \
NSMutableSet *requirements = [NSMutableSet set]; \
for (NSUInteger j = 0; j < sizeof(selectors)/ sizeof(SEL); j++) { \
SEL selector = selectors[j]; \
[requirements addObject:NSStringFromSelector(selector)]; \
} \
return JSObjectionUtils.buildDependenciesForClass(self, requirements); \
}

#define objection_requires_names(namedDependencies) \
+ (NSDictionary *)objectionRequiresNames { \
return JSObjectionUtils.buildNamedDependenciesForClass(self, namedDependencies); \
}

这里就是上面提到的用于指定需要属性注入的方法,不做展开介绍了。

5.3 指定初始化方法

#define objection_initializer_sel(selectorSymbol, args...) \
+ (NSDictionary *)objectionInitializer { \
id objs[] = {args}; \
NSArray *defaultArguments = [NSArray arrayWithObjects: objs count:sizeof(objs)/sizeof(id)]; \
return JSObjectionUtils.buildInitializer(selectorSymbol, defaultArguments); \
}

这个也是为了buildObject提供的。用于指定构造方法,细节可以看buildObject中。

1. 开源库基本信息:

开源地址


2. 源码解析

BeeHive 整体结构

BeeHive 整个模块由上图几个模块构成:

  • BeeHive:

BeeHive是BeeHive的核心接口类,它主要用于注册Module,注册Service,查找符合某个协议的Service,以及触发自定义事件。
它在setContext的时候会加载一系列的静态服务和模块

  • BHAppDelegate:

BHAppDelegate负责转发系统消息的代理,项目中的代理需要继承这个方法。

  • BHContext:

BHContext用于存放公共数据的上下文,某个服务的对应的实现类对象也会缓存到这个地方。

  • BHRouter:
    BHRouter是BeeHive的路由,支持查找服务,跳转页面,注册服务。是基于URL的格式:目前支持的协议如下:
    //url - >  com.alibaba.beehive://call.service.beehive/pathComponentKey.protocolName.selector/...?params={}(value url encode)
    //url - > com.alibaba.beehive://register.beehive/pathComponentKey.protocolName/...?params={}(value url encode)
    //url - > com.alibaba.beehive://jump.vc.beehive/pathComponentKey.protocolName.push(modal)/...?params={}(value url encode)#push
    //params -> {pathComponentKey:{paramName:paramValue,...},...}
  • BHModuleManager:

BHModuleManager 主要负责管理模块和分发事件

  • BHServiceManager:

BHModuleManager 负责注册,查找,定位,删除服务

不论是服务还是模块,BeeHive都提供了三种不同的注册形式:静态plist,动态注册,annotation。

个人觉得可以将事件从BHModuleManager中独立出来会显得比较清晰,并且将路由功能也通过BeeHive暴露接口,这样用户需要什么功能都可以直接调用BeeHive接口。

也就是说整个项目以BeeHive为中心,BHAppDelegate持有BeeHive,并向BeeHive注入系统事件,BHContext向BeeHive注入上下文,BeeHive作为对外的门面,向外界暴露模块,服务,事件,路由管理功能,而模块功能由BHModuleManager实际管理,服务功能由BHServiceManager具体管理,事件由BHEventManager管理,路由由BHRouter管理。大体如下图所示:

模块管理器 BHModuleManager

BHModuleManager 主要负责两大类任务:

1. 通过模块的静态加载,动态注册添加模块
2. 注册模块事件
模块添加移除

模块动态注册

模块注册是通过registerDynamicModule函数注册的,参数是模块的Class。

- (void)registerDynamicModule:(Class)moduleClass {
[self registerDynamicModule:moduleClass shouldTriggerInitEvent:NO];
}
- (void)registerDynamicModule:(Class)moduleClass
shouldTriggerInitEvent:(BOOL)shouldTriggerInitEvent {
[self addModuleFromObject:moduleClass shouldTriggerInitEvent:shouldTriggerInitEvent];
}
- (void)addModuleFromObject:(id)object
shouldTriggerInitEvent:(BOOL)shouldTriggerInitEvent {

Class class;
NSString *moduleName = nil;

if (!object) return;

//使用传入的参数初始化关键参数
class = object;
moduleName = NSStringFromClass(class);

//遍历已经注册的Module查看是否已经添加过
__block BOOL flag = YES;
[self.BHModules enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:class]) {
flag = NO;
*stop = YES;
}
}];

// 如果已经添加过则直接返回
if (!flag) return;

if ([class conformsToProtocol:@protocol(BHModuleProtocol)]) {
NSMutableDictionary *moduleInfo = [NSMutableDictionary dictionary];
BOOL responseBasicLevel = [class instancesRespondToSelector:@selector(basicModuleLevel)];
int levelInt = 1;
if (responseBasicLevel) {
levelInt = 0;
}
//填充Module基本信息
[moduleInfo setObject:@(levelInt) forKey:kModuleInfoLevelKey];
if (moduleName) {
[moduleInfo setObject:moduleName forKey:kModuleInfoNameKey];
}
//添加到模块基本信息里面
[self.BHModuleInfos addObject:moduleInfo];
//将模块实例添加到BHModules
id<BHModuleProtocol> moduleInstance = [[class alloc] init];
[self.BHModules addObject:moduleInstance];
[moduleInfo setObject:@(YES) forKey:kModuleInfoHasInstantiatedKey];

//模块排序:先根据基本模块等级 再根据模块优先级
[self.BHModules sortUsingComparator:^NSComparisonResult(id<BHModuleProtocol> moduleInstance1, id<BHModuleProtocol> moduleInstance2) {
NSNumber *module1Level = @(BHModuleNormal);
NSNumber *module2Level = @(BHModuleNormal);
if ([moduleInstance1 respondsToSelector:@selector(basicModuleLevel)]) {
module1Level = @(BHModuleBasic);
}
if ([moduleInstance2 respondsToSelector:@selector(basicModuleLevel)]) {
module2Level = @(BHModuleBasic);
}
if (module1Level.integerValue != module2Level.integerValue) {
return module1Level.integerValue > module2Level.integerValue;
} else {
NSInteger module1Priority = 0;
NSInteger module2Priority = 0;
if ([moduleInstance1 respondsToSelector:@selector(modulePriority)]) {
module1Priority = [moduleInstance1 modulePriority];
}
if ([moduleInstance2 respondsToSelector:@selector(modulePriority)]) {
module2Priority = [moduleInstance2 modulePriority];
}
return module1Priority < module2Priority;
}
}];
//注册模块监听事件
[self registerEventsByModuleInstance:moduleInstance];

//触发基本的事件
if (shouldTriggerInitEvent) {
//触发启动
[self handleModuleEvent:BHMSetupEvent forTarget:moduleInstance withSeletorStr:nil andCustomParam:nil];
//触发初始化
[self handleModulesInitEventForTarget:moduleInstance withCustomParam:nil];
dispatch_async(dispatch_get_main_queue(), ^{
//触发闪现
[self handleModuleEvent:BHMSplashEvent forTarget:moduleInstance withSeletorStr:nil andCustomParam:nil];
});
}
}
}

模块注册涉及到两个数组BHModuleInfos 以及 BHModules一个用于存放模块的信息,一个用于存放模块,模块信息包括:模块名(kModuleInfoNameKey),模块等级(kModuleInfoLevelKey),模块是否有实例对象(kModuleInfoHasInstantiatedKey)。每个模块都必须遵循BHModuleProtocol协议。在每次添加的时候都会按照模块level以及模块优先级进行排序。

然后在通过registerEventsByModuleInstance来注册消息事件的监听,如果shouldTriggerInitEvent为YES那么就会触发BHMSetupEvent,BHMInitEvent,BHMSplashEvent事件。

模块移除

- (void)unRegisterDynamicModule:(Class)moduleClass {
if (!moduleClass) {
return;
}
[self.BHModuleInfos filterUsingPredicate:[NSPredicate predicateWithFormat:@"%@!=%@", kModuleInfoNameKey, NSStringFromClass(moduleClass)]];
__block NSInteger index = -1;
//查找要移除的模块
[self.BHModules enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:moduleClass]) {
index = idx;
*stop = YES;
}
}];
//移除模块
if (index >= 0) {
[self.BHModules removeObjectAtIndex:index];
}
//遍历每个事件,有关于当前实例的就从某个事件的监听列表中移除
[self.BHModulesByEvent enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, NSMutableArray<id<BHModuleProtocol>> * _Nonnull obj, BOOL * _Nonnull stop) {
__block NSInteger index = -1;
[obj enumerateObjectsUsingBlock:^(id<BHModuleProtocol> _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:moduleClass]) {
index = idx;
*stop = NO;
}
}];
//从事件中移除
if (index >= 0) {
[obj removeObjectAtIndex:index];
}
}];
}

模块移除也遵循上面的步骤,首先是会从BHModules中移除,而后再从事件中移除,后者会放在后面介绍事件的时候进行详细介绍。

全部模块注册

全部模块注册的任务是对模块信息中没有实例化的模块进行实例化后添加到self.BHModules,并对这些模块同一监听系统事件。

- (void)registedAllModules {

[self.BHModuleInfos sortUsingComparator:^NSComparisonResult(NSDictionary *module1, NSDictionary *module2) {
NSNumber *module1Level = (NSNumber *)[module1 objectForKey:kModuleInfoLevelKey];
NSNumber *module2Level = (NSNumber *)[module2 objectForKey:kModuleInfoLevelKey];
if (module1Level.integerValue != module2Level.integerValue) {
return module1Level.integerValue > module2Level.integerValue;
} else {
NSNumber *module1Priority = (NSNumber *)[module1 objectForKey:kModuleInfoPriorityKey];
NSNumber *module2Priority = (NSNumber *)[module2 objectForKey:kModuleInfoPriorityKey];
return module1Priority.integerValue < module2Priority.integerValue;
}
}];
NSMutableArray *tmpArray = [NSMutableArray array];
//对于模块信息中没有实例化的模块进行实例化后添加到self.BHModules,并对这些模块同一监听系统事件
[self.BHModuleInfos enumerateObjectsUsingBlock:^(NSDictionary *module, NSUInteger idx, BOOL * _Nonnull stop) {
NSString *classStr = [module objectForKey:kModuleInfoNameKey];
Class moduleClass = NSClassFromString(classStr);
BOOL hasInstantiated = ((NSNumber *)[module objectForKey:kModuleInfoHasInstantiatedKey]).boolValue;
if (NSStringFromClass(moduleClass) && !hasInstantiated) {
id<BHModuleProtocol> moduleInstance = [[moduleClass alloc] init];
[tmpArray addObject:moduleInstance];
}
}];

[self.BHModules addObjectsFromArray:tmpArray];
[self registerAllSystemEvents];
}

那么哪些模块是不会在注册的时候实例化呢?其实我们刚刚看到了,注册方法中有个Dynamic关键字表示动态注册,相对的BeeHive支持从plist文件中静态注册,这些静态注册的模块需要调用registedAllModules进行实例化,并注册事件。

模块静态注册

- (void)loadLocalModules {

//从plist中加载Module
NSString *plistPath = [[NSBundle mainBundle] pathForResource:[BHContext shareInstance].moduleConfigName ofType:@"plist"];
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath]) {
return;
}
NSDictionary *moduleList = [[NSDictionary alloc] initWithContentsOfFile:plistPath];

NSArray<NSDictionary *> *modulesArray = [moduleList objectForKey:kModuleArrayKey];
NSMutableDictionary<NSString *, NSNumber *> *moduleInfoByClass = @{}.mutableCopy;
//构建出一个key --> XXXXClass value--> @(1) 便于查询,其实这里感觉BHModuleInfos设计不是很合理,为什么不用Dictionary
[self.BHModuleInfos enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[moduleInfoByClass setObject:@1 forKey:[obj objectForKey:kModuleInfoNameKey]]; //key --> XXXXClass value--> @(1)
}];
//没有的话添加到self.BHModuleInfos
[modulesArray enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (!moduleInfoByClass[[obj objectForKey:kModuleInfoNameKey]]) {
[self.BHModuleInfos addObject:obj];
}
}];
}
模块事件

BeeHive的事件分成两类:系统事件以及自定义事件,自定义事件的值规定大于1000.
整个实现涉及到两个字典:BHSelectorByEventBHSelectorByEvent,两个字典的key都是EventType,可以通过EventType在BHSelectorByEvent找到响应selector,通过BHSelectorByEvent能够找到对应的模块。我们知道事件的三要素要明确:哪个实例? 监听哪个事件? 用哪个selector处理*,这些都可以通过这两个字典串起来。

事件注册:

系统事件是默认添加的,但是注册是需要手动注册的,在注册模块的时候可以选择默认注册BHMSetupEvent,BHMInitEvent,BHMSplashEvent事件。

- (NSMutableDictionary<NSNumber *, NSString *> *)BHSelectorByEvent {
if (!_BHSelectorByEvent) {
_BHSelectorByEvent = @{
@(BHMSetupEvent):kSetupSelector,
@(BHMInitEvent):kInitSelector,
@(BHMTearDownEvent):kTearDownSelector,
@(BHMSplashEvent):kSplashSeletor,
@(BHMWillResignActiveEvent):kWillResignActiveSelector,
@(BHMDidEnterBackgroundEvent):kDidEnterBackgroundSelector,
@(BHMWillEnterForegroundEvent):kWillEnterForegroundSelector,
@(BHMDidBecomeActiveEvent):kDidBecomeActiveSelector,
@(BHMWillTerminateEvent):kWillTerminateSelector,
@(BHMUnmountEvent):kUnmountEventSelector,
@(BHMOpenURLEvent):kOpenURLSelector,
@(BHMDidReceiveMemoryWarningEvent):kDidReceiveMemoryWarningSelector,
@(BHMDidReceiveRemoteNotificationEvent):kDidReceiveRemoteNotificationsSelector,
@(BHMWillPresentNotificationEvent):kWillPresentNotificationSelector,
@(BHMDidReceiveNotificationResponseEvent):kDidReceiveNotificationResponseSelector,
@(BHMDidFailToRegisterForRemoteNotificationsEvent):kFailToRegisterForRemoteNotificationsSelector,
@(BHMDidRegisterForRemoteNotificationsEvent):kDidRegisterForRemoteNotificationsSelector,
@(BHMDidReceiveLocalNotificationEvent):kDidReceiveLocalNotificationsSelector,
@(BHMWillContinueUserActivityEvent):kWillContinueUserActivitySelector,
@(BHMContinueUserActivityEvent):kContinueUserActivitySelector,
@(BHMDidFailToContinueUserActivityEvent):kFailToContinueUserActivitySelector,
@(BHMDidUpdateUserActivityEvent):kDidUpdateContinueUserActivitySelector,
@(BHMQuickActionEvent):kQuickActionSelector,
@(BHMHandleWatchKitExtensionRequestEvent):kHandleWatchKitExtensionRequestSelector,
@(BHMDidCustomEvent):kAppCustomSelector,
}.mutableCopy;
}
return _BHSelectorByEvent;
}

我们先来看下事件的注册方法:

首先会做一些预先判断:要注册的实例是否能够响应指定的selector,如果不能就不注册了,如果可以响应那么将eventType为key,selectorStr为value添加到BHSelectorByEvent,然后在查看BHModulesByEvent对应的eventType下面是否添加了当前的实例。添加后再按照modelLevel以及priority对实例进行排序,保证BHModulesByEvent中每个事件的实例列表是按照优先级排序的,优先级高的优先收到事件。

- (void)registerEvent:(NSInteger)eventType
withModuleInstance:(id)moduleInstance
andSelectorStr:(NSString *)selectorStr {

//判断当前实例是否能够响应selector
SEL selector = NSSelectorFromString(selectorStr);
if (!selector || ![moduleInstance respondsToSelector:selector]) return;

//添加到BHSelectorByEvent key-->eventTypeNumber value selectorStr
NSNumber *eventTypeNumber = @(eventType);
if (!self.BHSelectorByEvent[eventTypeNumber]) {
[self.BHSelectorByEvent setObject:selectorStr forKey:eventTypeNumber];
}
//添加到事件实例上
if (!self.BHModulesByEvent[eventTypeNumber]) {
[self.BHModulesByEvent setObject:@[].mutableCopy forKey:eventTypeNumber];
}
//拿到module
NSMutableArray *eventModules = [self.BHModulesByEvent objectForKey:eventTypeNumber];
//不包含的情况下添加到eventModules,并排序
if (![eventModules containsObject:moduleInstance]) {
[eventModules addObject:moduleInstance];
[eventModules sortUsingComparator:^NSComparisonResult(id<BHModuleProtocol> moduleInstance1, id<BHModuleProtocol> moduleInstance2) {
NSNumber *module1Level = @(BHModuleNormal);
NSNumber *module2Level = @(BHModuleNormal);
if ([moduleInstance1 respondsToSelector:@selector(basicModuleLevel)]) {
module1Level = @(BHModuleBasic);
}
if ([moduleInstance2 respondsToSelector:@selector(basicModuleLevel)]) {
module2Level = @(BHModuleBasic);
}
if (module1Level.integerValue != module2Level.integerValue) {
return module1Level.integerValue > module2Level.integerValue;
} else {
NSInteger module1Priority = 0;
NSInteger module2Priority = 0;
if ([moduleInstance1 respondsToSelector:@selector(modulePriority)]) {
module1Priority = [moduleInstance1 modulePriority];
}
if ([moduleInstance2 respondsToSelector:@selector(modulePriority)]) {
module2Priority = [moduleInstance2 modulePriority];
}
return module1Priority < module2Priority;
}
}];
}
}

事件触发

- (void)triggerEvent:(NSInteger)eventType
withCustomParam:(NSDictionary *)customParam {
[self handleModuleEvent:eventType forTarget:nil withCustomParam:customParam];
}
- (void)handleModuleEvent:(NSInteger)eventType
forTarget:(id<BHModuleProtocol>)target
withSeletorStr:(NSString *)selectorStr
andCustomParam:(NSDictionary *)customParam {

BHContext *context = [BHContext shareInstance].copy;
context.customParam = customParam;
context.customEvent = eventType;
//首先尝试使用传入的selectorStr来处理,如果不行都去BHSelectorByEvent里面去获取对应的selector
if (!selectorStr.length) {
selectorStr = [self.BHSelectorByEvent objectForKey:@(eventType)];
}
SEL seletor = NSSelectorFromString(selectorStr);
if (!seletor) {
selectorStr = [self.BHSelectorByEvent objectForKey:@(eventType)];
seletor = NSSelectorFromString(selectorStr);
}

//如果target有传入特定的,那么直接触发特定实例中的方法就好,否则触发能够响应当前事件的所有实例的selector方法
NSArray<id<BHModuleProtocol>> *moduleInstances;
if (target) {
moduleInstances = @[target];
} else {
moduleInstances = [self.BHModulesByEvent objectForKey:@(eventType)];
}
[moduleInstances enumerateObjectsUsingBlock:^(id<BHModuleProtocol> moduleInstance, NSUInteger idx, BOOL * _Nonnull stop) {
if ([moduleInstance respondsToSelector:seletor]) {
//通过performSelector触发方法
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[moduleInstance performSelector:seletor withObject:context];
#pragma clang diagnostic pop
//记录时间
[[BHTimeProfiler sharedTimeProfiler] recordEventTime:[NSString stringWithFormat:@"%@ --- %@", [moduleInstance class], NSStringFromSelector(seletor)]];
}
}];
}

事件触发实际上是通过performSelector来完成的,它需要知道要触发哪个实例的哪个事件,介绍了上面的注册流程后就可以很清楚得了解到整个事件的组织结构,那么事件的触发就是通过上面介绍的两个字典,找到事件的响应方法,以及需要触发哪些实例的对应方法了。BeeHive将模块初始化以及模块销毁独立开来是因为这两个事件有特殊之处,模块初始化有些模块是支持异步的,有些则是同步的,模块销毁的特别之处在于要按照优先级从低到高销毁。

服务管理器 BHServiceManager

在BeeHive中另一个比较重要的就是Servic了,它类似于一个提供某个处理的接口,BHServiceManager负责服务的注册,服务的移除,服务的搜索,同样BHServiceManager也提供了动态和静态注册服务的两种方式,BeeHive的服务实际上是Protocal - Class(Instance) 模式,Protocal提供了服务能够提供哪些服务的外在接口协议,而Class(Instance) 实际上则是这些服务的真正提供实例。

服务添加,移除,定位

服务动态注册

这没啥好说的一看就懂:

- (void)registerService:(Protocol *)service implClass:(Class)implClass {

NSParameterAssert(service != nil);
NSParameterAssert(implClass != nil);
//添加前判断
if (![implClass conformsToProtocol:service]) {
if (self.enableException) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ module does not comply with %@ protocol", NSStringFromClass(implClass), NSStringFromProtocol(service)] userInfo:nil];
}
return;
}

if ([self checkValidService:service]) {
if (self.enableException) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ protocol has been registed", NSStringFromProtocol(service)] userInfo:nil];
}
return;
}

NSString *key = NSStringFromProtocol(service);
NSString *value = NSStringFromClass(implClass);

//加入BHModuleManager key 为 Protocol value 为 实现类Class
if (key.length > 0 && value.length > 0) {
[self.lock lock];
[self.allServicesDict addEntriesFromDictionary:@{key:value}];
[self.lock unlock];
}
}

服务静态注册

还是不说哼哼哈哈^V^

- (void)registerLocalServices {

NSString *serviceConfigName = [BHContext shareInstance].serviceConfigName;
NSString *plistPath = [[NSBundle mainBundle] pathForResource:serviceConfigName ofType:@"plist"];
if (!plistPath) return;

NSArray *serviceList = [[NSArray alloc] initWithContentsOfFile:plistPath];

[self.lock lock];
for (NSDictionary *dict in serviceList) {
NSString *protocolKey = [dict objectForKey:@"service"];
NSString *protocolImplClass = [dict objectForKey:@"impl"];
if (protocolKey.length > 0 && protocolImplClass.length > 0) {
[self.allServicesDict addEntriesFromDictionary:@{protocolKey:protocolImplClass}];
}
}
[self.lock unlock];
}

服务定位

- (id)createService:(Protocol *)service withServiceName:(NSString *)serviceName shouldCache:(BOOL)shouldCache/*是否从缓存中找*/ {
if (!serviceName.length) {
serviceName = NSStringFromProtocol(service);
}
id implInstance = nil;

//没有对应的实现
if (![self checkValidService:service]) {
if (self.enableException) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ protocol does not been registed", NSStringFromProtocol(service)] userInfo:nil];
}
}

NSString *serviceStr = serviceName;
//是否从缓存中查找,如果允许的话则从BHContext中找,找到直接返回
if (shouldCache) {
id protocolImpl = [[BHContext shareInstance] getServiceInstanceFromServiceName:serviceStr];
if (protocolImpl) {
return protocolImpl;
}
}

//如果没有在BHContext中则从serviceImplClass找
Class implClass = [self serviceImplClass:service];

if ([[implClass class] respondsToSelector:@selector(singleton)]) {
//是否是单例如果是单例则通过shareInstance创建对象,否则通过alloc创建
if ([[implClass class] singleton]) {

if ([[implClass class] respondsToSelector:@selector(shareInstance)])
implInstance = [[implClass class] shareInstance];
else
implInstance = [[implClass alloc] init];
//如果shouldCache 则添加到BHContext
if (shouldCache) {
[[BHContext shareInstance] addServiceWithImplInstance:implInstance serviceName:serviceStr];
return implInstance;
} else {
return implInstance;
}
}
}
return [[implClass alloc] init];
}

关于createService这个方法名起得个人觉得不是很好,刚看到这个方法名的时候我的第一印象是:服务不是通过regist方法注册了吗,为什么需要create,实际上这只是通过服务协议来找到服务提供对象而已,个人建议可以叫做:

- (id)serviceConfirmToProtocol:(Protocol *)service withServiceName:(NSString *)serviceName shouldCache:(BOOL)shouldCache;

这里有一个需要注意的是我们这里返回的是提供服务的实例,而我们注册的提供服务的Class,为了避免每次都根据Class创建实例 ,BHContext 中有提供对应的缓存存储在[BHContext shareInstance].servicesByName中,而我们注册的服务存储在serviceImplClass中,所以每次我们要做的就是通过Protocal从先从[BHContext shareInstance].servicesByName中找,如果有之前已经缓存的实例的话就不需要重新创建了,直接返回就可以了,如果没有那么从serviceImplClass中找到对应的class,然后初始化出实例,然后添加到[BHContext shareInstance].servicesByName中缓存。

3.Annotation 注解

这是我看BeeHive眼前一亮的一块代码,本来想将这块内容放到服务和模块注册地方来介绍的,因为BeeHive注解主要用于注册模块和服务,之所以单独成一块来介绍主要是它有一定的通用性。

我们就开始看这块内容:

注册模块的时候使用的宏定义

#define BeeHiveMod(name) \
class BeeHive; char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name"";

注册服务时候使用的宏定义:

#define BeeHiveService(servicename,impl) \
class BeeHive; char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ \""#servicename"\" : \""#impl"\"}";

在项目中可以这样使用:

@BeeHiveMod(ShopModule)
@interface ShopModule() <BHModuleProtocol>
@end
@BeeHiveService(HomeServiceProtocol,BHViewController)
@interface BHViewController ()<HomeServiceProtocol>

@end

我们专注来看BeeHiveMod:

#define BeeHiveMod(name) \
class BeeHive; char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name"";

@BeeHiveMod(ShopModule) 替换后会变成:

class BeeHive; char * kShopModule_mod BeeHiveDATA(BeehiveMods) = ""ShopModule"";

再来看下:BeeHiveDATA

#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))

替换后变成:

@class BeeHive; char * kShopModule_mod __attribute((used, section("__DATA,""BeehiveMods"" "))) = """ShopModule""";

去掉__attribute的属性,相当于:

@class BeeHive; char * kShopModule_mod = """ShopModule""";

这里最重要的关键字是__attribute,由于kShopModule_mod字符串没有使用到,在Release包的时候会被优化掉所以需要使用used来避免被编译器优化,section用于指定kShopModule_mod存放的位置,section(“__DATA,””BeehiveMods”” “)表示,将kShopModule_mod存储在__DATA数据段里面的”BeehiveMods”section中。

那如何读取到这些字段呢?我们看下BHAnnotation类,我们先看下initProphet,

__attribute__((constructor))
void initProphet() {
_dyld_register_func_for_add_image(dyld_callback);
}

它用编译标记__attribute__((constructor))进行标记,****attribute((constructor))**** 的用法大家可以看下:

attribute((constructor))是在main函数之前,执行一个函数,也就是initProphet会在编译之前执行,_dyld_register_func_for_add_image(dyld_callback);回来镜像加载后调用dyld_callback。在这里面可以对加载完的镜像文件进行处理,读取出我们写到指定Session中的数据。

NSArray<NSString *>* BHReadConfiguration(char *sectionName,const struct mach_header *mhp) {
NSMutableArray *configs = [NSMutableArray array];
unsigned long size = 0;
#ifndef __LP64__
uintptr_t *memory = (uintptr_t*)getsectiondata(mhp, SEG_DATA, sectionName, &size);
#else
const struct mach_header_64 *mhp64 = (const struct mach_header_64 *)mhp;
uintptr_t *memory = (uintptr_t*)getsectiondata(mhp64, SEG_DATA, sectionName, &size);
#endif

unsigned long counter = size/sizeof(void*);
for(int idx = 0; idx < counter; ++idx){
char *string = (char*)memory[idx];
NSString *str = [NSString stringWithUTF8String:string];
if(!str)continue;
BHLog(@"config = %@", str);
if(str) [configs addObject:str];
}
return configs;
}

BHReadConfiguration 用于从__DATA中读取指定session下的数据。

static void dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide) {
NSArray *mods = BHReadConfiguration(BeehiveModSectName, mhp);
for (NSString *modName in mods) {
Class cls;
if (modName) {
cls = NSClassFromString(modName);
if (cls) {
[[BHModuleManager sharedManager] registerDynamicModule:cls];
}
}
}
//register services
NSArray<NSString *> *services = BHReadConfiguration(BeehiveServiceSectName,mhp);
for (NSString *map in services) {
NSData *jsonData = [map dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
id json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
if (!error) {
if ([json isKindOfClass:[NSDictionary class]] && [json allKeys].count) {
NSString *protocol = [json allKeys][0];
NSString *clsName = [json allValues][0];
if (protocol && clsName) {
[[BHServiceManager sharedManager] registerService:NSProtocolFromString(protocol) implClass:NSClassFromString(clsName)];
}
}
}
}
}

也就是在镜像文件加载完毕后,通过BHReadConfiguration将上面注解方式添加的字符串读取出来,并使用这些数据进行注册服务和模块。

4.较好的文章推荐

开源信息

IGKListKit 是一个数据驱动的用于快速创建列表的框架,它有如下特点:

  • 🙅 Never call performBatchUpdates(_:, completion:) or reloadData() again
  • 🏠 Better architecture with reusable cells and components
  • 🔠 Create collections with multiple data types
  • 🔑 Decoupled diffing algorithm
  • ✅ Fully unit tested
  • 🔍 Customize your diffing behavior for your models
  • 📱 Simply UICollectionView at its core
  • 🚀 Extendable API
  • 🐦 Written in Objective-C with full Swift interop support
源码解析

整个IGKListKit代码还是蛮多的,我们在进行源码解析之前先对这些代码进行整理下,看下整个IGKListKit是由哪些部分构成的:

这里大致将整个代码分成两大部分:

  1. IGListDiffKit 里面存放的是IGListKit的diff 算法相关类
  2. IGListKit 里面存放的是整个框架代码,它是由如下几部分构成:
  • Adapter: 这是最核心的部分,是它将CollectionView,SessionController,Updater,Context关联起来。它从SessionController获取每个session的数据,通过Updater将数据加载到CollectionView。
  • Updater: 负责将数据加载到CollectionView
  • CollectionView: 负责数据的展示
  • Context: 一些环境上下文全局内容
  • Debug: 用于辅助调试的内容

这篇博客会从数据源开始,从数据源的提供,根据数据源选择SessionController,再通过Updater将数据加载到UICollectionView.

数据源

IGListKit 数据源都必须遵循IGListAdapterDataSource协议:

/**
根据传入的IGListAdapter来返回要展示的数据源,注意这里的数组中的每个元素对应的是一个session中要展示的数据集,返回的是多个session数据集的集合。
*/
- (NSArray<id <IGListDiffable>> *)objectsForListAdapter:(IGListAdapter *)listAdapter;

/**
传入特定的对象,也就是一个session的数据集合,返回它对应的sessionController,我们可以在这里建立sessionController与对象数据集合的对应关系。这里也是新建sessionController的地方,我们可以在这里传数据给sessionController
*/
- (IGListSectionController *)listAdapter:(IGListAdapter *)listAdapter sectionControllerForObject:(id)object;

/**
当列表的数据集合为空的时候可以通过这个位置返回空页面需要展示的内容
*/
- (nullable UIView *)emptyViewForListAdapter:(IGListAdapter *)listAdapter;

IGListAdapterDataSource中我们可以提供数据,空数据时候所要展示的界面,以及session数据与sessionController之间的映射关系。

IGListAdapter的初始化工作

IGListAdapter是整个IGListKit的核心部分,它负责调度各个类来完成从数据源获取数据,数据与sessionController的映射关系,使用Updater将数据加载到UICollectionView,分发各个代理事件等等功能,所以我们先来看下它是由哪些重要的组件构成,以及这些组件都有哪些功能:

/**
容纳适配器的视图控制器。
*/
@property (nonatomic, nullable, weak) UIViewController *viewController;
/**
适配器使用的UICollectionView
*/
@property (nonatomic, nullable, weak) UICollectionView *collectionView;
/**
IGListAdapter的数据源
*/
@property (nonatomic, nullable, weak) id <IGListAdapterDataSource> dataSource;
/**
某些对象将要显示或者已经显示完毕后的通知
*/
@property (nonatomic, nullable, weak) id <IGListAdapterDelegate> delegate;
/**
接受UICollectionView 代理事件的delegate
*/
@property (nonatomic, nullable, weak) id <UICollectionViewDelegate> collectionViewDelegate;
/**
接受UIScrollView代理事件的delegate
*/
@property (nonatomic, nullable, weak) id <UIScrollViewDelegate> scrollViewDelegate;
/**
重排session代理事件的delegate
*/
@property (nonatomic, nullable, weak) id <IGListAdapterMoveDelegate> moveDelegate NS_AVAILABLE_IOS(9_0);
/**
用于性能检测点的的delegate
*/
@property (nonatomic, nullable, weak) id <IGListAdapterPerformanceDelegate> performanceDelegate;
/**
Adapter的更新器
*/
@property (nonatomic, strong, readonly) id <IGListUpdatingDelegate> updater;
/**
对象与SessionController的映射关系
*/
@property (nonatomic, strong, readonly) IGListSectionMap *sectionMap;
/**
显示cell的管理
*/
@property (nonatomic, strong, readonly) IGListDisplayHandler *displayHandler;
/**
用于可见范围的管理
*/
@property (nonatomic, strong, readonly) IGListWorkingRangeHandler *workingRangeHandler;
/**
负责代理事件的分发
*/
@property (nonatomic, strong, nullable) IGListAdapterProxy *delegateProxy;
/**
空视图界面
*/
@property (nonatomic, strong, nullable) UIView *emptyBackgroundView;

/**
用于注册Cell
*/
@property (nonatomic, strong) NSMutableSet<NSString *> *registeredCellIdentifiers;
@property (nonatomic, strong) NSMutableSet<NSString *> *registeredNibNames;
@property (nonatomic, strong) NSMutableSet<NSString *> *registeredSupplementaryViewIdentifiers;
@property (nonatomic, strong) NSMutableSet<NSString *> *registeredSupplementaryViewNibNames;

上面列出了比较重要的组件,但是从整体功能上看最主要的组件包括:

UICollectionView:UICollectionView是整个列表的界面承载实体
IGListAdapterDataSource:IGListAdapterDataSource为整个IGListAdapter提供数据对象,并且指定每个session对应的sessionController,以及空数据时候的界面
IGListUpdatingDelegate:用于将数据加载到UICollectionView
****IGListAdapterDelegate/UICollectionViewDelegate/UIScrollViewDelegate/****:提供事件处理代理方法
IGListAdapterProxy:负责事件分发
IGListSectionMap:维护数据集与sessionController的映射关系
IGListDisplayHandler:负责可见对象的控制,这些可见对象存储于visibleViewObjectMap
IGListWorkingRangeHandler:负责工作区的管理
IGListAdapterMoveDelegate/IGListAdapterPerformanceDelegate:移动sessionController以及性能检测埋点的代理

我们看下 IGListAdapter 的初始化阶段:

- (instancetype)initWithUpdater:(id <IGListUpdatingDelegate>)updater
viewController:(UIViewController *)viewController
workingRangeSize:(NSInteger)workingRangeSize {

//........
if (self = [super init]) {
//初始化重要组件
NSPointerFunctions *keyFunctions = [updater objectLookupPointerFunctions];
NSPointerFunctions *valueFunctions = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory];
NSMapTable *table = [[NSMapTable alloc] initWithKeyPointerFunctions:keyFunctions valuePointerFunctions:valueFunctions capacity:0];
_sectionMap = [[IGListSectionMap alloc] initWithMapTable:table];
_displayHandler = [IGListDisplayHandler new];
_workingRangeHandler = [[IGListWorkingRangeHandler alloc] initWithWorkingRangeSize:workingRangeSize];
_updateListeners = [NSHashTable weakObjectsHashTable];
_viewSectionControllerMap = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality | NSMapTableStrongMemory
valueOptions:NSMapTableStrongMemory];
_updater = updater;
_viewController = viewController;
[IGListDebugger trackAdapter:self];
}
return self;
}

初始化任务中完成了大部分重要组件的初始化,上面已经介绍了这些组件的功能,这里就不重复介绍了。

- (void)setCollectionView:(UICollectionView *)collectionView {
// 如果collectionView 被用于另一个adapter,那么我们需要对它进行重置,断开与之前adapter之间的关联,避免会被之前对adapter错误更新。
if (_collectionView != collectionView || _collectionView.dataSource != self) {
//每个UICollectionView 都对应一个 IGListAdapter,globalCollectionViewAdapterMap 里面存放的是之间的对应关系
static NSMapTable<UICollectionView *, IGListAdapter *> *globalCollectionViewAdapterMap = nil;
if (globalCollectionViewAdapterMap == nil) {
globalCollectionViewAdapterMap = [NSMapTable weakToWeakObjectsMapTable];
}
[globalCollectionViewAdapterMap removeObjectForKey:_collectionView];
[[globalCollectionViewAdapterMap objectForKey:collectionView] setCollectionView:nil];
[globalCollectionViewAdapterMap setObject:self forKey:collectionView];
//globalCollectionViewAdapterMap ---> key:UICollectionView value:IGListAdapter

//注册cell之类的名字
_registeredCellIdentifiers = [NSMutableSet new];
_registeredNibNames = [NSMutableSet new];
_registeredSupplementaryViewIdentifiers = [NSMutableSet new];
_registeredSupplementaryViewNibNames = [NSMutableSet new];

const BOOL settingFirstCollectionView = _collectionView == nil;

//持有collectionView,并将adapter作为它的数据源
_collectionView = collectionView;
_collectionView.dataSource = self;

//.....
//将adapter保存到collectionViewLayout
[_collectionView.collectionViewLayout ig_hijackLayoutInteractiveReorderingMethodForAdapter:self];
[_collectionView.collectionViewLayout invalidateLayout];

//CollectionViewDelegate 要么设置为 delegateProxy 要么设置为adapter
[self _updateCollectionViewDelegate];

// only construct
if (!IGListExperimentEnabled(self.experiments, IGListExperimentGetCollectionViewAtUpdate)
|| settingFirstCollectionView) {
[self _updateAfterPublicSettingsChange];
}
}
}

- (void)_updateCollectionViewDelegate {
_collectionView.delegate = (id<UICollectionViewDelegate>)self.delegateProxy ?: self;
}

为IGListAdapter设置collectionView的时候要注意如果globalCollectionViewAdapterMap中已经存在了一个映射关系,需要重新移除后添加,之后为collectionView重新设置当前的Adapter作为新的dataSource,以及delegateProxy的设置,对于第一次创建的时候还需要调用_updateAfterPublicSettingsChange从datasource中取出属于当前adapter的数据,进行相关的更新。这部分在接下来的设置数据源也会涉及,我们就来看下数据源的设置:

IGListAdapter数据源的设置

- (void)setDataSource:(id<IGListAdapterDataSource>)dataSource {
if (_dataSource != dataSource) {
_dataSource = dataSource;
//数据源改变的时候会剔除重复数据后更新数据
[self _updateAfterPublicSettingsChange];
}
}

- (void)_updateAfterPublicSettingsChange {
id<IGListAdapterDataSource> dataSource = _dataSource;
if (_collectionView != nil && dataSource != nil) {
//去除当前Adapter中重复的元素
NSArray *uniqueObjects = objectsWithDuplicateIdentifiersRemoved([dataSource objectsForListAdapter:self]);
//更新数据源
[self _updateObjects:uniqueObjects dataSource:dataSource];
}
}

这里会通过[dataSource objectsForListAdapter:self],取出属于该adapter的数据对象。然后通过objectsWithDuplicateIdentifiersRemoved剔除掉重复的数据后,通过_updateObjects更新adapter中的其他模块数据,我们重点看下这个部分:

- (void)_updateObjects:(NSArray *)objects dataSource:(id<IGListAdapterDataSource>)dataSource {

//.......
NSMutableArray<IGListSectionController *> *sectionControllers = [NSMutableArray new];
NSMutableArray *validObjects = [NSMutableArray new];

IGListSectionMap *map = self.sectionMap;

// collect items that have changed since the last update
NSMutableSet *updatedObjects = [NSMutableSet new];

//遍历从数据源获取到的每个数据,注意这里的object其实是一个数据集,每个数据集针对一个sessionController
for (id object in objects) {
// 查看是否之前已经创建了sectionController,如果有就直接使用
IGListSectionController *sectionController = [map sectionControllerForObject:object];

// 如果之前没有我们就询问dataSource返回一个指定数据集对应的sessionController
if (sectionController == nil) {
sectionController = [dataSource listAdapter:self sectionControllerForObject:object];
}

//如果再没有直接报错
if (sectionController == nil) {
IGLKLog(@"WARNING: Ignoring nil section controller returned by data source %@ for object %@.",
dataSource, object);
continue;
}

// 将一些环境变量赋给sectionController,比如将当前的adapter作为collectionContext赋给sectionController
sectionController.collectionContext = self;
sectionController.viewController = self.viewController;

// 检查当前的对象是全新的还是对已经有的对象进行更新
const NSInteger oldSection = [map sectionForObject:object];
if (oldSection == NSNotFound || [map objectForSection:oldSection] != object) {
//更新的对象
[updatedObjects addObject:object];
}

//将结果记录到sectionControllers 与 validObjects
[sectionControllers addObject:sectionController];
[validObjects addObject:object];
}

// clear the view controller and collection context
IGListSectionControllerPopThread();

//更新到IGListSectionMap IGListSectionMap用于维护object 与sessionController的之间的关系
[map updateWithObjects:validObjects sectionControllers:sectionControllers];

// object 与 sessionController之间的关系已经建立,这时候可以认为sessionController已经加载完毕,加载完成后sessionController会收到didUpdateToObject的通知
for (id object in updatedObjects) {
[[map sectionControllerForObject:object] didUpdateToObject:object];
}

//计算总数量
NSInteger itemCount = 0;
for (IGListSectionController *sectionController in sectionControllers) {
itemCount += [sectionController numberOfItems];
}

//根据总数量决定显示空界面还是正常的UICollectionView
[self _updateBackgroundViewShouldHide:itemCount > 0];
}

- (void)_updateBackgroundViewShouldHide:(BOOL)shouldHide {

//如果还在更新那么先返回,在update block执行结束之后还会调用
if (self.isInUpdateBlock) {
return;
}
//根据itemCount数量决定是否显示空白页面
UIView *backgroundView = [self.dataSource emptyViewForListAdapter:self];
// don't do anything if the client is using the same view
if (backgroundView != _collectionView.backgroundView) {
// collection view will just stack the background views underneath each other if we do not remove the previous
// one first. also fine if it is nil
[_collectionView.backgroundView removeFromSuperview];
_collectionView.backgroundView = backgroundView;
}
_collectionView.backgroundView.hidden = shouldHide;
}

我们上面传入_updateObjects的objects是整个adapter所对应的数据集的数组,那么在_updateObjects中首先需要遍历数组中的每个数据集,我们知道object与sessionController的对应关系存在于两个地方IGListSectionMap以及IGListAdapter。首先我们会从IGListSectionMap中去检查是否有缓存了这种关系,如果没有再从IGListAdapter去获取。拿到之后再更新到IGListSectionMap以供下次使用。对于数据有变动的情况还会调用sessionController的didUpdateToObject方法。最后计算整个adapter的数据总数,如果数据总数为0那么会调用emptyViewForListAdapter获取空界面进行展示。

IGListAdapter代理设置

- (void)setCollectionViewDelegate:(id<UICollectionViewDelegate>)collectionViewDelegate {
//....
//_collectionViewDelegate 这个会传递到IGListAdapterProxy
if (_collectionViewDelegate != collectionViewDelegate) {
_collectionViewDelegate = collectionViewDelegate;
//创建IGListAdapterProxy并设置为delegate
[self _createProxyAndUpdateCollectionViewDelegate];
}
}

- (void)setScrollViewDelegate:(id<UIScrollViewDelegate>)scrollViewDelegate {
if (_scrollViewDelegate != scrollViewDelegate) {
_scrollViewDelegate = scrollViewDelegate;
//_scrollViewDelegate 这个会传递到IGListAdapterProxy
[self _createProxyAndUpdateCollectionViewDelegate];
}
}

- (void)_createProxyAndUpdateCollectionViewDelegate {
_collectionView.delegate = nil;
self.delegateProxy = [[IGListAdapterProxy alloc] initWithCollectionViewTarget:_collectionViewDelegate
scrollViewTarget:_scrollViewDelegate
interceptor:self];
[self _updateCollectionViewDelegate];
}

从上面代码看IGListAdapterProxy接受三个对象_collectionViewDelegate,_scrollViewDelegate以及adapter。然后再将IGListAdapterProxy作为collectionView的delegate。这样做的好处是可以通过IGListAdapterProxy进行统一管理,IGListAdapterProxy负责这些代理事件的集中分发。

- (BOOL)respondsToSelector:(SEL)aSelector {
return isInterceptedSelector(aSelector)
|| [_collectionViewTarget respondsToSelector:aSelector]
|| [_scrollViewTarget respondsToSelector:aSelector];
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
if (isInterceptedSelector(aSelector)) {
return _interceptor;
}
return [_scrollViewTarget respondsToSelector:aSelector] ? _scrollViewTarget : _collectionViewTarget;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
void *nullPointer = NULL;
[invocation setReturnValue:&nullPointer];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}

在IGListAdapterProxy中会对这些代理方法进行拦截,如果在拦截清单内的话会将这些代理方法转到_interceptor也就是adapter,其他的则会根据_scrollViewTarget以及_collectionViewTarget是否有响应的方法来确定转发给谁,下面是拦截的方法:

static BOOL isInterceptedSelector(SEL sel) {
return (
// UIScrollViewDelegate
sel == @selector(scrollViewDidScroll:) ||
sel == @selector(scrollViewWillBeginDragging:) ||
sel == @selector(scrollViewDidEndDragging:willDecelerate:) ||
sel == @selector(scrollViewDidEndDecelerating:) ||
// UICollectionViewDelegate
sel == @selector(collectionView:willDisplayCell:forItemAtIndexPath:) ||
sel == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) ||
sel == @selector(collectionView:didSelectItemAtIndexPath:) ||
sel == @selector(collectionView:didDeselectItemAtIndexPath:) ||
sel == @selector(collectionView:didHighlightItemAtIndexPath:) ||
sel == @selector(collectionView:didUnhighlightItemAtIndexPath:) ||
// UICollectionViewDelegateFlowLayout
sel == @selector(collectionView:layout:sizeForItemAtIndexPath:) ||
sel == @selector(collectionView:layout:insetForSectionAtIndex:) ||
sel == @selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:) ||
sel == @selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:) ||
sel == @selector(collectionView:layout:referenceSizeForFooterInSection:) ||
sel == @selector(collectionView:layout:referenceSizeForHeaderInSection:) ||

// IGListCollectionViewDelegateLayout
sel == @selector(collectionView:layout:customizedInitialLayoutAttributes:atIndexPath:) ||
sel == @selector(collectionView:layout:customizedFinalLayoutAttributes:atIndexPath:)
);
}

Object && sessionController 之间的映射关系

目前在IGListKit可以完成如下几种对象之间的映射关系:

* indexPath <--indexPath.section--> section <---sectionControllerForSection/sectionForSectionController---> IGListSectionController <--sectionControllerForObject/objectForSection-->object

* indexPath --sectionControllerForSection--supplementaryViewSource--> IGListSupplementaryViewSource

这部分代码就不贴出来了,最底层逻辑在sectionMap上,大家可以查看对应的源码。

可见性通知

这部分主要涉及IGListDisplayDelegateIGListAdapterDelegateIGListDisplayHandler这三个类,我们可以指定IGListAdapter的delegate来监听可见性情况:

@protocol IGListAdapterDelegate <NSObject>
/**
通知delegate某个数据集即将显示
*/
- (void)listAdapter:(IGListAdapter *)listAdapter willDisplayObject:(id)object atIndex:(NSInteger)index;

/**
通知delegate某个数据集合将不在显示范围内
*/
- (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingObject:(id)object atIndex:(NSInteger)index;
@end

IGListAdapterDelegate其实和IGListDisplayDelegate是一样的,只不过一个是在adapter上层监听,一个是在sessionController监听。IGListDisplayDelegate其实监听的是更细粒度的可以通过代理知道具体哪个cell进入可见区域,我们在看下IGListDisplayDelegate:

@protocol IGListDisplayDelegate <NSObject>

/**
指定的sessionController进入到可见区域
*/
- (void)listAdapter:(IGListAdapter *)listAdapter willDisplaySectionController:(IGListSectionController *)sectionController;

/**
指定的sessionController离开可见区域
*/
- (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingSectionController:(IGListSectionController *)sectionController;

/**
在某个sessionController的某个cell进入到可见区域
*/
- (void)listAdapter:(IGListAdapter *)listAdapter willDisplaySectionController:(IGListSectionController *)sectionController
cell:(UICollectionViewCell *)cell
atIndex:(NSInteger)index;

/**
某个sessionController的某个cell离开可见区域
*/
- (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingSectionController:(IGListSectionController *)sectionController
cell:(UICollectionViewCell *)cell
atIndex:(NSInteger)index;

@end

IGListAdapterDelegate和IGListDisplayDelegate对应的方法是在IGListDisplayHandler中被调用的。IGListDisplayHandler中维护着一份visibleListSections以及visibleViewObjectMap;visibleListSections里面存储着当前可见的sessionController,visibleViewObjectMap里面存储的是数据对象以及CellView的映射关系。在sessionController进入离开可见区域的时候都会维护这两个对象里面的内容,并且通知adapter以及sessionController中的对应delegate。

- (void)_willDisplayReusableView:(UICollectionReusableView *)view
forListAdapter:(IGListAdapter *)listAdapter
sectionController:(IGListSectionController *)sectionController
object:(id)object
indexPath:(NSIndexPath *)indexPath {
//....
//self.visibleViewObjectMap 中存储的是数据对象以及CellView
[self.visibleViewObjectMap setObject:object forKey:view];
//可见的session部分
NSCountedSet *visibleListSections = self.visibleListSections;
if ([visibleListSections countForObject:sectionController] == 0) {
/*如果当前的sectionController没有在可见session列表中则触发通知给sectionController以及listAdapter*/
[sectionController.displayDelegate listAdapter:listAdapter willDisplaySectionController:sectionController];
[listAdapter.delegate listAdapter:listAdapter willDisplayObject:object atIndex:indexPath.section];
}
//将当前的sectionController添加到visibleListSections 避免重复通知
[visibleListSections addObject:sectionController];
}

- (void)_didEndDisplayingReusableView:(UICollectionReusableView *)view
forListAdapter:(IGListAdapter *)listAdapter
sectionController:(IGListSectionController *)sectionController
object:(id)object
indexPath:(NSIndexPath *)indexPath {
IGParameterAssert(view != nil);
IGParameterAssert(listAdapter != nil);
IGParameterAssert(indexPath != nil);

if (object == nil || sectionController == nil) {
return;
}

const NSInteger section = indexPath.section;
//移除sectionController并通知sectionController以及listAdapter
NSCountedSet *visibleSections = self.visibleListSections;
[visibleSections removeObject:sectionController];

if ([visibleSections countForObject:sectionController] == 0) {
[sectionController.displayDelegate listAdapter:listAdapter didEndDisplayingSectionController:sectionController];
[listAdapter.delegate listAdapter:listAdapter didEndDisplayingObject:object atIndex:section];
}
}

那么IGListDisplayHandler这些方法是谁触发的呢?嗯,是Adapter触发的,我们上面介绍IGListAdapterProxy的时候讲到Adapter会拦截部分的代理方法,在这部分代理方法中就可以调用IGListDisplayHandler发出通知。这部分代码在IGListAdapter+UICollectionView,这里面包括了可见区域的通知,以及工作区相关的处理。由于同时需要注意的是由于在IGListAdapterProxy会对这部分代理方法进行拦截,所以这里还需要将事件通知到collectionViewDelegate

- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
id<IGListAdapterPerformanceDelegate> performanceDelegate = self.performanceDelegate;
[performanceDelegate listAdapterWillCallDisplayCell:self];

// 由于在IGListAdapterProxy会对这部分请求进行拦截,所以这里还需要将事件通知到collectionViewDelegate
id<UICollectionViewDelegate> collectionViewDelegate = self.collectionViewDelegate;
if ([collectionViewDelegate respondsToSelector:@selector(collectionView:willDisplayCell:forItemAtIndexPath:)]) {
[collectionViewDelegate collectionView:collectionView willDisplayCell:cell forItemAtIndexPath:indexPath];
}

// 获取到sessionController
IGListSectionController *sectionController = [self sectionControllerForView:cell];
// if the section controller relationship was destroyed, reconnect it
// this happens with iOS 10 UICollectionView display range changes
if (sectionController == nil) {
sectionController = [self sectionControllerForSection:indexPath.section];
//这里为了建立cell与sessionController之间的关系,供sectionControllerForView使用
[self mapView:cell toSectionController:sectionController];
}

//取出当前sessionController对应的数据集
id object = [self.sectionMap objectForSection:indexPath.section];
//通知对应的delegate
[self.displayHandler willDisplayCell:cell forListAdapter:self sectionController:sectionController object:object indexPath:indexPath];

//工作区相关更新
_isSendingWorkingRangeDisplayUpdates = YES;
[self.workingRangeHandler willDisplayItemAtIndexPath:indexPath forListAdapter:self];
_isSendingWorkingRangeDisplayUpdates = NO;

[performanceDelegate listAdapter:self didCallDisplayCell:cell onSectionController:sectionController atIndex:indexPath.item];
}

- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath {
id<IGListAdapterPerformanceDelegate> performanceDelegate = self.performanceDelegate;
[performanceDelegate listAdapterWillCallEndDisplayCell:self];

// 由于在IGListAdapterProxy会对这部分请求进行拦截,所以这里还需要将事件通知到collectionViewDelegate
id<UICollectionViewDelegate> collectionViewDelegate = self.collectionViewDelegate;
if ([collectionViewDelegate respondsToSelector:@selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:)]) {
[collectionViewDelegate collectionView:collectionView didEndDisplayingCell:cell forItemAtIndexPath:indexPath];
}

// 获取到sessionController
IGListSectionController *sectionController = [self sectionControllerForView:cell];
//通知对应的delegate
[self.displayHandler didEndDisplayingCell:cell forListAdapter:self sectionController:sectionController indexPath:indexPath];
//工作区相关更新
[self.workingRangeHandler didEndDisplayingItemAtIndexPath:indexPath forListAdapter:self];

// break the association between the cell and the section controller
[self removeMapForView:cell];

[performanceDelegate listAdapter:self didCallEndDisplayCell:cell onSectionController:sectionController atIndex:indexPath.item];
}

- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath {

// 由于在IGListAdapterProxy会对这部分请求进行拦截,所以这里还需要将事件通知到collectionViewDelegate
id<UICollectionViewDelegate> collectionViewDelegate = self.collectionViewDelegate;
if ([collectionViewDelegate respondsToSelector:@selector(collectionView:willDisplaySupplementaryView:forElementKind:atIndexPath:)]) {
[collectionViewDelegate collectionView:collectionView willDisplaySupplementaryView:view forElementKind:elementKind atIndexPath:indexPath];
}

IGListSectionController *sectionController = [self sectionControllerForView:view];
// if the section controller relationship was destroyed, reconnect it
// this happens with iOS 10 UICollectionView display range changes
if (sectionController == nil) {
sectionController = [self.sectionMap sectionControllerForSection:indexPath.section];
[self mapView:view toSectionController:sectionController];
}

id object = [self.sectionMap objectForSection:indexPath.section];
//通知对应的delegate
[self.displayHandler willDisplaySupplementaryView:view forListAdapter:self sectionController:sectionController object:object indexPath:indexPath];
}

- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath {
// 由于在IGListAdapterProxy会对这部分请求进行拦截,所以这里还需要将事件通知到collectionViewDelegate
id<UICollectionViewDelegate> collectionViewDelegate = self.collectionViewDelegate;
if ([collectionViewDelegate respondsToSelector:@selector(collectionView:didEndDisplayingSupplementaryView:forElementOfKind:atIndexPath:)]) {
[collectionViewDelegate collectionView:collectionView didEndDisplayingSupplementaryView:view forElementOfKind:elementKind atIndexPath:indexPath];
}

IGListSectionController *sectionController = [self sectionControllerForView:view];
//通知对应的delegate
[self.displayHandler didEndDisplayingSupplementaryView:view forListAdapter:self sectionController:sectionController indexPath:indexPath];

[self removeMapForView:view];
}

工作区管理

对于工作区管理会在进入显示区的时候将当前的indexPath 信息添加到_visibleSectionIndices,退出可视区域的时候从_visibleSectionIndices中删除。然后根据workingRangeSize来确定起始和结束的index,根据确定的起始index和结束index将可见的sessionController添加到workingRangeSectionControllers,再通过新的workingRangeSectionControllers与旧的workingRangeSectionControllers进行对比,如果只出现再新的,而在老的没出现,这时候表示当前的sessionController为新进入工作区的,反之则为退出工作区的。

- (void)willDisplayItemAtIndexPath:(NSIndexPath *)indexPath
forListAdapter:(IGListAdapter *)listAdapter {

// 往_visibleSectionIndices中插入可见的NSIndexPath信息
_visibleSectionIndices.insert({
.section = indexPath.section,
.row = indexPath.row,
.hash = indexPath.hash
});
[self _updateWorkingRangesWithListAdapter:listAdapter];
}

- (void)didEndDisplayingItemAtIndexPath:(NSIndexPath *)indexPath
forListAdapter:(IGListAdapter *)listAdapter {
//将将要移除到可见范围之外的cell的NSIndexPath从_visibleSectionIndices中移除
_visibleSectionIndices.erase({
.section = indexPath.section,
.row = indexPath.row,
.hash = indexPath.hash
});
[self _updateWorkingRangesWithListAdapter:listAdapter];
}

#pragma mark - Working Ranges

- (void)_updateWorkingRangesWithListAdapter:(IGListAdapter *)listAdapter {
IGAssertMainThread();
// This method is optimized C++ to improve straight-line speed of these operations. Change at your peril.
// We use a std::set because it is ordered.
std::set<NSInteger> visibleSectionSet = std::set<NSInteger>();
for (const _IGListWorkingRangeHandlerIndexPath &indexPath : _visibleSectionIndices) {
visibleSectionSet.insert(indexPath.section);
}

//根据workingRangeSize计算workingRange的start 和 end
NSInteger start;
NSInteger end;
if (visibleSectionSet.size() == 0) {
// We're now devoid of any visible sections. Bail
start = 0;
end = 0;
} else {
start = MAX(*visibleSectionSet.begin() - _workingRangeSize, 0);
end = MIN(*visibleSectionSet.rbegin() + 1 + _workingRangeSize, (NSInteger)listAdapter.objects.count);
}

// Build the current set of working range section controllers
// 构建新的可见sessionController 集合
_IGListWorkingRangeSectionControllerSet workingRangeSectionControllers (visibleSectionSet.size());
for (NSInteger idx = start; idx < end; idx++) {
//取出工作区中的sessionController
id item = [listAdapter objectAtSection:idx];
IGListSectionController *sectionController = [listAdapter sectionControllerForObject:item];
workingRangeSectionControllers.insert({sectionController});
}

//工作区不能大于1000
IGAssert(workingRangeSectionControllers.size() < 1000, @"This algorithm is way too slow with so many objects:%lu", workingRangeSectionControllers.size());

// Tell any new section controllers that they have entered the working range
// 从新的可见sessionController 集合中遍历每个元素看下是否在旧的存在,如果在旧的不存在 说明这个是新进入工作区的通知对应的sessionController
for (const _IGListWorkingRangeHandlerSectionControllerWrapper &wrapper : workingRangeSectionControllers) {
// Check if the item exists in the old working range item array.
auto it = _workingRangeSectionControllers.find(wrapper);
if (it == _workingRangeSectionControllers.end()) {
// The section controller isn't in the existing list, so it's new.
id <IGListWorkingRangeDelegate> workingRangeDelegate = wrapper.sectionController.workingRangeDelegate;
[workingRangeDelegate listAdapter:listAdapter sectionControllerWillEnterWorkingRange:wrapper.sectionController];
}
}

// 同理处理退出工作区的事件
// Tell any removed section controllers that they have exited the working range
for (const _IGListWorkingRangeHandlerSectionControllerWrapper &wrapper : _workingRangeSectionControllers) {
// Check if the item exists in the new list of section controllers
auto it = workingRangeSectionControllers.find(wrapper);
if (it == workingRangeSectionControllers.end()) {
// If the item does not exist in the new list, then it's been removed.
id <IGListWorkingRangeDelegate> workingRangeDelegate = wrapper.sectionController.workingRangeDelegate;
[workingRangeDelegate listAdapter:listAdapter sectionControllerDidExitWorkingRange:wrapper.sectionController];
}
}

_workingRangeSectionControllers = workingRangeSectionControllers;
}

加载数据到UICollectionView

- (void)reloadDataWithCompletion:(nullable IGListUpdaterCompletion)completion {

id<IGListAdapterDataSource> dataSource = self.dataSource;
UICollectionView *collectionView = self.collectionView;
//.......
//从数据源里面获取当前adapter的全部数据集
NSArray *uniqueObjects = objectsWithDuplicateIdentifiersRemoved([dataSource objectsForListAdapter:self]);
__weak __typeof__(self) weakSelf = self;
//调用updater 加载数据
[self.updater reloadDataWithCollectionViewBlock:[self _collectionViewBlock]
reloadUpdateBlock:^{
// purge all section controllers from the item map so that they are regenerated
[weakSelf.sectionMap reset];
[weakSelf _updateObjects:uniqueObjects dataSource:dataSource];
} completion:^(BOOL finished) {
[weakSelf _notifyDidUpdate:IGListAdapterUpdateTypeReloadData animated:NO];
if (completion) {
completion(finished);
}
}];
}

在adapter调用加载数据的时候会先从datasource中获取到当前adapter全部sessionConstroller所需要的全部数据集。这些数据数据集被包裹reloadUpdateBlock,等到需要执行reloadUpdateBlock的时候更新Adapter里面的sessionMap等相关等数据集合,因为后面调用UICollectionView reloadData后会通过UICollectionView 的dateSource也就是 adapter中通过对应的代理方法从sessionController中获取数据。

完成上面的设置后就可以调用updater的reloadDataWithCollectionViewBlock进行数据加载。

- (void)reloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock
reloadUpdateBlock:(IGListReloadUpdateBlock)reloadUpdateBlock
completion:(nullable IGListUpdatingCompletion)completion {

//.......
//将完成所需要调用的结束回调添加到completionBlocks
IGListUpdatingCompletion localCompletion = completion;
if (localCompletion) {
[self.completionBlocks addObject:localCompletion];
}
self.reloadUpdates = reloadUpdateBlock;
self.queuedReloadData = YES;
[self _queueUpdateWithCollectionViewBlock:collectionViewBlock];
}

reloadDataWithCollectionViewBlock中会将queuedReloadData设置为YES,然后触发_queueUpdateWithCollectionViewBlock,在_queueUpdateWithCollectionViewBlock中由于queuedReloadData设置为YES,所以执行performReloadDataWithCollectionViewBlock


- (void)_queueUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock {
//.....
dispatch_async(dispatch_get_main_queue(), ^{
//如果正在处于批量更新数据中,或者没有新的变更则直接返回
if (weakSelf.state != IGListBatchUpdateStateIdle
|| ![weakSelf hasChanges]) {
return;
}
if (weakSelf.hasQueuedReloadData) {
//执行加载
[weakSelf performReloadDataWithCollectionViewBlock:collectionViewBlock];
} else {
//批量加载
[weakSelf performBatchUpdatesWithCollectionViewBlock:collectionViewBlock];
}
});
}

加载数据到UICollectionView

- (void)performReloadDataWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock {
IGAssertMainThread();

id<IGListAdapterUpdaterDelegate> delegate = self.delegate;
void (^reloadUpdates)(void) = self.reloadUpdates;
IGListBatchUpdates *batchUpdates = self.batchUpdates;
NSMutableArray *completionBlocks = [self.completionBlocks mutableCopy];

//开始加载前清除相关的状态变量
[self cleanStateBeforeUpdates];

//加载结束之后执行全部的completeBlock,并且将状态改为IGListBatchUpdateStateIdle
void (^executeCompletionBlocks)(BOOL) = ^(BOOL finished) {
for (IGListUpdatingCompletion block in completionBlocks) {
block(finished);
}
self.state = IGListBatchUpdateStateIdle;
};
// 在更新任务还在队列的时候如果返回执行的时候发现collection已经被释放则重置对应的状态并调用completeBlock,将状态设置为IGListBatchUpdateStateIdle
UICollectionView *collectionView = collectionViewBlock();
if (collectionView == nil) {
[self _cleanStateAfterUpdates];
executeCompletionBlocks(NO);
return;
}

// item updates must not send mutations to the collection view while we are reloading
// 将状态设置为正在加载数据
self.state = IGListBatchUpdateStateExecutingBatchUpdateBlock;

//调用_updateObjects 重建object 和 sessionController之间的关系
if (reloadUpdates) {
reloadUpdates();
}

// 执行批量更新中的单个更新block
// execute all stored item update blocks even if we are just calling reloadData. the actual collection view
// mutations will be discarded, but clients are encouraged to put their actual /data/ mutations inside the
// update block as well, so if we don't execute the block the changes will never happen
for (IGListItemUpdateBlock itemUpdateBlock in batchUpdates.itemUpdateBlocks) {
itemUpdateBlock();
}
[completionBlocks addObjectsFromArray:batchUpdates.itemCompletionBlocks];
self.state = IGListBatchUpdateStateExecutedBatchUpdateBlock;
//将状态设置为批量更新执行完毕
[self _cleanStateAfterUpdates];
//通知外部当前的UICollectionView即将开始加载数据
[delegate listAdapterUpdater:self willReloadDataWithCollectionView:collectionView];
//加载数据
[collectionView reloadData];
[collectionView.collectionViewLayout invalidateLayout];
[collectionView layoutIfNeeded];
//通知外部当前的UICollectionView已经加载完数据
[delegate listAdapterUpdater:self didReloadDataWithCollectionView:collectionView];

//执行完成后的block
executeCompletionBlocks(YES);
}

performReloadDataWithCollectionViewBlock方法中会执行传入的reloadUpdates以及batchUpdates.itemUpdateBlocks中对应的更新block然后在调用对应的
delegate 通知UICollectionView将要开始进行数据加载。然后调用[collectionView reloadData]加载数据。

调用[collectionView reloadData] 后便会触发UICollectionView的对应代理,由于IGListAdapter会对UICollectionView 的UICollectionViewDataSource 代理进行拦截,所以这些处理出现在IGListAdapter中:

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
return self.sectionMap.objects.count;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
IGListSectionController * sectionController = [self sectionControllerForSection:section];
IGAssert(sectionController != nil, @"Nil section controller for section %li for item %@. Check your -diffIdentifier and -isEqual: implementations.",
(long)section, [self.sectionMap objectForSection:section]);
const NSInteger numberOfItems = [sectionController numberOfItems];
IGAssert(numberOfItems >= 0, @"Cannot return negative number of items %li for section controller %@.", (long)numberOfItems, sectionController);
return numberOfItems;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
id<IGListAdapterPerformanceDelegate> performanceDelegate = self.performanceDelegate;
[performanceDelegate listAdapterWillCallDequeueCell:self];
IGListSectionController *sectionController = [self sectionControllerForSection:indexPath.section];
// flag that a cell is being dequeued in case it tries to access a cell in the process
_isDequeuingCell = YES;
UICollectionViewCell *cell = [sectionController cellForItemAtIndex:indexPath.item];
_isDequeuingCell = NO;
IGAssert(cell != nil, @"Returned a nil cell at indexPath <%@> from section controller: <%@>", indexPath, sectionController);
// associate the section controller with the cell so that we know which section controller is using it
[self mapView:cell toSectionController:sectionController];
[performanceDelegate listAdapter:self didCallDequeueCell:cell onSectionController:sectionController atIndex:indexPath.item];
return cell;
}

这些方法大致思路是通过indexPath获取到对应的SessionController然后再通过SessionController中的方法获取对应的数据。

批量更新UICollectionView数据

- (void)performUpdatesAnimated:(BOOL)animated completion:(IGListUpdaterCompletion)completion {

//.......
id<IGListAdapterDataSource> dataSource = self.dataSource;
UICollectionView *collectionView = self.collectionView;
//....
//旧的数据源
NSArray *fromObjects = self.sectionMap.objects;

IGListToObjectBlock toObjectsBlock;
__weak __typeof__(self) weakSelf = self;
if (IGListExperimentEnabled(self.experiments, IGListExperimentDeferredToObjectCreation)) {
toObjectsBlock = ^NSArray *{
__typeof__(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return nil;
}
return [dataSource objectsForListAdapter:strongSelf];
};
} else {
//从dataSource中获取最新的数据源
NSArray *newObjects = [dataSource objectsForListAdapter:self];
toObjectsBlock = ^NSArray *{
return newObjects;
};
}

[self _enterBatchUpdates];
[self.updater performUpdateWithCollectionViewBlock:[self _collectionViewBlock]
fromObjects:fromObjects
toObjectsBlock:toObjectsBlock
animated:animated
objectTransitionBlock:^(NSArray *toObjects) {
// 更新数据源
weakSelf.previousSectionMap = [weakSelf.sectionMap copy];
[weakSelf _updateObjects:toObjects dataSource:dataSource];
} completion:^(BOOL finished) {
weakSelf.previousSectionMap = nil;
[weakSelf _notifyDidUpdate:IGListAdapterUpdateTypePerformUpdates animated:animated];
if (completion) {
completion(finished);
}
[weakSelf _exitBatchUpdates];
}];
}
- (void)performUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock
fromObjects:(NSArray *)fromObjects
toObjectsBlock:(IGListToObjectBlock)toObjectsBlock
animated:(BOOL)animated
objectTransitionBlock:(IGListObjectTransitionBlock)objectTransitionBlock
completion:(IGListUpdatingCompletion)completion {

//........
self.fromObjects = self.fromObjects ?: self.pendingTransitionToObjects ?: fromObjects;
self.toObjectsBlock = toObjectsBlock;

self.queuedUpdateIsAnimated = self.queuedUpdateIsAnimated && animated;
self.objectTransitionBlock = objectTransitionBlock;

IGListUpdatingCompletion localCompletion = completion;
if (localCompletion) {
[self.completionBlocks addObject:localCompletion];
}
[self _queueUpdateWithCollectionViewBlock:collectionViewBlock];
}

- (void)_queueUpdateWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock {
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
if (weakSelf.state != IGListBatchUpdateStateIdle
|| ![weakSelf hasChanges]) {
return;
}
if (weakSelf.hasQueuedReloadData) {
[weakSelf performReloadDataWithCollectionViewBlock:collectionViewBlock];
} else {
//更新的时候走这里
[weakSelf performBatchUpdatesWithCollectionViewBlock:collectionViewBlock];
}
});
}

- (void)performBatchUpdatesWithCollectionViewBlock:(IGListCollectionViewBlock)collectionViewBlock {
//........
// create local variables so we can immediately clean our state but pass these items into the batch update block
id<IGListAdapterUpdaterDelegate> delegate = self.delegate;
NSArray *fromObjects = [self.fromObjects copy];
IGListToObjectBlock toObjectsBlock = [self.toObjectsBlock copy];
NSMutableArray *completionBlocks = [self.completionBlocks mutableCopy];
void (^objectTransitionBlock)(NSArray *) = [self.objectTransitionBlock copy];
const BOOL animated = self.queuedUpdateIsAnimated;
IGListBatchUpdates *batchUpdates = self.batchUpdates;

// 在更新前重置状态为初始状态
// clean up all state so that new updates can be coalesced while the current update is in flight
[self cleanStateBeforeUpdates];

// 更新完成之后的回调
void (^executeCompletionBlocks)(BOOL) = ^(BOOL finished) {
self.applyingUpdateData = nil;
self.state = IGListBatchUpdateStateIdle;
for (IGListUpdatingCompletion block in completionBlocks) {
block(finished);
}
};

//.......
NSArray *toObjects = nil;
if (toObjectsBlock != nil) {
//剔除掉重复数据
toObjects = objectsWithDuplicateIdentifiersRemoved(toObjectsBlock());
}

//数据刷新的闭包
void (^executeUpdateBlocks)(void) = ^{
self.state = IGListBatchUpdateStateExecutingBatchUpdateBlock;
// 更新包括 IGListAdapter 的 sectionController 和 objects 的映射关系等数据,保证执行刷新前数据已经是最新的
if (objectTransitionBlock != nil) {
objectTransitionBlock(toObjects);
}
// 触发批量刷新任务的数据更新闭包(包括插入、删除、刷新单个 section 的数据)objectTransitionBlock 之后执行是为了保证 section 级别的刷新在 item 级别刷新之前进行
for (IGListItemUpdateBlock itemUpdateBlock in batchUpdates.itemUpdateBlocks) {
itemUpdateBlock();
}

//收集批量刷新完成的回调,后续所有操作完了之后一并处理
[completionBlocks addObjectsFromArray:batchUpdates.itemCompletionBlocks];

self.state = IGListBatchUpdateStateExecutedBatchUpdateBlock;
};

// 执行全量的数据更新并刷新 UI
void (^reloadDataFallback)(void) = ^{
//执行更新
executeUpdateBlocks();
//清除batchUpdates
[self _cleanStateAfterUpdates];
[self _performBatchUpdatesItemBlockApplied];
//加载数据
[collectionView reloadData];
[collectionView layoutIfNeeded];
executeCompletionBlocks(YES);
};

if (self.allowsBackgroundReloading && collectionView.window == nil) {
[self _beginPerformBatchUpdatesToObjects:toObjects];
reloadDataFallback();
return;
}
// disables multiple performBatchUpdates: from happening at the same time
[self _beginPerformBatchUpdatesToObjects:toObjects];

const IGListExperiment experiments = self.experiments;

IGListIndexSetResult *(^performDiff)(void) = ^{
return IGListDiffExperiment(fromObjects, toObjects, IGListDiffEquality, experiments);
};

// block executed in the first param block of -[UICollectionView performBatchUpdates:completion:]
void (^batchUpdatesBlock)(IGListIndexSetResult *result) = ^(IGListIndexSetResult *result){
//执行更新
executeUpdateBlocks();
self.applyingUpdateData = [self _flushCollectionView:collectionView
withDiffResult:result
batchUpdates:self.batchUpdates
fromObjects:fromObjects];
//重置状态
[self _cleanStateAfterUpdates];
[self _performBatchUpdatesItemBlockApplied];
};

// block used as the second param of -[UICollectionView performBatchUpdates:completion:]
void (^batchUpdatesCompletionBlock)(BOOL) = ^(BOOL finished) {
IGListBatchUpdateData *oldApplyingUpdateData = self.applyingUpdateData;
executeCompletionBlocks(finished);

[delegate listAdapterUpdater:self didPerformBatchUpdates:oldApplyingUpdateData collectionView:collectionView];

// queue another update in case something changed during batch updates. this method will bail next runloop if
// there are no changes
[self _queueUpdateWithCollectionViewBlock:collectionViewBlock];
};

// block that executes the batch update and exception handling
void (^performUpdate)(IGListIndexSetResult *) = ^(IGListIndexSetResult *result){
[collectionView layoutIfNeeded];

@try {
// 对外通知即将进行 batch update
[delegate listAdapterUpdater:self
willPerformBatchUpdatesWithCollectionView:collectionView
fromObjects:fromObjects
toObjects:toObjects
listIndexSetResult:result];
if (collectionView.dataSource == nil) {
// 如果数据源为空则不再刷新的 UICollectionview
batchUpdatesCompletionBlock(NO);
} else if (result.changeCount > 100 && IGListExperimentEnabled(experiments, IGListExperimentReloadDataFallback)) {
// 如果变化数量超过100,进行全量刷新
reloadDataFallback();
} else if (animated) {
//执行更新的批量动画
[collectionView performBatchUpdates:^{
batchUpdatesBlock(result);
} completion:batchUpdatesCompletionBlock];
} else {
[CATransaction begin];
[CATransaction setDisableActions:YES];
[collectionView performBatchUpdates:^{
batchUpdatesBlock(result);
} completion:^(BOOL finished) {
[CATransaction commit];
batchUpdatesCompletionBlock(finished);
}];
}
} @catch (NSException *exception) {
[delegate listAdapterUpdater:self
collectionView:collectionView
willCrashWithException:exception
fromObjects:fromObjects
toObjects:toObjects
diffResult:result
updates:(id)self.applyingUpdateData];
@throw exception;
}
};

if (IGListExperimentEnabled(experiments, IGListExperimentBackgroundDiffing)) {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
IGListIndexSetResult *result = performDiff();
dispatch_async(dispatch_get_main_queue(), ^{
performUpdate(result);
});
});
} else {
IGListIndexSetResult *result = performDiff();
performUpdate(result);
}
}

批量更新这部分细节有点多,这里归纳如下:

如果allowsBackgroundReloading为YES,也就是说允许后台加载数据的情况下如果collectionView不显示,则跳过数据diff和批量更新直接进行全量加载刷新。否则在子线程调用performDiff() 通过 IGListDiffExperiment 计算数据的变化,然后在主线程调用performUpdate进行批量更新操作:
在performUpdate开始的时候先通过代理对外通知即将进行 batch update 批量更新,紧接着判断如果 collectionView 的 dataSource 为 nil,结束更新过程,如果变化的数据个数超过100,就不进行批量更新了直接调用 reloadData 全量刷新数据;如果变化数据小于100,则调用 -[UICollectionView performBatchUpdates:completion:] 批量刷新数据,刷新过程中会调用 -_flushCollectionView:withDiffResult:batchUpdates:fromObjects: 将数据源提供的数据和 diff 结果包装成批量更新的数据类型 IGListBatchUpdateData 以便 UICollectionView 进行读取。这里最关键的代码在****_flushCollectionView****

- (IGListBatchUpdateData *)_flushCollectionView:(UICollectionView *)collectionView
withDiffResult:(IGListIndexSetResult *)diffResult
batchUpdates:(IGListBatchUpdates *)batchUpdates
fromObjects:(NSArray <id<IGListDiffable>> *)fromObjects {
//移动session
NSSet *moves = [[NSSet alloc] initWithArray:diffResult.moves];
//reload的item:合并通过diff和通过reloadItems手动reloads的session
NSMutableIndexSet *reloads = [diffResult.updates mutableCopy];
[reloads addIndexes:batchUpdates.sectionReloads];
//插入的session
NSMutableIndexSet *inserts = [diffResult.inserts mutableCopy];
//删除的session
NSMutableIndexSet *deletes = [diffResult.deletes mutableCopy];
NSMutableArray<NSIndexPath *> *itemUpdates = [NSMutableArray new];
//如果movesAsDeletesInserts = YES 那么就将moves中的转换为deletes和inserts操作
if (self.movesAsDeletesInserts) {
for (IGListMoveIndex *move in moves) {
[deletes addIndex:move.from];
[inserts addIndex:move.to];
}
// clear out all moves
moves = [NSSet new];
}

// Item reloads are not safe, if any section moves happened or there are inserts/deletes.
if (self.preferItemReloadsForSectionReloads
&& moves.count == 0 && inserts.count == 0 && deletes.count == 0 && reloads.count > 0) {
[reloads enumerateIndexesUsingBlock:^(NSUInteger sectionIndex, BOOL * _Nonnull stop) {
NSMutableIndexSet *localIndexSet = [NSMutableIndexSet indexSetWithIndex:sectionIndex];
if (sectionIndex < [collectionView numberOfSections]
&& sectionIndex < [collectionView.dataSource numberOfSectionsInCollectionView:collectionView]
&& [collectionView numberOfItemsInSection:sectionIndex] == [collectionView.dataSource collectionView:collectionView numberOfItemsInSection:sectionIndex]) {
// Perfer to do item reloads instead, if the number of items in section is unchanged.
[itemUpdates addObjectsFromArray:convertSectionReloadToItemUpdates(localIndexSet, collectionView)];
} else {
// Otherwise, fallback to convert into delete+insert section operation.
convertReloadToDeleteInsert(localIndexSet, deletes, inserts, diffResult, fromObjects);
}
}];
} else {
// 在performBatchUpdates中使用reloadSections是不安全的,所以需要将reloads转换为deletes+inserts
convertReloadToDeleteInsert(reloads, deletes, inserts, diffResult, fromObjects);
}

//插入,删除,移动 item处理
NSMutableArray<NSIndexPath *> *itemInserts = batchUpdates.itemInserts;
NSMutableArray<NSIndexPath *> *itemDeletes = batchUpdates.itemDeletes;
NSMutableArray<IGListMoveIndexPath *> *itemMoves = batchUpdates.itemMoves;

NSSet<NSIndexPath *> *uniqueDeletes = [NSSet setWithArray:itemDeletes];
NSMutableSet<NSIndexPath *> *reloadDeletePaths = [NSMutableSet new];
NSMutableSet<NSIndexPath *> *reloadInsertPaths = [NSMutableSet new];
for (IGListReloadIndexPath *reload in batchUpdates.itemReloads) {
if (![uniqueDeletes containsObject:reload.fromIndexPath]) {
[reloadDeletePaths addObject:reload.fromIndexPath];
[reloadInsertPaths addObject:reload.toIndexPath];
}
}
[itemDeletes addObjectsFromArray:[reloadDeletePaths allObjects]];
[itemInserts addObjectsFromArray:[reloadInsertPaths allObjects]];

const BOOL fixIndexPathImbalance = IGListExperimentEnabled(self.experiments, IGListExperimentFixIndexPathImbalance);
IGListBatchUpdateData *updateData = [[IGListBatchUpdateData alloc] initWithInsertSections:inserts
deleteSections:deletes
moveSections:moves
insertIndexPaths:itemInserts
deleteIndexPaths:itemDeletes
updateIndexPaths:itemUpdates
moveIndexPaths:itemMoves
fixIndexPathImbalance:fixIndexPathImbalance];
[collectionView ig_applyBatchUpdateData:updateData];
return updateData;
}

- (void)ig_applyBatchUpdateData:(IGListBatchUpdateData *)updateData {
[self deleteItemsAtIndexPaths:updateData.deleteIndexPaths];
[self insertItemsAtIndexPaths:updateData.insertIndexPaths];
[self reloadItemsAtIndexPaths:updateData.updateIndexPaths];

for (IGListMoveIndexPath *move in updateData.moveIndexPaths) {
[self moveItemAtIndexPath:move.from toIndexPath:move.to];
}

for (IGListMoveIndex *move in updateData.moveSections) {
[self moveSection:move.from toSection:move.to];
}

[self deleteSections:updateData.deleteSections];
[self insertSections:updateData.insertSections];
}


在_flushCollectionView中会对手动添加的批量操作和通过diff算法确定的更新操作统一转换为对session的insert,delete,reload的操作,最后调用UICollectionView的deleteItemsAtIndexPathsinsertItemsAtIndexPathsreloadItemsAtIndexPathsmoveItemAtIndexPathmoveSectiondeleteSectionsinsertSections。对UICollectionView进行更新。

IGListSectionController

IGListSectionController 在IGListKit中也是一个十分关键的对象,我们先来看下它拥有的属性:

@interface IGListSectionController : NSObject

/**
Session中的item数量
*/
- (NSInteger)numberOfItems;

/**
指定index位置上的item尺寸
*/
- (CGSize)sizeForItemAtIndex:(NSInteger)index;

/**
返回指定位置的Cell对象
*/
- (__kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index;

/**
从外界注入该Session所拥有的整个对象数组
*/
- (void)didUpdateToObject:(id)object;


- (void)didSelectItemAtIndex:(NSInteger)index;
- (void)didDeselectItemAtIndex:(NSInteger)index;
- (void)didHighlightItemAtIndex:(NSInteger)index;
- (void)didUnhighlightItemAtIndex:(NSInteger)index;


- (BOOL)canMoveItemAtIndex:(NSInteger)index;
- (void)moveObjectFromIndex:(NSInteger)sourceIndex toIndex:(NSInteger)destinationIndex NS_AVAILABLE_IOS(9_0);

/**
持有创建该SessionController的Adapter的ViewController
*/
@property (nonatomic, weak, nullable, readonly) UIViewController *viewController;

/**
与collectionView 交互的上下文
Use this property for accessing the collection size, dequeuing cells, reloading, inserting, deleting, etc.
*/
@property (nonatomic, weak, nullable, readonly) id <IGListCollectionContext> collectionContext;

/**
当前sessionController对应的session
*/
@property (nonatomic, assign, readonly) NSInteger section;


@property (nonatomic, assign, readonly) BOOL isFirstSection;
@property (nonatomic, assign, readonly) BOOL isLastSection;

@property (nonatomic, assign) UIEdgeInsets inset;
@property (nonatomic, assign) CGFloat minimumLineSpacing;
@property (nonatomic, assign) CGFloat minimumInteritemSpacing;

@property (nonatomic, weak, nullable) id <IGListSupplementaryViewSource> supplementaryViewSource;

/**
section controller 的显示事件代理
*/
@property (nonatomic, weak, nullable) id <IGListDisplayDelegate> displayDelegate;

/**
section controller 的working range事件代理
*/
@property (nonatomic, weak, nullable) id <IGListWorkingRangeDelegate> workingRangeDelegate;

/**
section controller 的滚动事件代理
*/
@property (nonatomic, weak, nullable) id <IGListScrollDelegate> scrollDelegate;

@property (nonatomic, weak, nullable) id<IGListTransitionDelegate> transitionDelegate;

@end

从上面上看外部主要通过didUpdateToObject将数据注入到IGListSectionController,然后通过numberOfItemssizeForItemAtIndexcellForItemAtIndexsupplementaryViewSource 分别指定item数量,尺寸,cell实例,SessionController的header,footer。以及通过collectionContext和UICollectionView进行交互,

我们最后来看下IGListCollectionContext,它也是一个比较重要的类,我们在确定sessionController的时候用得比较多。

@protocol IGListCollectionContext <NSObject>

@property (nonatomic, readonly) CGSize containerSize;
@property (nonatomic, readonly) UIEdgeInsets containerInset;
@property (nonatomic, readonly) UIEdgeInsets adjustedContainerInset;
@property (nonatomic, readonly) CGSize insetContainerSize;

- (CGSize)containerSizeForSectionController:(IGListSectionController *)sectionController;

@property (nonatomic, readonly) IGListCollectionScrollingTraits scrollingTraits;

@property (nonatomic, assign) IGListExperiment experiments;

- (NSInteger)indexForCell:(UICollectionViewCell *)cell
sectionController:(IGListSectionController *)sectionController;
- (nullable __kindof UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index
sectionController:(IGListSectionController *)sectionController;

- (NSArray<UICollectionViewCell *> *)visibleCellsForSectionController:(IGListSectionController *)sectionController;
- (NSArray<NSIndexPath *> *)visibleIndexPathsForSectionController:(IGListSectionController *) sectionController;


- (void)deselectItemAtIndex:(NSInteger)index
sectionController:(IGListSectionController *)sectionController
animated:(BOOL)animated;

- (void)selectItemAtIndex:(NSInteger)index
sectionController:(IGListSectionController *)sectionController
animated:(BOOL)animated
scrollPosition:(UICollectionViewScrollPosition)scrollPosition;


- (__kindof UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass
withReuseIdentifier:(nullable NSString *)reuseIdentifier
forSectionController:(IGListSectionController *)sectionController
atIndex:(NSInteger)index;
- (__kindof UICollectionViewCell *)dequeueReusableCellOfClass:(Class)cellClass
forSectionController:(IGListSectionController *)sectionController
atIndex:(NSInteger)index;
- (__kindof UICollectionViewCell *)dequeueReusableCellWithNibName:(NSString *)nibName
bundle:(nullable NSBundle *)bundle
forSectionController:(IGListSectionController *)sectionController
atIndex:(NSInteger)index;
- (__kindof UICollectionViewCell *)dequeueReusableCellFromStoryboardWithIdentifier:(NSString *)identifier
forSectionController:(IGListSectionController *)sectionController
atIndex:(NSInteger)index;
- (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind
forSectionController:(IGListSectionController *)sectionController
class:(Class)viewClass
atIndex:(NSInteger)index;
- (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewFromStoryboardOfKind:(NSString *)elementKind
withIdentifier:(NSString *)identifier
forSectionController:(IGListSectionController *)sectionController
atIndex:(NSInteger)index;
- (__kindof UICollectionReusableView *)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind
forSectionController:(IGListSectionController *)sectionController
nibName:(NSString *)nibName
bundle:(nullable NSBundle *)bundle
atIndex:(NSInteger)index;


- (void)invalidateLayoutForSectionController:(IGListSectionController *)sectionController
completion:(nullable void (^)(BOOL finished))completion;


- (void)performBatchAnimated:(BOOL)animated
updates:(void (^)(id<IGListBatchContext> batchContext))updates
completion:(nullable void (^)(BOOL finished))completion;

- (void)scrollToSectionController:(IGListSectionController *)sectionController
atIndex:(NSInteger)index
scrollPosition:(UICollectionViewScrollPosition)scrollPosition
animated:(BOOL)animated;

@end
相关文章推荐

开源库信息:

MJRefresh是目前用得比较多的下拉刷新,上拉加载开源库了,它支持UIScrollViewUITableViewUICollectionViewUIWebView

整个类结构图如下:

下面是在网上找的比较好的一张图,也附带给大家

在开始讲解MJRefresh源码之前大家最好对UIScrollView的各个尺寸数据有个明确的认识,下面是在网上找的一个比较好的图大家可以对照着这个来看:

MJRefreshComponent

MJRefreshComponent是所有上拉下拉控件的基类,它主要是通过KVO实现对scrollView contentOffsetcontentSize,以及scrollView 手势状态也就是: self.scrollView.panGestureRecognizer state的监听,来分别触发下面的对应方法,这些方法在不同的子类都有自己的实现,这个后面会具体展开介绍:

// scrollView ContentOffset改变的时候触发
- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change{}
// scrollView ContentSize改变的时候触发
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change{}
// scrollView 手势状态改变的时候触发
- (void)scrollViewPanStateDidChange:(NSDictionary *)change{}

MJRefreshHeader

MJRefreshHeader主要负责通过ContentOffset的变化以及是否正在滑动状态来确定上拉,下滑控件的状态。并负责上一次刷新时间的记录。

MJRefreshStateHeader

MJRefreshStateHeader 负责状态文本以及上一次更新时间文本的显示。

MJRefreshNormalHeader

MJRefreshNormalHeader 负责箭头和加载菊花的状态显示。

MJRefreshGifHeader

MJRefreshGifHeader 负责加载Gif图片的状态控制

MJRefreshFooter

MJRefreshFooter 主要负责上拉控件状态的维护

MJRefreshAutoFooter

MJRefreshAutoFooter 负责维护ContentOffset 与 state的对应关系。

MJRefreshAutoStateFooter

MJRefreshAutoStateFooter 主要负责状态文本的控制。

MJRefreshAutoNormalFooter

MJRefreshAutoNormalFooter 主要负责加载菊花的状态控制

MJRefreshAutoGifFooter

MJRefreshAutoGifFooter 主要负责Gif类型的加载动画控制

MJRefreshBackStateFooter

MJRefreshBackStateFooter 负责自动返回类型上拉控件的状态文本控制。

MJRefreshBackNormalFooter

MJRefreshBackNormalFooter负责自动返回类型上拉控件的加载菊花状态控制。

MJRefreshBackGifFooter

MJRefreshBackGifFooter 负责自动返回类型上拉控件加载Gif动画的播放控制。

MJRefreshConst

MJRefreshConst 里面放置的是整个MJRefresh的常量以及关键宏定义。

UIScrollView+MJExtension

UIScrollView+MJExtension存放的是UIScrollView尺寸位置数据的便捷方法

UIView+MJExtension
UIView+MJExtension 和 UIScrollView+MJExtension类似存放的是UIView尺寸位置数据的便捷方法

UIScrollView+MJRefresh

UIScrollView+MJRefresh 中通过关联属性方式为UIScrollView添加了MJRefreshHeader和MJRefreshFooter

NSBundle+MJRefresh

NSBundle+MJRefresh 是 MJRefresh 内的文本以及图片资源获取方法。

MJRefreshConfig
用于存放MJRefresh的配置的类,目前只存放languageCode,暂时没多大用处。

其实整个大的方向还是遵循

手动滑动ScrollView --> ScrollView ContentOffset / 滑动状态 发生变化 --> MJRefresh 状态发生变化 --> 触发对应的改变,比如上一次更新时间,状态文本,菊花状态等等。

下面就顺着这个思路来对MJRefresh源码进行解析:

源码解析

MJRefreshComponent

状态常量的定义

/** 刷新控件的状态 */
typedef NS_ENUM(NSInteger, MJRefreshState) {
/** 普通闲置状态 */
MJRefreshStateIdle = 1,
/** 松开就可以进行刷新的状态 */
MJRefreshStatePulling,
/** 正在刷新中的状态 */
MJRefreshStateRefreshing,
/** 即将刷新的状态 */
MJRefreshStateWillRefresh,
/** 所有数据加载完毕,没有更多的数据了 */
MJRefreshStateNoMoreData
};

状态改变的回调类型定义

/** 进入刷新状态的回调 */
typedef void (^MJRefreshComponentRefreshingBlock)(void);
/** 开始刷新后的回调(进入刷新状态后的回调) */
typedef void (^MJRefreshComponentBeginRefreshingCompletionBlock)(void);
/** 结束刷新后的回调 */
typedef void (^MJRefreshComponentEndRefreshingCompletionBlock)(void);

关键属性

/** 刷新状态 一般交给子类内部实现 */
@property (assign, nonatomic) MJRefreshState state;
/** 记录scrollView刚开始的inset */
@property (assign, nonatomic, readonly) UIEdgeInsets scrollViewOriginalInset;
/** 父控件 */
@property (weak, nonatomic, readonly) UIScrollView *scrollView;
/** 拉拽的百分比(交给子类重写) */
@property (assign, nonatomic) CGFloat pullingPercent;
/** 根据拖拽比例自动切换透明度 */
@property (assign, nonatomic, getter=isAutomaticallyChangeAlpha) BOOL automaticallyChangeAlpha;
/** 是否正在刷新 */
@property (assign, nonatomic, readonly, getter=isRefreshing) BOOL refreshing;

状态触发,及状态关键节点回调

状态触发:

/** 进入刷新状态 */
- (void)beginRefreshing;
- (void)beginRefreshingWithCompletionBlock:(void (^)(void))completionBlock;
/** 结束刷新状态 */
- (void)endRefreshing;
- (void)endRefreshingWithCompletionBlock:(void (^)(void))completionBlock;

状态关键节点回调:

/** 正在刷新的回调 */
@property (copy, nonatomic, nullable) MJRefreshComponentRefreshingBlock refreshingBlock;
/** 开始刷新后的回调(进入刷新状态后的回调) */
@property (copy, nonatomic, nullable) MJRefreshComponentBeginRefreshingCompletionBlock beginRefreshingCompletionBlock;
/** 带动画的结束刷新的回调 */
@property (copy, nonatomic, nullable) MJRefreshComponentEndRefreshingCompletionBlock endRefreshingAnimateCompletionBlock;
/** 结束刷新的回调 */
@property (copy, nonatomic, nullable) MJRefreshComponentEndRefreshingCompletionBlock endRefreshingCompletionBlock;

供给子类实现的方法

/** 初始化 */
- (void)prepare NS_REQUIRES_SUPER;
/** 摆放子控件frame */
- (void)placeSubviews NS_REQUIRES_SUPER;
/** 当scrollView的contentOffset发生改变的时候调用 */
- (void)scrollViewContentOffsetDidChange:(nullable NSDictionary *)change NS_REQUIRES_SUPER;
/** 当scrollView的contentSize发生改变的时候调用 */
- (void)scrollViewContentSizeDidChange:(nullable NSDictionary *)change NS_REQUIRES_SUPER;
/** 当scrollView的拖拽状态发生改变的时候调用 */
- (void)scrollViewPanStateDidChange:(nullable NSDictionary *)change NS_REQUIRES_SUPER;

我们截取官方Demo例子作为一个场景来对整个代码解析:

__unsafe_unretained UITableView *tableView = self.tableView;
tableView.mj_header= [MJRefreshNormalHeader headerWithRefreshingBlock:^{
//......
}];

当然我们会先将注意力聚焦在MJRefreshComponent。

1. MJRefreshComponent初始化

- (instancetype)initWithFrame:(CGRect)frame {

if (self = [super initWithFrame:frame]) {
// 准备工作
[self prepare];
// 默认是普通状态
self.state = MJRefreshStateIdle;
}
return self;
}
- (void)prepare {
// 基本属性
self.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.backgroundColor = [UIColor clearColor];
}

这部分的工作主要是初始状态的设置。在设置状态的时候会调用state的setter。这里面会触发布局,这也是为什么autoresizingMask一定要在prepare进行设置的原因:

- (void)setState:(MJRefreshState)state{
_state = state;
// 加入主队列的目的是等setState:方法调用完毕、设置完文字后再去布局子控件
MJRefreshDispatchAsyncOnMainQueue([self setNeedsLayout];)
}

对于MJRefreshComponent这里面,placeSubviews是空的。主要是对子类placeSubviews的触发。

- (void)layoutSubviews {
[self placeSubviews];
[super layoutSubviews];
}

所以初始化过程主要完成如下任务:

  • 调用prepare做一些准备工作
  • 设置MJRefresh的状态为Idle状态
  • 触发布局

2. MJRefreshComponent添加到父控件:

新建MJRefreshComponent子类成功后会通过setMj_header添加到UIScrollView或者它的子类上。

- (void)setMj_header:(MJRefreshHeader *)mj_header {
if (mj_header != self.mj_header) {
// 删除旧的,添加新的
[self.mj_header removeFromSuperview];
[self insertSubview:mj_header atIndex:0];
// 存储新的
objc_setAssociatedObject(self, &MJRefreshHeaderKey,
mj_header, OBJC_ASSOCIATION_RETAIN);
}
}

在调用insertSubview 的时候 MJRefreshComponent的 willMoveToSuperview:

- (void)willMoveToSuperview:(UIView *)newSuperview {
[super willMoveToSuperview:newSuperview];
// 如果不是UIScrollView,不做任何事情
if (newSuperview && ![newSuperview isKindOfClass:[UIScrollView class]]) return;
// 旧的父控件移除监听
[self removeObservers];
if (newSuperview) { // 新的父控件
// 记录UIScrollView
_scrollView = (UIScrollView *)newSuperview;
// 设置宽度
self.mj_w = _scrollView.mj_w;
// 设置位置
self.mj_x = -_scrollView.mj_insetL;
// 设置永远支持垂直弹簧效果
_scrollView.alwaysBounceVertical = YES;
// 记录UIScrollView最开始的contentInset
_scrollViewOriginalInset = _scrollView.mj_inset;

// 添加监听
[self addObservers];
}
}

willMoveToSuperview 中会先移除对旧父控件的关键事件监听,监听新的父UIScollView的关键事件,并记录下UIScrollView最开始的contentInset。

addObservers 方法中主要通过KVO方式让MJRefreshComponent监听父控件scrollView的ContentOffset,ContentSize 以及
scrollView的手势状态。

- (void)addObservers {
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.scrollView addObserver:self forKeyPath:MJRefreshKeyPathContentOffset options:options context:nil];
[self.scrollView addObserver:self forKeyPath:MJRefreshKeyPathContentSize options:options context:nil];
self.pan = self.scrollView.panGestureRecognizer;
[self.pan addObserver:self forKeyPath:MJRefreshKeyPathPanState options:options context:nil];
}

一旦监听的变量发生变化那么就会到observeValueForKeyPath中进行处理:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
// 遇到这些情况就直接返回
if (!self.userInteractionEnabled) return;

// 这个就算看不见也需要处理
if ([keyPath isEqualToString:MJRefreshKeyPathContentSize]) {
[self scrollViewContentSizeDidChange:change];
}

// 看不见
if (self.hidden) return;
if ([keyPath isEqualToString:MJRefreshKeyPathContentOffset]) {
[self scrollViewContentOffsetDidChange:change];
} else if ([keyPath isEqualToString:MJRefreshKeyPathPanState]) {
[self scrollViewPanStateDidChange:change];
}
}

- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change{}
- (void)scrollViewContentSizeDidChange:(NSDictionary *)change{}
- (void)scrollViewPanStateDidChange:(NSDictionary *)change{}

````

其中ContentOffset 以及scrollView的手势状态 只有在可见的时候才会触发子类对应的方法。

****3. 状态的管理:****

MJRefreshComponent 中的setState主要是提供给子类设置的,但是在MJRefreshComponent中的beginRefreshing以及endRefreshing也会触发MJRefresh的状态,beginRefreshing会将状态设置为MJRefreshStateRefreshing,endRefreshing中会将状态设置为MJRefreshStateIdle。

  • (void)beginRefreshing {
    [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
    self.alpha = 1.0;
    }];
    self.pullingPercent = 1.0;
    // 只要正在刷新,就完全显示
    if (self.window) {
    self.state = MJRefreshStateRefreshing;
    } else {
    //…..
    }
    }

  • (void)endRefreshing {
    MJRefreshDispatchAsyncOnMainQueue(self.state = MJRefreshStateIdle;)
    }


    ****4. 下拉进度的管理:****

    和setState一样下拉进度也是受contentOffset影响的,所以下拉进度的设置也是提供给子类调用的。下拉进度的改变会导致下拉控件透明度的同步变化。

  • (void)setPullingPercent:(CGFloat)pullingPercent {
    _pullingPercent = pullingPercent;
    if (self.isRefreshing) return;
    if (self.isAutomaticallyChangeAlpha) {
    self.alpha = pullingPercent;
    }
    }


    ****MJRefreshStateHeader****

    MJRefreshComponent像是一个没有灵魂的父类,它只负责监听scrollView的contentOffset,contentSize,以及触摸状态的变化,将这些变化传递给子类。

  • (void)scrollViewContentOffsetDidChange:(NSDictionary *)change {

    [super scrollViewContentOffsetDidChange:change];
    // 在刷新的refreshing状态
    if (self.state == MJRefreshStateRefreshing) {
    [self resetInset];
    return;
    }
    // 跳转到下一个控制器时,contentInset可能会变, 这时候将最初的contentInset保存在_scrollViewOriginalInset
    _scrollViewOriginalInset = self.scrollView.mj_inset;

    // 当前的contentOffset
    CGFloat offsetY = self.scrollView.mj_offsetY;
    // 头部控件刚好出现的offsetY,这里由于UIScrollView默认是贴着可见的边缘,也就是导航栏的底部。所以可以用- self.scrollViewOriginalInset.top来表示。
    CGFloat happenOffsetY = - self.scrollViewOriginalInset.top;

    // 如果是向上滚动到看不见头部控件,直接返回
    // >= -> >
    if (offsetY > happenOffsetY) return;

    // 普通 和 即将刷新 的临界点,normal2pullingOffsetY 表示MJRefreshHeader完全露出来的位置
    CGFloat normal2pullingOffsetY = happenOffsetY - self.mj_h;
    // 这个是MJRefreshHeader露出来的百分比
    CGFloat pullingPercent = (happenOffsetY - offsetY) / self.mj_h;

    if (self.scrollView.isDragging) { // 如果正在拖拽
    self.pullingPercent = pullingPercent;
    // 如果当前是空闲状态,并且偏移量超过了完全露出来的距离,但是由于当前正在处于拖拽状态则状态还是MJRefreshStatePulling
    if (self.state == MJRefreshStateIdle && offsetY < normal2pullingOffsetY) {
    // 转为即将刷新状态
    self.state = MJRefreshStatePulling;
    //如果当前处于MJRefreshStatePulling状态,偏移量小于完全露出来的距离,那么状态改为MJRefreshStateIdle
    } else if (self.state == MJRefreshStatePulling && offsetY >= normal2pullingOffsetY) {
    // 转为普通状态
    self.state = MJRefreshStateIdle;
    }
    //如果松收那么就切换到MJRefreshStateRefreshing状态
    } else if (self.state == MJRefreshStatePulling) {// 即将刷新 && 手松开
    // 开始刷新
    [self beginRefreshing];
    } else if (pullingPercent < 1) {
    self.pullingPercent = pullingPercent;
    }

}


scrollViewContentOffsetDidChange 主要负责设置MJRefreshHeader的state。影响到state的有两方面原因:
  1. 是否处于拖拽状态
  2. 当前UIScrollView的offset与下拉头完全露出来的位置的关系

    如果当前是空闲状态,并且偏移量超过了下拉头部完全露出来的距离,但是由于当前正在处于拖拽状态则状态还是MJRefreshStatePulling,在处于拖拽状态下,如果当前处于MJRefreshStatePulling状态,偏移量小于完全露出来的距离,那么状态改为MJRefreshStateIdle。如果松手那么就切换到MJRefreshStateRefreshing状态。并触发beginRefreshing调用上层业务进行刷新。

    下面我们来看下状态的设置:

  • (void)setState:(MJRefreshState)state {
    MJRefreshCheckState
    // 根据状态做事情
    if (state == MJRefreshStateIdle) {
    if (oldState != MJRefreshStateRefreshing) return;

    // 保存刷新时间
    [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:self.lastUpdatedTimeKey];
    [[NSUserDefaults standardUserDefaults] synchronize];

    // 恢复inset和offset
    [UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{
    self.scrollView.mj_insetT += self.insetTDelta;
    if (self.endRefreshingAnimateCompletionBlock) {
    self.endRefreshingAnimateCompletionBlock();
    }
    // 自动调整透明度
    if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0;
    } completion:^(BOOL finished) {
    self.pullingPercent = 0.0;
    if (self.endRefreshingCompletionBlock) {
    self.endRefreshingCompletionBlock();
    }
    }];
    } else if (state == MJRefreshStateRefreshing) {
    MJRefreshDispatchAsyncOnMainQueue({
    [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
    if (self.scrollView.panGestureRecognizer.state != UIGestureRecognizerStateCancelled) {
    CGFloat top = self.scrollViewOriginalInset.top + self.mj_h;
    // 增加滚动区域top
    self.scrollView.mj_insetT = top;
    // 设置滚动位置
    CGPoint offset = self.scrollView.contentOffset;
    offset.y = -top;
    [self.scrollView setContentOffset:offset animated:NO];
    }
    } completion:^(BOOL finished) {
    [self executeRefreshingCallback];
    }];
    })
    }

}


如果上一次为MJRefreshStateRefreshing,当前状态为MJRefreshStateIdle,表示一次刷新结束。这时候会将当前时间记录为上一次刷新时间记录在本地。并且以动画的形式将header隐藏,并在动画结束以及完全隐藏的时候调用****endRefreshingAnimateCompletionBlock********endRefreshingCompletionBlock****,如果状态要设置为MJRefreshStateRefreshing,那么需要通过setContentOffset将头部显示出来。并在完成后调用executeRefreshingCallback。

也就是说在进入refreshing状态的时候会调用refreshingBlock以及beginRefreshingCompletionBlock,在回到idle状态的时候会调用endRefreshingAnimateCompletionBlock以及endRefreshingCompletionBlock。

****MJRefreshAutoFooter****

我们看到MJRefreshFooter有两类MJRefreshAutoFooter以及MJRefreshBackFooter,MJRefreshAutoFooter的特点是在内容超过一个屏幕的时候,不松手的情况下只要footer露出来了就会自动启动刷新,这会持续autoTriggerTimes次,当然我们可以设置autoTriggerTimes为负数来支持无限次自动加载。在松手的情况下则会在指定的条件下触发刷新。
MJRefreshBackFooter的footer只会出现在UIScrollView的底部,并且只有在松手的情况下才会启动刷新。

我们先来看下MJRefreshAutoFooter,在新建MJRefreshAutoFooter的时候会先调用prepare方法:

  • (void)prepare {
    [super prepare];
    // 默认底部控件100%出现时才会自动刷新
    self.triggerAutomaticallyRefreshPercent = 1.0;
    // 设置为默认状态
    self.automaticallyRefresh = YES;
    // 自动刷新的次数
    self.autoTriggerTimes = 1;
    }


    在prepare方法中设置了触发自动刷新的百分比,以及将触发状态设置为自动刷新状态,autoTriggerTimes被设置为1次。autoTriggerTimes是上拉不松手的情况下触发自动刷新的次数超过这个次数,上拉只有松手的情况下才会触发刷新,如果要自动刷新次数不受限那么将autoTriggerTimes设置为-1即可。

    创建好后在我们将MJRefreshAutoFooter添加到UIScrollView上面的时候会调用willMoveToSuperview,这时候ContentInset Bottom会相应地增加mj_h,如果newSuperview = nil 表示从父控件中移除,ContentInset Bottom会减少mj_h。

  • (void)willMoveToSuperview:(UIView *)newSuperview {
    [super willMoveToSuperview:newSuperview];
    if (newSuperview) { // 新的父控件
    if (self.hidden == NO) {
    self.scrollView.mj_insetB += self.mj_h;
    }
    // 设置位置
    self.mj_y = _scrollView.mj_contentH;
    } else { // 被移除了
    if (self.hidden == NO) {
    self.scrollView.mj_insetB -= self.mj_h;
    }
    }
    }


    mj_h是在MJRefreshFooter prepare方法中设置的。

    一旦UIScrollView完成数据的加载就会触发ContentSize发生变化,scrollViewContentSizeDidChange会被调用,这时候MJRefreshAutoFooter的位置就会重新被设置,它会被追加到UIScrollView 内容的最底部,注意不是UIScrollView的最底部。

  • (void)scrollViewContentSizeDidChange:(NSDictionary *)change {
    [super scrollViewContentSizeDidChange:change];
    // 设置位置
    self.mj_y = self.scrollView.mj_contentH + self.ignoredScrollViewContentInsetBottom;
    }


    在内容超过一个屏幕后,如果我们向上拉页面,拉到footer显示出triggerAutomaticallyRefreshPercent百分比后即使我们不松手的情况下也会自动触发beginRefreshing进行刷新。

  • (void)scrollViewContentOffsetDidChange:(NSDictionary *)change {
    [super scrollViewContentOffsetDidChange:change];

    if (self.state != MJRefreshStateIdle || !self.automaticallyRefresh || self.mj_y == 0) return;

    if (_scrollView.mj_insetT + _scrollView.mj_contentH > _scrollView.mj_h) { // 内容超过一个屏幕
    // 这里的_scrollView.mj_contentH替换掉self.mj_y更为合理
    if (_scrollView.mj_offsetY >= _scrollView.mj_contentH - _scrollView.mj_h + self.mj_h * self.triggerAutomaticallyRefreshPercent + _scrollView.mj_insetB - self.mj_h) {
    // 防止手松开时连续调用
    CGPoint old = [change[@”old”] CGPointValue];
    CGPoint new = [change[@”new”] CGPointValue];
    if (new.y <= old.y) return;

    if (_scrollView.isDragging) {
    self.triggerByDrag = YES;
    }
    // 当底部刷新控件完全出现时,才刷新
    [self beginRefreshing];
    }
    }

}


如果我们手松开的时候,如果不够一个屏幕,那么在拉到offsetY超过顶部insetTop的时候开始刷新,如果超出一个屏幕,也会在拉到见到footer的时候开始刷新。

  • (void)scrollViewPanStateDidChange:(NSDictionary *)change {
    [super scrollViewPanStateDidChange:change];
    if (self.state != MJRefreshStateIdle) return;
    UIGestureRecognizerState panState = _scrollView.panGestureRecognizer.state;
    switch (panState) {
    // 手松开
    case UIGestureRecognizerStateEnded: {
    if (_scrollView.mj_insetT + _scrollView.mj_contentH <= _scrollView.mj_h) { // 不够一个屏幕
    if (_scrollView.mj_offsetY >= - _scrollView.mj_insetT) { // 向上拽
    self.triggerByDrag = YES;
    [self beginRefreshing];
    }
    } else { // 超出一个屏幕
    if (_scrollView.mj_offsetY >= _scrollView.mj_contentH + _scrollView.mj_insetB - _scrollView.mj_h) {
    self.triggerByDrag = YES;
    [self beginRefreshing];
    }
    }
    } break;
    case UIGestureRecognizerStateBegan: {
    [self resetTriggerTimes];
    } break;
    default: break;
    }
    }


    状态设置:

  • (void)setState:(MJRefreshState)state {
    MJRefreshCheckState

    if (state == MJRefreshStateRefreshing) {
    [self executeRefreshingCallback];
    } else if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) {
    if (self.triggerByDrag) {
    if (!self.unlimitedTrigger) {
    self.leftTriggerTimes -= 1;
    }
    self.triggerByDrag = NO;
    }
    if (MJRefreshStateRefreshing == oldState) {
    if (self.scrollView.pagingEnabled) {
    CGPoint offset = self.scrollView.contentOffset;
    offset.y -= self.scrollView.mj_insetB;
    [UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{
    self.scrollView.contentOffset = offset;
    if (self.endRefreshingAnimateCompletionBlock) {
    self.endRefreshingAnimateCompletionBlock();
    }
    } completion:^(BOOL finished) {
    if (self.endRefreshingCompletionBlock) {
    self.endRefreshingCompletionBlock();
    }
    }];
    return;
    }
    if (self.endRefreshingCompletionBlock) {
    self.endRefreshingCompletionBlock();
    }
    }
    }

}


****MJRefreshBackFooter****

MJRefreshBackFooter 和 MJRefreshAutoFooter不同的是它会永远出现在UIScrollView的最底部,为啥?我们看下代码:

  • (void)scrollViewContentSizeDidChange:(NSDictionary *)change {
    [super scrollViewContentSizeDidChange:change];
    // 内容的高度
    CGFloat contentHeight = self.scrollView.mj_contentH + self.ignoredScrollViewContentInsetBottom;
    // 表格的高度
    CGFloat scrollHeight = self.scrollView.mj_h - self.scrollViewOriginalInset.top - self.scrollViewOriginalInset.bottom + self.ignoredScrollViewContentInsetBottom;
    // 设置位置和尺寸
    self.mj_y = MAX(contentHeight, scrollHeight);
    }


    在内容高度大于UIScrollView的时候,它会追加在内容最后面,这时候MJRefreshBackFooter也是在UIScrollView的外面,在滑到UIScrollView的时候开始加载。如果UIScrollView的高度大于内容高度,那么它会被加在UIScrollView最底部。

    我们接下来看下MJRefreshBackFooter的状态变化:

  • (void)scrollViewContentOffsetDidChange:(NSDictionary *)change {
    [super scrollViewContentOffsetDidChange:change];

    // 如果正在刷新,直接返回
    if (self.state == MJRefreshStateRefreshing) return;

    _scrollViewOriginalInset = self.scrollView.mj_inset;

    // 当前的contentOffset
    CGFloat currentOffsetY = self.scrollView.mj_offsetY;
    // 尾部控件刚好出现的offsetY
    CGFloat happenOffsetY = [self happenOffsetY];
    // 如果是向下滚动到看不见尾部控件,直接返回
    if (currentOffsetY <= happenOffsetY) return;

    CGFloat pullingPercent = (currentOffsetY - happenOffsetY) / self.mj_h;

    // 如果已全部加载,仅设置pullingPercent,然后返回
    if (self.state == MJRefreshStateNoMoreData) {
    self.pullingPercent = pullingPercent;
    return;
    }

    if (self.scrollView.isDragging) {
    self.pullingPercent = pullingPercent;
    // 普通 和 即将刷新 的临界点
    CGFloat normal2pullingOffsetY = happenOffsetY + self.mj_h;

    if (self.state == MJRefreshStateIdle && currentOffsetY > normal2pullingOffsetY) {
    // 转为即将刷新状态
    self.state = MJRefreshStatePulling;
    } else if (self.state == MJRefreshStatePulling && currentOffsetY <= normal2pullingOffsetY) {
    // 转为普通状态
    self.state = MJRefreshStateIdle;
    }
    } else if (self.state == MJRefreshStatePulling) {// 即将刷新 && 手松开
    // 开始刷新
    [self beginRefreshing];
    } else if (pullingPercent < 1) {
    self.pullingPercent = pullingPercent;
    }

}


这里有两个比较关键的offset:

****happenOffsetY****: 它是MJRefreshBackFooter刚好露出来时候UIScrollView的ContentOffset.
****normal2pullingOffsetY****: 它是MJRefreshBackFooter完全露出来时候UIScrollView的ContentOffset.

所以 currentOffsetY但凡大于happenOffsetY就表示MJRefreshBackFooter已经露出来了,这时候,如果scrollView处于拖拽状态,并且currentOffsetY > normal2pullingOffsetY 表示整个MJRefreshBackFooter已经完全露出来了,这时候会切换到MJRefreshStatePulling状态,如果currentOffsetY <= normal2pullingOffsetY那么当前状态为MJRefreshStateIdle。如果处于MJRefreshStatePulling状态,也就是MJRefreshBackFooter已经完全露出来的情况下,松手的话就会触发刷新。

下面是状态改变的过程:

  • (void)setState:(MJRefreshState)state {
    MJRefreshCheckState
    // 根据状态来设置属性
    if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) {
    // 刷新完毕
    if (MJRefreshStateRefreshing == oldState) {
    [UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{
    self.scrollView.mj_insetB -= self.lastBottomDelta;
    if (self.endRefreshingAnimateCompletionBlock) {
    self.endRefreshingAnimateCompletionBlock();
    }
    // 自动调整透明度
    if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0;
    } completion:^(BOOL finished) {
    self.pullingPercent = 0.0;
    if (self.endRefreshingCompletionBlock) {
    self.endRefreshingCompletionBlock();
    }
    }];
    }
    CGFloat deltaH = [self heightForContentBreakView];
    // 刚刷新完毕
    if (MJRefreshStateRefreshing == oldState && deltaH > 0 && self.scrollView.mj_totalDataCount != self.lastRefreshCount) {
    self.scrollView.mj_offsetY = self.scrollView.mj_offsetY;
    }
    } else if (state == MJRefreshStateRefreshing) {
    // 记录刷新前的数量
    self.lastRefreshCount = self.scrollView.mj_totalDataCount;
    [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{
    CGFloat bottom = self.mj_h + self.scrollViewOriginalInset.bottom;
    CGFloat deltaH = [self heightForContentBreakView];
    if (deltaH < 0) { // 如果内容高度小于view的高度
    bottom -= deltaH;
    }
    self.lastBottomDelta = bottom - self.scrollView.mj_insetB;
    self.scrollView.mj_insetB = bottom;
    self.scrollView.mj_offsetY = [self happenOffsetY] + self.mj_h;
    } completion:^(BOOL finished) {
    [self executeRefreshingCallback];
    }];
    }
    }
    ```

MJRefresh最核心的部分代码已经介绍完毕了,后续的一些,比如包含State的Header以及Footer 都是用于处理与状态文本相关的类,包含Normal的Header以及Footer都是用于处理箭头和菊花状态的类,而包含Gif的Header以及Footer都是用于处理Gif动画的类,这些类都是基于state, pullingPercent 这些关键属性的变化而改变的,所以理解上面介绍的如何根据Content Offset 变化 确定state以及pullingPercent是理解MJRefresh的核心。

源码信息

Mars 是微信官方的终端基础组件,是一个使用 C++ 编写的业务性无关,平台性无关的基础组件,目前已经开源,下面是官方的源码以及文档地址:

Mars 概览

整个项目主要包含如下几个模块:

COMM:基础库,包括socket、线程、消息队列、协程等基础工具;
XLOG:通用日志模块,充分考虑移动终端的特点,提供高性能、高可用、安全性、容错性的日志功能;
SDT:网络诊断模块;
STN:是微信的信令传输网络模块,它是基于Socket层的网络解决方案(它并不支持完整的 HTTP 协议),负责终端与服务器的小数据信令通道。是微信日常中使用最频繁的网络通道,STN中包含了很多其他方面的实用设计:包括自定义DNS、容灾设计、负载考量、APP的前后台考量、休眠机制考量、省电机制等等。网络通道上,目前STN提供了长连、短连两种类型的通道,用于满足不同的需求。使用STN后,应用开发者只需关注业务开发。
CDN: 数据分发网络,负责大数据传输,这部分涉及具体的业务,所以未开源

下面是几种网络开源库的对比:

Mars Sample 代码分析

1. 让Sample项目跑起来

微信官方有专门的接入文档:
Mars iOS/OS X 接入指南 一种是以framework形式引入的,一般实际开发项目中会以这种方式接入,另一种是调试模式,初期分析Mars Sample代码的时候需要以这种方式引入,这里需要注意的是编译成功后需要将mars.framework拷贝到项目文件夹下再添加到项目中。不然会提示找不到某些头文件。

这里还需要注意的是如果要以调试模式进行接入,运行编译脚本的时候需要选择3,否则找不到mars.xcodeproj

Enter menu:
1. Clean && build mars.
2. Clean && build xlog.
3. Gen iOS mars Project.
4. Exit

下面就以调试模式来开始我们mars源码的分析:

2. Sample代码分析

Mars Sample 业务层核心部分主要由NetworkStatusNetworkServiceNetworkEvent 三大部分构成,在介绍Mars Sample 业务层代码之前我们先过下这部分功能。

NetworkStatus

这个类用于监听网络状态的,具体的思想和AFNetWorking 里面的AFNetworkReachabilityManager思路是一致的,只不过代码有点…..所以不贴代码了。大家可以看下之前介绍AFNetWorking的源码分析博客。

只要调用了Start方法之后在网络状态改变后都会调用它的ChangeReach方法。

这里我们在Appdelegate类中将NetworkService作为Start参数,也就是说在网络状态改变的时候会调用NetworkService的ChangeReach方法。

NetworkService

是整个业务层比较重要的一个类,它和底层mars关系最为密切。它主要有如下功能:

1. Mars 的上层调用
2. 网络状态NetworkStatus的监听器,并将这个网络状态传递给Mars底层的mars::baseevent
3. 将Mars底层的callback通知到****NetworkEvent****

NetworkEvent

NetworkEvent 管理着taskscontrollerspushrecvers

@interface NetworkEvent : NSObject<NetworkDelegate> {
NSMutableDictionary *tasks;
NSMutableDictionary* controllers;
NSMutableDictionary* pushrecvers;
}

在有数据需要分发下去的时候可以通过它来分发,一般事件的起源都是从Mars底层callback产生的,然后经过NetworkService传递给NetworkEventNetworkEvent负责分发给对应的对象。下面是这三者之间的关系,整体如下图所示:

NetworkService 负责设置 Mars,由于在NetworkService setCallBack中设置了对应的回调所以Mars一旦有回调就传给NetworkServiceNetworkService 将 Mars上传的事件传递给NetworkEventNetworkEvent再将事件传递给对应的controllers,pushrecvers。

接下来我们会以如下几个部分对业务层代码进行解析:

2.1 Mars 网络层初始化

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

// 设置NetworkEvent为NetworkService的代理以便将NetworkService感知的事件传递给NetworkEvent进行分发
[NetworkService sharedInstance].delegate = [[NetworkEvent alloc] init];
// 监听Mars的底层回调,在Mars有关键事件发生的时候可以通知到NetworkService
[[NetworkService sharedInstance] setCallBack];
// 创建Mars
[[NetworkService sharedInstance] createMars];
// 设置客户端版本
[[NetworkService sharedInstance] setClientVersion:200];
// 添加长链接地址和端口
[[NetworkService sharedInstance] setLongLinkAddress:@"localhost" port:8081];
// 添加短链接监听端口
[[NetworkService sharedInstance] setShortLinkPort:8080];
// 上报当前应用处于前台状态
[[NetworkService sharedInstance] reportEvent_OnForeground:YES];
// 确保长链接已经连接上
[[NetworkService sharedInstance] makesureLongLinkConnect];
// 启动网络状态监听
[[NetworkStatus sharedInstance] Start:[NetworkService sharedInstance]];

return YES;
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
//上报当前应用处于后台状态
[[NetworkService sharedInstance] reportEvent_OnForeground:NO];
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
//上报当前应用处于前端状态
[[NetworkService sharedInstance] reportEvent_OnForeground:YES];
}

- (void)applicationWillTerminate:(UIApplication *)application {
// 销毁Mars
[[NetworkService sharedInstance] destroyMars];
appender_close();
}

这部分我们先埋个坑,在介绍Mars底层代码的时候我们再展开介绍。

2.2 通过Mars 拉取数据

- (void)loadView {
[super loadView];

//......
converSations = [[NSArray alloc] init];
CGITask *convlstCGI = [[CGITask alloc] initAll:ChannelType_ShortConn AndCmdId:kConvLst AndCGIUri:@"/mars/getconvlist" AndHost:@"localhost"];
[[NetworkService sharedInstance] startTask:convlstCGI ForUI:self];
}

- (NSData*)requestSendData {
ConversationListRequest *convlstRequest = [ConversationListRequest new];
convlstRequest.type = 0;
convlstRequest.accessToken = @"123456";
NSData *data = [convlstRequest data];
return data;
}

- (int)onPostDecode:(NSData*)responseData {
convlstResponse = [ConversationListResponse parseFromData:responseData error:nil];
self->converSations = convlstResponse.listArray;
LOG_INFO(kModuleViewController, @"recv conversation list, size: %lu", (unsigned long)[self->converSations count]);
return [self->converSations count] > 0 ? 0 : -1;
}

- (int)onTaskEnd:(uint32_t)tid errType:(uint32_t)errtype errCode:(uint32_t)errcode {
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
return 0;
}

2.2 通过Mars 监听下发的数据

- (void)viewDidLoad {
[super viewDidLoad];
self.title = _conversation.notice;
//.......
_messages = [NSMutableArray new];
[[NetworkService sharedInstance] addPushObserver:self withCmdId:kPushMessageCmdId];
}

-(NSData*)requestSendData {
SendMessageRequest *sendMsgRequest = [SendMessageRequest new];
sendMsgRequest.from = [self username];
sendMsgRequest.to = @"all";
sendMsgRequest.text = _textField.text;
sendMsgRequest.accessToken = @"123456";
sendMsgRequest.topic = _conversation.topic;
LOG_INFO(kModuleViewController, @"send msg to topic:%@", _conversation.notice);
NSData* data = [sendMsgRequest data];
dispatch_async(dispatch_get_main_queue(), ^{
_textField.text = @"";
});
return data;
}

-(int)onPostDecode:(NSData*)responseData {
SendMessageResponse *sendMsgResponse = [SendMessageResponse parseFromData:responseData error:nil];
dispatch_async(dispatch_get_main_queue(), ^{
NSString *recvtext = [NSString stringWithFormat:@"%@ : %@", sendMsgResponse.from, sendMsgResponse.text];
[self.messages addObject:recvtext];
[self.tableView reloadData];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.messages.count-1 inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
});
return sendMsgResponse.errCode == 0 ? 0 : -1;
}

- (void)sendMessage {
CGITask *sendMsgCGI = [[CGITask alloc] initAll:ChannelType_LongConn AndCmdId:kSendMsg AndCGIUri:@"/mars/sendmessage" AndHost:@"localhost"];
[[NetworkService sharedInstance] startTask:sendMsgCGI ForUI:self];
}

- (void)notifyPushMessage:(NSData*)pushData withCmdId:(int)cmdId {
MessagePush* messagePush = [MessagePush parseFromData:pushData error:nil];
if (messagePush != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *recvtext = [NSString stringWithFormat:@"%@ : %@", messagePush.from, messagePush.content];
[self.messages addObject:recvtext];
[self.tableView reloadData];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.messages.count-1 inSection:0];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
});
}
}

- (int)onTaskEnd:(uint32_t)tid errType:(uint32_t)errtype errCode:(uint32_t)errcode {
return 0;
}

在Sameple 例子中每个请求都被定义成一个CGITask,这里面包含了任务id,长链接,短链接通道的选择,用于表示标示某个请求的id,url 以及host地址。

@interface CGITask : NSObject

@property(nonatomic) uint32_t taskid;
@property(nonatomic) ChannelType channel_select;
@property(nonatomic) uint32_t cmdid;
@property(nonatomic, copy) NSString *cgi;
@property(nonatomic, copy) NSString *host;

@end

紧接着调用startTask启动任务:

[[NetworkService sharedInstance] startTask:convlstCGI ForUI:self];
- (int)startTask:(CGITask *)task ForUI:(id<UINotifyDelegate>)delegateUI {
Task ctask;
ctask.cmdid = task.cmdid;
ctask.channel_select = task.channel_select;
ctask.cgi = std::string(task.cgi.UTF8String);
ctask.shortlink_host_list.push_back(std::string(task.host.UTF8String));
ctask.user_context = (__bridge void*)task;

mars::stn::StartTask(ctask);

NSString *taskIdKey = [NSString stringWithFormat:@"%d", ctask.taskid];
[_delegate addObserver:delegateUI forKey:taskIdKey];
[_delegate addCGITasks:task forKey:taskIdKey];
return ctask.taskid;
}

在startTask方法中会将CGITask转换为Task,并调用mars::stn::StartTask 然后将当前当前对象,以及task添加到NetworkEvent中,这样一旦有事件就会通知到它们。我们看下NetworkEvent 中与controllers有关的方法,这些方法都是task请求触发的。

- (NSData*)Request2BufferWithTaskID:(uint32_t)tid task:(CGITask *)task {
NSData* data = NULL;
NSString *taskIdKey = [NSString stringWithFormat:@"%d", tid];
id<UINotifyDelegate> uiObserver = [controllers objectForKey:taskIdKey];
if (uiObserver != nil) {
data = [uiObserver requestSendData];
}
return data;
}

- (NSInteger)Buffer2ResponseWithTaskID:(uint32_t)tid responseData:(NSData *)data task:(CGITask *)task {
int returnType = 0;
NSString *taskIdKey = [NSString stringWithFormat:@"%d", tid];
id<UINotifyDelegate> uiObserver = [controllers objectForKey:taskIdKey];
if (uiObserver != nil) {
returnType = [uiObserver onPostDecode:data];
}
else {
returnType = -1;
}
return returnType;
}

- (NSInteger)OnTaskEndWithTaskID:(uint32_t)tid task:(CGITask *)task errType:(uint32_t)errtype errCode:(uint32_t)errcode {
NSString *taskIdKey = [NSString stringWithFormat:@"%d", tid];
[tasks removeObjectForKey:taskIdKey];
id<UINotifyDelegate> uiObserver = [controllers objectForKey:taskIdKey];
[uiObserver onTaskEnd:tid errType:errtype errCode:errcode];
[controllers removeObjectForKey:taskIdKey];
return 0;
}

也就是说在Request2BufferWithTaskIDBuffer2ResponseWithTaskIDOnTaskEndWithTaskID 会分别触发 发起 Task类的requestSendDataonPostDecodeOnTaskEndWithTaskID方法。

而这三者在会在stn_callback.mm中的StnCallBack::Req2BufStnCallBack::Buf2RespStnCallBack::OnTaskEnd 调用NetworkService的Request2BufferWithTaskIDBuffer2ResponseWithTaskIDOnTaskEndWithTaskID方法。

bool StnCallBack::Req2Buf(uint32_t _taskid, void* const _user_context, AutoBuffer& _outbuffer, AutoBuffer& _extend, int& _error_code, const int _channel_select) {
NSData* requestData = [[NetworkService sharedInstance] Request2BufferWithTaskID:_taskid userContext:_user_context];
if (requestData == nil) {
requestData = [[NSData alloc] init];
}
_outbuffer.AllocWrite(requestData.length);
_outbuffer.Write(requestData.bytes,requestData.length);
return requestData.length > 0;
}

int StnCallBack::Buf2Resp(uint32_t _taskid, void* const _user_context, const AutoBuffer& _inbuffer, const AutoBuffer& _extend, int& _error_code, const int _channel_select) {
int handle_type = mars::stn::kTaskFailHandleNormal;
NSData* responseData = [NSData dataWithBytes:(const void *) _inbuffer.Ptr() length:_inbuffer.Length()];
NSInteger errorCode = [[NetworkService sharedInstance] Buffer2ResponseWithTaskID:_taskid ResponseData:responseData userContext:_user_context];
if (errorCode != 0) {
handle_type = mars::stn::kTaskFailHandleDefault;
}
return handle_type;
}

int StnCallBack::OnTaskEnd(uint32_t _taskid, void* const _user_context, int _error_type, int _error_code) {
return (int)[[NetworkService sharedInstance] OnTaskEndWithTaskID:_taskid userContext:_user_context errType:_error_type errCode:_error_code];
}

至于stn_callback什么时候被调用这里先不做过多的介绍,后续介绍底层的时候再来揭开这个答案。

现在我们看下通过上层拉取一个接口是怎样的一个过程:

1. 构建一个CGITask,传入使用哪种渠道:长链接?短链接?地址,域名,命令id
2. 调用mars::stn::StartTask
3. 将当前类添加到NetWorkEvent的controller列表,
4. 调用mars::stn::StartTask后,Mars 对应的事件会从底层传给NetWorkService,再通过NetWorkEvent分发给对应的controller,我们可以在requestSendData,onPostDecode,OnTaskEndWithTaskID做响应的处理。

看完拉取请求后,其实接收推送的过程也差不多类似:

stn_callback.mm

void StnCallBack::OnPush(uint64_t _channel_id, uint32_t _cmdid, uint32_t _taskid, const AutoBuffer& _body, const AutoBuffer& _extend) {
if (_body.Length() > 0) {
NSData* recvData = [NSData dataWithBytes:(const void *) _body.Ptr() length:_body.Length()];
[[NetworkService sharedInstance] OnPushWithCmd:_cmdid data:recvData];
}

}

NetworkService.m

- (void)OnPushWithCmd:(NSInteger)cid data:(NSData *)data {
return [_delegate OnPushWithCmd:cid data:data];
}

NetworkEvent.m

- (void)OnPushWithCmd:(NSInteger)cid data:(NSData *)data {
id<PushNotifyDelegate> pushObserver = [pushrecvers objectForKey:[NSString stringWithFormat:@"%d", cid]];
if (pushObserver != nil) {
[pushObserver notifyPushMessage:data withCmdId:cid];
}
}

注册监听下发对象

[[NetworkService sharedInstance] addPushObserver:self withCmdId:kPushMessageCmdId];

3. Mars 底层源码解析

3.1 回调设置

- (void)setCallBack {
mars::stn::SetCallback(mars::stn::StnCallBack::Instance());
mars::app::SetCallback(mars::app::AppCallBack::Instance());
}

在PublicComponentV2 文件夹下面有 app_callback 以及 stn_callback两组文件:

stn_callback这个文件是STN模块的回调接口,app_callback是应用相关的回调接口,Mars 通过这些回调接口从上层获取对应的定制化服务,从而将部分可定制的内容放在应用层来做,将这部分业务相关的从底层抽取出来。我们来看下这部分接口,至于具体的时候在介绍到对应的逻辑的时候再介绍,这里主要是了解,回调有哪些接口,这些接口在底层怎么使用:

StnCallBack 接口定义如下:

class StnCallBack : public Callback {

private:
//.....
public:
//......
//流量统计
virtual void TrafficData(ssize_t _send, ssize_t _recv);
//底层询问上层该host对应的ip列表
virtual std::vector<std::string> OnNewDns(const std::string& _host);
//网络层收到push消息回调
virtual void OnPush(uint64_t _channel_id, uint32_t _cmdid, uint32_t _taskid, const AutoBuffer& _body, const AutoBuffer& _extend);
//底层获取task要发送的数据
virtual bool Req2Buf(uint32_t _taskid, void* const _user_context, AutoBuffer& _outbuffer, AutoBuffer& _extend, int& _error_code, const int _channel_select);
//底层回包返回给上层解析
virtual int Buf2Resp(uint32_t _taskid, void* const _user_context, const AutoBuffer& _inbuffer, const AutoBuffer& _extend, int& _error_code, const int _channel_select);
//任务执行结束
virtual int OnTaskEnd(uint32_t _taskid, void* const _user_context, int _error_type, int _error_code);
//上报网络连接状态
virtual void ReportConnectStatus(int _status, int longlink_status);
//长连信令校验 ECHECK_NOW, ECHECK_NEVER = 1, ECHECK_NEXT = 2
virtual int GetLonglinkIdentifyCheckBuffer(AutoBuffer& _identify_buffer, AutoBuffer& _buffer_hash, int32_t& _cmdid);
//长连信令校验回包
virtual bool OnLonglinkIdentifyResponse(const AutoBuffer& _response_buffer, const AutoBuffer& _identify_buffer_hash);
//.....
private:
static StnCallBack* instance_;

};

AppCallBack 接口定义如下:

class AppCallBack : public Callback {

private:
//.....
public:
//.....
//获取应用路径
virtual std::string GetAppFilePath();
//获取当前用户信息
virtual AccountInfo GetAccountInfo();
//获取版本信息
virtual unsigned int GetClientVersion();
//获取设备信息
virtual DeviceInfo GetDeviceInfo();
private:
static AppCallBack* instance_;
};

调用SetCallback后就会将对应的callback保存到sg_callback中:

void SetCallback(Callback* const callback) {
sg_callback = callback;
}

StnCallBack 将会在stn_logic.cc被注册到Mars底层的stn模块中,AppCallBack 将会在 app_logic.cc中被注入到Mars底层的app模块中,至于这些模块的作用我们后面会具体介绍。设置完回调之后就可以通过这些回调就可以将与业务相关的逻辑交给业务层,比如流量统计这部分在Mars的stn模块就可以通过如下方式调用上层逻辑:

void (*TrafficData)(ssize_t _send, ssize_t _recv)
= [](ssize_t _send, ssize_t _recv) {
xassert2(sg_callback != NULL);
return sg_callback->TrafficData(_send, _recv);
};

3.2 创建Mars

Mars的创建是通过createMars方法进行创建的:

[[NetworkService sharedInstance] createMars];

这里调用了mars::baseevent的OnCreate

- (void) createMars {
mars::baseevent::OnCreate();
}

在继续介绍Mars创建的过程之前我们先看下mars::baseevent的工作机制:

mars::baseevent里面除了OnCreate外还有如下几个方法,它们的用法看方法名就可以看出,我们这里不介绍这些方法。

void OnCreate()
void OnDestroy()
void OnSingalCrash(int _sig)
void OnExceptionCrash()
void OnForeground(bool _isforeground)
void OnNetworkChange()

mars::baseevent::OnCreate() 实现如下:

void OnCreate() {
GetSignalOnCreate()();
}

baseprjevent.cc实现如下,这里利用了boost这个c++库的signals2信号槽的机制,接触过qt编程的大家都会理解信号槽这个概念。它相当于将一个信号与一个方法绑定在一起,只要发起那个信号,对应绑定的方法就会触发。

boost::signals2::signal<void ()>& GetSignalOnCreate() {
static boost::signals2::signal<void ()> SignalOnCreate;
return SignalOnCreate;
}

那么这个绑定关系是在哪里绑定的呢?

我们可以看到BOOT_RUN_STARTUP宏的定义,这些方法是会在启动的时候执行,

#define BOOT_RUN_STARTUP(func) VARIABLE_IS_NOT_USED static int __anonymous_run_variable_startup_##func = __boot_run_atstartup(func)

下面罗列出了包含BOOT_RUN_STARTUP的类

message_queue.cc

BOOT_RUN_STARTUP(__RgisterANRCheckCallback);

app_logic.cc

BOOT_RUN_STARTUP(__InitbindBaseprjevent);

active_logic.cc

BOOT_RUN_STARTUP(__initbind_baseprjevent);

active_logic.cc

BOOT_RUN_STARTUP(__initbind_baseprjevent);

sdt_logic.cc

BOOT_RUN_STARTUP(__initbind_baseprjevent);

stn_logic.cc

BOOT_RUN_STARTUP(__initbind_baseprjevent);

我们可以看到:sdt_logic.cc

static void __initbind_baseprjevent() {
//.....
GetSignalOnCreate().connect(&onCreate);
GetSignalOnDestroy().connect(5, &onDestroy);
}

以及stn_logic.cc

static void __initbind_baseprjevent() {

//.....
GetSignalOnCreate().connect(&onCreate);
GetSignalOnDestroy().connect(&onDestroy); //low priority signal func
GetSignalOnSingalCrash().connect(&onSingalCrash);
GetSignalOnExceptionCrash().connect(&onExceptionCrash);
GetSignalOnNetworkChange().connect(5, &onNetworkChange); //define group 5
//...
GetSignalOnNetworkDataChange().connect(&OnNetworkDataChange);
}

都包含将GetSignalOnCreate信号通过信号槽与对应方法进行绑定的工作:因此sdt_logic.cc以及stn_logic.cc中的onCreate方法就是我们调用createMars时候底层的所有操作:

sdt_logic.cc

static void onCreate() {
//....
SdtCore::Singleton::Instance();
}

SdtCore 模块的初始化:

SdtCore::SdtCore()
: thread_(boost::bind(&SdtCore::__RunOn, this))
, check_list_(std::list<BaseChecker*>())
, cancel_(false)
, checking_(false) {
xinfo_function();
}

stn_logic.cc

static void onCreate() {
//....
ActiveLogic::Singleton::Instance();
NetCore::Singleton::Instance();
}
ActiveLogic::ActiveLogic()
: isforeground_(false), isactive_(true)
, alarm_(boost::bind(&ActiveLogic::__OnInActive, this), false)
, lastforegroundchangetime_(::gettickcount())
{
//....
}
NetCore::NetCore()
: messagequeue_creater_(true, XLOGGER_TAG)
//很长很长的初始化
}

在这两个文件中分别初始化了SdtCoreNetCoreActiveLogic模块。

3.3 设置长链接地址和短链接地址

设置长链接地址:

[[NetworkService sharedInstance] setLongLinkAddress:@"localhost" port:8081];
void (*SetLonglinkSvrAddr)(const std::string& host, const std::vector<uint16_t> ports, const std::string& debugip)
= [](const std::string& host, const std::vector<uint16_t> ports, const std::string& debugip) {
std::vector<std::string> hosts;
if (!host.empty()) {
hosts.push_back(host);
}
NetSource::SetLongLink(hosts, ports, debugip);
};

设置短链接地址:

[[NetworkService sharedInstance] setShortLinkPort:8080];
void (*SetShortlinkSvrAddr)(const uint16_t port, const std::string& debugip)
= [](const uint16_t port, const std::string& debugip) {
NetSource::SetShortlink(port, debugip);
};
void NetSource::SetLongLink(const std::vector<std::string>& _hosts, const std::vector<uint16_t>& _ports, const std::string& _debugip) {
ScopedLock lock(sg_ip_mutex);
//......
if (!_hosts.empty()) {
sg_longlink_hosts = _hosts;
}
else {
//.......
}
sg_longlink_ports = _ports;
}

void NetSource::SetShortlink(const uint16_t _port, const std::string& _debugip) {
ScopedLock lock(sg_ip_mutex);
//......
sg_shortlink_port = _port;
sg_shortlink_debugip = _debugip;
}

最终会将长链接,短链接的host以及port存到NetSource的sg_longlink_hostssg_longlink_portssg_shortlink_port数组中。

3.3 上报处于前台后台的状态

[[NetworkService sharedInstance] reportEvent_OnForeground:YES/NO];

同样它也是依靠信号来触发的,最终的绑定在active_logic.cc中:

static void __initbind_baseprjevent() {
GetSignalOnForeground().connect(&onForeground);
}

static void onForeground(bool _isforeground) {
ActiveLogic::Singleton::Instance()->OnForeground(_isforeground);
}

void ActiveLogic::OnForeground(bool _isforeground) {

//....
if (_isforeground == isforeground_) return;

bool oldisactive = isactive_;
isactive_ = true;
isforeground_ = _isforeground;
lastforegroundchangetime_ = ::gettickcount();
alarm_.Cancel();

//.....
bool isnotify = oldisactive!=isactive_;
SignalForeground(isforeground_);

if (isnotify) {
SignalActive(isactive_);
}
}

ActiveLogic::OnForeground方法中最重要的就是SignalForeground,以及SignalActive的调用,这也是通过信号来发送的,所以直接略过中间的环境,看下最终的底层调用:

LongLinkConnectMonitor::LongLinkConnectMonitor(ActiveLogic& _activelogic, LongLink& _longlink, MessageQueue::MessageQueue_t _id)
: asyncreg_(MessageQueue::InstallAsyncHandler(_id))
, activelogic_(_activelogic), longlink_(_longlink), alarm_(boost::bind(&LongLinkConnectMonitor::__OnAlarm, this), _id)
, status_(LongLink::kDisConnected)
, last_connect_time_(0)
, last_connect_net_type_(kNoNet)
, thread_(boost::bind(&LongLinkConnectMonitor::__Run, this), XLOGGER_TAG"::con_mon")
, conti_suc_count_(0)
, isstart_(false) {
/*SignalActive 信号绑定*/
activelogic_.SignalActive.connect(boost::bind(&LongLinkConnectMonitor::__OnSignalActive, this, _1));
/*SignalForeground 信号绑定*/
activelogic_.SignalForeground.connect(boost::bind(&LongLinkConnectMonitor::__OnSignalForeground, this, _1));
longlink_.SignalConnection.connect(boost::bind(&LongLinkConnectMonitor::__OnLongLinkStatuChanged, this, _1));
}
void LongLinkConnectMonitor::__OnSignalForeground(bool _isForeground) {
//.......
if (_isForeground) {
longlink_.GetLastRecvTime().get(), int64_t(tickcount_t().gettickcount() - longlink_.GetLastRecvTime()));
if ((longlink_.ConnectStatus() == LongLink::kConnected) &&
(tickcount_t().gettickcount() - longlink_.GetLastRecvTime() > tickcountdiff_t(4.5 * 60 * 1000))) {
//如果超过4.5 分钟的话就会重新连接
__ReConnect();
}
}
/.......
}

SignalForeground 只有应用在前台的情况下如果当前时间与上一次长链接接收时间超过4.5 分钟的话就会重新连接

void LongLinkConnectMonitor::__OnSignalActive(bool _isactive) {
__AutoIntervalConnect();
}
uint64_t LongLinkConnectMonitor::__AutoIntervalConnect() {
alarm_.Cancel();
uint64_t remain = __IntervalConnect(kLongLinkConnect);
alarm_.Start((int)remain);
return remain;
}

SignalActive 会在__IntervalConnect 间隔时间内自动连接,__IntervalConnect 会根据连接类型,以及当前的应用的状态来查询下面的表格,进而确定间隔时间。

                            kForgroundOneMinute | kForgroundTenMinute | kForgroundActive | kBackgroundActive | kInactive
static unsigned long const sg_interval[][5] = {
kTaskConnect: { 5, 10, 20, 30, 300},
kLongLinkConnect: { 15, 30, 240, 300, 600},
kNetworkChangeConnect: { 0, 0, 0, 0, 0},
};

整个重连机制会在介绍长链接的时候进一步介绍。

3.4 检测长链接状态,保证长链接处于连接状态

我们继续来看Appdelegate中的Mars初始化最后一步makesureLongLinkConnect

[[NetworkService sharedInstance] makesureLongLinkConnect];

下面是层层的调用关系:

- (void)makesureLongLinkConnect {
mars::stn::MakesureLonglinkConnected();
}
void (*MakesureLonglinkConnected)()
= []() {
STN_WEAK_CALL(MakeSureLongLinkConnect());
};
void NetCore::MakeSureLongLinkConnect() {
#ifdef USE_LONG_LINK
ASYNC_BLOCK_START
longlink_task_manager_->LongLinkChannel().MakeSureConnected();
ASYNC_BLOCK_END
#endif
}
bool LongLink::MakeSureConnected(bool* _newone) {
if (_newone) *_newone = false;
ScopedLock lock(mutex_);
if (kConnected == ConnectStatus()) return true;
bool newone = false;
thread_.start(&newone);
if (newone) {
//.....
}
if (_newone) *_newone = newone;
return false;
}

LongLink::MakeSureConnected中最关键的是调用了thread_.start(&newone)

LongLink::LongLink(const mq::MessageQueue_t& _messagequeueid, NetSource& _netsource)
: asyncreg_(MessageQueue::InstallAsyncHandler(_messagequeueid))
, netsource_(_netsource)
/*绑定__Run方法*/
, thread_(boost::bind(&LongLink::__Run, this), XLOGGER_TAG "::lonklink")
, connectstatus_(kConnectIdle)
, disconnectinternalcode_(kNone)
, smartheartbeat_(NULL)
, wakelock_(NULL)
{}

在初始化LongLink的时候已经通过boost::bind将thread_绑定到了__Run所以,在调用start的时候调用的是__Run方法:

void LongLink::__Run() {

//.......

SOCKET sock = __RunConnect(conn_profile);
//......
ErrCmdType errtype = kEctOK;
int errcode = 0;
__RunReadWrite(sock, errtype, errcode, conn_profile);
//....
}

__Run中最关键的部分是__RunConnect以及__RunReadWrite:前者是执行socket连接,后者是在一个循环中等待执行数据的读写。关于数据的读写后面会专门介绍。

3.5 发起请求

我们上面代码可以看出发起请求是通过如下代码来实现的:

- (int)startTask:(CGITask *)task ForUI:(id<UINotifyDelegate>)delegateUI {
Task ctask;
ctask.cmdid = task.cmdid;
ctask.channel_select = task.channel_select;
ctask.cgi = std::string(task.cgi.UTF8String);
ctask.shortlink_host_list.push_back(std::string(task.host.UTF8String));
ctask.user_context = (__bridge void*)task;

mars::stn::StartTask(ctask);

NSString *taskIdKey = [NSString stringWithFormat:@"%d", ctask.taskid];
[_delegate addObserver:delegateUI forKey:taskIdKey];
[_delegate addCGITasks:task forKey:taskIdKey];

return ctask.taskid;
}

上面创建了一个Task对象,将请求所需要的全部数据都放在Task上,然后通过mars::stn::StartTask(ctask) 调用Mars发起请求:

stn_logic.cc

bool (*StartTask)(const Task& _task)
= [](const Task& _task) {
STN_RETURN_WEAK_CALL(StartTask(_task));
};

stn_logic.cc

void NetCore::StartTask(const Task& _task) {

//......
Task task = _task;
//判断当前请求是否有效,如果是下面的情况下就会返回错误
//1. 服务端处理时间超过2分钟
//2. 尝试次数超过30次
//3. 超时10分钟
//4. 长连接cmdid为0
//5. 短连接cgi为空
if (!__ValidAndInitDefault(task, group)) {
OnTaskEnd(task.taskid, task.user_context, kEctLocal, kEctLocalTaskParam);
return;
}
//.....
if (0 == task.channel_select) {
//通道类型错误
OnTaskEnd(task.taskid, task.user_context, kEctLocal, kEctLocalChannelSelect);
return;
}

//没有网络,并且在长链接的情况下未连接
if (task.network_status_sensitive && kNoNet ==::getNetInfo()
#ifdef USE_LONG_LINK
&& LongLink::kConnected != longlink_task_manager_->LongLinkChannel().ConnectStatus()
#endif
) {
OnTaskEnd(task.taskid, task.user_context, kEctLocal, kEctLocalNoNet);
return;
}

bool start_ok = false;

//如果发现长链接未连接,并且在前台超过15分钟没有发送消息了,就必须重新连接长链接。
if (LongLink::kConnected != longlink_task_manager_->LongLinkChannel().ConnectStatus()
&& (Task::kChannelLong & task.channel_select) && ActiveLogic::Singleton::Instance()->IsForeground()
&& (15 * 60 * 1000 >= gettickcount() - ActiveLogic::Singleton::Instance()->LastForegroundChangeTime()))
longlink_task_manager_->getLongLinkConnectMonitor().MakeSureConnected();

switch (task.channel_select) {
case Task::kChannelBoth: {

#ifdef USE_LONG_LINK
// 长链接已经连接并且当前长链接任务数目小于长链接所能支持的最大任务数,如果连接策略为kChannelFastStrategy的情况下使用长链接,
// 也就是说在长链接允许的情况下优先使用长链接。
bool bUseLongLink = LongLink::kConnected == longlink_task_manager_->LongLinkChannel().ConnectStatus();
if (bUseLongLink && task.channel_strategy == Task::kChannelFastStrategy) {
bUseLongLink = bUseLongLink && (longlink_task_manager_->GetTaskCount() <= kFastSendUseLonglinkTaskCntLimit);
}
if (bUseLongLink)
start_ok = longlink_task_manager_->StartTask(task);
else
#endif
start_ok = shortlink_task_manager_->StartTask(task);
}
break;
#ifdef USE_LONG_LINK

case Task::kChannelLong:
start_ok = longlink_task_manager_->StartTask(task);
break;
#endif

case Task::kChannelShort:
start_ok = shortlink_task_manager_->StartTask(task);
break;

default:
xassert2(false);
break;
}
//.......
}

在NetCore::StartTask中最主要的工作就是根据channel_select来选择调用长链接模块还是短链接模块的StartTask。当channel_select为kChannelBoth的时候在长链接允许的情况下优先使用长链接。

3.5.1 短链接StartTask

我们先看下短链接的情况下的StartTask:

bool ShortLinkTaskManager::StartTask(const Task& _task) {

//.....
TaskProfile task(_task);
task.link_type = Task::kChannelShort;
//添加到列表中,并按照优先级排序
lst_cmd_.push_back(task);
lst_cmd_.sort(__CompareTask);
//在__RunLoop中处理请求
__RunLoop();
return true;
}

短链接的情况下的StartTask会将Task添加到lst_cmd_然后启动__RunLoop中处理task,我们再来看下__RunLoop

void ShortLinkTaskManager::__RunLoop() {
//....
__RunOnTimeout();
__RunOnStartTask();

if (!lst_cmd_.empty()) {
MessageQueue::FasterMessage(asyncreg_.Get(),
MessageQueue::Message((MessageQueue::MessageTitle_t)this, boost::bind(&ShortLinkTaskManager::__RunLoop, this), "ShortLinkTaskManager::__RunLoop"),
MessageQueue::MessageTiming(1000));
} else {}
}

在__RunLoop方法中主要调用了****__RunOnTimeout__RunOnStartTask****然后如果lst_cmd_不为空的话会在间隔1秒后重新执行__RunLoop,从而形成一个循环。
__RunOnTimeout 主要处理请求超时的任务。而__RunOnStartTask才是发起请求的关键:

void ShortLinkTaskManager::__RunOnStartTask() {
//......
while (first != last) {
std::list<TaskProfile>::iterator next = first;
++next;

//如果当前任务正在执行
if (first->running_id) {
++sent_count;
first = next;
continue;
}

//当前间隔时间小于重试间隔时间
if (first->retry_time_interval > curtime - first->retry_start_time) {
xdebug2(TSF"retry interval, taskid:%0, task retry late task, wait:%1", first->task.taskid, (curtime - first->transfer_profile.loop_start_task_time) / 1000);
first = next;
continue;
}

// 如果需要登录的话,确保登录成功
if (first->task.need_authed) {

if (!ismakesureauthruned) {
ismakesureauthruned = true;
ismakesureauthsuccess = MakesureAuthed();
}

if (!ismakesureauthsuccess) {
xinfo2_if(curtime % 3 == 1, TSF"makeSureAuth retsult=%0", ismakesureauthsuccess);
first = next;
continue;
}
}

AutoBuffer bufreq;
AutoBuffer buffer_extension;
int error_code = 0;

//调用上层方法构建请求
if (!Req2Buf(first->task.taskid, first->task.user_context, bufreq, buffer_extension, error_code, Task::kChannelShort)) {
__SingleRespHandle(first, kEctEnDecode, error_code, kTaskFailHandleTaskEnd, 0, first->running_id ? ((ShortLinkInterface*)first->running_id)->Profile() : ConnectProfile());
first = next;
continue;
}

//雪崩检测
xassert2(fun_anti_avalanche_check_);
if (!fun_anti_avalanche_check_(first->task, bufreq.Ptr(), (int)bufreq.Length())) {
__SingleRespHandle(first, kEctLocal, kEctLocalAntiAvalanche, kTaskFailHandleTaskEnd, 0, first->running_id ? ((ShortLinkInterface*)first->running_id)->Profile() : ConnectProfile());
first = next;
continue;
}

first->transfer_profile.loop_start_task_time = ::gettickcount();
first->transfer_profile.first_pkg_timeout = __FirstPkgTimeout(first->task.server_process_cost, bufreq.Length(), sent_count, dynamic_timeout_.GetStatus());
first->current_dyntime_status = (first->task.server_process_cost <= 0) ? dynamic_timeout_.GetStatus() : kEValuating;
first->transfer_profile.read_write_timeout = __ReadWriteTimeout(first->transfer_profile.first_pkg_timeout);
first->transfer_profile.send_data_size = bufreq.Length();
first->use_proxy = (first->remain_retry_count == 0 && first->task.retry_count > 0) ? !default_use_proxy_ : default_use_proxy_;

//创建短链接接口
ShortLinkInterface* worker = ShortLinkChannelFactory::Create(MessageQueue::Handler2Queue(asyncreg_.Get()), net_source_, first->task, first->use_proxy);
//将ShortLinkTaskManager 的 __OnSend,__OnRecv, __OnResponse 绑定到ShortLinkInterface
worker->OnSend.set(boost::bind(&ShortLinkTaskManager::__OnSend, this, _1), AYNC_HANDLER);
worker->OnRecv.set(boost::bind(&ShortLinkTaskManager::__OnRecv, this, _1, _2, _3), AYNC_HANDLER);
worker->OnResponse.set(boost::bind(&ShortLinkTaskManager::__OnResponse, this, _1, _2, _3, _4, _5, _6, _7), AYNC_HANDLER);
//设置running_id,也就是发起请求后会有一个非0的running_id
first->running_id = (intptr_t)worker;

//...

worker->func_network_report.set(fun_notify_network_err_);
//调用SendRequest发送请求
worker->SendRequest(bufreq, buffer_extension);
//......
//发送计数+1
++sent_count;
first = next;
}
}

上面是给出的详细代码注释,我们接下来看下当中的关键代码:


//调用上层方法构建请求
if (!Req2Buf(first->task.taskid, first->task.user_context, bufreq, buffer_extension, error_code, Task::kChannelShort)) {
__SingleRespHandle(first, kEctEnDecode, error_code, kTaskFailHandleTaskEnd, 0, first->running_id ? ((ShortLinkInterface*)first->running_id)->Profile() : ConnectProfile());
first = next;
continue;
}

//创建短链接接口
ShortLinkInterface* worker = ShortLinkChannelFactory::Create(MessageQueue::Handler2Queue(asyncreg_.Get()), net_source_, first->task, first->use_proxy);
//将ShortLinkTaskManager 的 __OnSend,__OnRecv, __OnResponse 绑定到ShortLinkInterface
worker->OnSend.set(boost::bind(&ShortLinkTaskManager::__OnSend, this, _1), AYNC_HANDLER);
worker->OnRecv.set(boost::bind(&ShortLinkTaskManager::__OnRecv, this, _1, _2, _3), AYNC_HANDLER);
worker->OnResponse.set(boost::bind(&ShortLinkTaskManager::__OnResponse, this, _1, _2, _3, _4, _5, _6, _7), AYNC_HANDLER);
//设置running_id,也就是发起请求后会有一个非0的running_id
first->running_id = (intptr_t)worker;
//...
//调用SendRequest发送请求
worker->SendRequest(bufreq, buffer_extension);

从上面可以看出在经过一系列的检查后调用上层的Req2Buf方法,然后会通过ShortLinkChannelFactory创建ShortLinkInterface(ShortLink 对象)然后将ShortLinkInterface的OnSend,OnRecv,OnResponse与ShortLinkTaskManager进行绑定,然后为当前任务分配一个running_id,最后调用SendRequest将请求数据发送出去。

我们继续看ShortLink::SendRequest

void ShortLink::SendRequest(AutoBuffer& _buf_req, AutoBuffer& _buffer_extend) {
send_body_.Attach(_buf_req);
send_extend_.Attach(_buffer_extend);
thread_.start();
}

ShortLink::SendRequest最关键的就是调用了thread_.start()方法,从而运行__Run方法:

void ShortLink::__Run() {
//.....

SOCKET fd_socket = __RunConnect(conn_profile);

if (INVALID_SOCKET == fd_socket) return;
if (OnSend) {
OnSend(this);
} else {
//....
}
//....
__RunReadWrite(fd_socket, errtype, errcode, conn_profile);
//....
socket_close(fd_socket);
}

ShortLink::__Run 紧接着调用****__RunConnect__RunReadWrite****,在****__RunConnect****方法中主要任务是创建短链接,用于发送请求:

SOCKET ShortLink::__RunConnect(ConnectProfile& _conn_profile) {

//.....
ShortLinkConnectObserver connect_observer(*this);
ComplexConnect conn(kShortlinkConnTimeout, kShortlinkConnInterval);

SOCKET sock = conn.ConnectImpatient(vecaddr, breaker_, &connect_observer, _conn_profile.proxy_info.type, proxy_addr, _conn_profile.proxy_info.username, _conn_profile.proxy_info.password);
//.....
return sock;
}

__RunConnect同样方法很长,我们只看最关键的部分它调用ComplexConnect::ConnectImpatient来创建一个SOCKET返回:

SOCKET ComplexConnect::ConnectImpatient(const std::vector<socket_address>& _vecaddr, SocketBreaker& _breaker, MComplexConnect* _observer,
mars::comm::ProxyType _proxy_type, const socket_address* _proxy_addr,
const std::string& _proxy_username, const std::string& _proxy_pwd) {
//......
for (unsigned int i = 0; i < _vecaddr.size(); ++i) {
ConnectCheckFSM* ic = NULL;
if (mars::comm::kProxyHttpTunel == _proxy_type && _proxy_addr) {
ic = new ConnectHttpTunelCheckFSM(_vecaddr[i], *_proxy_addr, _proxy_username, _proxy_pwd, timeout_, i, _observer);
} else if (mars::comm::kProxySocks5 == _proxy_type && _proxy_addr) {
ic = new ConnectSocks5CheckFSM(_vecaddr[i], *_proxy_addr, _proxy_username, _proxy_pwd, timeout_, i, _observer);
} else {
ic = new ConnectCheckFSM(_vecaddr[i], timeout_, i, _observer);
}
//创建 ConnectCheckFSM 添加到vecsocketfsm
vecsocketfsm.push_back(ic);
}

//......
SOCKET retsocket = INVALID_SOCKET;

do {
//......

// socket
for (unsigned int i = 0; i < index; ++i) {
//......

if (TcpClientFSM::EReadWrite == vecsocketfsm[i]->Status() && ConnectCheckFSM::ECheckOK == vecsocketfsm[i]->CheckStatus()) {
if (_observer) _observer->OnFinished(i, socket_address(&vecsocketfsm[i]->Address()), vecsocketfsm[i]->Socket(), vecsocketfsm[i]->Error(),
vecsocketfsm[i]->Rtt(), vecsocketfsm[i]->TotalRtt(), (int)(gettickcount() - starttime));
//....
retsocket = vecsocketfsm[i]->Socket();
//....
break;
}
}
//......
} while (true);
//........
return retsocket;
}

接下来就是最关键的收发数据的阶段了,我们看下ShortLink::__RunReadWrite:

void ShortLink::__RunReadWrite(SOCKET _socket, int& _err_type, int& _err_code, ConnectProfile& _conn_profile) {
xmessage2_define(message)(TSF"taskid:%_, cgi:%_, @%_", task_.taskid, task_.cgi, this);

std::string url;
std::map<std::string, std::string> headers;

//.....
if (kIPSourceProxy == _conn_profile.ip_type) {
url += "http://";
url += _conn_profile.host;
}
url += task_.cgi;
//http:// + _conn_profile.host + task_.cgi
headers[http::HeaderFields::KStringHost] = _conn_profile.host;

//.......

//构建出请求后,将请求输出到out_buff
AutoBuffer out_buff;
shortlink_pack(url, headers, send_body_, send_extend_, out_buff, tracker_.get());

//发送请求数据
int send_ret = block_socket_send(_socket, (const unsigned char*)out_buff.Ptr(), (unsigned int)out_buff.Length(), breaker_, _err_code);

//.....
//recv response
AutoBuffer body;
AutoBuffer recv_buf;
AutoBuffer extension;
int status_code = -1;
off_t recv_pos = 0;
MemoryBodyReceiver* receiver = new MemoryBodyReceiver(body);
http::Parser parser(receiver, true);
while (true) {
int recv_ret = block_socket_recv(_socket, recv_buf, KBufferSize, breaker_, _err_code, 5000);
//错误校验
Parser::TRecvStatus parse_status = parser.Recv(recv_buf.Ptr(recv_buf.Length() - recv_ret), recv_ret);
if (parser.FirstLineReady()) {
//获取状态码
status_code = parser.Status().StatusCode();
}
if (parse_status == http::Parser::kFirstLineError) {
//....
} else if (parse_status == http::Parser::kHeaderFieldsError) {
//....
} else if (parse_status == http::Parser::kBodyError) {
//.....
} else if (parse_status == http::Parser::kEnd) {
if (status_code != 200) {
//......
} else {
__OnResponse(kEctOK, status_code, body, extension, _conn_profile, true);
}
break;
} else {
//........
}
}
//......
}

ShortLink::__RunReadWrite 会先调用shortlink_pack将所有的请求打包,然后通过block_socket_send发出去,然后不断轮询调用block_socket_recv获得请求,如果获得成功那么调用__OnResponse,我们之前在介绍创建ShortLink的时候会将ShortLink的__OnResponse方法与ShortLinkTaskManager::__OnResponse进行绑定,所以这里会触发ShortLinkTaskManager::__OnResponse的调用。

void ShortLinkTaskManager::__OnResponse(ShortLinkInterface* _worker, ErrCmdType _err_type, int _status, AutoBuffer& _body, AutoBuffer& _extension, bool _cancel_retry, ConnectProfile& _conn_profile) {

//.......
int handle_type = Buf2Resp(it->task.taskid, it->task.user_context, _body, _extension, err_code, Task::kChannelShort);

switch(handle_type){
case kTaskFailHandleNoError: {
//.....
} break;
case kTaskFailHandleSessionTimeout:{
//....
} break;
case kTaskFailHandleRetryAllTasks: {
//.....
} break;
case kTaskFailHandleTaskEnd: {
//.....
} break;
case kTaskFailHandleDefault: {
//...
} break;
default: {
//...
}
}
}

在上面的方法中通过Buf2Resp回调将底层Socket 返回数据传递到业务逻辑层进行处理。

短链接的收发数据大致流程如下图所示

3.5.2 长链接StartTask

介绍了短链接的整个数据通信流程后再来看长链接会显得相对轻松点,在长链接部分我们最需要注意的是心跳包的处理,为了完整起见这里还是从NetWorkService的StartTask开始介绍:

bool LongLinkTaskManager::StartTask(const Task& _task) {
//将task放置到lst_cmd_数组
lst_cmd_.push_back(task);
lst_cmd_.sort(__CompareTask);
//执行RunLoop
__RunLoop();
return true;
}

LongLinkTaskManager::StartTaskShortLinkTaskManager::StartTask 一样都是将任务添加到lst_cmd_,然后执行__RunLoop。


void LongLinkTaskManager::__RunLoop() {

//.....
__RunOnTimeout();
__RunOnStartTask();

if (!lst_cmd_.empty()) {
//........
MessageQueue::FasterMessage(asyncreg_.Get(),
MessageQueue::Message((MessageQueue::MessageTitle_t)this, boost::bind(&LongLinkTaskManager::__RunLoop, this), "LongLinkTaskManager::__RunLoop"),
MessageQueue::MessageTiming(1000));
} else {
//......
}
}

__RunLoop和短连接也没啥两样,最主要的工作还是放在__RunOnStartTask中,然后每间隔1秒重新发送一个FasterMessage。继续执行__RunLoop:


void LongLinkTaskManager::__RunOnStartTask() {

//.......
bool canretry = curtime - lastbatcherrortime_ >= retry_interval_;//超时间隔是否达到指定的要求
bool canprint = true;
int sent_count = 0;

while (first != last) {
std::list<TaskProfile>::iterator next = first;
++next;

//当前任务已经在运行了
//.......

//重试间隔, 不影响第一次发送的任务
//.......

// 登录处理
//.....

AutoBuffer bufreq;
AutoBuffer buffer_extension;
int error_code = 0;

//如果未进行雪崩检测则进行一次雪崩检测
if (!first->antiavalanche_checked) {
//...
first->antiavalanche_checked = true;
}

//确保长链接处于连接状态
if (!longlinkconnectmon_->MakeSureConnected()) {
//........
}

//如果当前请求缓存是空的则再次通过Req2Buf从上层拿到上层构建的请求
if (0 == bufreq.Length()) {
if (!Req2Buf(first->task.taskid, first->task.user_context, bufreq, buffer_extension, error_code, Task::kChannelLong)) {
__SingleRespHandle(first, kEctEnDecode, error_code, kTaskFailHandleTaskEnd, longlink_->Profile());
first = next;
continue;
}
// 雪崩检测
//......
}

first->transfer_profile.loop_start_task_time = ::gettickcount();
first->transfer_profile.first_pkg_timeout = __FirstPkgTimeout(first->task.server_process_cost, bufreq.Length(), sent_count, dynamic_timeout_.GetStatus());
first->current_dyntime_status = (first->task.server_process_cost <= 0) ? dynamic_timeout_.GetStatus() : kEValuating;
first->transfer_profile.read_write_timeout = __ReadWriteTimeout(first->transfer_profile.first_pkg_timeout);
first->transfer_profile.send_data_size = bufreq.Length();
//调用longlink_->Send 发送数据
first->running_id = longlink_->Send(bufreq, buffer_extension, first->task);

//.......
++sent_count;
first = next;
}
}

__RunOnStartTask 中最关键的部分在于MakeSureConnected* 以及 LongLink::Send

MakeSureConnected 起始在初始化Mars的时候已经简单介绍过了,我们这里展开介绍下,主要还是看,心跳包的控制:

bool LongLinkConnectMonitor::MakeSureConnected() {
__IntervalConnect(kTaskConnect);
return LongLink::kConnected == longlink_.ConnectStatus();
}

uint64_t LongLinkConnectMonitor::__IntervalConnect(int _type) {

//如果连接状态为正在连接或者已经连接的情况下返回0,这时候不会自动重连
if (LongLink::kConnecting == longlink_.ConnectStatus() || LongLink::kConnected == longlink_.ConnectStatus()) return 0;
//获取下一次心跳时间
uint64_t interval = __Interval(_type, activelogic_) * 1000ULL;
//....
if (posttime >= interval) {
bool ret = longlink_.MakeSureConnected(&newone);
//....
} else {
//....
}
}

我们重点看下****__Interval*****:

static unsigned long __Interval(int _type, const ActiveLogic& _activelogic) {

unsigned long interval = sg_interval[_type][__CurActiveState(_activelogic)];/*获得间隔时间*/
if (kLongLinkConnect != _type) return interval;
if (__CurActiveState(_activelogic) == kInactive || __CurActiveState(_activelogic) == kForgroundActive) { // now - LastForegroundChangeTime>10min
//不活跃的情况下用户名为空
if (!_activelogic.IsActive() && GetAccountInfo().username.empty()) {
interval = kNoAccountInfoInactiveInterval;
xwarn2(TSF"no account info and inactive, interval:%_", interval);
//无网络的情况下
} else if (kNoNet == getNetInfo()) {
interval = interval * kNoNetSaltRate + kNoNetSaltRise;
xinfo2(TSF"no net, interval:%0", interval);
//无用户名的情况下
} else if (GetAccountInfo().username.empty()) {
interval = interval * kNoAccountInfoSaltRate + kNoAccountInfoSaltRise;
xinfo2(TSF"no account info, interval:%0", interval);

} else {
// default value
interval += rand() % (20);
}
}
return interval;
}

它会根据连接类型,当前设备活跃状态设置下一次心跳的时间,它的值存在二维数组sg_interval中:

                            kForgroundOneMinute | kForgroundTenMinute | kForgroundActive | kBackgroundActive | kInactive
static unsigned long const sg_interval[][5] = {
kTaskConnect: { 5, 10, 20, 30, 300},
kLongLinkConnect: { 15, 30, 240, 300, 600},
kNetworkChangeConnect: { 0, 0, 0, 0, 0},
};

我们继续回到LongLink::MakeSureConnected

bool LongLink::MakeSureConnected(bool* _newone) {
//.......
thread_.start(&newone);
//....
return false;
}

LongLink::MakeSureConnected的处理非常简单只是调用了thread_.start,实际上会执行LongLink::__Run方法:

void LongLink::__Run() {
//.....
SOCKET sock = __RunConnect(conn_profile);
//......
__RunReadWrite(sock, errtype, errcode, conn_profile);

socket_close(sock);
//...
}

LongLink::__Run方法和短链接一样都只执行两个方法****__RunConnect创建SOCKET,__RunReadWrite****开始运行读写循环。

SOCKET LongLink::__RunConnect(ConnectProfile& _conn_profile) {
//........
LongLinkConnectObserver connect_observer(*this, ip_items);
ComplexConnect com_connect(kLonglinkConnTimeout, kLonglinkConnInteral, kLonglinkConnInteral, kLonglinkConnMax);
SOCKET sock = com_connect.ConnectImpatient(vecaddr, connectbreak_, &connect_observer, proxy_info.type, proxy_addr, proxy_info.username, proxy_info.password);
//......
return sock;
}

长短链接最大的区别还是在****__RunReadWrite****方法上:

void LongLink::__RunReadWrite(SOCKET _sock, ErrCmdType& _errtype, int& _errcode, ConnectProfile& _profile) {

//心跳间隔时间一到就执行__OnAlarm
Alarm alarmnoopinterval(boost::bind(&LongLink::__OnAlarm, this), false);
//超时定时器
Alarm alarmnooptimeout(boost::bind(&LongLink::__OnAlarm, this), false);

//.......

while (true) {
if (!alarmnoopinterval.IsWaiting()) {
if (first_noop_sent && alarmnoopinterval.Status() != Alarm::kOnAlarm) {
xassert2(false, "noop interval alarm not running");
}

if(first_noop_sent && alarmnoopinterval.Status() == Alarm::kOnAlarm) {
__NotifySmartHeartbeatJudgeDozeStyle();
}
xgroup2_define(noop_xlog);
uint64_t last_noop_interval = alarmnoopinterval.After();
uint64_t last_noop_actual_interval = (alarmnoopinterval.Status() == Alarm::kOnAlarm) ? alarmnoopinterval.ElapseTime() : 0;
bool has_late_toomuch = (last_noop_actual_interval >= (15*60*1000));

//发送起始心跳
if (__NoopReq(noop_xlog, alarmnooptimeout, has_late_toomuch)) {
nooping = true;
__NotifySmartHeartbeatHeartReq(_profile, last_noop_interval, last_noop_actual_interval);
}
first_noop_sent = true;
//获得下一个心跳时间
uint64_t noop_interval = __GetNextHeartbeatInterval();
alarmnoopinterval.Cancel();
//启动心跳计时
alarmnoopinterval.Start((int)noop_interval);
}
//.........
if (sel.Write_FD_ISSET(_sock) && !lstsenddata_.empty()) {
xgroup2_define(xlog_group);
xinfo2(TSF"task socket send sock:%0, ", _sock) >> xlog_group;

iovec* vecwrite = (iovec*)calloc(lstsenddata_.size(), sizeof(iovec));
unsigned int offset = 0;
//将数据放到缓存中,lstsenddata_是Send方法添加的。
for (auto it = lstsenddata_.begin(); it != lstsenddata_.end(); ++it) {
vecwrite[offset].iov_base = it->second->PosPtr();
vecwrite[offset].iov_len = it->second->PosLength();
++offset;
}
//写到发送端口,将数据发送出去
ssize_t writelen = writev(_sock, vecwrite, (int)lstsenddata_.size());
free(vecwrite);
//获得下一个心跳时间
unsigned long long noop_interval = __GetNextHeartbeatInterval();
alarmnoopinterval.Cancel();
alarmnoopinterval.Start((int)noop_interval);

auto it = lstsenddata_.begin();

while (it != lstsenddata_.end() && 0 < writelen) {
if (0 == it->second->Pos() && OnSend) OnSend(it->first.taskid);
if ((size_t)writelen >= it->second->PosLength()) {
//......
LongLinkNWriteData nwrite(it->second->Length(), it->first);
nsent_datas.push_back(nwrite);
it = lstsenddata_.erase(it);
} else {
//......
}
}
}

if (sel.Read_FD_ISSET(_sock)) {
bufrecv.AllocWrite(64 * 1024, false);
ssize_t recvlen = recv(_sock, bufrecv.PosPtr(), 64 * 1024, 0);
//.......
while (0 < bufrecv.Length()) {
uint32_t cmdid = 0;
uint32_t taskid = Task::kInvalidTaskID;
size_t packlen = 0;
AutoBuffer body;
AutoBuffer extension;
//读取数据,解压数据
int unpackret = longlink_unpack(bufrecv, cmdid, taskid, packlen, body, extension, tracker_.get());

//.......
if (LONGLINK_UNPACK_STREAM_PACKAGE == unpackret) {
if (OnRecv)
OnRecv(taskid, packlen, packlen);
} else if (!__NoopResp(cmdid, taskid, stream_resp.stream, stream_resp.extension, alarmnooptimeout, nooping, _profile)) {
//交给上层处理
if (OnResponse)
OnResponse(kEctOK, 0, cmdid, taskid, stream_resp.stream, stream_resp.extension, _profile);
sent_taskids.erase(taskid);
}
}
}
}
//............
}
bool LongLink::Send(const AutoBuffer& _body, const AutoBuffer& _extension, const Task& _task) {
//......
lstsenddata_.push_back(std::make_pair(_task, move_wrapper<AutoBuffer>(AutoBuffer())));
longlink_pack(_task.cmdid, _task.taskid, _body, _extension, lstsenddata_.back().second, tracker_.get());
lstsenddata_.back().second->Seek(0, AutoBuffer::ESeekStart);
//...
return true;
}

关于长链接的数据收发,在发送的时候会调用LongLink::Send这时候会将数据放置到lstsenddata_,在有写数据的socket的时候,会将lstsenddata_的数据调用writev写到网络端口,从而发送出去,而读数据和短链接流程类似都是将数据从端口读出,然后通过onResponse将数据交给LongLink,再由LongLink将数据交给LongLinkManager,LongLinkManager再将数据通过持有的Req2Buf callback,将数据传递到应用层。

整个Mars的流程图如下所示:

开源库信息
源码解析

我们在分析代码之前先看下YYAsyncLayer应用层的用法,下面是官网上给出的一个例子:

@implementation YYLabel

- (void)setText:(NSString *)text {
_text = text.copy;
[[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit];
}

- (void)layoutSubviews {
[super layoutSubviews];
[[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit];
}

- (void)contentsNeedUpdated {
[self.layer setNeedsDisplay];
}

#pragma mark - YYAsyncLayer

+ (Class)layerClass {
return YYAsyncLayer.class;
}

- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask {

YYAsyncLayerDisplayTask *task = [YYAsyncLayerDisplayTask new];
task.willDisplay = ^(CALayer *layer) {
//...
};

task.display = ^(CGContextRef context, CGSize size, BOOL(^isCancelled)(void)) {
if (isCancelled()) return;
//.......
这里完成界面的绘制
};

task.didDisplay = ^(CALayer *layer, BOOL finished) {
if (finished) {
// finished
} else {
// cancelled
}
};
return task;
}
@end

在每个需要更新界面的地方,都需要调用:

[[YYTransaction transactionWithTarget:self selector:@selector(contentsNeedUpdated)] commit];

contentsNeedUpdated方法如下:

- (void)contentsNeedUpdated {
[self.layer setNeedsDisplay];
}

它会调用setNeedsDisplay触发layer的绘制。而我们要绘制的内容都放在newAsyncDisplayTask方法中,在newAsyncDisplayTask方法中返回一个新建的YYAsyncLayerDisplayTask。在YYAsyncLayerDisplayTask的willDisplay,display,didDisplay中分别覆写对应的block在整个绘制过程中插入操作:

- (YYAsyncLayerDisplayTask *)newAsyncDisplayTask {

YYAsyncLayerDisplayTask *task = [YYAsyncLayerDisplayTask new];
task.willDisplay = ^(CALayer *layer) {
//...
};

task.display = ^(CGContextRef context, CGSize size, BOOL(^isCancelled)(void)) {
if (isCancelled()) return;
//.......
这里完成界面的绘制
};

task.didDisplay = ^(CALayer *layer, BOOL finished) {
if (finished) {
// finished
} else {
// cancelled
}
};
return task;
}
@end

YYAsyncLayer代码量不多,我们下面就顺着上面的例子过下整个源码,整个过程分成两部分:YYTransaction的提交,以及异步绘制过程。

1.YYTransaction的提交

首先是YYTransaction的创建,每个YYTransaction代表一个提交的操作,它有两个属性target,selector,这个不用过多解释吧,用于指定当前提交的操作是哪个对象中的哪个操作。

+ (YYTransaction *)transactionWithTarget:(id)target selector:(SEL)selector{
if (!target || !selector) return nil;
YYTransaction *t = [YYTransaction new];
t.target = target;
t.selector = selector;
return t;
}

我们最关键的是要了解这些操作是提交到哪里?会在什么时候触发。我们继续往下看:

- (void)commit {
if (!_target || !_selector) return;
YYTransactionSetup();
[transactionSet addObject:self];
}

static void YYTransactionSetup() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
transactionSet = [NSMutableSet new];
CFRunLoopRef runloop = CFRunLoopGetMain();
CFRunLoopObserverRef observer;
observer = CFRunLoopObserverCreate(CFAllocatorGetDefault(),
kCFRunLoopBeforeWaiting | kCFRunLoopExit,
true, // repeat
0xFFFFFF, // after CATransaction(2000000)
YYRunLoopObserverCallBack, NULL);
CFRunLoopAddObserver(runloop, observer, kCFRunLoopCommonModes);
CFRelease(observer);
});
}

看过之前介绍RunLoop的文章会对上面的代码比较熟悉,YYTransactionSetup方法中,向主RunLoop中注册了一个Observer,在主RunLoop 进入休眠之前(kCFRunLoopBeforeWaiting)以及 主RunLoop退出(kCFRunLoopExit)之前会回调YYRunLoopObserverCallBack,这个observer的优先级为0xFFFFFF,CATransaction为2000000,也就是在CATransaction之后。我们看YYRunLoopObserverCallBack中的任务吧:

static void YYRunLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
if (transactionSet.count == 0) return;
NSSet *currentSet = transactionSet;
transactionSet = [NSMutableSet new];
[currentSet enumerateObjectsUsingBlock:^(YYTransaction *transaction, BOOL *stop) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[transaction.target performSelector:transaction.selector];
#pragma clang diagnostic pop
}];
}

YYRunLoopObserverCallBack 方法中会遍历transactionSet中每个YYTransaction,通过performSelector执行提交的selector。
也就是在主RunLoop进入休眠,退出之前会将遍历当前提交的所有YYTransaction,执行里面的selector。在上面的例子中我们在selector中调用了[self.layer setNeedsDisplay],也就是启动了layer的绘制,我们来看下绘制过程。

2.异步绘制过程

在UIKit 中界面渲染是发生在主线程的,我们来看下YYAsyncLayer的异步绘制过程:

我们上面提到了在主RunLoop进入休眠,退出之前会将遍历当前提交的所有YYTransaction,执行里面的selector从而调用[self.layer setNeedsDisplay],在YYAsyncLayer方法中覆写了setNeedsDisplay,它在调用父类的setNeedsDisplay之前,会调用****_cancelAsyncDisplay****:

- (void)setNeedsDisplay {
[self _cancelAsyncDisplay];
[super setNeedsDisplay];
}

_cancelAsyncDisplay 只有一行代码:

- (void)_cancelAsyncDisplay {
[_sentinel increase];
}

_sentinel其实是用来标记每次操作,增加了这个值之前的所有操作都会失效。在绘制代码中有如下的一行代码:

YYSentinel *sentinel = _sentinel;
int32_t value = sentinel.value;
BOOL (^isCancelled)(void) = ^BOOL() {
return value != sentinel.value;
};

所以每次调用_cancelAsyncDisplay 就会导致 value != sentinel.value。从而isCancelled是YES,就会取消过期的操作。

我们继续往下看,在[super setNeedsDisplay]之后,会触发绘制过程:

- (void)display {
super.contents = super.contents;
[self _displayAsync:_displaysAsynchronously];
}

display方法中会调用_displayAsync:

- (void)_displayAsync:(BOOL)async {
__strong id<YYAsyncLayerDelegate> delegate = (id)self.delegate;
YYAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];
if (!task.display) {
if (task.willDisplay) task.willDisplay(self);
self.contents = nil;
if (task.didDisplay) task.didDisplay(self, YES);
return;
}

if (async) {
if (task.willDisplay) task.willDisplay(self);
YYSentinel *sentinel = _sentinel;
int32_t value = sentinel.value;
BOOL (^isCancelled)(void) = ^BOOL() {
return value != sentinel.value;
};
CGSize size = self.bounds.size;
BOOL opaque = self.opaque;
CGFloat scale = self.contentsScale;
CGColorRef backgroundColor = (opaque && self.backgroundColor) ? CGColorRetain(self.backgroundColor) : NULL;
if (size.width < 1 || size.height < 1) {
CGImageRef image = (__bridge_retained CGImageRef)(self.contents);
self.contents = nil;
if (image) {
dispatch_async(YYAsyncLayerGetReleaseQueue(), ^{
CFRelease(image);
});
}
if (task.didDisplay) task.didDisplay(self, YES);
CGColorRelease(backgroundColor);
return;
}

dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{
if (isCancelled()) {
CGColorRelease(backgroundColor);
return;
}
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
if (opaque) {
CGContextSaveGState(context); {
if (!backgroundColor || CGColorGetAlpha(backgroundColor) < 1) {
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
CGContextFillPath(context);
}
if (backgroundColor) {
CGContextSetFillColorWithColor(context, backgroundColor);
CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
CGContextFillPath(context);
}
} CGContextRestoreGState(context);
CGColorRelease(backgroundColor);
}
task.display(context, size, isCancelled);
if (isCancelled()) {
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
if (task.didDisplay) task.didDisplay(self, NO);
});
return;
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (isCancelled()) {
dispatch_async(dispatch_get_main_queue(), ^{
if (task.didDisplay) task.didDisplay(self, NO);
});
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
if (isCancelled()) {
if (task.didDisplay) task.didDisplay(self, NO);
} else {
self.contents = (__bridge id)(image.CGImage);
if (task.didDisplay) task.didDisplay(self, YES);
}
});
});
} else {
//..........
}
}

_displayAsync 代码很长我们只看异步绘制的情况:

__strong id<YYAsyncLayerDelegate> delegate = (id)self.delegate;
YYAsyncLayerDisplayTask *task = [delegate newAsyncDisplayTask];

在_displayAsync的开始会调用上层的newAsyncDisplayTask,来创建一个YYAsyncLayerDisplayTask

接下来就进行异步绘制:

dispatch_async(YYAsyncLayerGetDisplayQueue(), ^{

if (isCancelled()) {
//取消绘制
CGColorRelease(backgroundColor);
return;
}

//创建绘制上下文
UIGraphicsBeginImageContextWithOptions(size, opaque, scale);
CGContextRef context = UIGraphicsGetCurrentContext();

if (opaque/*不透明*/) {
//背景绘制
CGContextSaveGState(context); {
if (!backgroundColor || CGColorGetAlpha(backgroundColor) < 1) {
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
CGContextFillPath(context);
}
if (backgroundColor) {
CGContextSetFillColorWithColor(context, backgroundColor);
CGContextAddRect(context, CGRectMake(0, 0, size.width * scale, size.height * scale));
CGContextFillPath(context);
}
} CGContextRestoreGState(context);
CGColorRelease(backgroundColor);
}
//将绘制上下文传递出去调用YYAsyncLayerDisplayTask的display方法在上下文进行绘制
task.display(context, size, isCancelled);
//.........
//取出task.display绘制的图片image
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//........
dispatch_async(dispatch_get_main_queue(), ^{
if (isCancelled()) {
if (task.didDisplay) task.didDisplay(self, NO);
} else {
//在主线程中将image内容设置到self.contents
self.contents = (__bridge id)(image.CGImage);
if (task.didDisplay) task.didDisplay(self, YES);
}
});
});

上面的代码中绘制部分都放在YYAsyncLayerGetDisplayQueue队列中完成,具体绘制的部分看上面的注释,最后在dispatch_get_main_queue队列中将绘制的界面image设置到self.contents。

那YYAsyncLayerGetDisplayQueue又是怎么组织的呢?

static dispatch_queue_t YYAsyncLayerGetDisplayQueue() {
#ifdef YYDispatchQueuePool_h
return YYDispatchQueueGetForQOS(NSQualityOfServiceUserInitiated);
#else
#define MAX_QUEUE_COUNT 16
static int queueCount;
static dispatch_queue_t queues[MAX_QUEUE_COUNT];
static dispatch_once_t onceToken;
static int32_t counter = 0;
dispatch_once(&onceToken, ^{
//队列长度设置为处理器的数量
queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;
if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
// 比如我们电脑是四核的那么就创建四个串行dispatch_queue。
for (NSUInteger i = 0; i < queueCount; i++) {
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
queues[i] = dispatch_queue_create("com.ibireme.yykit.render", attr);
}
} else {
//.....
}
});
int32_t cur = OSAtomicIncrement32(&counter);
if (cur < 0) cur = -cur;
//每次在取队列的时候就会从queues[0],queues[1],queues[2],queues[3] 选择一个串行队列返回,在上面完成绘制。
return queues[(cur) % queueCount];
#undef MAX_QUEUE_COUNT
#endif
}

这里其实可以使用YYDispatchQueuePool,但是我们这里先看下不使用YYDispatchQueuePool的情景。上面代码中创建了和处理器数量相同的串行queues。每次会循环从这些队列中取出一个queues,在上面完成绘制,这样的好处是为了避免某些时候某些线程卡住了,导致不断为后来任务不断创建新的线程,导致线程爆炸。这个问题会在YYDispatchQueuePool中进行详细介绍。

开源库信息

为什么需要WebSocket

对于一个应用来说Http协议几乎是必备的协议,当客户端需要数据的时候通过Http协议发起请求,服务端响应请求返回对应的数据给客户端,但是如果客户端需要实时知道服务的某个状态变化怎么办?在不借助WebSocket的情况下,只能通过轮询来不断向服务端查询这个状态,这种方式对服务端和客户端都会带来资源上的损耗,客户端要不断地发送请求给服务端来请求当前的状态,这种方式一来耗费了大量CPU时间,二来带来了不必要的流量损耗。对于服务端来说多出了一系列的无用的请求,给服务器带来了不必要的负担。最后的结果还是没办法做到真正的实时,当然这里可以做些优化,在某次拉取数据发现状态没有改变的时候,增加下一次请求的时间,直到达到某个阈值后再减小两次请求的间隔时间。还可以使用长轮询:客户端发送一个超时时间很长的请求,服务端长时间持有这个请求,在服务端状态改变的时候通过这个请求返回,长轮询这种方式虽然省去了大量无效请求,减少了服务器压力和一定的网络带宽的占用,但是还是需要保持大量的连接。

但是这里还需要明确一个问题:Http长连接和WebSocket长连接的区别

要弄清楚这个问题必须要明确三个概念HTTP keep-alive,Comet,WebSocket

在之前的HTTP 1.0 之前默认使用的是短连接,短连接的特点是客户端和服务端每进行一次通信就会建立一次连接,这里的连接指的是传输层的TCP连接,也就是说每次请求都会建立一次TCP连接,然后发送请求,等待收到服务端返回的请求后,断开这个连接。整个过程是由客户端驱动的,服务端只能响应某个具体的请求,不能主动和客户端建立连接发送数据。

HTTP 1.1之后就默认使用长连接,它会在响应头加入如下属性:

Connection:keep-alive;

那么这种长连接是不是就可以实现客户端和服务端全双工通信呢?其实不能的,因为这里的长连接还是传输层的长连接并不是应用层面的长连接,它相对于HTTP 1.0 来说,每次请求客户端收到来自服务端的返回体之后,底层的TCP连接不会立马断掉,如果后续有 HTTP 请求还是会复用这个底层的TCP连接。但是应用层面上还是遵循了HTTP协议规定的一个Request,一个Response的过程。一个请求获得一个响应后应用层必定会断掉。而且只有客户端发起的请求才会有响应,也就是说整个过程还是客户端来驱动的,客户端索要数据,服务端响应请求返回数据。即使采用keep-alive技术来保证TCP连接不会断,如果服务端也无法主动给客户端发起一个HTTP请求。

Comet是指客户端发送一个HTTP请求,但是服务端不会立刻返回,而是一直持有着这个请求直到有客户端需要的内容后再返回,在这个期间这个 HTTP请求可以连接维持比较长的时间。类似一种服务端推送机制:客户端发起请求相当于先把连接建立好,等服务端有消息需要返回时再返回给客户端,但是本质还是不变的,服务器的每次数据下发都是针对客户端先前发起的某次请求,服务端不能无缘无故地向客户端下发数据。

WebSocketHTTP两个协议在协议堆栈里面其实是两个对等的协议,都是位于应用层,是两个完全不同的概念,WebSocket可以不用像HTTP协议那样,要先有请求后服务端才能向客户端返回数据,它的长链接是应用层上的长连接,而不再单单是传输层上TCP上的长连接,但是它和HTTP协议都是基于TCP基础之上的。在WebSocket连接建立后,服务端可以主动地向客户端发送数据,从而实现全双工通讯。

所以什么是WebSocket,为什么需要有WebSocket? WebSocket 实际上是一个与Http协议一样都是基于TCP协议之上的应用层协议,它和Http协议的区别在于它不再遵循客户端主动发起请求,服务端响应的Request-Response 机制,而是可以在客户端没有发送请求的情况下,服务端主动下发数据给客户端。实现客户端和服务端的全双工通讯,也就是说WebSocket是为了弥补Http不能全双工数据通信的不足而推出的,连接一旦建立,双方可以随时向对方发送数据。

WebSocket 协议简要介绍

WebSocket的默认端口是80和443,分别对应的协议为ws://,wss://,整个协议包含三大部分:
建立连接握手数据通信

建立连接

在客户端与服务端进行握手之前,客户端和服务端需要建立好连接:

  • 一个客户端对于相同的目标地址(这里简单理解成一个服务器IP),同一时刻只能有一个处于CONNECTING状态的连接。如果当前已有指向目标地址的连接处于CONNECTING状态,就要等待这个连接成功,或者建立失败之后才能建立新的连接。

  • 如果客户端处于一个代理环境中,它首先要请求它的代理来建立一个到达目标地址的TCP连接。例如,如果客户端处于代理环境中,它想要连接某目标地址的80端口,它可能要首先发送以下消息:

CONNECT example.com:80 HTTP/1.1
Host: example.com
  • 如果客户端没有处于代理环境中,那么就需要建立一个到达目标地址的直接的TCP连接

握手

建立好客户端和服务端的连接后,客户端就可以向服务端发起握手请求了。握手请求消息的方法必须是GET,HTTP版本必须大于1.1 。

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

出于兼容性的考虑,WS的建立连接还是依赖HTTP来实现,这种方式的好处是:握手时不容易被屏蔽,能通过各种 HTTP 代理服务器。

下面针对上述给出的包一一进行介绍:

  • ****Upgrade ****
Upgrade是HTTP 1.1中用于定义转换协议的header域,它表示如果服务器支持的话,客户端希望从已建立好的连接协议,切换到另外一个应用层协议,这里是从HTTP协议切换到WebSocket协议。
  • **** Connection ****
HTTP 1.1中规定Upgrade只能应用在直接连接中,也就是说WS的连接不能通过中间人来转发,它必须是一个直接连接。如果客户端和服务器之间是通过代理连接的,那么在发送这个握手消息之前首先要发送CONNECT消息来建立直接连接。

  • **** Sec-WebSocket-Key ****
请求消息中的"Sec-WebSocket-Key"是一个Base64编码的16位随机字符,服务器端会用这些数据来构造出一个SHA-1的信息摘要,把"Sec-WebSocket-Key"加上一个魔幻字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"。使用SHA-1加密,然后进行BASE-64编码,将结果做为"Sec-WebSocket-Accept"头的值,返回给客户端。
  • **** Origin ****
用于防止跨站攻击,客户端会使用这个来标识原始域。
  • Sec-WebSocket-Protocol
客户端支持的子协议的列表,这个字段用于协商应用子协议,在创建 WebSocket 对象的时候,可以传递一个可选的子协议数组,告诉服务器,客户端可以理解哪些协议。服务器必须从数据里面选择几个支持的协议进行返回,如果一个都不支持,那么会直接导致握手失败。客户端可以不发送子协议,但是一旦发送,服务器无法支持其中任意一个都会导致握手失败。

  • Sec-WebSocket-Version
客户端支持的WS协议的版本列表,客户端可以初始请求它选择的 WebSocket 协议版本,如果服务器支持请求的版本服务器将接受该版本。如果服务器不支持请求的版本,它会返回一个包含所有它所能支持的Sec-WebSocket-Version头字段。 在这时候客户端可以根据服务端返回的服务端支持的版本,重新构建请求发起握手。
下面是一个服务端返回的响应头信息:

HTTP/1.1 400 Bad Request
Sec-WebSocket-Version: 13, 8

在收到请求后服务端作为回应会返回如下的报文:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

101表示服务器收到了客户端切换协议的请求,并且同意切换到此协议,Sec-WebSocket-Accept的生成方式在上面已经介绍过了,通过这个步骤,客户端和服务端会建立起一个长连接。

客户端收到服务端发送过来的应答数据后,如果返回的状态码为101,则可以开始解析header域:

* 判断是否含有Upgrade头,且内容包含websocket。
* 判断是否含有Connection头,且内容包含Upgrade
* 判断是否含有Sec-WebSocket-Accept头,并对这个字段进行校验。
* 如果含有Sec-WebSocket-Extensions头,要判断是否之前的Request握手带有此内容,如果没有,则连接失败。
* 如果含有Sec-WebSocket-Protocol头,要判断是否之前的Request握手带有此协议,如果没有,则连接失败。

上面最关键的就是Sec-WebSocket-Accept字段的校验。

WebSocket数据通信

WebSocket 会把应用的消息分割成一个或多个帧,接收方接到到多个帧会进行组装,等到接收到完整消息之后再通知接收端。

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
FIN      1bit 表示信息的最后一帧,flag,也就是标记符
RSV 1-3 1bit each 以后备用的 默认都为 0
Opcode 4bit 帧类型,稍后细说
Mask 1bit 掩码,是否加密数据,只适用于客户端发给服务器的消息,客户端给服务器发送消息,这里一定为 1
Payload 7bit 数据的长度
Masking-key 1 or 4 bit 掩码Key
Payload data (x + y) bytes 数据
Extension data x bytes 扩展数据
Application data y bytes 程序数据

Opcode 字段代表的意思如下所示:

%x0:表示一个延续帧。当Opcode为0时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。
%x1:表示这是一个文本帧(frame)
%x2:表示这是一个二进制帧(frame)
%x3-7:保留的操作代码,用于后续定义的非控制帧。
%x8:表示连接断开。
%x9:表示这是一个ping操作。
%xA:表示这是一个pong操作。
%xB-F:保留的操作代码,用于后续定义的控制帧。

WebSocket分帧规则

分帧是通过将消息分割为更小的一个个分段以更好地共享输出通道。

RFC 6455 规定的分帧规则如下:

1. 一个没有分片的消息由一个带有FIN值为1以及一个非0操作码的帧组成。
2. 一个分片的消息由单个带有FIN为0 和一个非0操作码的帧组成,跟随零个或多个带有FIN位为0和操作码设置为0的帧,且终止于一个带有FIN等于1且操作码为0的帧。
3. 消息分片必须按发送者发送顺序交付给接收者,片段中的消息不能与片段中的另一个消息交替。
4. 控制帧本身必须不被分割,中间件必须不尝试改变控制帧的分片。
5. 一个端点必须能处理一个分片消息中间的控制帧。

也就是说一个分帧后的数据可能会以如下形式呈现:

开始帧 :单个帧,FIN 设为 0,opcode 非 0;
中间帧 :0 个或多个帧,FIN 设为 0,opcode 设为 0;
结束帧:单个帧,FIN 设为 1,opcode 设为 0的帧。

其中开始帧和结束帧可以带数据也可以不带数据

WebSocket采用排队的机制,希望发送出去的数据会先丢到数据缓存区中,然后按照排队的顺序进行发送。

SRWebSocket 源码解析

上层使用方法

SRWebSocket 整个代码量其实不多,就两个文件,在使用上也十分方便,几行代码就可以完成SRWebSocket的接入:

- (void)connectWebSocketServer:(NSString *)server port:(NSString *)port {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"ws://%@:%@",server,port]]];
_socket = [[SRWebSocket alloc] initWithURLRequest:request];
_socket.delegate = self;
[_socket open];
}

- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {

}

- (void)webSocketDidOpen:(SRWebSocket *)webSocket{

}

- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {

}

- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {

}

有了上面WebSocket原理的介绍对整个代码的理解会轻松很多,接下来我们就逐步对SRWebSocket进行解析:

1. SRWebSocket初始化

- (id)initWithURLRequest:(NSURLRequest *)request
protocols:(NSArray *)protocols
allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates {

if (self = [super init]) {
assert(request.URL);
_url = request.URL;
_urlRequest = request;
_allowsUntrustedSSLCertificates = allowsUntrustedSSLCertificates;
_requestedProtocols = [protocols copy];
[self _SR_commonInit];
}
return self;
}

initWithURLRequest最关键的部分在于对****_SR_commonInit****方法的调用,其中_allowsUntrustedSSLCertificates表示是否允许未经信任的SSL证书,_requestedProtocols就是上面提到的WebSocket子协议。

- (void)_SR_commonInit {

//校验URL的scheme,必须是ws,http,wss,https
NSString *scheme = _url.scheme.lowercaseString;
assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]);

//是否是安全的请求
if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) {
_secure = YES;
}

// 初始化状态为SR_CONNECTING
_readyState = SR_CONNECTING;
_consumerStopped = YES;
//webSocket 版本
_webSocketVersion = 13;

//初始化串行工作队列
_workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(_workQueue, (__bridge void *)self, maybe_bridge(_workQueue), NULL);

//设置代理queue为主队列
_delegateDispatchQueue = dispatch_get_main_queue();
sr_dispatch_retain(_delegateDispatchQueue);

//读写缓存
_readBuffer = [[NSMutableData alloc] init];
_outputBuffer = [[NSMutableData alloc] init];
//当前数据帧
_currentFrameData = [[NSMutableData alloc] init];

//消费者队列
_consumers = [[NSMutableArray alloc] init];

_consumerPool = [[SRIOConsumerPool alloc] init];

//RunLoopes
_scheduledRunloops = [[NSMutableSet alloc] init];
[self _initializeStreams];
}

_SR_commonInit 在最开始的时候对当前协议进行了校验,如果不是ws,http,wss,https,就会报错。而后就是WebSocket状态的设置,以及WebSocket版本的设置。紧接着初始化工作队列_workQueue,WebSocket工作在串行队列中,后续在介绍到数据收发的时候会介绍到这个队列。至于为什么是串行队列,通过上面的理论介绍估计大家心里已经有答案了,但是不急后面会给大家详细介绍_workQueue在数据收发过程中的使用。再接着就是_delegateDispatchQueue的初始化,它是一个运行在主线程上的一个队列。再接着就是读写缓存的初始化,以及消费者数组,对应Runloop 的初始化。

- (void)_initializeStreams {
//断言 port值小于UINT32_MAX
assert(_url.port.unsignedIntValue <= UINT32_MAX);
uint32_t port = _url.port.unsignedIntValue;
//如果没有指定port值则使用webSocket默认的端口值
if (port == 0) {
if (!_secure) {
port = 80;
} else {
port = 443;
}
}
NSString *host = _url.host;

CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;

//CFStreamCreatePairWithSocketToHost接口用于创建一对Socket stream,一个用于读取,一个用于写入
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);

_outputStream = CFBridgingRelease(writeStream);
_inputStream = CFBridgingRelease(readStream);

//代理设为自己
_inputStream.delegate = self;
_outputStream.delegate = self;
}

_initializeStreams 最主要的是通过CFStreamCreatePairWithSocketToHost接口创建一对Socket stream,一个用于读取(readStream),一个用于写入(writeStream)并为读写stream。设置对应的代理。

到此为止整个初始化过程结束,最主要的工作如下:

  1. 串行工作队列_workQueue的创建
  2. 代理分发队列_delegateDispatchQueue的创建
  3. 输入输出缓存 _readBuffer,_outputBuffer的创建
  4. 消费者数组_consumers的创建
  5. WebSocket 相关的runloop的创建
  6. 输入输出流的创建,以及对应代理NSStreamDelegate的设置

NSStreamDelegate定义如下所示:

````
@protocol NSStreamDelegate
@optional

  • (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode;
    @end

typedef NS_OPTIONS(NSUInteger, NSStreamEvent) {
NSStreamEventNone = 0,
NSStreamEventOpenCompleted = 1UL << 0,
NSStreamEventHasBytesAvailable = 1UL << 1,
NSStreamEventHasSpaceAvailable = 1UL << 2,
NSStreamEventErrorOccurred = 1UL << 3,
NSStreamEventEndEncountered = 1UL << 4
};


****2. 开启连接****

  • (void)open {

    assert(_url);
    NSAssert(_readyState == SR_CONNECTING, @”Cannot call -(void)open on SRWebSocket more than once”);

    //如果有指定超时时间则延迟指定超时时间后查看状态是否还是SR_CONNECTING 如果是则返回超时消息
    _selfRetain = self;
    if (_urlRequest.timeoutInterval > 0) {
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, _urlRequest.timeoutInterval * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    if (self.readyState == SR_CONNECTING)
    [self _failWithError:[NSError errorWithDomain:@”com.squareup.SocketRocket” code:504 userInfo:@{NSLocalizedDescriptionKey: @”Timeout Connecting to Server”}]];
    });
    }

    //执行连接任务
    [self openConnection];

}


  • (void)openConnection {

    //更新安全、流配置
    [self _updateSecureStreamOptions];

    //判断有没有runloop,如果没有的话就新建对应的runloop
    if (!_scheduledRunloops.count) {
    [self scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode];
    }

    //开启输入输出流
    [_outputStream open];
    [_inputStream open];

}


这里最关键有三个部分:

1. ****_updateSecureStreamOptions****

如果是****wss://****类型的WebSocket
  • (void)_updateSecureStreamOptions {
    if (_secure) {
    NSMutableDictionary *SSLOptions = [[NSMutableDictionary alloc] init];
    [_outputStream setProperty:(__bridge id)kCFStreamSocketSecurityLevelNegotiatedSSL
    forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel];
    // 如果我们正在使用pinned certs 我们将不对证书链进行验证
    if ([_urlRequest SR_SSLPinnedCertificates].count) {
    [SSLOptions setValue:@NO forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain];
    }
    #if DEBUG
    //如果是debug模式则允许非信任的证书
    self.allowsUntrustedSSLCertificates = YES;
    #endif
    //如果允许非信任的证书则将配置设置到SSLOptions
    if (self.allowsUntrustedSSLCertificates) {
    [SSLOptions setValue:@NO forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain];
    SRFastLog(@”Allowing connection to any root cert”);
    }
    //将SSLOptions设置为输出流设置属性
    [_outputStream setProperty:SSLOptions
    forKey:(__bridge id)kCFStreamPropertySSLSettings];
    }
    //重新设置输入流和输出流的delegate
    _inputStream.delegate = self;
    _outputStream.delegate = self;
    //为输入流和输出流设置networkServiceType
    [self setupNetworkServiceType:_urlRequest.networkServiceType];
    }
_updateSecureStreamOptions 只针对安全流才需要设置的,在这里会根据SR_SSLPinnedCertificates是否存在,以及allowsUntrustedSSLCertificates值设置_outputStream的SSL设置。然后重新设置_inputStream以及_outputStream的delegate。

  • (void)setupNetworkServiceType:(NSURLRequestNetworkServiceType)requestNetworkServiceType {
    NSString *networkServiceType;
    switch (requestNetworkServiceType) {
    case NSURLNetworkServiceTypeDefault:
    break;
    case NSURLNetworkServiceTypeVoIP: {
    networkServiceType = NSStreamNetworkServiceTypeVoIP;
    #if TARGET_OS_IPHONE && __IPHONE_9_0
    if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_8_3) {
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
    NSLog(@”SocketRocket: %@ - this service type is deprecated in favor of using PushKit for VoIP control”, networkServiceType);
    });
    }
    #endif
    break;
    }
    case NSURLNetworkServiceTypeVideo:
    networkServiceType = NSStreamNetworkServiceTypeVideo;
    break;
    case NSURLNetworkServiceTypeBackground:
    networkServiceType = NSStreamNetworkServiceTypeBackground;
    break;
    case NSURLNetworkServiceTypeVoice:
    networkServiceType = NSStreamNetworkServiceTypeVoice;
    break;
    }
    if (networkServiceType != nil) {
    [_inputStream setProperty:networkServiceType forKey:NSStreamNetworkServiceType];
    [_outputStream setProperty:networkServiceType forKey:NSStreamNetworkServiceType];
    }
    }

    然后再设置_inputStream 以及 _outputStream 的 NSStreamNetworkServiceType属性。

    2. ****_scheduledRunloops****

  • (NSRunLoop *)SR_networkRunLoop {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    networkThread = [[_SRRunLoopThread alloc] init];
    networkThread.name = @”com.squareup.SocketRocket.NetworkThread”;
    [networkThread start];
    networkRunLoop = networkThread.runLoop;
    });
    return networkRunLoop;
    }

    在SR_networkRunLoop方法中会新创建一个NSThread的线程,然后持有这个线程的runLoop作为networkRunLoop,由于这个线程是通过单例创建的所以,在整个应用生命周期都会存在。提到networkThread不得不提_workQueue,networkThread主要用于支持读写的工作线程,_workQueue是用于处理控制任务的队列,两者单独分开,各司其职。这样做的目的主要是为了避免数据的读写任务阻塞了控制任务,从而影响了事件的实时性。

    3. ****_outputStream/_inputStream open****

    调用_outputStream/_inputStream open之后端口就会被打开,这时候输入输出流就会有数据通过代理传递进来。


    ****3. 通过代理处理数据****

  • (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
    __weak typeof(self) weakSelf = self;

    //如果协议是wss://类型而且_pinnedCertFound 为NO,并且事件类型是有可读数据未读,或者事件类型是还有空余空间可写
    if (_secure && !_pinnedCertFound && (eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) {
    //获取到SSLPinnedCertificates
    NSArray *sslCerts = [_urlRequest SR_SSLPinnedCertificates];
    if (sslCerts) {
    将NSStream中的证书与服务端请求中的SSLPinnedCertificates一个一个进行比较,查看是否有找到匹配的。
    SecTrustRef secTrust = (__bridge SecTrustRef)[aStream propertyForKey:(__bridge id)kCFStreamPropertySSLPeerTrust];
    if (secTrust) {
    NSInteger numCerts = SecTrustGetCertificateCount(secTrust);
    for (NSInteger i = 0; i < numCerts && !_pinnedCertFound; i++) {
    SecCertificateRef cert = SecTrustGetCertificateAtIndex(secTrust, i);
    NSData *certData = CFBridgingRelease(SecCertificateCopyData(cert));

    for (id ref in sslCerts) {
    SecCertificateRef trustedCert = (__bridge SecCertificateRef)ref;
    NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(trustedCert));

    if ([trustedCertData isEqualToData:certData]) {
    _pinnedCertFound = YES;
    break;
    }
    }
    }
    }
    if (!_pinnedCertFound) {
    //服务端证书无效
    dispatch_async(_workQueue, ^{
    NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : @”Invalid server cert” };
    [weakSelf _failWithError:[NSError errorWithDomain:@”org.lolrus.SocketRocket” code:23556 userInfo:userInfo]];
    });
    return;
    } else if (aStream == _outputStream) {
    //如果流是输出流,则打开流成功
    dispatch_async(_workQueue, ^{
    [self didConnect];
    });
    }
    }
    }

    dispatch_async(_workQueue, ^{
    //将当前数据交给_workQueue处理
    [weakSelf safeHandleEvent:eventCode stream:aStream];
    });

}


  • (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode

    是数据的主要集中地,对于未认证的请求,会先对服务端的证书进行认证,如果认证成功会调用didConnect进行握手,然后不论是什么类型的请求,都会进过safeHandleEvent将数据分发出去。接下来我们重点看下didConnect以及safeHandleEvent:

  • (void)didConnect {

    // 设置GET请求,并指定HTTP 协议版本为1.1
    CFHTTPMessageRef request = CFHTTPMessageCreateRequest(NULL, CFSTR(“GET”), (__bridge CFURLRef)_url, kCFHTTPVersion1_1);

    // 设置Host字段
    CFHTTPMessageSetHeaderFieldValue(request, CFSTR(“Host”), (__bridge CFStringRef)(_url.port ? [NSString stringWithFormat:@”%@:%@”, _url.host, _url.port] : _url.host));

    //生成随机数
    NSMutableData *keyBytes = [[NSMutableData alloc] initWithLength:16];
    SecRandomCopyBytes(kSecRandomDefault, keyBytes.length, keyBytes.mutableBytes);

    //Base64加密
    if ([keyBytes respondsToSelector:@selector(base64EncodedStringWithOptions:)]) {
    _secKey = [keyBytes base64EncodedStringWithOptions:0];
    } else {

#pragma clang diagnostic push
#pragma clang diagnostic ignored “-Wdeprecated-declarations”
_secKey = [keyBytes base64Encoding];
#pragma clang diagnostic pop
}

assert([_secKey length] == 24);

// Apply cookies if any have been provided
NSDictionary * cookies = [NSHTTPCookie requestHeaderFieldsWithCookies:[self requestCookies]];
for (NSString * cookieKey in cookies) {
    NSString * cookieValue = [cookies objectForKey:cookieKey];
    if ([cookieKey length] && [cookieValue length]) {
        CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)cookieKey, (__bridge CFStringRef)cookieValue);
    }
}

// 添加认证字段
if (_url.user.length && _url.password.length) {
    NSData *userAndPassword = [[NSString stringWithFormat:@"%@:%@", _url.user, _url.password] dataUsingEncoding:NSUTF8StringEncoding];
    NSString *userAndPasswordBase64Encoded;
    if ([keyBytes respondsToSelector:@selector(base64EncodedStringWithOptions:)]) {
        userAndPasswordBase64Encoded = [userAndPassword base64EncodedStringWithOptions:0];
    } else {

#pragma clang diagnostic push
#pragma clang diagnostic ignored “-Wdeprecated-declarations”
userAndPasswordBase64Encoded = [userAndPassword base64Encoding];
#pragma clang diagnostic pop
}
_basicAuthorizationString = [NSString stringWithFormat:@”Basic %@”, userAndPasswordBase64Encoded];
CFHTTPMessageSetHeaderFieldValue(request, CFSTR(“Authorization”), (__bridge CFStringRef)_basicAuthorizationString);
}

CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Upgrade"), CFSTR("websocket"));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Upgrade"));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), (__bridge CFStringRef)_secKey);
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Version"), (__bridge CFStringRef)[NSString stringWithFormat:@"%ld", (long)_webSocketVersion]);
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)_url.SR_origin);

//追加子协议
if (_requestedProtocols) {
    CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Protocol"), (__bridge CFStringRef)[_requestedProtocols componentsJoinedByString:@", "]);
}

//添加http头
[_urlRequest.allHTTPHeaderFields enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
    CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
}];

NSData *message = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(request));

CFRelease(request);

//将数据送出
[self _writeData:message];
//指派数据消费者等待响应头部准备就绪后读取HttpHeader
[self _readHTTPHeader];

}


上述的****didConnect****其实就是之前介绍的WebSocket握手阶段。它会构建出一个Http 1.1 的请求头,然后发送出去,并分配数据消费者,等待消费服务端返回的应答头部。

  • (void)_readHTTPHeader {
    if (_receivedHTTPHeaders == NULL) {
    _receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO);
    }
    //等待头数据读取完毕后执行下面的block
    [self _readUntilHeaderCompleteWithCallback:^(SRWebSocket *self, NSData *data) {
    //将获取到的数据追加到_receivedHTTPHeaders
    CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length);
    //判断请求头响应部分是否已经读取完毕
    if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) {
    // 如果读取完毕则调用_HTTPHeadersDidFinish
    [self _HTTPHeadersDidFinish];
    } else {
    // 否则继续读返回的头部数据
    [self _readHTTPHeader];
    }
    }];
    }


  • (void)_HTTPHeadersDidFinish {

    //获取返回的状态码
    NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(_receivedHTTPHeaders);

    //如果大于400 表示握手失败
    if (responseCode >= 400) {
    SRFastLog(@”Request failed with response code %d”, responseCode);
    [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2132 userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@”received bad response code from server %ld”, (long)responseCode], SRHTTPResponseErrorKey:@(responseCode)}]];
    return;
    }

    //检查服务器返回的Sec-WebSocket-Accept字段是否正确
    if(![self _checkHandshake:_receivedHTTPHeaders]) {
    [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@”Invalid Sec-WebSocket-Accept response”] forKey:NSLocalizedDescriptionKey]]];
    return;
    }

    //对子协议进行校验
    NSString *negotiatedProtocol = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(_receivedHTTPHeaders, CFSTR(“Sec-WebSocket-Protocol”)));
    if (negotiatedProtocol) {
    // Make sure we requested the protocol
    if ([_requestedProtocols indexOfObject:negotiatedProtocol] == NSNotFound) {
    [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@”Server specified Sec-WebSocket-Protocol that wasn’t requested”] forKey:NSLocalizedDescriptionKey]]];
    return;
    }

    _protocol = negotiatedProtocol;
    }

    //上述校验结束后代表整个WebSocket已经打开了
    self.readyState = SR_OPEN;

    //如果没有错误发生则读取新的帧
    if (!_didFail) {
    [self _readFrameNew];
    }

    //通过代理通知业务层WebSocket已经打开了
    [self _performDelegateBlock:^{
    if ([self.delegate respondsToSelector:@selector(webSocketDidOpen:)]) {
    [self.delegate webSocketDidOpen:self];
    };
    }];

}


我们接下来看下每次数据进来是怎么进行交付的。我们再回到:

  • (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
    除了上述的认证过程外,还有个十分重要的方法safeHandleEvent:stream,每个数据进来都会通过这个方法进行处理:

  • (void)safeHandleEvent:(NSStreamEvent)eventCode stream:(NSStream *)aStream {
    switch (eventCode) {
    case NSStreamEventOpenCompleted: {
    SRFastLog(@”NSStreamEventOpenCompleted %@”, aStream);
    if (self.readyState >= SR_CLOSING) {
    return;
    }
    assert(_readBuffer);

    // didConnect fires after certificate verification if we’re using pinned certificates.
    BOOL usingPinnedCerts = [[_urlRequest SR_SSLPinnedCertificates] count] > 0;
    if ((!_secure || !usingPinnedCerts) && self.readyState == SR_CONNECTING && aStream == _inputStream) {
    [self didConnect];
    }
    [self _pumpWriting];
    [self _pumpScanner];
    break;
    }

    case NSStreamEventErrorOccurred: {
    SRFastLog(@”NSStreamEventErrorOccurred %@ %@”, aStream, [[aStream streamError] copy]);
    /// TODO specify error better!
    [self _failWithError:aStream.streamError];
    _readBufferOffset = 0;
    [_readBuffer setLength:0];
    break;

    }

    case NSStreamEventEndEncountered: {
    [self _pumpScanner];
    SRFastLog(@”NSStreamEventEndEncountered %@”, aStream);
    if (aStream.streamError) {
    [self _failWithError:aStream.streamError];
    } else {
    dispatch_async(_workQueue, ^{
    if (self.readyState != SR_CLOSED) {
    self.readyState = SR_CLOSED;
    [self _scheduleCleanup];
    }

    if (!_sentClose && !_failed) {
    _sentClose = YES;
    // If we get closed in this state it’s probably not clean because we should be sending this when we send messages
    [self _performDelegateBlock:^{
    if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) {
    [self.delegate webSocket:self didCloseWithCode:SRStatusCodeGoingAway reason:@”Stream end encountered” wasClean:NO];
    }
    }];
    }
    });
    }

    break;
    }

    case NSStreamEventHasBytesAvailable: {
    SRFastLog(@”NSStreamEventHasBytesAvailable %@”, aStream);
    const int bufferSize = 2048;
    uint8_t buffer[bufferSize];

    while (_inputStream.hasBytesAvailable) {
    NSInteger bytes_read = [_inputStream read:buffer maxLength:bufferSize];

    if (bytes_read > 0) {
    [_readBuffer appendBytes:buffer length:bytes_read];
    } else if (bytes_read < 0) {
    [self _failWithError:_inputStream.streamError];
    }

    if (bytes_read != bufferSize) {
    break;
    }
    };
    [self _pumpScanner];
    break;
    }

    case NSStreamEventHasSpaceAvailable: {
    SRFastLog(@”NSStreamEventHasSpaceAvailable %@”, aStream);
    [self _pumpWriting];
    break;
    }

    default:
    SRFastLog(@”(default) %@”, aStream);
    break;
    }

}


这里有个十分关键的类型:****NSStreamEvent****用于表示当前消息的类型:

typedef NS_OPTIONS(NSUInteger, NSStreamEvent) {
NSStreamEventNone = 0,
NSStreamEventOpenCompleted = 1UL << 0, //打开完成
NSStreamEventHasBytesAvailable = 1UL << 1, //流中有数据可读
NSStreamEventHasSpaceAvailable = 1UL << 2, //缓存中有空间可写
NSStreamEventErrorOccurred = 1UL << 3, //遇到错误
NSStreamEventEndEncountered = 1UL << 4 //遇到结束符
};


我们这里先看下 ****NSStreamEventOpenCompleted****,后面介绍读写的时候还会接着介绍****NSStreamEventHasBytesAvailable********NSStreamEventHasSpaceAvailable****这两个事件。

//连接完成
case NSStreamEventOpenCompleted: {
SRFastLog(@”NSStreamEventOpenCompleted %@”, aStream);
//如果就绪状态为关闭或者正在关闭,直接返回
if (self.readyState >= SR_CLOSING) {
return;
}
assert(_readBuffer);

//如果是ws,或者无自签证书,而且是正准备连接,而且aStream是输入流
// didConnect fires after certificate verification if we're using pinned certificates.
BOOL usingPinnedCerts = [[_urlRequest SR_SSLPinnedCertificates] count] > 0;
if ((!_secure || !usingPinnedCerts) && self.readyState == SR_CONNECTING && aStream == _inputStream) {
    //进行握手连接
    [self didConnect];
}
//开始写数据
[self _pumpWriting];
//读取应答数据
[self _pumpScanner];
break;

}

之前介绍的是wss协议类型的情况,这里则是ws或者无自签名证书的情形,主要流程还是类似的。


上面介绍了从初始化,到建立连接,再到握手的整个流程,在介绍WebSocket读写数据之前,我们再从头梳理下这个阶段的关键过程:

  1. 最开始我们会对协议类型进行校验,判断是否是ws,http,wss,https这些类型的一种
  2. 设置当前状态为SR_CONNECTING
  3. 创建_workQueue,networkThread以及_delegateDispatchQueue
  4. 初始化读写缓存_readBuffer,_outputBuffer,消费者数组_consumers以及消费对象池_consumerPool。
  5. 通过CFStreamCreatePairWithSocketToHost创建读写数据流readStream,writeStream。并设置读写流代理,指定对应的Runloop,如果是wss协议,还要更新流的安全属性, 上面设置完毕后调用输入输出流的open方法打开流。
  6. 流打开后就会有数据从代理进来,对于wss类型的协议,如果证书还没找到,那么会进行证书的匹配,如果匹配成功则调用didConnect,在didConnect中发送握手消息头到服务端,进行握手通信。
  7. 通过数据消费者从缓存中读取应答头数据,这时候分别对服务端返回的头部的状态码,Sec-WebSocket-Accept,子协议进行校验,如果校验通过则将状态设置为SR_OPEN并通过代理webSocketDidOpen通知业务层websocket已经成功连接。

****3. 数据读写****

最后剩下一个问题,数据是怎样从输入流读入,怎样从输出流发出,数据消费者在整个过程中扮演的角色,workQueue,networkThread在整个过程扮演的角色。

我们来看下****NSStreamEventHasBytesAvailable****用于表示可以从缓存中读取数据了:

case NSStreamEventHasBytesAvailable: {
SRFastLog(@”NSStreamEventHasBytesAvailable %@”, aStream);
const int bufferSize = 2048;
uint8_t buffer[bufferSize];

while (_inputStream.hasBytesAvailable) {
    //从输入流中读取数据到缓存中
    NSInteger bytes_read = [_inputStream read:buffer maxLength:bufferSize];
    if (bytes_read > 0) {
        //将数据追加到读缓存中
        [_readBuffer appendBytes:buffer length:bytes_read];
    } else if (bytes_read < 0) {
        [self _failWithError:_inputStream.streamError];
    }
    //如果读取的不等于最大的,说明读完了,跳出循环
    if (bytes_read != bufferSize) {
        break;
    }
};
//开始扫描,看消费者什么时候消费数据
[self _pumpScanner];
break;

}


-(void)_pumpScanner {
[self assertOnWorkQueue];
//判断是否在扫描
if (!_isPumping) {
_isPumping = YES;
} else {
return;
}
while ([self _innerPumpScanner]) {}
_isPumping = NO;
}


  • (BOOL)_innerPumpScanner {

    BOOL didWork = NO;
    //如果当前状态为已关闭 返回NO
    if (self.readyState >= SR_CLOSED) {
    return didWork;
    }
    //如果数据消费者列表为空,返回NO
    if (!_consumers.count) {
    return didWork;
    }

    //读取的buffer长度 - 偏移量 = 可以被读取的数据帧的长度
    size_t curSize = _readBuffer.length - _readBufferOffset;
    //如果未读为空,返回NO
    if (!curSize) {
    return didWork;
    }

    //取出数据消费者队列中的第一个消费者
    SRIOConsumer *consumer = [_consumers objectAtIndex:0];
    //得到需要的字节数
    size_t bytesNeeded = consumer.bytesNeeded;
    //消费者本次找到的要读取的数据大小
    size_t foundSize = 0;

    // 确定foundSize 大小
    //consumer.consumer是指定了用于查找要读取数据的规则。
    if (consumer.consumer) {
    //把未读数据从readBuffer中赋值到tempView里,待consumer.consumer按照它的规则进行查找要读取的数据
    NSData *tempView = [NSData dataWithBytesNoCopy:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset freeWhenDone:NO];
    //得到要读取的数据大小
    foundSize = consumer.consumer(tempView);
    } else {
    //如果没有指定查找规则则按bytesNeeded来确定foundSize
    assert(consumer.bytesNeeded);
    //如果未读字节大于需要字节,直接等于需要字节
    if (curSize >= bytesNeeded) {
    foundSize = bytesNeeded;
    }
    //如果为读取当前帧
    else if (consumer.readToCurrentFrame) {
    //消费大小等于当前未读字节
    foundSize = curSize;
    }
    }

    //通过上面的foundSize得到需要读取的数据,并且释放已读的空间
    NSData *slice = nil;
    //如果读取当前帧或者foundSize大于0
    if (consumer.readToCurrentFrame || foundSize) {
    //从已读偏移到要读的字节处
    NSRange sliceRange = NSMakeRange(_readBufferOffset, foundSize);
    //得到data
    slice = [_readBuffer subdataWithRange:sliceRange];
    //增加已读偏移
    _readBufferOffset += foundSize;
    //如果读取偏移的大小大于4096,或者读取偏移大于 1/2的buffer大小
    if (_readBufferOffset > 4096 && _readBufferOffset > (_readBuffer.length >> 1)) {
    //重新创建,释放已读那部分的data空间
    _readBuffer = [[NSMutableData alloc] initWithBytes:(char *)_readBuffer.bytes + _readBufferOffset length:_readBuffer.length - _readBufferOffset]; _readBufferOffset = 0;
    }

    //如果用户未掩码的数据
    if (consumer.unmaskBytes) {
    //copy切片
    NSMutableData *mutableSlice = [slice mutableCopy];
    //得到长度字节数
    NSUInteger len = mutableSlice.length;
    uint8_t *bytes = mutableSlice.mutableBytes;

    for (NSUInteger i = 0; i < len; i++) {
    //得到一个读取掩码key,为偏移量_currentReadMaskOffset取余_currentReadMaskKey,当前掩码key,
    //再和字节异或运算(相同为0,不同为1)
    bytes[i] = bytes[i] ^ _currentReadMaskKey[_currentReadMaskOffset % sizeof(_currentReadMaskKey)];
    //偏移量+1
    _currentReadMaskOffset += 1;
    }
    //把数据改成掩码后的
    slice = mutableSlice;
    }

    //如果读取当前帧
    if (consumer.readToCurrentFrame) {
    //拼接数据
    [_currentFrameData appendData:slice];
    //+1
    _readOpCount += 1;
    //判断Opcode,如果是文本帧
    if (_currentFrameOpcode == SROpCodeTextFrame) {
    // Validate UTF8 stuff.
    //得到当前帧数据的长度
    size_t currentDataSize = _currentFrameData.length;
    //如果currentDataSize 大于0
    if (_currentFrameOpcode == SROpCodeTextFrame && currentDataSize > 0) {
    // TODO: Optimize the crap out of this. Don’t really have to copy all the data each time
    //判断需要scan的大小
    size_t scanSize = currentDataSize - _currentStringScanPosition;
    //得到要sacn的data
    NSData *scan_data = [_currentFrameData subdataWithRange:NSMakeRange(_currentStringScanPosition, scanSize)];
    //验证数据
    int32_t valid_utf8_size = validate_dispatch_data_partial_string(scan_data);

    //-1为错误,关闭连接
    if (valid_utf8_size == -1) {
    [self closeWithCode:SRStatusCodeInvalidUTF8 reason:@”Text frames must be valid UTF-8”];
    dispatch_async(_workQueue, ^{
    [self closeConnection];
    });
    return didWork;
    } else {
    //扫描的位置+上合法数据的size
    _currentStringScanPosition += valid_utf8_size;
    }
    }

    }
    //需要的size - 已操作的size
    consumer.bytesNeeded -= foundSize;
    //如果还需要的字节数 = 0,移除消费者
    if (consumer.bytesNeeded == 0) {
    [_consumers removeObjectAtIndex:0];
    //回调读取完成
    consumer.handler(self, nil);//由于读取当前帧的时候数据存放在_currentFrameData,所以这里返回为nil
    //把要返回的consumer,先放在_consumerPool缓冲池中
    [_consumerPool returnConsumer:consumer];
    //已经工作为YES
    didWork = YES;
    }
    } else if (foundSize) {
    //移除消费者
    [_consumers removeObjectAtIndex:0];
    //回调回去当前接受到的数据
    consumer.handler(self, slice);

    [_consumerPool returnConsumer:consumer];
    didWork = YES;
    }
    }
    return didWork;

}


上面是比较详细的代码注释,接下来我们抠出这段代码中关键的部分,让大家对这部分流程更加清晰:

  • (BOOL)_innerPumpScanner {

    //第一部分: 前置条件判断
    //…………….

    //取出数据消费者队列中的第一个消费者
    SRIOConsumer *consumer = [_consumers objectAtIndex:0];

    //第二部分:得到需要的字节数foundSize
    size_t bytesNeeded = consumer.bytesNeeded;
    //消费者本次找到的要读取的数据大小
    size_t foundSize = 0;
    // 确定foundSize 大小
    //consumer.consumer是指定了用于查找要读取数据的规则。
    if (consumer.consumer) {
    //指定了用于查找要读取数据的规则的情况
    } else {
    //读取当前帧或者读取部分数据的情况
    }

    //第三部分:通过上面的foundSize得到需要读取的数据,并且释放已读的空间
    //如果读取当前帧或者foundSize大于0
    if (consumer.readToCurrentFrame || foundSize) {
    //从已读偏移到要读的字节处
    NSRange sliceRange = NSMakeRange(_readBufferOffset, foundSize);
    //得到data
    slice = [_readBuffer subdataWithRange:sliceRange];
    //…………….

    //如果读取当前帧
    if (consumer.readToCurrentFrame) {
    //拼接数据
    [_currentFrameData appendData:slice];
    //…………..
    //当前帧已经读取完毕
    if (consumer.bytesNeeded == 0) {
    [_consumers removeObjectAtIndex:0];
    //回调读取完成
    consumer.handler(self, nil);//由于读取当前帧的时候数据存放在_currentFrameData,所以这里返回为nil
    //把要返回的consumer,先放在_consumerPool缓冲池中
    [_consumerPool returnConsumer:consumer];
    //已经工作为YES
    didWork = YES;
    }
    }
    //非当前帧的情况
    else if (foundSize) {
    //第四部分:将得到所需要数据的消费者从消费者列表中移除,并通过对应的block交付数据,将consumer放到对象池中。
    [_consumers removeObjectAtIndex:0];
    //回调回去当前接受到的数据
    consumer.handler(self, slice);
    [_consumerPool returnConsumer:consumer];
    didWork = YES;
    }
    }
    return didWork;

}


整个流程分成四个步骤:

第一部分:前置条件判断
第二部分:确定消费者需要的字节数foundSize
第三部分:通过上面的foundSize得到需要读取的数据,并且释放已读的空间
第四部分:将得到所需要数据的消费者从消费者列表中移除,并通过对应的block交付数据,将consumer放到对象池中。


再回到NSStreamEventHasBytesAvailable事件的处理中做个总结:

这个事件中,首先将数据从数据流中取出数据然后添加到读缓存中,然后从数据消费者队列中取出一个消费者,让它再读缓存中按需读取出它所需要的数据,并通过block交付给应用层。至于消费者是何时添加的我们放在后面集中介绍,我们接下来看NSStreamEventHasSpaceAvailable

case NSStreamEventHasSpaceAvailable: {
SRFastLog(@”NSStreamEventHasSpaceAvailable %@”, aStream);
[self _pumpWriting];
break;
}


写相对读来说相对简单,这里就不做过多介绍,大家可以直接看下面注释:

  • (void)_writeData:(NSData *)data {
    [self assertOnWorkQueue];

    if (_closeWhenFinishedWriting) {
    return;
    }
    [_outputBuffer appendData:data];
    [self _pumpWriting];

}


  • (void)_pumpWriting {
    [self assertOnWorkQueue];
    //输出缓存大小
    NSUInteger dataLength = _outputBuffer.length;
    //输出缓存和输出流都还有空间剩余
    if (dataLength - _outputBufferOffset > 0 && _outputStream.hasSpaceAvailable) {
    //通过输出缓存输出数据
    NSInteger bytesWritten = [_outputStream write:_outputBuffer.bytes + _outputBufferOffset maxLength:dataLength - _outputBufferOffset];
    //………..
    //修改缓存偏移
    _outputBufferOffset += bytesWritten;
    //……….
    }
    //如果_closeWhenFinishedWriting表示写完数据后关闭连接则走下面流程
    if (_closeWhenFinishedWriting &&
    _outputBuffer.length - _outputBufferOffset == 0 &&
    (_inputStream.streamStatus != NSStreamStatusNotOpen &&
    _inputStream.streamStatus != NSStreamStatusClosed) &&
    !_sentClose) {
    _sentClose = YES;
    @synchronized(self) {
    [_outputStream close];
    [_inputStream close];
    for (NSArray *runLoop in [_scheduledRunloops copy]) {
    [self unscheduleFromRunLoop:[runLoop objectAtIndex:0] forMode:[runLoop objectAtIndex:1]];
    }
    }
    if (!_failed) {
    //通过代理告诉应用层关闭的原因
    [self _performDelegateBlock:^{
    if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) {
    [self.delegate webSocket:self didCloseWithCode:_closeCode reason:_closeReason wasClean:YES];
    }
    }];
    }
    [self _scheduleCleanup];
    }
    }


    我们接下来看下数据消费者是如何管理的:

  • (void)_addConsumerWithDataLength:(size_t)dataLength
    callback:(data_callback)callback
    readToCurrentFrame:(BOOL)readToCurrentFrame
    unmaskBytes:(BOOL)unmaskBytes {
    [self assertOnWorkQueue];
    assert(dataLength);

    [_consumers addObject:[_consumerPool consumerWithScanner:nil
    handler:callback
    bytesNeeded:dataLength
    readToCurrentFrame:readToCurrentFrame
    unmaskBytes:unmaskBytes]];
    [self _pumpScanner];

}


  • (SRIOConsumer *)consumerWithScanner:(stream_scanner)scanner
    handler:(data_callback)handler
    bytesNeeded:(size_t)bytesNeeded
    readToCurrentFrame:(BOOL)readToCurrentFrame
    unmaskBytes:(BOOL)unmaskBytes {
    SRIOConsumer *consumer = nil;
    if (_bufferedConsumers.count) {
    consumer = [_bufferedConsumers lastObject];
    [_bufferedConsumers removeLastObject];
    } else {
    consumer = [[SRIOConsumer alloc] init];
    }

    [consumer setupWithScanner:scanner handler:handler bytesNeeded:bytesNeeded readToCurrentFrame:readToCurrentFrame unmaskBytes:unmaskBytes];

    return consumer;

}


  • (void)returnConsumer:(SRIOConsumer *)consumer {
    if (_bufferedConsumers.count < _poolSize) {
    [_bufferedConsumers addObject:consumer];
    }
    }


    数据消费者是通过****_addConsumerWithDataLength****添加到消费者数组_consumers中的,同时为了避免频繁创建消费者对象这里使用了对象池,默认消费者对象池中的对象为8个,每次用完都会归还到对象池,在需要的时候会从对象池中拿取最后一个返回。

    所以添加消费者对象的时候会从消费者对象池中取出一个闲置的消费者对象,然后添加到消费者队列中,然后调用_pumpScanner,从消费者列表中取出第一个消费者,让它从读缓存中读取需要的数据,并通过handler交付读到的数据。

    SRWebSocket会在如下情况下添加消费者:

  • (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler; // 握手阶段,发送完握手头信息后会去读取服务端返回的响应头

  • (void)_readFrameContinue;持续读帧信息的时候

    也就是每次需要数据的时候都会往消费者队列中添加一个消费者,等到有数据之后会去除这些消费者读取数据,并通过block交付。

    ****读取接收到的连续帧****

    介绍了缓存的读写,读消费者的管理,以及帧格式的介绍后下面我们来看下怎样读取帧和发送帧,首先看下读取连续帧:

  • (void)_readFrameContinue {

    assert((_currentFrameCount == 0 && _currentFrameOpcode == 0) || (_currentFrameCount > 0 && _currentFrameOpcode > 0));

    [self _addConsumerWithDataLength:2 callback:^(SRWebSocket *self, NSData *data) {

      __block frame_header header = {0};
      
      const uint8_t *headerBuffer = data.bytes;
      assert(data.length >= 2);
      
      //读取RSV,默认必须是0
      if (headerBuffer[0] & SRRsvMask) {
          [self _closeWithProtocolError:@"Server used RSV bits"];
          return;
      }
      //获取操作码
      uint8_t receivedOpcode = (SROpCodeMask & headerBuffer[0]);
      //当前帧是否是控制帧
      BOOL isControlFrame = (receivedOpcode == SROpCodePing || receivedOpcode == SROpCodePong || receivedOpcode == SROpCodeConnectionClose);
      
      //中间帧的操作码必须为0
      if (!isControlFrame && receivedOpcode != 0 && self->_currentFrameCount > 0) {
          [self _closeWithProtocolError:@"all data frames after the initial data frame must have opcode 0"];
          return;
      }
      if (receivedOpcode == 0 && self->_currentFrameCount == 0) {
          [self _closeWithProtocolError:@"cannot continue a message"];
          return;
      }
      //操作码赋给header
      header.opcode = receivedOpcode == 0 ? self->_currentFrameOpcode : receivedOpcode;
      //fin结束码赋给header
      header.fin = !!(SRFinMask & headerBuffer[0]);
      //是否mask字段
      header.masked = !!(SRMaskMask & headerBuffer[1]);
      //内容长度
      header.payload_length = SRPayloadLenMask & headerBuffer[1];
      headerBuffer = NULL;
      //客户端收到的数据必须是unmasked数据
      if (header.masked) {
          [self _closeWithProtocolError:@"Client must receive unmasked data"];
      }
      //额外内容
      size_t extra_bytes_needed = header.masked ? sizeof(_currentReadMaskKey) : 0;
      //.........
      if (extra_bytes_needed == 0) {
          [self _handleFrameHeader:header curData:self->_currentFrameData];
      } else {
          //..........
      }
    

    } readToCurrentFrame:NO unmaskBytes:NO];

}


  • (void)_handleFrameHeader:(frame_header)frame_header curData:(NSData *)curData {

    //当前状态检查
    if (self.readyState == SR_CLOSED) {
    return;
    }

    //是否是控制帧
    BOOL isControlFrame = (frame_header.opcode == SROpCodePing || frame_header.opcode == SROpCodePong || frame_header.opcode == SROpCodeConnectionClose);

    //控制帧不能分片
    if (isControlFrame && !frame_header.fin) {
    [self _closeWithProtocolError:@”Fragmented control frames not allowed”];
    return;
    }

    //控制帧大小不能大于126
    if (isControlFrame && frame_header.payload_length >= 126) {
    [self _closeWithProtocolError:@”Control frames cannot have payloads larger than 126 bytes”];
    return;
    }

    if (!isControlFrame) {
    _currentFrameOpcode = frame_header.opcode;
    _currentFrameCount += 1;
    }

    if (frame_header.payload_length == 0) {
    //是控制帧的情况
    if (isControlFrame) {
    [self _handleFrameWithData:curData opCode:frame_header.opcode];
    } else {
    if (frame_header.fin) {
    //数据帧读取完毕
    [self _handleFrameWithData:_currentFrameData opCode:frame_header.opcode];
    } else {
    //数据帧还未读取完毕
    [self _readFrameContinue];
    }
    }
    } else {
    //有负载数据
    assert(frame_header.payload_length <= SIZE_T_MAX);
    [self _addConsumerWithDataLength:(size_t)frame_header.payload_length callback:^(SRWebSocket *self, NSData *newData) {
    //是控制帧的情况
    if (isControlFrame) {
    [self _handleFrameWithData:newData opCode:frame_header.opcode];
    } else {
    if (frame_header.fin) {
    //数据帧读取完毕
    [self _handleFrameWithData:self->_currentFrameData opCode:frame_header.opcode];
    } else {
    //数据帧还未读取完毕
    [self _readFrameContinue];
    }

    }
    } readToCurrentFrame:!isControlFrame unmaskBytes:frame_header.masked];
    }

}


  • (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode {
    // Check that the current data is valid UTF8

    BOOL isControlFrame = (opcode == SROpCodePing || opcode == SROpCodePong || opcode == SROpCodeConnectionClose);
    //………….
    switch (opcode) {
    //文本流
    case SROpCodeTextFrame: {
    //如果当前类型为文本类型,是否转为string
    if ([self.delegate respondsToSelector:@selector(webSocketShouldConvertTextFrameToString:)] && ![self.delegate webSocketShouldConvertTextFrameToString:self]) {
    //不转为string
    [self _handleMessage:[frameData copy]];
    } else {
    //转为string
    NSString *str = [[NSString alloc] initWithData:frameData encoding:NSUTF8StringEncoding];
    //有数据但是转换后string为空,表明转换失败
    if (str == nil && frameData) {
    [self closeWithCode:SRStatusCodeInvalidUTF8 reason:@”Text frames must be valid UTF-8”];
    dispatch_async(_workQueue, ^{
    [self closeConnection];
    });
    return;
    }
    [self _handleMessage:str];
    }
    break;
    }
    case SROpCodeBinaryFrame:
    //二进制流
    [self _handleMessage:[frameData copy]];
    break;
    //关闭消息
    case SROpCodeConnectionClose:
    [self handleCloseWithData:[frameData copy]];
    break;
    // ping
    case SROpCodePing:
    [self handlePing:[frameData copy]];
    break;
    // pong
    case SROpCodePong:
    [self handlePong:[frameData copy]];
    break;
    default:
    [self _closeWithProtocolError:[NSString stringWithFormat:@”Unknown opcode %ld”, (long)opcode]];
    // TODO: Handle invalid opcode
    break;
    }

}



  • (void)_handleMessage:(id)message {
    SRFastLog(@”Received message”);
    [self _performDelegateBlock:^{
    [self.delegate webSocket:self didReceiveMessage:message];
    }];
    }

    上面将所有读连续帧的代码都贴出来了,并做了对应的注释,下面我们来做个总结,****_readFrameContinue****会先读取帧的前两个字节取出fin,rsv,opcode,payload len,并对这些字段进行校验。****_handleFrameHeader****继续读取payload数据,再将数据以及opcode送到****_handleFrameWithData**** 通过 ****_handleMessage**** 将消息送到业务层。从这里可以看出虽然WebSocket会将数据进行分包,但是SRWebSocket会再底层将这些分包进行拼接后将一个完整的数据交付给业务层。


    ****发送帧数据****

  • (void)send:(id)data {
    data = [data copy];
    dispatch_async(_workQueue, ^{
    if ([data isKindOfClass:[NSString class]]) {
    [self _sendFrameWithOpcode:SROpCodeTextFrame data:[(NSString *)data dataUsingEncoding:NSUTF8StringEncoding]];
    } else if ([data isKindOfClass:[NSData class]]) {
    [self _sendFrameWithOpcode:SROpCodeBinaryFrame data:data];
    } else if (data == nil) {
    [self _sendFrameWithOpcode:SROpCodeTextFrame data:data];
    } else {
    assert(NO);
    }
    });
    }

  • (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data {

    //….

    size_t payloadLength = [data isKindOfClass:[NSString class]] ? [(NSString *)data lengthOfBytesUsingEncoding:NSUTF8StringEncoding] : [data length];

    NSMutableData *frame = [[NSMutableData alloc] initWithLength:payloadLength + SRFrameHeaderOverhead];

    //…..

    uint8_t *frame_buffer = (uint8_t *)[frame mutableBytes];

    // 设置fin
    frame_buffer[0] = SRFinMask | opcode;

    BOOL useMask = YES;

#ifdef NOMASK
useMask = NO;
#endif

// 客户端发送给服务端的必须使用mask
if (useMask) {
// set the mask and header
    frame_buffer[1] |= SRMaskMask;
}

size_t frame_buffer_size = 2;

//填充数据
const uint8_t *unmasked_payload = NULL;
if ([data isKindOfClass:[NSData class]]) {
    unmasked_payload = (uint8_t *)[data bytes];
} else if ([data isKindOfClass:[NSString class]]) {
    unmasked_payload =  (const uint8_t *)[data UTF8String];
} else {
    return;
}

//设置payload字段
if (payloadLength < 126) {
    frame_buffer[1] |= payloadLength;
} else if (payloadLength <= UINT16_MAX) {
    frame_buffer[1] |= 126;
    *((uint16_t *)(frame_buffer + frame_buffer_size)) = EndianU16_BtoN((uint16_t)payloadLength);
    frame_buffer_size += sizeof(uint16_t);
} else {
    frame_buffer[1] |= 127;
    *((uint64_t *)(frame_buffer + frame_buffer_size)) = EndianU64_BtoN((uint64_t)payloadLength);
    frame_buffer_size += sizeof(uint64_t);
}
    
if (!useMask) {
    for (size_t i = 0; i < payloadLength; i++) {
        frame_buffer[frame_buffer_size] = unmasked_payload[i];
        frame_buffer_size += 1;
    }
} else {
    //对数据进行mark 处理
    uint8_t *mask_key = frame_buffer + frame_buffer_size;
    SecRandomCopyBytes(kSecRandomDefault, sizeof(uint32_t), (uint8_t *)mask_key);
    frame_buffer_size += sizeof(uint32_t);
    
    // TODO: could probably optimize this with SIMD
    for (size_t i = 0; i < payloadLength; i++) {
        frame_buffer[frame_buffer_size] = unmasked_payload[i] ^ mask_key[i % sizeof(uint32_t)];
        frame_buffer_size += 1;
    }
}

assert(frame_buffer_size <= [frame length]);
frame.length = frame_buffer_size;

//调用_writeData 写到流当中
[self _writeData:frame];

}

```

发送帧流程比较简单,就是构造帧数据,然后将数据塞入帧中,往输出流写。

之前再规划这篇博客的时候本来只想简单介绍下SRWebSocket的但是写着写着,发现它还是满有意思的,特别是数据消费者部分的设计提起了兴趣,所以还是硬着头皮看将整个代码过了一遍,下面是一些比较好的文章,可以供大家深入阅读,个人觉得只要弄清楚了WebSocket理论基础后看这部分代码会显得十分轻松,希望这篇博客能够帮助到大家。好了今天就到这里。

较好的文章推荐

开源库信息

AFNetWorking 源码解析

AFNetWorking 组成

AFNetWorking 主要由如下图几个部分构成

  • AFURLSessionManager

AFURLSessionManager 是整个开源库的核心,它连接着AFNetWorking其他几个重要部分,它是AFHTTPSessionManager的父类,一般我们在使用的时候一般使用AFHTTPSessionManager。但是核心部分还是集中在AFURLSessionManager中。AFURLSessionManager遵循了NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate一大堆的协议,并将AFHTTPSessionManager作为URLSession 的 delegate。这样在URLSession 触发一系列关键事件的时候,够可以交给AFURLSessionManager接管处理。

  • AFHTTPRequestSerializer
  • AFHTTPResponseSerializer

AFHTTPRequestSerializer,AFHTTPResponseSerializer 分别是请求序列化器和返回体序列化器,引入序列化器后,序列化器就相当于一个插件一样,这样做的好处不言而喻,增加了整个系统的灵活性,比如我们应用中可能会对接不同的后端,这些后端可能有着不同的数据交付格式,有了序列化器后就可以针对不同的后端数据交付格式实现一对序列化器。整个框架就显得十分灵活,AFNetworking 也根据常见的需求实现了一些常用的序列化器,例如 AFJSONRequestSerializer,AFPropertyListRequestSerializer这两类请求序列化器;AFJSONResponseSerializer,AFXMLParserResponseSerializer,AFXMLDocumentResponseSerializer,
AFPropertyListResponseSerializer,AFImageResponseSerializer,AFCompoundResponseSerializer 这几类返回体序列化器。

  • AFNetworkReachabilityManager

AFNetworkReachabilityManager 主要用于检测网络可用性,这对于应用来说也是非常常用的一项功能。

  • AFSecurityPolicy

AFSecurityPolicy 主要用于提供安全认证相关的处理,

AFNetWorking 框架总览

下面是AFNetWorking 整个框架重要流程的大致结构图,下面将针对这张图对源码进行进一步解析:

  • AFURLSessionManager的组成

上面提到AFURLSessionManager是整个AFNetWorking的核心部分,在iOS中我们如果需要执行网络请求需要URLSession,AFNetWorking也一样,它只不过是对URLSession的一个封装,最核心的部分还是借助URLSession来完成,因此我们最先要弄清楚AFURLSessionManager是如何串起来的,我们上面提到了AFURLSessionManager实现了一系列的协议,并作为URLSession的delegate。这样我们在使用URLSession进行网络请求的时候就会触发delegate的对应方法进行处理,从而将流程交接给AFURLSessionManager。然后再由AFURLSessionManager调用其他的部件对数据进行后续处理。我们来看下AFURLSessionManager的初始化代码,看下AFURLSessionManager是由哪些部分组成:

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {

//.......

// sessionConfiguration URLSession 配置类,通过它可以对URLSession进行配置
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;

//operationQueue:代理执行的OperationQueue,通过maxConcurrentOperationCount 限制每次只执行一个任务。
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;

//session: AFURLSessionManager 最核心的部分,在这里将AFURLSessionManager 作为NSURLSession delegate,并指定代理的执行队列
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

//指定返回体序列化器
self.responseSerializer = [AFJSONResponseSerializer serializer];

//指定安全认证相关类
self.securityPolicy = [AFSecurityPolicy defaultPolicy];

//初始化网络状态检测对象
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];

//这个是task与AFURLSessionManagerTaskDelegate映射的表格,AFURLSessionManagerTaskDelegate后面会详细介绍。
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];

//.....

return self;
}

在初始化方法中,主要初始化了self.session,self.responseSerializer,self.securityPolicy,self.reachabilityManager 这些对象,并给self.session指定了delegate,delegate执行的队列,以及配置。

介绍了AFURLSessionManager接下来就按照应用场景对AFNetWorking进行介绍:

  • 发起数据请求

这部分我们会以常见的GET请求为例子来过下整个流程:

- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
progress:(void (^)(NSProgress * _Nonnull))downloadProgress
success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure {

NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
URLString:URLString
parameters:parameters
uploadProgress:nil
downloadProgress:downloadProgress
success:success
failure:failure];

[dataTask resume];
return dataTask;
}

在GET中主要通过dataTaskWithHTTPMethod创建了一个NSURLSessionDataTask对象,然后通过resume执行这个task。

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure {
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}

return nil;
}

__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(dataTask, error);
}
} else {
if (success) {
success(dataTask, responseObject);
}
}
}];

return dataTask;
}

dataTaskWithHTTPMethod 方法中主要分成两大部分:

1. 构建NSMutableURLRequest
2. 构建NSURLSessionDataTask
  • 构建NSMutableURLRequest
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error {
//....
//使用URLString 创建 NSURL
NSURL *url = [NSURL URLWithString:URLString];

//url 创建 NSMutableURLRequest
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
//NSMutableURLRequest method
mutableRequest.HTTPMethod = method;

//将属性映射到NSMutableURLRequest
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
//将参数添加到NSMutableURLRequest
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

return mutableRequest;
}

NSMutableURLRequest 主要由NSURL,HTTPMethod,HTTPBody,allHTTPHeaderFields,parameters 以及一些配置属性构成。这里有个比较关键的点,如何将对当前属性的设置同步到mutableRequest中。这里用的是KVO来实现,下面这部分的关键代码:

for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}

首先会遍历AFHTTPRequestSerializerObservedKeyPaths,如果有在mutableObservedChangedKeyPaths中,mutableRequest就对这个属性进行监听。下面是AFHTTPRequestSerializerObservedKeyPaths的定义:

static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_AFHTTPRequestSerializerObservedKeyPaths = @[

NSStringFromSelector(@selector(allowsCellularAccess)),
NSStringFromSelector(@selector(cachePolicy)),
NSStringFromSelector(@selector(HTTPShouldHandleCookies)),
NSStringFromSelector(@selector(HTTPShouldUsePipelining)),
NSStringFromSelector(@selector(networkServiceType)),
NSStringFromSelector(@selector(timeoutInterval))

];
});

return _AFHTTPRequestSerializerObservedKeyPaths;
}

那么mutableObservedChangedKeyPaths是怎么来的?这部分在AFHTTPRequestSerializer请求序列化器的初始化方法中有做处理,我们看下这部分代码:

- (instancetype)init {

//.......
// 指定字符编码格式
self.stringEncoding = NSUTF8StringEncoding;

// 用于存储请求头
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];

// 请求头更新队列
self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);

// --- Accept-Language 设置 ----

// Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
[[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
float q = 1.0f - (idx * 0.1f); /*0 , 1 ,2 ,3 ,4 */
[acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
*stop = q <= 0.5f;
}];
[self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];

// ----- User-Agent 设置 -------
NSString *userAgent = nil;
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];

if (userAgent) {
if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
NSMutableString *mutableUserAgent = [userAgent mutableCopy];
if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
userAgent = mutableUserAgent;
}
}
[self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
}

// ------ 指定哪些请求的parameter添加到URL上 -------
// HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];

// ------- 通过keyPath 方式设置 -------
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
return self;
}

这里主要对请求的字符串编码格式,请求头进行设置,以及通过keyPath方式往mutableObservedChangedKeyPaths中添加要监听的keyPath。KVO 方式监听的方式是NSKeyValueObservingOptionNew,也就是这些被监听的属性新建的时候就会被通知,我们接下来看下收到通知后的处理:

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(__unused id)object
change:(NSDictionary *)change
context:(void *)context {
if (context == AFHTTPRequestSerializerObserverContext) {
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
} else {
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}

看到这里大家就可以明白了mutableObservedChangedKeyPaths是怎么来的:一旦我们监听的属性被创建后,就会跑到observeValueForKeyPath中,在observeValueForKeyPath方法中,会根据该属性是否为nil来决定是从mutableObservedChangedKeyPaths删除还是添加到mutableObservedChangedKeyPaths中。

我们这里对这部分做个总结:
在AFHTTPRequestSerializer请求序列化器初始化过程中会对AFHTTPRequestSerializerObservedKeyPaths中指定的属性通过KVO进行监听,监听这些对象的新建事件,一旦新建就会添加到mutableObservedChangedKeyPaths,然后每次发起请求前构造NSMutableRequest的时候,对存在于mutableObservedChangedKeyPaths中的属性进行KVO监听。这样一旦我们设置了序列化器中的对应属性,这些属性的状态就会同步到NSMutableRequest中。

我们接下来再回到请求的构建过程中:

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error {
NSParameterAssert(request);

//复制一份request
NSMutableURLRequest *mutableRequest = [request mutableCopy];

//将原始的Header添加到新的request
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];

NSString *query = nil;
if (parameters) {
if (self.queryStringSerialization) {
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
//通过error传出
*error = serializationError;
}
return nil;
}
} else {
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
//将参数转换成key1 = value1 & key2 = value2的形式
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
//如果需要将参数添加到URL最尾部,默认`GET`, `HEAD`, `DELETE` 这些是需要将参数添加到URL尾部的
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
//将参数添加到body部分
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}

在这部分中我们会将初始化序列化器方法中指定的UA Accept-Language 这些头信息放置到请求中。
接着根据queryStringSerialization来决定需要对parameters进行序列化处理还是转换成“key1 = value1 & key2 = value2的形式”形式。再根据self.HTTPMethodsEncodingParametersInURI决定是将参数放到url上还是body中。到此为止请求对象构建完毕。

  • 构建NSURLSessionDataTask
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {

__block NSURLSessionDataTask *dataTask = nil;
url_session_manager_create_task_safely(^{
//通过一个request让session创建一个dataTask
dataTask = [self.session dataTaskWithRequest:request];
});
//为这个dataTask添加uploadProgressBlock,downloadProgressBlock,completionHandler 并创建一个AFURLSessionManagerTaskDelegate与dataTask关联
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}

NSURLSessionDataTask的创建是通过URLSession创建并管理的,要创建NSURLSessionDataTask需要将上个步骤创建的NSMutableRequest传给URLSession通过dataTaskWithRequest方法返回NSURLSessionDataTask实例,然后再通过addDelegateForDataTask,往task中添加
uploadProgressBlock,downloadProgressBlock,completionHandler 这些都是AFNetWorking向外面传递数据的接口,我们接下来看下addDelegateForDataTask

- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler {

//新建一个AFURLSessionManagerTaskDelegate每个AFURLSessionManagerTaskDelegate持有uploadProgressBlock,downloadProgressBlock,completionHandler以及AFURLSessionManager
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
delegate.manager = self;
delegate.completionHandler = completionHandler;

dataTask.taskDescription = self.taskDescriptionForSessionTasks;

//将taskidentify 作为key delegate为value 存到 mutableTaskDelegatesKeyedByTaskIdentifier字典中
[self setDelegate:delegate forTask:dataTask];

delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}

在这个方法中会创建一个AFURLSessionManagerTaskDelegate,每个NSURLSessionDataTask都会与一个AFURLSessionManagerTaskDelegate相关联,这是通过****[self setDelegate:delegate forTask:dataTask]****来完成的,在setDelegate方法中将taskidentify 作为key delegate为value 存到 mutableTaskDelegatesKeyedByTaskIdentifier字典中。
AFURLSessionManagerTaskDelegate主要负责两个任务,一个是负责进度的更新,一个是的网络数据mutableData的管理。这个会在后面进行介绍。每个AFURLSessionManagerTaskDelegate持有uploadProgressBlock,downloadProgressBlock,completionHandler以及AFURLSessionManager,在进度发生变化的时候都会通过uploadProgressBlock,downloadProgressBlock传出,当网络数据结束后,通过completionHandler传出。

那么AFURLSessionManagerTaskDelegate又是怎么和AFURLSessionManager产生关联的?
我们上面提到,所有的事件源于URLSession的各个代理,而通过将AFURLSessionManager设置为URLSession从而将事件传递到AFURLSessionManager,那么怎么将这些事件传给AFURLSessionManagerTaskDelegate呢?

- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// delegate may be nil when completing a task in the background
if (delegate) {
[delegate URLSession:session task:task didCompleteWithError:error];
[self removeDelegateForTask:task];
}
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}

这就涉及到上面提到的mutableTaskDelegatesKeyedByTaskIdentifier,我们每个dataTask被新建的时候,都会同时新建一个AFURLSessionManagerTaskDelegate,并添加到mutableTaskDelegatesKeyedByTaskIdentifier,建立起以taskidentify为key AFURLSessionManagerTaskDelegate为value的映射关系。我们看上面的代理方法,会将task作为参数传进来,来表示具体是哪个task的回调,这时候通过task就可以找到对应的AFURLSessionManagerTaskDelegate 对象,并调用对应的方法进行处理,这就完成了AFURLSessionManager向AFURLSessionManagerTaskDelegate关联的环节。

  • 接受数据请求,交付数据

在介绍这部分之前我们先来看下NSURLSessionDataDelegate

#pragma mark - NSURLSessionDataDelegate

//接收到服务器响应后会先调用这个方法,在这个方法中判定该次任务是否继续执行,或者转化为其他类型的任务

- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler{

/**
判定该次任务是否继续执行,或者转化为其他类型的任务
typedef NS_ENUM(NSInteger, NSURLSessionResponseDisposition) {
NSURLSessionResponseCancel = 0, //取消本次task任务
NSURLSessionResponseAllow = 1, //本次task任务继续
NSURLSessionResponseBecomeDownload = 2, //转变本次dataTask任务为一个downloadTask下载任务
NSURLSessionResponseBecomeStream //转变本次dataTask任务为一个StreamTask流任务
} NS_ENUM_AVAILABLE(NSURLSESSION_AVAILABLE, 7_0);
*/
//允许本次task继续执行
completionHandler(NSURLSessionResponseAllow);

}

// 数据传输的过程中该方法会被调用 如果返回数据特别长,该方法会被多次调用
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {
//拼接返回的数据
[self.receiveData appendData:data];
}

//数据传输完成最后该方法会被调用 不管最后该次请求成功或者失败该方法都会调用
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
if(error){
// 请求失败
NSLog(@"error = %@\n",error);
} else {
// 请求成功
//解析返回的数据
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:self.receiveData options:NSJSONReadingMutableContainers | NSJSONReadingAllowFragments error:nil];
NSLog(@"response content = %@",dic);
}
}

在收到数据的时候会首先调用URLSession:dataTask:didReceiveResponse:completionHandler这个代理方法,可以在这个方法中根据返回的response来决定该任务是继续执行,还是转化为其他类型的任务。然后在 URLSession:dataTask:didReceiveData中接送回传的数据,最后走 URLSession: task: didCompleteWithError 对数据进行交付。

我们紧接着就来看下AFNetWorking中是如何处理这部分流程的:

URLSession:dataTask:didReceiveResponse:completionHandler 方法中,AFURLSessionManager会通过block介入整个流程来决定到底是继续执行还是转换为其他类型任务。

- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {

NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
if (self.dataTaskDidReceiveResponse) {
disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
}
if (completionHandler) {
completionHandler(disposition);
}
}

在接到数据后先通过delegateForTask方法拿到当前task对应的AFURLSessionManagerTaskDelegate,然后交给它进行处理。

- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {

AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
[delegate URLSession:session dataTask:dataTask didReceiveData:data];

if (self.dataTaskDidReceiveData) {
self.dataTaskDidReceiveData(session, dataTask, data);
}
}

在NSURLSessionDataDelegate 中将该次到达的数据通过appendData添加到self.mutableData,到数据接收完毕后就会将self.mutableData交付出去。

- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {

self.downloadProgress.totalUnitCount = dataTask.countOfBytesExpectedToReceive;
self.downloadProgress.completedUnitCount = dataTask.countOfBytesReceived;

[self.mutableData appendData:data];
}

等到数据接收结束后会回调URLSession: task: didCompleteWithError这里同样会将流程转到task对应的AFURLSessionManagerTaskDelegate进行处理,最后将task从字典中移除。

- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// delegate may be nil when completing a task in the background
if (delegate) {
[delegate URLSession:session task:task didCompleteWithError:error];
[self removeDelegateForTask:task];
}
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}

在AFURLSessionManagerTaskDelegate中会将数据传递给responseSerializer,对数据进行预先处理后,通过bock以及通知将数据交付给应用层。

- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {

// ....

if (error) {

userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;

dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, error);
}

dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
} else {
dispatch_async(url_session_manager_processing_queue(), ^{
NSError *serializationError = nil;
//使用responseSerializer 进行处理
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];

if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}

if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}

if (serializationError) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
}

dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, serializationError);
}

dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
});
}
}

我们看下返回体序列化器的处理过程,这里以最常用的AFJSONResponseSerializer来进行分析:

- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error {
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
//....
NSError *serializationError = nil;

id responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];

//.....

return responseObject;
}

responseObjectForResponse 方法就完成两件事情:

1. 对返回response数据进行合法性校验
2. 对NSData 进行对应处理后(序列化)后返回

在validateResponse 中主要完成contentType 以及 statusCode校验。

- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error {

BOOL responseIsValid = YES;
NSError *validationError = nil;
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
//对返回体中的contentType 进行校验
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
!([response MIMEType] == nil && [data length] == 0)) {
if ([data length] > 0 && [response URL]) {
//.....
}
responseIsValid = NO;
}
//对返回体中的StatusCodes进行校验
if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
//.....
responseIsValid = NO;
}
}

if (error && !responseIsValid) {
*error = validationError;
}
return responseIsValid;
}
  • 进度的更新

在上传数据或者下载数据的时候会分别回调didSendBodyData,didWriteData进行处理,在这两个代理回调中都会将数据传递到AFURLSessionManagerTaskDelegate中。

- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {

AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
if (delegate) {
[delegate URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite];
}

if (self.downloadTaskDidWriteData) {
self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
}

- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {

//如果返回的数据不知道大小,则从Content-Length字段中获取。
int64_t totalUnitCount = totalBytesExpectedToSend;
if(totalUnitCount == NSURLSessionTransferSizeUnknown) {
NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"];
if(contentLength) {
totalUnitCount = (int64_t) [contentLength longLongValue];
}
}
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
if (delegate) {
[delegate URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend];
}

if (self.taskDidSendBodyData) {
self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount);
}
}

在AFURLSessionManagerTaskDelegate中分别更新self.uploadProgress以及self.downloadProgress

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{

self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
self.uploadProgress.completedUnitCount = task.countOfBytesSent;
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{

self.downloadProgress.totalUnitCount = totalBytesExpectedToWrite;
self.downloadProgress.completedUnitCount = totalBytesWritten;
}

但是我们并没看到进度数据的交付啊,这部分其实是在初始化AFURLSessionManagerTaskDelegate的时候利用KVO将进度数据与对应的Block的调用进行了关联:

- (instancetype)initWithTask:(NSURLSessionTask *)task {

//.....
_uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
_downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
__weak __typeof__(task) weakTask = task;
for (NSProgress *progress in @[ _uploadProgress, _downloadProgress ]){
progress.totalUnitCount = NSURLSessionTransferSizeUnknown;
progress.cancellable = YES;

//.....
[progress addObserver:self
forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
}
return self;
}

在initWithTask中利用KVO监听progress 的 fractionCompleted。一旦有数据变动,都会触发KVO:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
if ([object isEqual:self.downloadProgress]) {
if (self.downloadProgressBlock) {
self.downloadProgressBlock(object);
}
}
else if ([object isEqual:self.uploadProgress]) {
if (self.uploadProgressBlock) {
self.uploadProgressBlock(object);
}
}
}

在observeValueForKeyPath中调用对应的downloadProgressBlock以及uploadProgressBlock 进行数据交付。
扯个不是非常必要的一个问题:为什么需要AFURLSessionManagerTaskDelegateAFURLSessionManagerTaskDelegate
的职责是什么?直接说答案吧:AFURLSessionManagerTaskDelegate 主要处理与task相关的代理,因为不同的task有不同的进度,不同task完成数据接收的时刻也不同,AFURLSessionManagerTaskDelegate就是用来与task相关的代理。

  • resume && suspend 请求

这部分主要在调用默认的resume以及suspend方法的时候增加AFNSURLSessionTaskDidResumeNotificationAFNSURLSessionTaskDidSuspendNotification的通知,实现方式是通过在load方法中替换resume以及suspand的实现,为自己的af_resume以及af_suspend,在这里添加对应的通知。那么为什么要添加这两个通知?我们在介绍任务创建的时候会调用setDelegate:forTask方法。在这里会调用****[self addNotificationObserverForTask:task]****

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task {
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[self addNotificationObserverForTask:task];
[self.lock unlock];
}

在addNotificationObserverForTask方法中会让NSURLSessionTask监听AFNSURLSessionTaskDidResumeNotification,以及AFNSURLSessionTaskDidSuspendNotification

- (void)addNotificationObserverForTask:(NSURLSessionTask *)task {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task];
}

在这里会将task通过通知传递出去,交给业务层进行处理:

- (void)taskDidResume:(NSNotification *)notification {
NSURLSessionTask *task = notification.object;
if ([task respondsToSelector:@selector(taskDescription)]) {
if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidResumeNotification object:task];
});
}
}
}

- (void)taskDidSuspend:(NSNotification *)notification {
NSURLSessionTask *task = notification.object;
if ([task respondsToSelector:@selector(taskDescription)]) {
if ([task.taskDescription isEqualToString:self.taskDescriptionForSessionTasks]) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidSuspendNotification object:task];
});
}
}
}
  • 通过 AFSecurityPolicy 保证请求安全

AFSecurityPolicy AFSecurityPolicy 作为 AFURLSessionManager一个主要的组件,主要用于验证 HTTPS 请求的证书是否有效。它有三种模式:

typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
};
* AFSSLPinningModeNone          不使用****pinned certificates****进行认证,只会在系统的信任的证书列表中对服务端返回的证书进行验证
* AFSSLPinningModeCertificate 需要客户端保存服务端的证书
* AFSSLPinningModePublicKey 也需要预先保存服务端发送的证书,但是这里只会验证证书中的公钥是否正确

我们先来看下在AFURLSessionManager中什么时机使用AFSecurityPolicy,在发起某个请求的时候如果需要认证的时候会回调NSURLSession中的didReceiveChallenge方法,同样这里也会将该处理转发给AFURLSessionManager,下面是AFURLSessionManager对didReceiveChallenge的处理:

- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {

NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;

//这里会将处理流程转到taskDidReceiveAuthenticationChallenge,通过业务层对认证进行处理,并返回disposition

if (self.taskDidReceiveAuthenticationChallenge) {
disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential);
} else {
//如果SecTrustRef validation 是必须对就走下面的流程:
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//调用evaluateServerTrust方法进行处理
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
//如果认证成功生成disposition,credential
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
//将生成的disposition,以及credential传递出去
if (completionHandler) {
completionHandler(disposition, credential);
}
}

所以整个环节中最重要的就是evaluateServerTrust方法了,在看这个方法之前我们先看下AFSecurityPolicy是怎么初始化的:

如果我们没有人为指定证书文件,AFNetWorking 会使用defaultPolicy

+ (instancetype)defaultPolicy {
AFSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = AFSSLPinningModeNone;
return securityPolicy;
}

defaultPolicy实现十分简单,就是创建了一个AFSecurityPolicy对象后指定对应的SSLPinningMode为AFSSLPinningModeNone。

紧接着我们看下evaluateServerTrust这个方法,关键的地方已经加了注释,大家可以对照注释进行理解,这里就不再展开介绍了:

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain {
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
// https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
// According to the docs, you should only trust your provided certs for evaluation.
// Pinned certificates are added to the trust. Without pinned certificates,
// there is nothing to evaluate against.
//
// From Apple Docs:
// "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
// Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
return NO;
}

NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
//需要验证域名的情况
//返回用于验证SSL证书链的policy对象,当第一个参数传入的是true的时候会创建一个SSL服务端证书。
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
//不需要验证域名的情况
//创建一个默认的X.509 policy
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}

//设置验证serverTrust所需要的policies
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

//这里应该是进行系统认证[不确定]
if (self.SSLPinningMode == AFSSLPinningModeNone) {
//如果SSLPinningMode为AFSSLPinningModeNone如果允许无效的证书或者调用AFServerTrustIsValid返回YES的时候返回YES 表示认证通过
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
/*模式不是AFSSLPinningModeNone 不允许无效证书的情况下如果AFServerTrustIsValid返回NO 则返回NO,表示认证不通过*/
return NO;
}

switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
case AFSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
//使用self.pinnedCertificates通过SecCertificateCreateWithData方法创建证书数据
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
//使用 SecTrustSetAnchorCertificates 为serverTrust设置证书
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
//使用证书认证
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
//使用 AFCertificateTrustChainForServerTrust 获取serverTrust中的全部 DER 表示的证书
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
//如果 pinnedCertificates 中有相同的证书,就会返回 YES
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
//从serverTrust中获取公钥
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);

for (id trustChainPublicKey in publicKeys) {
//pinnedPublicKeys 中的公钥包含serverTrust中的公钥的时候认证通过
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0;
}
}
return NO;
}

那如果我们需要添加证书文件要怎么实现呢?下面是一个例子供大家参考

- (void)setSecurityPolicyWithCerPath:(NSString *)cerPath validatesDomainName:(BOOL)validatesDomainName {
NSData *cerData = [NSData dataWithContentsOfFile:cerPath];
// 使用证书验证模式
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
// 如果需要验证自建证书(无效证书),需要设置为YES
securityPolicy.allowInvalidCertificates = YES;
// 是否需要验证域名,默认为YES;
securityPolicy.validatesDomainName = validatesDomainName;
securityPolicy.pinnedCertificates = [[NSSet alloc] initWithObjects:cerData, nil];
[self setSecurityPolicy:securityPolicy];
}
  • 通过AFNetworkReachabilityManager 监控网络状态

一般我们应用都会有个应用场景:在网络断开或者异常的时候,显示重试页面,当网络恢复的时候自动调用数据获取接口,恢复正常界面。这个功能就可以通过AFNetworkReachabilityManager来实现了。

  • AFNetworkReachabilityManager实例的创建:

+ (instancetype)sharedManager {
static AFNetworkReachabilityManager *_sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_sharedManager = [self manager];
});

return _sharedManager;
}

不解释,嗯!

- (void)startMonitoring {

//开始监听之前先取消之前的监听,重新开始网络监听
[self stopMonitoring];

//如果没有设置networkReachability 直接返回
if (!self.networkReachability) {
return;
}

//网络状态变更的时候传递出去的Block
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};

//创建SCNetworkReachabilityContext,这里最关键是callback,而AFNetworkReachabilityRetainCallback,AFNetworkReachabilityReleaseCallback是用于管理AFNetworkReachabilityStatusBlock内存的回调。
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);

//在 Main Runloop 中对应的模式开启监控网络状态
SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
//获取当前的网络状态
if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
AFPostReachabilityStatusChange(flags, callback);
}
});
}

通过上述设置后,每次网络状态改变就会调用 AFNetworkReachabilityCallback 函数,在这里会调用AFPostReachabilityStatusChange抛出状态改变的通知:

static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);
}

我们知道状态抛出是通过开启监听之前设置的AFNetworkReachabilityStatusBlock 类型的callback block 传递到业务层的。

static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block) {
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block(status);
}
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
[notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo];
});
}

AFPostReachabilityStatusChange会先通过AFNetworkReachabilityStatusForFlags从SCNetworkReachabilityFlags中提取对应的标志生成AFNetworkReachabilityStatus对象,在使用AFNetworkReachabilityStatusBlock传递出去,同时通过AFNetworkingReachabilityDidChangeNotification广播通知其他部件。

上述的flag是一个kSCNetworkReachabilityFlagsReachable类型,不同的位代表不同的状态。

static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) {
BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0);
BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0);
BOOL canConnectionAutomatically = (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0));
BOOL canConnectWithoutUserInteraction = (canConnectionAutomatically && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0);
BOOL isNetworkReachable = (isReachable && (!needsConnection || canConnectWithoutUserInteraction));

AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown;
if (isNetworkReachable == NO) {
status = AFNetworkReachabilityStatusNotReachable;
}
#if TARGET_OS_IPHONE
else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) {
status = AFNetworkReachabilityStatusReachableViaWWAN;
}
#endif
else {
status = AFNetworkReachabilityStatusReachableViaWiFi;
}

return status;
}

好了好了,说好不熬夜的又2点了,不早了就这样吧。……(^_^)v