Moya + RxSwift + SwiftyJSON + Realm 封装网络请求
先看一个例子,这段代码是请求数据然后展示在Label上
viewModel.loadData(IPModel.self)
.map { $0?.city }
.bindTo(self.showResult.rx.text)
.addDisposableTo(disposeBag)
看起来是不是很优雅,接下来一步一步来详细解释是怎么实现的
先简单介绍一下用到的第三方库(具体用法请自己搜索)
- Moya 一个网络抽象层库
- Realm 一款支持运行在手机、平板和可穿戴设备上的嵌入式数据库(旨在取代CoreData和Sqlite)
- RxSwift 响应式编程里超级优雅的框架
- SwiftyJSON 强大的JSON转换库
- RxCoCoa RxSwift对Cocoa的扩展
建立Moya的Target
/// 建立请求
enum ApiManager {
case github
}
// MARK: - 实现Moya基本参数
extension ApiManager: TargetType {
/// 基类API
var baseURL: URL {
return URL.init(string: "http://ditu.amap.com")!
}
/// 拼接请求路径
var path: String {
switch self {
case .github:
return "/service/regeo"
}
}
/// 设置请求方式
var method: Moya.Method {
switch self {
case .github:
return .get
}
}
/// 设置传参
var parameters: [String: Any]? {
switch self {
case .github:
return ["longitude" : "121.04925573429551", "latitude" : "31.315590522490712"]
}
}
/// 设置编码方式
var parameterEncoding: ParameterEncoding {
return URLEncoding.default
}
/// 这个用于测试,对此不太熟悉!
var sampleData: Data {
return "".data(using: String.Encoding.utf8)!
}
/// 设置任务的请求方式(可以改成上传upload、下载download)
var task: Task {
return .request
}
/// Alamofire中的验证,默认是false
var validate: Bool {
return false
}
}
Extension+Observable
// MARK: - 扩展Map
extension Observable {
/// 数据转JSON
fileprivate func resultToJSON(_ jsonData: JSON, ModelType: T.Type) -> T? {
return T(jsonData: jsonData)
}
/// 数据是JSON使用这个转
func mapResponseToObj(_ type: T.Type) -> Observable {
return map { representor in
//检查是否是Moya.Response
guard let response = representor as? Moya.Response else {
throw XHError.XHNoMoyaResponse
}
//检查是否是一次成功的响应
guard ((200...209) ~= response.statusCode) else {
throw XHError.XHFailureHTTP
}
//将data转为JSON
let json = JSON.init(data: response.data)
//判断是否有状态码
if let code = json[RESULT_CODE].string {
//判断返回的状态码是否与成功状态码一致
if code == XHStatus.XHSuccess.rawValue {
//将数据结构中的数据包字段转为JSON传出
return self.resultToJSON(json[RESULT_DATA], ModelType: type)
}else {
//状态码与成功状态码不一致的时候,返回提示信息
throw XHError.XHMsgError(statusCode: json[RESULT_CODE].string, errorMsg: json[RESULT_MESSAGE].string)
}
}else {
//报错非对象
throw XHError.XHNotMakeObjectError
}
}
}
Extension+RxMoyaProvider
extension RxMoyaProvider {
func XHOffLineCacheRequest(token: Target) -> Observable {
return Observable.create({[weak self] (observer) -> Disposable in
//拼接成为数据库的key
let key = token.baseURL.absoluteString + token.path + (self?.toJSONString(dict: token.parameters))!
//建立Realm
let realm = try! Realm()
//设置过滤条件
let pre = NSPredicate(format: "key = %@",key)
//过滤出来的数据(为数组)
let ewresponse = realm.objects(ResultModel.self).filter(pre)
//先看有无缓存的话,如果有数据,数组即不为0
if ewresponse.count != 0 {
//因为设置了过滤条件,只会出现一个数据,直接取
let filterResult = ewresponse[0]
//重新创建成Response发送出去
let creatResponse = Response(statusCode: filterResult.statuCode, data: filterResult.data!)
observer.onNext(creatResponse)
}
//进行正常的网络请求
let cancellableToken = self?.request(token) { result in
switch result {
case let .success(response):
observer.onNext(response)
observer.onCompleted()
//建立数据库模型并赋值
let model = ResultModel()
model.data = response.data
model.key = key
model.statuCode = response.statusCode
//写入数据库(注意:update参数,如果在设置模型的时候没有设置主键的话,这里是不能使用update参数的,update参数可以保证如果有相同主键的数据就直接更新数据而不是新建)
try! realm.write {
realm.add(model, update: true)
}
case let .failure(error):
observer.onError(error)
}
}
return Disposables.create {
cancellableToken?.cancel()
}
})
}
/// 字典转JSON字符串(用于设置数据库key的唯一性)
func toJSONString(dict:Dictionary?)->String{
let data = try? JSONSerialization.data(withJSONObject: dict!, options: JSONSerialization.WritingOptions.prettyPrinted)
let strJson = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
return strJson! as String
}
}
这里我存在数据库中使用的key是(baseurl + path + 参数的json字符串),这样可以保证每个请求地址的唯一性,避免同一接口不同参数的数据混淆,由于Moya的Response被final修饰了,不能继承,所以我是把Response中的属性单独提出来保存到数据库,需要的时候再从数据库中获取出来重新组成Response发送出去
这里我使用的是Realm默认创建的数据库,我有写一个方法来创建自定义名字的数据库,需要修改的才调用,Realm会自动建立默认数据库
/// 配置数据库(如果不需要修改默认数据库的,就不调用这个方法)
func creatDataBase() {
//获取当前配置
var config = Realm.Configuration()
// 使用默认的目录,替换默认数据库
config.fileURL = config.fileURL!.deletingLastPathComponent()
.appendingPathComponent(cacheDatabaseName)
// 将这个配置应用到默认的 Realm 数据库当中
Realm.Configuration.defaultConfiguration = config
}
建立数据模型
这是服务器返回的数据建立的模型(我只写了一个字段做测试)
- 先创建协议
/// 定义数据转JSON协议
public protocol Mapable {
init?(jsonData:JSON)
}
- 建立数据模型
struct IPModel: Mapable {
let city: String?
init?(jsonData: JSON) {
self.city = jsonData["city"].string
}
}
建立ViewModel
这里将RxMoya默认的Request改成刚刚我自己写的Extension的方法
class ViewModel {
private let provider = RxMoyaProvider()
func loadData(_ model: T.Type) -> Observable {
return provider.XHOffLineCacheRequest(token: .github)
.debug()
.subscribeOn(ConcurrentDispatchQueueScheduler.init(qos:
.default))
.observeOn(MainScheduler.instance)
.distinctUntilChanged()
.catchError({ (error) -> Observable in
//捕获错误,不然离线访问会导致Binding error to UI,可以再此显示HUD等操作
print(error.localizedDescription)
return Observable.empty()
})
.mapResponseToObj(T.self)
}
}
最终在ViewControl里调用
viewModel.loadData(IPModel.self)
.map { $0?.city }
.bindTo(self.showResult.rx.text)
.addDisposableTo(disposeBag)
写这个小封装看了不少大神的博客资料等,特别感谢前人们的博客资料!
因为没存地址,这里就不贴链接了,以后找到了贴上来!
第一次分享,希望大家指点其中的错误以及不足!万分感谢!
最后贴上完整Demo地址
觉得不错的求给个Star,欢迎提Issues