借鉴Alamofire解决异步回调问题(Swift)
Posted WoodBear009
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了借鉴Alamofire解决异步回调问题(Swift)相关的知识,希望对你有一定的参考价值。
遇到的问题
今天在做一个swift练习demo时遇到了这样一个问题,我需要实现一个gps定位功能,于是封装一个LocationManager类去处理定位相关的逻辑,外部调用者需要获取定位信息时,直接调用LocationManager提供的getCurrentGpsInfo方法即可获取到当前的gps信息。
getCurrentGpsInfo函数内部实现大概是这样的
.......
//clManager是一个CLLocationManager对象
clManager.startUpdatingLocation()
.......
主要就是调用CLLocationManager的startUpdatingLocation方法获取gps信息。但是现在问题来了,这个startUpdatingLocation是个异步方法,它的gps信息是通过代理(<CLLocationManagerDelegate>)的locationManager(manager:locations:)方法反馈的,我应该如何把gps信息返回给外部调用者呢?
可能的解决方式
这种情况其实在开发中会经常遇到,一般我们可以用如下几种方式去处理
1.通知:
结果返回后,通过通知将结果抛出去。呃,理论上没问题,但这种设计实在不是很合适,既不存在一对多的关系,也不存在跨层反馈的情况,信息取到了满世界喊,调用者还要维护通知的管理......,pass
2.代理:
代理不错,CLLocationManager通过代理把结果反馈给了LocationManager,我再让LocationManager通过代理将信息传递出去就可以了。实现起来很简单,但这也不是我想要的,代理会造成逻辑的割裂,对调用者不是十分友好,pass
3.block:
在oc中,通常情况下,我优先会选择使用block回调方式将结果返回,大概的实现应该是这样的
为LocationManager定义一个block属性
//为LocationManager定义一个block属性
@property (nonatomic,copy) void (^returnGpsInfoBlk)(GpsInfo *gpsInfo);
getCurrentGpsInfo函数定义一个block参数,在函数体中,将block参数通过returnGpsInfoBlk属性保持住
-(void)getCurrentGpsInfo:(void(^)(GpsInfo *gpsInfo))result
...........
//维持住block回调
self.returnGpsInfoBlk = result;
[_clManager startUpdatingLocation];
..........
CLLocationManager回调中,回调returnGpsInfoBlk将信息反馈给外部调用者
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
........
_returnGpsInfoBlk(gpsInfo);
.......
swift中尝试解决
OC中我们可以通过block的方式实现我们的需求,Swift中我们有闭包,有函数,函数更是成为了一等公民,类比实现应该不难. getCurrentGpsInfo在Swift中的定义func getGpsInfo(result:(GpsInfo)->Void) -> Void
然后我们可以为LocationManager定义一个函数类型属性,但是......
哦,对,我们要初始化这个属性,但是......
难道只能这样?
给它初始化一个空实现,这样确实没问题,但不是我想要的,设计上感觉很别扭,这个回调是我用来让外部调用者去实现的,为什么我要去给它实现一个空方法?理论上,可以把这个空实现类比成nil,但它毕竟不是nil。如此一来,我无法区分出returnGpsInfo到底有没有真正被调用者实现,感觉这个是很危险的事情。
寻求办法:
既然我一时想不到满意的解决方式,于是就想到了看看别人是如何处理的,上来想到的就是Alamofire,网络请求肯定也会遇到和我类似的问题,让我看看大牛是如何处理的.
api调用方式如下,request发出请求,responseString是个异步回调得到结果
看看responseString内部具体细节,来到了response方法
delegate.queue是个OperationQueue
可以看到,这个函数将主体实现(拿到dataResponse,通过completionHandler将response反馈出去)全部加到了operation队列中.
似乎明白点儿什么,网络请求是异步的,请求的结果也是在别处(网络请求相关代理方法)返回的,之前我遇到的问题也是一样的,结果是在别处(定位相关代理方法)返回的,所以我们试图保持住回调闭包,以便在代理方法中可以访问到被保持的闭包,将结果反馈出去,但是最终发现没有一个合理的方案去保持这个闭包。
换个思路,闭包不好保持,但结果数据好保持啊,具体到这里就是网络请求回来的数据对象,我们在网络请求代理回调里,可以把拿到的数据对象保持住,然后再告知上层去获取解析这个数据对象,于是问题就从如何保持一个闭包转化为了如何协调:
调用者发起请求->调用者等待结果->异步请求,拿到结果,通过对象保持住结果->告知调用者(通过对象)取结果,并将结果进一步反馈。调用者等待的时候完全不用离开发起请求的函数体,唤醒后继续执行当前函数体,于是也就规避了闭包的保持问题.
问题从保持闭包转化为了流程协调,Alamofire的解决方式就是通过OperationQueue,具体一点就是借助OperationQueue的suspend.
大概流程是这样的:
1.发起请求,建立一个队列
2.将队列暂停住
3.把调用者的回调处理加入队列
4.在网络数据返回后,通过对象保持住返回的数据,取消队列暂停
5.调用者的回调处理被队列执行,调用者此时可以借助保持对象拿到返回的数据结果,进行后续的处理.
问题解决:
OK,解决的思路学会了,下面在我的demo中尝试一下,不过我个人更习惯使用GCD,于是最终实现基于的是GCD的 大概实现(Demo还没写完,所以很多逻辑都没补全,也不是十分严谨,仅为描述最终的解决方式) //定义一个队列
let queue:DispatchQueue
//用于保持结果的变量
var gpsInfo:GpsInfo?
func getGpsInfo(result:@escaping (GpsInfo)->Void) -> Void
//挂起队列
queue.suspend()
//将回调处理加入队列
queue.async
result(self.gpsInfo!)
//开始定位
clManager.startUpdatingLocation()
//CLLocationManager回调
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
//停止定位
clManager.stopUpdatingLocation()
//停止后还会返回几次回调,临时简单加个保护措施,仅为实验能正常跑完
guard gpsInfo != nil else
//保持结果,随便写个假数据
gpsInfo = GpsInfo(longitude: 0, latitude: 0, country: "", province: "", city: "")
//恢复队列
queue.resume()
return
//外部调用,一个button的点击
func butClick() -> Void
LocationManager.shareLocationManager.getGpsInfo (gpsInfo) in
print(gpsInfo)
print("go on")
运行结果:
go on先被打印,异步回调print(gpsInfo)也在数据返回后被正确回调。
AFNetworking是如何处理的?
问题解决了,但是这么费波折有点儿出乎了我的意料,于是我不禁想起,OC中常用的AFNetworking库(二者的作者是同一个人)是如何处理这个问题的呢?会不会也有一些出乎我意料的地方?看了一下,结果还好,并没有出人意料,使用的方式就是block保持
另一种解决方式与思考
就在我折腾半天终于把问题解决了之后,在一篇介绍逃逸闭包的博文中看到了另一种特别简单的解决方式,截图:
呃,好简单,用个数组维护就好了,似乎是个很好的方法,但是
1.从设计层面上想,还是觉得有点儿别扭,我的回调只有一个,为什么要用数组?
2.这种方式会不会有什么逻辑隐患?后期数组的维护会涉及到哪些问题?
3.为什么alamofire没有采取这种方式?使用OperationQueue仅仅是为了解决和我相同的问题吗,还是另有别的好处或别的原因?
4.如果这种方式是好的,既然要引入了一个包装对象,我也不想用数组。借助泛型,自己定义实现一个结构,专门用于维持函数回调,而不借助数组
......
想不到写个练习小demo,最后牵扯出这么多问题,后面还需要日后继续思考总结
修正:在后续的工作实践中发现,其实当初还是对swift语法不熟悉导致了弯路,正确的写法应该是var returnGpsInfo:((GpsInfo)->Void)?,少写了个括号,可选型的修饰给了返回值,而不是函数变量本身,所以产生了错误。不过弯路绕的也值得,也学到了一些新的知识。
以上是关于借鉴Alamofire解决异步回调问题(Swift)的主要内容,如果未能解决你的问题,请参考以下文章
AlamoFire downloadProgress 完成处理程序到异步/等待
我的Android进阶之旅关于Android使用bindService()绑定服务,onServiceConnected()方法是异步回调的问题以及借鉴NotificationManager来优化(代
我的Android进阶之旅关于Android使用bindService()绑定服务,onServiceConnected()方法是异步回调的问题以及借鉴NotificationManager来优化(代
swift Alamofire+ObjectMapper——swift(学习九)