前言
在上一篇文章中
菜鸟开始学习ReactiveObjc运用的记录(二)
自己已经对RAC的一些基本概念有了一点了解,当然自己了解的远不止那一篇中写的东西。RAC的学习对于我来说还是很有难度的,每当遇到不懂的问题点,就是自己百度学习。继上篇后,心里就想着用RAC怎样重写网络封装。
写网络请求的过程中,发现无非是把网络请求封装成Command,也就是如下格式:
- (RACSignal *)requestNetworkData:(HttpBaseRequest *)req{
/// request 必须的有值 容错
if (!req) return [RACSignal error:[NSError errorWithDomain:HTTPServiceErrorDomain code:-1 userInfo:nil]];
@weakify(self);
/// 创建信号
RACSignal *signal = [RACSignal createSignal:^(id subscriber) {
@strongify(self);
/// 获取request
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.manager.requestSerializer requestWithMethod:req.method URLString:[[NSURL URLWithString:req.path relativeToURL:[NSURL URLWithString:BaseUrl] ] absoluteString] parameters:req.parameters error:&serializationError];
if (serializationError) {
dispatch_async(self.manager.completionQueue ?: dispatch_get_main_queue(), ^{
[subscriber sendError:serializationError];
});
return [RACDisposable disposableWithBlock:^{
}];
}
/// 获取请求任务
__block NSURLSessionDataTask *task = nil;
task = [self.manager dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
@strongify(self);
if (error) {
NSError *parseError = [self errorFromRequestWithTask:task httpResponse:(NSHTTPURLResponse *)response responseObject:responseObject error:error];
NSInteger code = [parseError.userInfo[HTTPServiceErrorHTTPStatusCodeKey] integerValue];
NSString *msgStr = parseError.userInfo[HTTPServiceErrorDescriptionKey];
//初始化、返回数据模型
HttpBaseResonse *response = [[HttpBaseResonse alloc] initWithResponseError:parseError code:code msg:msgStr];
[subscriber sendNext:response];
[subscriber sendCompleted];
//错误可以在此处处理
} else {
/// 判断
NSInteger statusCode = [responseObject[HTTPServiceResponseCodeKey] integerValue];
if (statusCode == HTTPResponseCodeSuccess) {
FMHttpResonse *response = [[FMHttpResonse alloc] initWithResponseSuccess:responseObject[HTTPServiceResponseDataKey] code:statusCode];
[subscriber sendNext:response];
[subscriber sendCompleted];
}else{
if (statusCode == HTTPResponseCodeNotLogin) {
}else{
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[HTTPServiceErrorResponseCodeKey] = @(statusCode);
//取出服务给的提示
NSString *msgTips = responseObject[HTTPServiceResponseMsgKey];
//服务器没有返回,错误信息
if ((msgTips.length == 0 || msgTips == nil || [msgTips isKindOfClass:[NSNull class]])) {
msgTips = @"服务器出错了,请稍后重试~";
}
userInfo[HTTPServiceErrorMessagesKey] = msgTips;
if (task.currentRequest.URL != nil) userInfo[HTTPServiceErrorRequestURLKey] = task.currentRequest.URL.absoluteString;
if (task.error != nil) userInfo[NSUnderlyingErrorKey] = task.error;
NSError *requestError = [NSError errorWithDomain:HTTPServiceErrorDomain code:statusCode userInfo:userInfo];
//错误信息反馈回去了、可以在此做响应的弹窗处理,展示出服务器给我们的信息
FMHttpResonse *response = [[FMHttpResonse alloc] initWithResponseError:requestError code:statusCode msg:msgTips];
[subscriber sendNext:response];
[subscriber sendCompleted];
//错误处理
}
}
}
}];
/// 开启请求任务
[task resume];
return [RACDisposable disposableWithBlock:^{
[task cancel];
}];
}];
return [signal replayLazily]; //多次订阅同样的信号,执行一次
}
在看这种写法的时候对我还是很熟悉的,以前没用RAC的时候也是这种模式很像,但让我感兴趣的是return [signal replayLazily]; //多次订阅同样的信号,执行一次
这一句。因为RAC中有 replay,replayLast,ReplayLazily
这三个用法。所以疑惑之。
思考
当网上看到大神写的一篇Comparing replay, replayLast, and replayLazily心里如获至宝。自己也只是照着大神写的栗子细细体会。才发现之前自己都没有懂得这么细节。
为什么会有replay, replayLast, and replayLazily的出现
看下面的栗子1
-(void)exampleOne
{
__block int num = 0;
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
num++;
NSLog(@"Increment num to: %i", num);
[subscriber sendNext:@(num)];
return nil;
}];
NSLog(@"Start subscriptions");
// Subscriber 1 (S1)
[signal subscribeNext:^(id x) {
NSLog(@"S1: %@", x);
}];
// Subscriber 2 (S2)
[signal subscribeNext:^(id x) {
NSLog(@"S2: %@", x);
}];
// Subscriber 3 (S3)
[signal subscribeNext:^(id x) {
NSLog(@"S3: %@", x);
}];
}
结论:每次订阅都会重新执行订阅代码
之前我了解RAC的时候都没注意到这一点。
粟子2演示信号被添加订阅的时候,订阅者是怎么接收发送的值的
-(void)exampleTwo
{
RACSubject *letters = [RACSubject subject];
RACSignal *signal = letters;
NSLog(@"Subscribe S1");
[signal subscribeNext:^(id x) {
NSLog(@"S1: %@", x);
}];
NSLog(@"Send A");
[letters sendNext:@"A"];
NSLog(@"Send B");
[letters sendNext:@"B"];
NSLog(@"Subscribe S2");
[signal subscribeNext:^(id x) {
NSLog(@"S2: %@", x);
}];
NSLog(@"Send C");
[letters sendNext:@"C"];
NSLog(@"Send D");
[letters sendNext:@"D"];
NSLog(@"Subscribe S3");
[signal subscribeNext:^(id x) {
NSLog(@"S3: %@", x);
}];
[letters sendNext:@"E"];
}
输出结果:
Subscribe S1
Send A
S1: A
Send B
S1: B
Subscribe S2
Send C
S1: C
S2: C
Send D
S1: D
S2: D
Subscribe S3
S1: E
S2: E
S3: E
总结:当信号被多次订阅时,就会有多次接收
栗子1:不满足我希望在网络中应用的场景- 无论有多少个监听者,请求只发送一下
栗子2:不满足场景希望拿到订阅前信号发送过的值
replay
Signal这个replay方法将返回一个新的信号,当源信号被订阅时,会立即发送给订阅者全部历史的值,不会重复执行源信号中的订阅代码,不仅如此,订阅者还将收到所有未来发送过去的值。
栗子3
-(void)exampleThree
{
__block id sub = nil;
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
NSLog(@"我被订阅了,%@", subscriber); ////用到replay 此Block立即会被调用一次,此后不会调用
sub = subscriber; //将发送者保存到外部变量,因为block外部需要发送信号
return nil;
}] replay];
NSLog(@"----发送消息A------");
[sub sendNext:@"消息A"]; ////此时此刻发送成功,因为上面Block已经执行了,sub有值
NSLog(@"Start 1 subscriptions");
// Subscriber 1 (S1)
[signal subscribeNext:^(id x) { //收到上面的历史消息A
NSLog(@"S1: %@", x);
}];
NSLog(@"--------发送消息B------------");
[sub sendNext:@"消息B"];
NSLog(@"--------发送消息C------------");
[sub sendNext:@"消息C"];
// Subscriber 3 (S3)
NSLog(@"Start 2 subscriptions");
[signal subscribeNext:^(id x) { //订阅就会收到历史消息A,B,C
NSLog(@"S2: %@", x);
}];
NSLog(@"--------发送消息D------------");
[sub sendNext:@"消息D"]; //前面的订阅都能收到消息D
}
//输出结果:
我被订阅了,
----发送消息A------
Start 1 subscriptions
S1: 消息A
--------发送消息B------------
S1: 消息B
--------发送消息C------------
S1: 消息C
Start 2 subscriptions
S2: 消息A
S2: 消息B
S2: 消息C
--------发送消息D------------
S1: 消息D
S2: 消息D
replay总结:
1.调用replay,会导致源信号的订阅代码先被调用。
2.后续信号多次被订阅再也不会重复执行源信号的订阅代码了。
3.后面的订阅者能收到之前所有历史已经发送的值
。和后续将来发送的所有消息。
replayLast
栗子4
-(void)exampleFour
{
__block id sub = nil;
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
NSLog(@"我被订阅了,%@", subscriber); ////用到replayLast 此Block立即会被调用一次,此后不会调用
sub = subscriber; //将发送者保存到外部变量,因为block外部需要发送信号
return nil;
}] replayLast];
NSLog(@"----发送消息A------");
[sub sendNext:@"消息A"]; ////此时此刻发送成功,因为上面Block已经执行了,sub有值
NSLog(@"Start 1 subscriptions");
// Subscriber 1 (S1)
[signal subscribeNext:^(id x) { //收到上面的历史消息A
NSLog(@"S1: %@", x);
}];
NSLog(@"--------发送消息B------------");
[sub sendNext:@"消息B"];
NSLog(@"--------发送消息C------------");
[sub sendNext:@"消息C"];
// Subscriber 3 (S3)
NSLog(@"Start 2 subscriptions");
[signal subscribeNext:^(id x) { //订阅就只会收到最近的历史消息C
NSLog(@"S2: %@", x);
}];
NSLog(@"--------发送消息D------------");
[sub sendNext:@"消息D"]; //前面的订阅都能收到消息D
}
输出:
我被订阅了,
----发送消息A------
Start 1 subscriptions
S1: 消息A
--------发送消息B------------
S1: 消息B
--------发送消息C------------
S1: 消息C
Start 2 subscriptions
S2: 消息C
--------发送消息D------------
S1: 消息D
S2: 消息D
这里replayLast与replay相比,重点是那个Last
,Last也就是最后的意思
上面的栗子可以看出来,第二次订阅只收到了最后的那条历史消息C,而没有收到消息A,B,这里是和replay不同的地方
总结
1.调用replayLast,会导致源信号的订阅代码先被调用(与replay相同)
2.信号多次被订阅不会重复执行源信号的订阅代码(与replay相同)
3.订阅者只能收到历史已经发送的最后的那条信息
(这与replay唯一不同)、以及未来发送的所有消息(与replay相同)。
replayLazily
栗子五:
-(void)exampleFive
{
__block id sub = nil;
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id subscriber) {
NSLog(@"我被订阅了,%@", subscriber); ////首次订阅此信号导致此Block会被调用一次,此后不会调用
sub = subscriber; //将发送者保存到外部变量,因为block外部需要发送信号
return nil;
}] replayLazily];
NSLog(@"----发送消息A------");
[sub sendNext:@"消息A"]; ////此时此刻发送失败,因为上面Block未执行,sub值是nil
NSLog(@"Start 1 subscriptions");
// Subscriber 1 (S1)
[signal subscribeNext:^(id x) { //第一次订阅就会触发源信号的Block,后面订阅不会触发
NSLog(@"S1: %@", x);
}];
NSLog(@"--------发送消息B------------");
[sub sendNext:@"消息B"];
NSLog(@"--------发送消息C------------");
[sub sendNext:@"消息C"];
// Subscriber 3 (S3)
NSLog(@"Start 2 subscriptions");
[signal subscribeNext:^(id x) { //一订阅就会收到最近的历史消息C
NSLog(@"S2: %@", x);
}];
NSLog(@"--------发送消息D------------");
[sub sendNext:@"消息D"]; //前面的订阅都能收到消息D
}
//输出
----发送消息A------
Start 1 subscriptions
我被订阅了,
--------发送消息B------------
S1: 消息B
--------发送消息C------------
S1: 消息C
Start 2 subscriptions
S2: 消息B
S2: 消息C
--------发送消息D------------
S1: 消息D
S2: 消息D
总结
1.调用replayLazily,会导致源信号的订阅代码只在信号首次被订阅
时调用(与replay唯一不同
)
2.信号多次被订阅不会重复执行源信号的订阅代码(与replay相同)
3.订阅者能收到所有历史已经发送的、未来发送的所有消息。(与replay相同)