借鉴Alamofire解决异步回调问题(Swift)
来源:互联网 发布:印度人在中国 知乎 编辑:程序博客网 时间:2024/05/17 22:56
遇到的问题
今天在做一个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中尝试解决
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.调用者的回调处理被队列执行,调用者此时可以借助保持对象拿到返回的数据结果,进行后续的处理.
问题解决:
//定义一个队列 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库(二者的作者是同一个人)是如何处理这个问题的呢?会不会也有一些出乎我意料的地方?另一种解决方式与思考
就在我折腾半天终于把问题解决了之后,在一篇介绍逃逸闭包的博文中看到了另一种特别简单的解决方式,截图:
呃,好简单,用个数组维护就好了,似乎是个很好的方法,但是
1.从设计层面上想,还是觉得有点儿别扭,我的回调只有一个,为什么要用数组?
2.这种方式会不会有什么逻辑隐患?后期数组的维护会涉及到哪些问题?
3.为什么alamofire没有采取这种方式?使用OperationQueue仅仅是为了解决和我相同的问题吗,还是另有别的好处或别的原因?
4.如果这种方式是好的,既然要引入了一个包装对象,我也不想用数组。借助泛型,自己定义实现一个结构,专门用于维持函数回调,而不借助数组
......
想不到写个练习小demo,最后牵扯出这么多问题,后面还需要日后继续思考总结
修正:在后续的工作实践中发现,其实当初还是对swift语法不熟悉导致了弯路,正确的写法应该是var returnGpsInfo:((GpsInfo)->Void)?,少写了个括号,可选型的修饰给了返回值,而不是函数变量本身,所以产生了错误。不过弯路绕的也值得,也学到了一些新的知识。
- 借鉴Alamofire解决异步回调问题(Swift)
- SWIFT闭包,介绍,使用(ALAMOFIRE封装 异步请求)
- Swift 中的网络请求问题 OC(AFNetworking) && Swift(Alamofire)
- Alamofire上传图片解决绑定参数问题
- swift 关于 CocoaPods引入Alamofire报警告问题
- jquery Deferred 解决异步回调问题
- swift开发之Alamofire
- Swift网络库Alamofire
- Swift-解读Alamofire
- 五、Swift Alamofire入门
- Alamofire-Swift Networking网络库
- Alamofire-Swift Networking网络库
- Alamofire-Swift Networking网络库
- Swift 第三方库 - Alamofire
- Swift网络请求库Alamofire
- swift利用Alamofire上传图片
- swift开发笔记:Alamofire 4.5
- Angular使用Promise解决多个异步回调问题
- concatgroup_concatmysql行转列列转行concat_wsmysql连接字符串
- MySQL约束:非空约束、主键约束、唯一约束、默认约束、外键约束
- PuTTY登录到Ubuntu虚拟机时显示Network error: Connection refused
- JDK分析工具&JVM垃圾回收
- javaScript编辑器-Sublime Text
- 借鉴Alamofire解决异步回调问题(Swift)
- 为什么361行的数据存进去,却有410
- Media 媒体查询
- 应聘技巧(所有行业都可以使用的技巧。个人看法)
- Fix:webpackJsonp is not defined
- MySQL数据库表如何水平拆分和垂直拆分
- 获取汉字首拼函数(据说该方法来自BCB函数库)
- 第一课: 通过案例对SparkStreaming透彻理解三板斧之一
- 简单粗暴的是时间选择器