Moya + RxSwift + ObjectMapper / HandyJson 创造简洁的网络请求代码

2017.07.05更新:

  • 移除Alamofire 不支持iOS8解决方案,Alamofire 在我当时项目立项的时候是不支持iOS8的,所以引入了不支持iOS8解决方案,但是之后不久就不固执的要求 iOS9+ 了,所以这篇博客里的解决方案部分就没有什么意义了,徒增篇幅,也就移除了。
  • 增加了Demo ,点击传送至Demo
  • 增加了HanyJSON

Alamofire

在Swift中我们发送网络请求一般都是使用一个第三方库 Alamofire ,设置好URL和parameter然后发送网络请求,就像下面这样:

// Alamofire 4
let parameters: Parameters = ["foo": "bar"]

Alamofire.request(urlString, method: .get, parameters: parameters, encoding: JSONEncoding.default)
    .downloadProgress(queue: DispatchQueue.global(qos: .utility)) { progress in
        print("Progress: \(progress.fractionCompleted)")
    }
    .validate { request, response, data in
        // Custom evaluation closure now includes data (allows you to parse data to dig out error messages if necessary)
        return .success
    }
    .responseJSON { response in
        debugPrint(response)
    }

当然这是Alamofire官方给出的最简单使用例子,你要真的在项目里每个网络请求都这么写。。那真的是太耿直了,来一次网络库版本更新,你就知道什么是痛苦了,项目里遍地都是要改的代码。

给Alamofire加个Router

我们在项目开发中,很少像上边那样直接拿来用,这篇不是Alamofire的教程,感兴趣的可以到 这里 (这里是国人翻译版)看看是如何从零开始一步步把Alamofire封装成一个更加好用的框架的。

如果只想用 Alamofire不想引入 Moya 在我的demo里的Router.swift文件 也提供好了类似的封装,可以直接复制,稍微修改一下就可以拿来用。点击跳转

使用封装好的Alamofire只需要这样:

// get请求  根据不同需求,可以在router里 自定义网络请求需要的字段, 业务代码只需要关心传入uid 和 info,然后处理请求到的response
Alamofire.request(Router.getInfo(uid: "1", token: "abc")).responseJSON { response in
    // 在这里处理 请求到的数据
    debugPrint(response)
}

// post请求  根据不同需求,可以在router里 自定义网络请求需要的字段, 业务代码只需要关心传入uid 和 info,然后处理请求到的response
Alamofire.request(Router.postInfo(uid: "1", info: "110")).responseJSON { response in
    // 在这里处理 请求到的数据
    debugPrint(response)
}

比上边的直接用看起来简洁多了。

Moya

其实Moya已经把我们上边做的都写好了,使用moya进行网络请求是这样的:

provider.request(.zen) { result in
    ...
    ...
}

也很简洁。同样在我的demo里的Moya.swift文件 也提供好了Moya在项目里的写法,可以直接复制,稍微修改一下就可以拿来用。点击跳转,在项目里使用见ViewController.swift,是这样的:

let provider = MoyaProvider()
provider.request(.login(username: "username", password: "password")) { result in
    debugPrint(result)
}

Moya + RxSwift

接下来我们看看 Moya配合上RxSwift能有什么不同的魔法。因为我们要用的是Moya提供的Rx扩展,所以Podfile里要这样写:

pod 'Moya/RxSwift'

发起网络请求使用起来和单独使用Moya差不多:

let rxProvider = RxMoyaProvider()
rxProvider(.login(username: "username", password: "password")) { result in
    debugPrint(result)
}

但是,配合起来Rx,Moya提供了更多的方便用法。

  • 比如我们刚才写的代码,其实对请求到的错误信息都没有处理。我们现在可以这样写:
rxProvider.request(.login(username: "username", password: "password"))
    .filterSuccessfulStatusCodes()
    .subscribe(onNext: {
        debugPrint($0)
    })
    .addDisposableTo(disposeBag)
  • 想要过滤出特定的code?比如说在用户密码更改时和后台约定的code:
rxProvider.request(.login(username: "username", password: "password"))
    .filter(statusCode: 0)
    .subscribe(onNext: {
        debugPrint($0)
        //在这里添加处理返回特定code的逻辑
    })
    .addDisposableTo(disposeBag)
  • 还有提供过滤 一个范围的code:
.filter(statusCodes: 200...300)
  • 一句话把请求到的数据转成JSON
rxProvider.request(.login(username: "username", password: "password"))
    .mapJSON()
    .subscribe(onNext: { json in
        debugPrint(json) //已经帮你转成了 JSON,在这里只需要拿到JSON进行接下来的逻辑就可以了
    })
    .addDisposableTo(disposeBag)
  • 还可以mapString
.mapString(atKeyPath: "")

Moya + RxSwift + ObjectMapper

在上一部分,我们已经可以将网络请求回来的数据一键转为JSON了,项目中肯定是经常会遇到将JSON转为对象的操作,ObjectMapper 就是Swift中常用的JSON转对象框架。

将JSON转为对象:

let object = Mapper().map(JSON: data)!

具体详细用法可以看看项目说明,这里默认已经是有过Swift项目经验,使用过ObjectMapper,就不讲解ObjectMapper的用法了,因为如果写出来,这篇文章就太长了。如果你用的是HandyJson,请看下一节的介绍。

接下来,我们把json解析为对象的过程也简化一下。

我们请求回来的数据可能是这样的:

{
    "code" : 200
   "message" : "success"
    "data" : {
        "user" : {
        "nickname" : "name"
        "age" : 18
        }
    }
}

也可能是这样的:

{   
    "code" : 200
    "message" : "success"
    "data" : [

        "user" : {
            "nickname" : "name1"
            "age" : 18
        },

        "user" : {
            "nickname" : "name2"
            "age" : 19
        },

        "user" : {
            "nickname" : "name3"
            "age" : 13
        }
    ]

}

这就要求我们能把JSON解析为一个单独的User对象,也要能解析成一个User对象数组

ObjectMapper + RxSwift

Podfile中添加:

pod 'ObjectMapper', '~> 2.2'

然后记得pod install

接下来,创建RxMoyaMapper.swift文件
因为网络请求和解析的过程中,会有各种错误出现,首先要在文件中定义错误类型

enum DCUError : Swift.Error {
    // 解析失败
    case ParseJSONError
    // 网络请求发生错误
    case RequestFailed
    // 接收到的返回没有data
    case NoResponse
    //服务器返回了一个错误代码
    case UnexpectedResult(resultCode: String?, resultMsg: String?)
}

然后定义一个RequestStatus的枚举,用来表示服务器返回给我们的code是不是约定的成功code,比方约定返回200为认证成功,401表示用户登录信息失效,等等,那么我们是需要在返回认证不成功code时,进行一些逻辑处理的(比方弹出登录框,让用户重新登录)。

enum RequestStatus: Int {
    case requestSuccess = 200
    case requestError
}

定义 code ,message ,data字段,方便后边解析时候使用(请根据后台返回内容不同做相应修改,这里是拿我们后台的返回规则举例):

let RESULT_CODE = "code"
let RESULT_MSG = "message"
let RESULT_DATA = "data"

然后我们给RxSwift 的Observable添加extension,两个方法分别对应解析为一个单独的User对象,和解析成一个User对象数组:

extension Observable {
    func mapResponseToObject(type: T.Type) -> Observable {
        return map { response in
            
            // get Moya.Response
            guard let response = response as? Moya.Response else {
                throw DCUError.NoResponse
            }
            
            // check http status
            guard ((200...209) ~= response.statusCode) else {
                throw DCUError.RequestFailed
            }
            
            guard let json = try? JSONSerialization.jsonObject(with: response.data, options: JSONSerialization.ReadingOptions(rawValue: 0)) as! [String: Any]  else {
                throw DCUError.NoResponse
            }
            
            
            if let code = json[RESULT_CODE] as? Int {
                if code == RequestStatus.requestSuccess.rawValue {
                    let data =  json[RESULT_DATA]
                    if let data = data as? [String: Any] {
                        let object = Mapper().map(JSON: data)!
                        return object
                    }else {
                        throw DCUError.ParseJSONError
                    }
                } else {
                    throw DCUError.UnexpectedResult(resultCode: json[RESULT_CODE] as? Int , resultMsg: json[RESULT_MSG] as? String)
                }
            } else {
                throw DCUError.ParseJSONError
            }
            
        }
    }
    
    func mapResponseToObjectArray(type: T.Type) -> Observable<[T]> {
        return map { response in
            
            // get Moya.Response
            guard let response = response as? Moya.Response else {
                throw DCUError.NoResponse
            }
            
            // check http status
            guard ((200...209) ~= response.statusCode) else {
                throw DCUError.RequestFailed
            }

            guard let json = try? JSONSerialization.jsonObject(with: response.data, options: JSONSerialization.ReadingOptions(rawValue: 0)) as! [String: Any]  else {
                throw DCUError.NoResponse
            }
            
            
            if let code = json[RESULT_CODE] as? Int {
                if code == RequestStatus.requestSuccess.rawValue {
                    var objects = [T]()
                    guard let objectsArrays = json[RESULT_DATA] as? [Any] else {
                        throw DCUError.ParseJSONError
                    }
                    for object in objectsArrays {
                        if let data = object as? [String: Any] {
                            let object = Mapper().map(JSON: data)!
                            objects.append(object)
                        }
                    }
                    return objects

                } else {
                    throw DCUError.UnexpectedResult(resultCode: json[RESULT_CODE] as? Int , resultMsg: json[RESULT_MSG] as? String)

                }
            } else {
                throw DCUError.ParseJSONError
            }
        }
    }
}

同样在我的demo里的RxMoyaMapper.swift文件, 也提供好了完整的实现,可以现在项目看一下,稍微修改一下就可以拿来在项目里使用。点击跳转

在项目里写起来是这样的:

//解析成User对象
let rxProvider = RxMoyaProvider()
rxProvider.request(.login(username: "username", password: "password"))
    .mapResponseToObject(type: User.self)
    .subscribe(onNext: { user in
        debugPrint(user)
    })
    .addDisposableTo(disposeBag)

//解析成User对象数组
rxProvider.request(.login(username: "username", password: "password"))
    .mapResponseToObjectArray(type: User.self)
    .subscribe(onNext: { users in
        debugPrint(users)
    })
    .addDisposableTo(disposeBag)

Moya + RxSwift + ObjectMapper

首先在Podfile中添加:

pod 'HandyJSON', '~> 1.7.2'

然后

pod install

我们可以直接来之前定义的DCUError来用,其他的实现也都很类似,只需要把解析的部分换成handyJSON就行了:

let object = JSONDeserializer.deserializeFrom(json: jsonString)
let objArray = JSONDeserializer.deserializeModelArrayFrom(array: objectsArrays)

使用起来也是一模一样的:

func handyJSON() {
    //解析成People对象
    let rxProvider = RxMoyaProvider()
    rxProvider.request(.login(username: "username", password: "password"))
        .mapResponseToObject(type: People.self)
        .subscribe(onNext: { _ in
            
        })
        .addDisposableTo(disposeBag)
    
    //解析成People对象数组
    rxProvider.request(.login(username: "username", password: "password"))
        .mapResponseToObjectArray(type: People.self)
        .subscribe(onNext: { users in
            debugPrint(users)
        })
        .addDisposableTo(disposeBag)
}

同样,handyJSON的代码我也放在我的demo里的RxHandyJSON.swift文件了, 提供好了完整的实现,可以现在项目看一下,稍微修改一下就可以拿来在项目里使用。点击跳转

你可能感兴趣的:(Moya + RxSwift + ObjectMapper / HandyJson 创造简洁的网络请求代码)