Moya + RxSwift + SwiftyJSON + Realm 封裝網(wǎng)絡(luò)請(qǐng)求
先看一個(gè)例子,這段代碼是請(qǐng)求數(shù)據(jù)然后展示在Label上
viewModel.loadData(IPModel.self)
.map { $0?.city }
.bindTo(self.showResult.rx.text)
.addDisposableTo(disposeBag)
看起來是不是很優(yōu)雅,接下來一步一步來詳細(xì)解釋是怎么實(shí)現(xiàn)的
先簡(jiǎn)單介紹一下用到的第三方庫(kù)(具體用法請(qǐng)自己搜索)
- Moya 一個(gè)網(wǎng)絡(luò)抽象層庫(kù)
- Realm 一款支持運(yùn)行在手機(jī)铐刘、平板和可穿戴設(shè)備上的嵌入式數(shù)據(jù)庫(kù)(旨在取代CoreData和Sqlite)
- RxSwift 響應(yīng)式編程里超級(jí)優(yōu)雅的框架
- SwiftyJSON 強(qiáng)大的JSON轉(zhuǎn)換庫(kù)
- RxCoCoa RxSwift對(duì)Cocoa的擴(kuò)展
建立Moya的Target
/// 建立請(qǐng)求
enum ApiManager {
case github
}
// MARK: - 實(shí)現(xiàn)Moya基本參數(shù)
extension ApiManager: TargetType {
/// 基類API
var baseURL: URL {
return URL.init(string: "http://ditu.amap.com")!
}
/// 拼接請(qǐng)求路徑
var path: String {
switch self {
case .github:
return "/service/regeo"
}
}
/// 設(shè)置請(qǐng)求方式
var method: Moya.Method {
switch self {
case .github:
return .get
}
}
/// 設(shè)置傳參
var parameters: [String: Any]? {
switch self {
case .github:
return ["longitude" : "121.04925573429551", "latitude" : "31.315590522490712"]
}
}
/// 設(shè)置編碼方式
var parameterEncoding: ParameterEncoding {
return URLEncoding.default
}
/// 這個(gè)用于測(cè)試,對(duì)此不太熟悉!
var sampleData: Data {
return "".data(using: String.Encoding.utf8)!
}
/// 設(shè)置任務(wù)的請(qǐng)求方式(可以改成上傳upload晨抡、下載download)
var task: Task {
return .request
}
/// Alamofire中的驗(yàn)證设捐,默認(rèn)是false
var validate: Bool {
return false
}
}
Extension+Observable
// MARK: - 擴(kuò)展Map
extension Observable {
/// 數(shù)據(jù)轉(zhuǎn)JSON
fileprivate func resultToJSON<T: Mapable>(_ jsonData: JSON, ModelType: T.Type) -> T? {
return T(jsonData: jsonData)
}
/// 數(shù)據(jù)是JSON使用這個(gè)轉(zhuǎn)
func mapResponseToObj<T: Mapable>(_ type: T.Type) -> Observable<T?> {
return map { representor in
//檢查是否是Moya.Response
guard let response = representor as? Moya.Response else {
throw XHError.XHNoMoyaResponse
}
//檢查是否是一次成功的響應(yīng)
guard ((200...209) ~= response.statusCode) else {
throw XHError.XHFailureHTTP
}
//將data轉(zhuǎn)為JSON
let json = JSON.init(data: response.data)
//判斷是否有狀態(tài)碼
if let code = json[RESULT_CODE].string {
//判斷返回的狀態(tài)碼是否與成功狀態(tài)碼一致
if code == XHStatus.XHSuccess.rawValue {
//將數(shù)據(jù)結(jié)構(gòu)中的數(shù)據(jù)包字段轉(zhuǎn)為JSON傳出
return self.resultToJSON(json[RESULT_DATA], ModelType: type)
}else {
//狀態(tài)碼與成功狀態(tài)碼不一致的時(shí)候期揪,返回提示信息
throw XHError.XHMsgError(statusCode: json[RESULT_CODE].string, errorMsg: json[RESULT_MESSAGE].string)
}
}else {
//報(bào)錯(cuò)非對(duì)象
throw XHError.XHNotMakeObjectError
}
}
}
Extension+RxMoyaProvider
extension RxMoyaProvider {
func XHOffLineCacheRequest(token: Target) -> Observable<Moya.Response> {
return Observable.create({[weak self] (observer) -> Disposable in
//拼接成為數(shù)據(jù)庫(kù)的key
let key = token.baseURL.absoluteString + token.path + (self?.toJSONString(dict: token.parameters))!
//建立Realm
let realm = try! Realm()
//設(shè)置過濾條件
let pre = NSPredicate(format: "key = %@",key)
//過濾出來的數(shù)據(jù)(為數(shù)組)
let ewresponse = realm.objects(ResultModel.self).filter(pre)
//先看有無緩存的話,如果有數(shù)據(jù),數(shù)組即不為0
if ewresponse.count != 0 {
//因?yàn)樵O(shè)置了過濾條件狰晚,只會(huì)出現(xiàn)一個(gè)數(shù)據(jù),直接取
let filterResult = ewresponse[0]
//重新創(chuàng)建成Response發(fā)送出去
let creatResponse = Response(statusCode: filterResult.statuCode, data: filterResult.data!)
observer.onNext(creatResponse)
}
//進(jìn)行正常的網(wǎng)絡(luò)請(qǐng)求
let cancellableToken = self?.request(token) { result in
switch result {
case let .success(response):
observer.onNext(response)
observer.onCompleted()
//建立數(shù)據(jù)庫(kù)模型并賦值
let model = ResultModel()
model.data = response.data
model.key = key
model.statuCode = response.statusCode
//寫入數(shù)據(jù)庫(kù)(注意:update參數(shù),如果在設(shè)置模型的時(shí)候沒有設(shè)置主鍵的話,這里是不能使用update參數(shù)的,update參數(shù)可以保證如果有相同主鍵的數(shù)據(jù)就直接更新數(shù)據(jù)而不是新建)
try! realm.write {
realm.add(model, update: true)
}
case let .failure(error):
observer.onError(error)
}
}
return Disposables.create {
cancellableToken?.cancel()
}
})
}
/// 字典轉(zhuǎn)JSON字符串(用于設(shè)置數(shù)據(jù)庫(kù)key的唯一性)
func toJSONString(dict:Dictionary<String, Any>?)->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
}
}
這里我存在數(shù)據(jù)庫(kù)中使用的key是(baseurl + path + 參數(shù)的json字符串)堂污,這樣可以保證每個(gè)請(qǐng)求地址的唯一性家肯,避免同一接口不同參數(shù)的數(shù)據(jù)混淆,由于Moya的Response被final修飾了盟猖,不能繼承讨衣,所以我是把Response中的屬性單獨(dú)提出來保存到數(shù)據(jù)庫(kù),需要的時(shí)候再?gòu)臄?shù)據(jù)庫(kù)中獲取出來重新組成Response發(fā)送出去
這里我使用的是Realm默認(rèn)創(chuàng)建的數(shù)據(jù)庫(kù),我有寫一個(gè)方法來創(chuàng)建自定義名字的數(shù)據(jù)庫(kù)式镐,需要修改的才調(diào)用反镇,Realm會(huì)自動(dòng)建立默認(rèn)數(shù)據(jù)庫(kù)
/// 配置數(shù)據(jù)庫(kù)(如果不需要修改默認(rèn)數(shù)據(jù)庫(kù)的,就不調(diào)用這個(gè)方法)
func creatDataBase() {
//獲取當(dāng)前配置
var config = Realm.Configuration()
// 使用默認(rèn)的目錄娘汞,替換默認(rèn)數(shù)據(jù)庫(kù)
config.fileURL = config.fileURL!.deletingLastPathComponent()
.appendingPathComponent(cacheDatabaseName)
// 將這個(gè)配置應(yīng)用到默認(rèn)的 Realm 數(shù)據(jù)庫(kù)當(dāng)中
Realm.Configuration.defaultConfiguration = config
}
建立數(shù)據(jù)模型
這是服務(wù)器返回的數(shù)據(jù)建立的模型(我只寫了一個(gè)字段做測(cè)試)
- 先創(chuàng)建協(xié)議
/// 定義數(shù)據(jù)轉(zhuǎn)JSON協(xié)議
public protocol Mapable {
init?(jsonData:JSON)
}
- 建立數(shù)據(jù)模型
struct IPModel: Mapable {
let city: String?
init?(jsonData: JSON) {
self.city = jsonData["city"].string
}
}
建立ViewModel
這里將RxMoya默認(rèn)的Request改成剛剛我自己寫的Extension的方法
class ViewModel {
private let provider = RxMoyaProvider<ApiManager>()
func loadData<T: Mapable>(_ model: T.Type) -> Observable<T?> {
return provider.XHOffLineCacheRequest(token: .github)
.debug()
.subscribeOn(ConcurrentDispatchQueueScheduler.init(qos:
.default))
.observeOn(MainScheduler.instance)
.distinctUntilChanged()
.catchError({ (error) -> Observable<Response> in
//捕獲錯(cuò)誤歹茶,不然離線訪問會(huì)導(dǎo)致Binding error to UI,可以再此顯示HUD等操作
print(error.localizedDescription)
return Observable.empty()
})
.mapResponseToObj(T.self)
}
}
最終在ViewControl里調(diào)用
viewModel.loadData(IPModel.self)
.map { $0?.city }
.bindTo(self.showResult.rx.text)
.addDisposableTo(disposeBag)
寫這個(gè)小封裝看了不少大神的博客資料等你弦,特別感謝前人們的博客資料!
因?yàn)闆]存地址惊豺,這里就不貼鏈接了,以后找到了貼上來禽作!
第一次分享尸昧,希望大家指點(diǎn)其中的錯(cuò)誤以及不足!萬分感謝旷偿!
最后貼上完整Demo地址
覺得不錯(cuò)的求給個(gè)Star烹俗,歡迎提Issues