iOS 多个网络请求并发执行的解决方案 --LX

闲来无事做,不如学学习

平常项目场景中,肯定会出现这样一个场景,多个网络请求同时进行,但却要将多个网络请求的结果同时进行处理

网上的解决办法很多,我之前项目中采用的就是GCD的dispatch_group_notify来做的,但是这样有一个弊端,代码写起来杂乱,看起来很不美观,管理不方便,不容易维护

后来学习了Reactive Cocoa框架(不得不说这个框架是十分nb啊)

其中有一个是专门用来解决这个问题的,就是rac_liftSelector,有兴趣的朋友可以去网上搜一搜rac_liftSelector这个方法,就会发现真的很方便,也很强大

那既然RAC已经完美解决了这个问题,为什么我还要说一说呢,因为我们项目没用rac啊,哈哈哈

项目最开始就是按照MVC搭建,如果换MVVM+RAC 需要把整个项目重新写一遍了。。。(本人也是比较懒),所以决定,秉承着懒人原则,自己动手造一个轮子

先说明RAC处理办法:

RAC 在NSObject的一个信号Category中写了rac_liftSelector,而rac整套框架都基于一个概念,信号(RACSingle)

可以理解为,一个操作就是一个信号,未被触发的操作为冷信号,已触发的操作为热信号

Github Demo地址: https://github.com/985589477/Signal

未解决bugList(后面会一一解决,解决后会添加删除线):

NSObject的分类中添加KVO会导致bug,假如其他使用该方法继承NSObject的类实现了KVO的observeValueForKeyPath,会导致NSObject的分类中的observeValueForKeyPath不执行。

需要优化列表

对Signal进行功能扩展,下一功能: 实现异步顺序执行队列,类似js中的Promise(仅仅是类似),可以不断在后面追加处理结果的block

添加信号组的销毁功能,防止使用时某个信号忘记使用sendSignal: 而导致无法dealloc,同时也可以解决在控制器已经pop了,还有残留信号响应,直到响应结束才dealloc的问题,只要调用信号组销毁,则该组内的所有子信号都会进行释放

代码结构可以添加信号组(SingleGroup)优化

命名问题: Signal(信号),我居然写成了Single(???) ,what?,我不是单身狗啊,为什么会用Single???

使用时需要注意的地方

每一个Signal的block中都需要进行[subscriber sendSingle:(nil)||(结果)]; 必须调用该sendSingle函数,否则最后的通知selector无法执行

2018 - 11 - 19日更新(当前最新)  (下面有以前版本,但可能存在部分bug,最新的是修改过的)

此版本主要优化:

添加信号组的销毁功能,防止使用时某个信号忘记使用sendSignal: 而导致无法dealloc,同时也可以解决在控制器已经pop了,还有残留信号响应,直到响应结束才dealloc的问题,只要调用信号组销毁,则该组内的所有子信号都会进行释放

本次迭代结束后基本可以达到一个正常使用的框架而不会担心有bug问题

之前无法sendSignal而出现意外导致的无法释放,可能出现在,网络请求超时,等待时间过长,而这个时候控制器已被销毁,而网络请求只有走完了才会sendSignal,最终导致的就是控制器释放问题,

那么我们需要提供函数方法,来解决这个问题,正常大部分人都喜欢把操作放入dealloc中,但是这种需要视情况而定,因为一些新手同学,认为放入dealloc就一定可以释放,比如NSTimer,把timer的停止以及赋值为nil都放在了dealloc中,dealloc是释放了才会走,那不释放就不会走,而把timer处理掉就是为了走dealloc,所以陷入了一个死循环,所以我们这个函数需要写在viewDidDisappear中,也就是视图已经消失的回调方法,而我们发送信号组是与self进行绑定的,那么我们的释放信号也应该与self进行绑定,那么我们在NSObject+Signal.h中提供一个方法

- (void)destructionSignal;


iOS 多个网络请求并发执行的解决方案 --LX_第1张图片

提供了这个方法之后我们需思考之后的逻辑,我们的单个信号是存放于信号组内的,而self绑定了我们的信号组,所以,我需要给信号组也提供一个销毁的函数方法

我们定义一个GroupSubscriber Protocol ,提供方法- (void)destructionSignal;并让SignalGroup遵守该协议

那么我们方法已经定义好了,下面需要考虑实现了,

我们在NSObject+Signal.m中使用了NSMutableDictionary中保存了信号组,而信号组也有销毁信号的方法,所以我们只需要遍历获取所有groupSignal,并且调用释放方法,最终将NSMutableDictionary中所有的数据进行移除


iOS 多个网络请求并发执行的解决方案 --LX_第2张图片

group中该如何做呢?

其实我们只需要对每个信号[signal sendSignal:nil] ;就可以了,这样就解决了信号的释放问题

这样难道就没有bug或者准确来说,没有体验不好的地方了嘛?当然有,我们的信号每个都send nil,最终还是会调用最终的回调方法,可以当我们进行释放的时候,这一步是多余的操作,可以不用的,而且假如用户传入了必传的数据,在使用的时候没有进行判空处理,那么将会引起崩溃

所以我们接下来要做的就是,收到释放的信号,就让他不走最终的通知回调,我们在GroupSignal中提供一个isDestruction属性,在调用Group的destructionSignal时,我们将属性设置为true,那么在KVO的监听回调方法observeValueForKeyPath: ofObject: change: context:中,进行一个判断过滤掉就OK了


iOS 多个网络请求并发执行的解决方案 --LX_第3张图片
iOS 多个网络请求并发执行的解决方案 --LX_第4张图片

2018 - 11 - 16日更新

此版本主要优化:

代码结构添加信号组概念

命名问题: Signal(信号),我居然写成了Single(???) ,what?,我不是单身狗啊,为什么会用Single???

Bug修复:

NSObject的分类中添加KVO会导致bug,假如其他使用该方法继承NSObject的类实现了KVO的observeValueForKeyPath,会导致NSObject的分类中的observeValueForKeyPath不执行。

本次修改也是大幅度修改

1、首先移除了WaitSignal类

2、建立SignalGroup类

3、将KVO的响应者转移为SignalGroup,解决分类中实现KVO导致的bug

Signal与Subscriber类都没有进行修改,对外暴露的使用方式也没有修改

首先我们来看一下NSObject+Signal中的实现


iOS 多个网络请求并发执行的解决方案 --LX_第5张图片
NSObject+Signal.h


iOS 多个网络请求并发执行的解决方案 --LX_第6张图片
NSObject+Signal.m

可以看到,代码少了很多,是因为将Observer转移给了signalGroup

我们从上到下看一遍

首先利用提供给外界的方法来调用,初始化一个SignalGroup对象,因为我们要对每一组信号做一个绑定,所以在group中提供了一个identifier的属性,并且存储在signalsDictionary中

其次调用 signalWithGroup: 方法,对每一个信号添加观察并且执行代码块中的代码

接下来最重要的代码就是SignalGroup了

iOS 多个网络请求并发执行的解决方案 --LX_第7张图片
SignalGroup.h
iOS 多个网络请求并发执行的解决方案 --LX_第8张图片
SignalGroup.m(上)
iOS 多个网络请求并发执行的解决方案 --LX_第9张图片
SignalGroup.m(中)
iOS 多个网络请求并发执行的解决方案 --LX_第10张图片
SignalGroup.m(下)

可以看到,最上面的构造方法中创建了一个group对象并对其赋值,其次为该group分配了一个identifier,然后在singleArray的set方法中,将该信号组内的信号的identifier与信号组的identifier进行绑定,最终将observeValueForKeyPath实现即可

需要注意的是:

1、我们需要在每一个信号执行结束后对其移除观察者

2、我们需要在一个信号组执行结束后取消其保存,否则无法dealloc

2018 - 11 - 13日更新 

本修改基于上一版本,如果有疑问可以参考上版本代码

首先我说明我上个版本的bug以及问题

1、代码没有rac简洁

2、SingleManager如果不强引用则会导致crash

3、若该组方法未执行完毕,又重新reload一次会导致SingleManager强引用失效,造成crash

4、即使解决了SingleManager的强引用,也会导致在多次执行通知回调触发方法结束后回调的数据错乱,很严重的问题

基于以上问题,对代码进行修改及重构

还是本着一个原则,参考RAC,抱大腿

首先忽略第一个问题,看第二个问题

SingleManager不强引用会导致crash,RAC做法:写在NSObject的分类中

写在Category中很好的解决了这个问题,为什么?因为当前对象就是控制器,控制器不释放,他就不会释放,也就不会造成野指针崩溃(之前我就忽略了这个细节,果然需要打磨)

第三个问题&&第四个问题

其实解决了第二个问题就相当于解决了第三个问题,但是他们是一个连锁反应,我需要把遇到的问题提出来,大家可以共同参考进步

而第四个问题就是我在解决了SingleManager强引用问题的同时导致的,同时调用两次,会发现,回调数据错乱,因为代码没有做数据绑定

有了以上的解决方案,那么来解决一下,代码变动较大,直接从头捋清思路

创建 NSObject+Single的Category、创建Single的Class、创建Subscriber的Protocol、创建WaitSingle的Class(处理数据信号绑定)

首先创建Subscriber的Protocol ,为其添加方法 - (void)sendSingle:(id)result;

此处需要有一个修改 将 NS_ASSUME_NONNULL_BEGIN 与NS_ASSUME_NONNULL_END 删除,否则在该方法传入nil的时候有一个黄色警告,比较烦


iOS 多个网络请求并发执行的解决方案 --LX_第11张图片
Subscriber protocol


然后我们来看一下Single类中的修改

我们首先把之前SingleManager中的创建信号任务的方法转移到Single中,并且修改了一下命名,以及添加了Single的标示符号

以下是.h 和 .m


iOS 多个网络请求并发执行的解决方案 --LX_第12张图片
Single.h
iOS 多个网络请求并发执行的解决方案 --LX_第13张图片
Single.m


可以看到,只把之前的SingleManager中创建信号方法转移到了Single中,实现没有改变

接下来就是最重要的了

将SingleManager中的触发方法放入NSObject+Single中

并且创建WaitSingle用来做数据绑定模型类

将SingleManager.m中的属性全都转移到该类

其次提供方法为Single创建组合标示identifier

然后修改报错部分代码进行替换,以下是实现


iOS 多个网络请求并发执行的解决方案 --LX_第14张图片
NSObject+Single.m
iOS 多个网络请求并发执行的解决方案 --LX_第15张图片
NSObject+Single.m
iOS 多个网络请求并发执行的解决方案 --LX_第16张图片
NSObject+Single.m

这样我们就完成了对代码的修改,以下是代码的使用方式


iOS 多个网络请求并发执行的解决方案 --LX_第17张图片
实现方式

2018 - 11 - 12 日初稿

基于该rac原理,对信号进行思考,无非就是多个请求结束后,触发某个操作

首先想到的是什么,KVO啊,每一个操作都为一个信号,我想要监听其改变,那么我利用KVO进行观察就OK啦

其次思考,rac中利用sendNext来处理结果,那么我也提供一个函数方法来处理结果

然后开始动手

首先创建2个类: Single 、 SingleManager

Single主要就是作为一个信号,而SingleManager就是用来管理信号的

既然SingleManager是用来管理信号的,那么好,我需要向singleManager添加任务(一次网络请求,或者是一些代码等被我统称为任务 task),其次,我需要提供多个方法执行结束后所触发的方法(其实就好像一个UIButton  对其addTarget,只不过换了一个形式)

那么Single类是用来操作信号的,所以我需要对Single类添加存储外界传入的task,以及提供一个id 类型的result来调用以及一个方法 发送信号,将冷信号设置为热信号

下面是我的Single.h定义


iOS 多个网络请求并发执行的解决方案 --LX_第18张图片
iOS 多个网络请求并发执行的解决方案 --LX_第19张图片

下面是我的SingleManager.h的定义


iOS 多个网络请求并发执行的解决方案 --LX_第20张图片
SingleManager.h

这里我采用block的方式进行添加任务

使用- (LXSingle*)singleAddTask:(void(^)(LXSingle*lx_single))task;来进行定义任务

使用 - (void)singleWithSelector:(NSArray *)array target:(id)target notificationSelector:(SEL)selector;来触发任务


iOS 多个网络请求并发执行的解决方案 --LX_第21张图片
iOS 多个网络请求并发执行的解决方案 --LX_第22张图片

使用代码如下


iOS 多个网络请求并发执行的解决方案 --LX_第23张图片
ViewController.m中的调用方式


注意:singleManager一定要使用全局变量进行强引用,否则Single中sendSingle会因为局部变量的释放而导致Crash


-----------------分割线(以上是初稿)--------------

最后说一句,喜欢的话给个小红心,有问题可以评论提问

爱你们呦 尊重原创,转载请著名作者以及地址

https://www.jianshu.com/writer#/notebooks/11755935/notes/36638571

你可能感兴趣的:(iOS 多个网络请求并发执行的解决方案 --LX)