Moya
在Swift開發中起著重要的網路互動作用,但是還有不如之處,比如網路不可用時,返回的Response
為nil
,這時還得去解析相應的Error
Codable
可以幫助我們快速的解析資料,但是一旦宣告的屬性型別與json中的不一致,將無法正常解析; 而且對於模型中自定義屬性名的處理也十分繁瑣
解決的方案有很多,不過我比較習慣使用 MoyaMapper
,不僅可以解決上述問題,還提供了多種模型轉換
、資料互轉
、多種資料型別任意儲存
的便捷方法。掌控Moya的網路請求、資料解析與快取簡直易如反掌。
MoyaMapper
是基於Moya和SwiftyJSON封裝的工具,以Moya的plugin的方式來實現間接解析,支援RxSwiftGitHub: MoyaMapper
? 詳細的使用請檢視手冊 MoyaMapper.github.io
特點
- 支援
json
轉Model
自動對映 與 自定義對映 - 無視
json
中值的型別,Model
中屬性宣告的是什麼型別,它就是什麼型別 - 支援
Data
字典
JSON
json字串
Model
互轉 - 外掛方式,全方位保障
Moya.Response
,拒絕各種網路問題導致Response
為nil
,將各式各樣的原因導致的資料載入失敗進行統一處理,開發者只需要關注Response
- 可選 - 支援資料隨意快取(
JSON
、Number
、String
、Bool
、Moya.Response
) - 可選 - 支援網路請求快取
資料解析
一、外掛注入
1、定義適用於專案介面的 ModelableParameterType
// statusCodeKey、tipStrKey、 modelKey 可以任意指定級別的路徑,如: "error>used"
struct NetParameter : ModelableParameterType {
var successValue = "000"
var statusCodeKey = "retStatus"
var tipStrKey = "retMsg"
var modelKey = "retBody"
}
複製程式碼
2、在 MoyaProvider
中使用 MoyaMapperPlugin
外掛,並指定 ModelableParameterType
let lxfNetTool = MoyaProvider<LXFNetworkTool>(plugins: [MoyaMapperPlugin(NetParameter())])
複製程式碼
❗ 使用 MoyaMapperPlugin
外掛是整個 MoyaMapper
的核心所在!
二、Model宣告
Model
需遵守Modelable
協議
MoyaMapper
支援模型自動對映 和 自定義對映- 不需要考慮源json資料的真實型別,這裡統一按
Model
中屬性宣告的型別進行轉換
1、一般情況下如下寫法即可
struct CompanyModel: Modelable {
var name : String = ""
var catchPhrase : String = ""
init() { }
}
複製程式碼
2、如果自定義對映,則可以實現方法 mutating func mapping(_ json: JSON)
struct CompanyModel: Modelable {
var name : String = ""
var catchPhrase : String = ""
init() { }
mutating func mapping(_ json: JSON) {
self.name = json["nickname"].stringValue
}
}
複製程式碼
3、支援模型巢狀
struct UserModel: Modelable {
var id : String = ""
var name : String = ""
var company : CompanyModel = CompanyModel()
init() { }
}
複製程式碼
三、Response 解析
1、以下示例皆使用了
MoyaMapperPlugin
,所以不需要指定解析路徑
2、如果沒有使用
MoyaMapperPlugin
則需要指定解析路徑
,否則無法正常解析ps:
解析路徑
可以使用a>b
這種形式來解決多級路徑的問題
解析方法如下列表所示
方法 | 描述 (支援RxSwift) |
---|---|
toJSON | Response 轉 JSON ( toJSON | rx.toJSON) |
fetchString | 獲取指定路徑的字串( fetchString | rx.fetchString) |
fetchJSONString | 獲取指定路徑的原始json字串 ( fetchJSONString | rx.fetchJSONString ) |
mapResult | Response -> MoyaMapperResult (Bool, String) ( mapResult | rx.mapResult ) |
mapObject | Response -> Model ( mapObject | rx.mapObject) |
mapObjResult | Response -> (MoyaMapperResult, Model) ( mapObjResult | rx.mapObjResult) |
mapArray | Response -> [Model]( mapArray | rx.mapArray) |
mapArrayResult | Response -> (MoyaMapperResult, [Model]) ( mapArrayResult | rx.mapArrayResult) |
❗除了 fetchJSONString
的預設解析路徑是根路徑
之外,其它方法的預設解析路徑為外掛物件中的 modelKey
如果介面請求後 json
的資料結構與下圖類似,則使用 MoyaMapper
是最合適不過了
// Normal
let model = response.mapObject(MMModel.self)
print("name -- \(model.name)")
print("github -- \(model.github)")
// 列印json
print(response.fetchJSONString())
// Rx
rxRequest.mapObject(MMModel.self)
.subscribe(onSuccess: { (model) in
print("name -- \(model.name)")
print("github -- \(model.github)")
}).disposed(by: disposeBag)
複製程式碼
// Normal
let models = response.mapArray(MMModel.self)
let name = models[0].name
print("count -- \(models.count)")
print("name -- \(name)")
// 列印 json 模型陣列中第一個的name
print(response.fetchString(keys: [0, "name"]))
// Rx
rxRequest.mapArray(MMModel.self)
.subscribe(onSuccess: { models in
let name = models[0].name
print("count -- \(models.count)")
print("name -- \(name)")
}).disposed(by: disposeBag)
複製程式碼
// Normal
let (isSuccess, tipStr) = response.mapResult()
print("isSuccess -- \(isSuccess)")
print("tipStr -- \(tipStr)")
// Rx
rxRequest.mapResult()
.subscribe(onSuccess: { (isSuccess, tipStr) in
print("isSuccess -- \(isSuccess)") // 是否為 "000"
print("retMsg -- \(retMsg)") // "缺少必要引數"
}).disposed(by: disposeBag)
複製程式碼
統一處理網路請求結果
在APP的實際使用過程中,會遇到各種各樣的網路請求結果,如:伺服器掛了、手機無網路,此時
Moya
返回的Response
為 nil,這樣我們就不得不去判斷Error
。但是使用MoyaMapperPlugin
就可以讓我們只關注Response
// MoyaMapperPlugin 的初始化方法
public init<T: ModelableParameterType>(
_ type: T,
transformError: Bool = true
)
type : ModelableParameterType 用於定義欄位路徑,做為全域性解析資料的依據
transformError : Bool 是否當網路請求失敗時,自動轉換請求結果,預設為 true
複製程式碼
- 當請求失敗的時候,此時的
result.response
為nil
,根據transformError
是否為true
判斷是否建立一個自定義的response
並返回出去。
➡ 本來可以請求到的資料內容
➡ 現在關閉網路,再請求資料
-
正常情況下,即不做任何不處理的時候,
Response
為nil
-
經過
MoyaMapperPlugin
處理的後可得到轉換後的Response
,如圖
這裡將請求失敗進行了統一處理,無論是伺服器還是自身網路的問題,retStatus
都為 MMStatusCode.loadFail,但是 errorDescription 會保持原來的樣子並賦值給 retMsg
。
retStatus
值會從列舉MMStatusCode
中取loadFail.rawValue
,即700
- 取 型別為
ModelableParameterType
的type
中statusCodeKey
所指定的值 為鍵名,retMsg
也同理
ps: 這個時候可以通過判斷 retStatus
或 response.statusCode
是否與 MMStatusCode.loadFail.rawValue
相同來判斷是否顯示載入失敗的空白頁佔點陣圖
enum MMStatusCode: Int {
case cache = 230
case loadFail = 700
}
複製程式碼
列舉 MMStatusCode
中除了 loadFail
,還有 cache
,我們已經知道 loadFail
在資料載入失敗的時候會出現,那 cache
是在什麼時候出來呢?不急,看下一節就知道了。
資料快取
一、基本使用
// 快取
@discardableResult
MMCache.shared.cache`XXX`(value : XXX, key: String, cacheContainer: MMCache.CacheContainer = .RAM) -> Bool
// 取捨
MMCache.shared.fetch`XXX`Cache(key: String, cacheContainer: MMCache.CacheContainer = .RAM)
複製程式碼
快取成功會返回一個 Bool
值,這裡可不接收
XXX 所支援型別 | |
---|---|
Bool | - |
Float | - |
Double | - |
String | - |
JSON | - |
Modelable | [Modelable] |
Moya.Response | - |
Int | UInt |
Int8 | UInt8 |
Int16 | UInt16 |
Int32 | UInt32 |
Int64 | UInt64 |
其中,除了
Moya.Response
之外,其它型別皆是通過JSON
來實現快取
所以,如果你想清除這些型別的快取,只需要呼叫如下方法即可
@discardableResult
func removeJSONCache(_ key: String, cacheContainer: MMCache.CacheContainer = .RAM) -> Bool
@discardableResult
func removeAllJSONCache(cacheContainer: MMCache.CacheContainer = .RAM) -> Bool
複製程式碼
清除 Moya.Response
則使用如下兩個方法
@discardableResult
func removeResponseCache(_ key: String) -> Bool
@discardableResult
func removeAllResponseCache() -> Bool
複製程式碼
再來看看MMCache.CacheContainer
enum CacheContainer {
case RAM // 只快取於記憶體的容器
case hybrid // 快取於記憶體與磁碟的容器
}
複製程式碼
這兩種容器互不相通,即 即使key相同,使用
hybrid
來快取後,再通過RAM
取值是取不到的。
- RAM : 僅快取於記憶體之中,快取的資料在APP使用期間一直存在
- hybrid :快取於記憶體與磁碟中,APP重啟後也可以獲取到資料
二、快取網路請求
內部快取過程:
- APP首次啟動並進行網路請求,網路資料將快取起來
- APP再次啟動並進行網路請求時,會先返回快取的資料,等請求成功後再返回網路資料
- 其它情況只會載入網路資料
- 每次成功請求到資料後,都會對快取的資料進行更新
// Normal
func cacheRequest(
_ target: Target,
cacheType: MMCache.CacheKeyType = .default,
callbackQueue: DispatchQueue? = nil,
progress: Moya.ProgressBlock? = nil,
completion: @escaping Moya.Completion
) -> Cancellable
// Rx
func cacheRequest(
_ target: Base.Target,
callbackQueue: DispatchQueue? = nil,
cacheType: MMCache.CacheKeyType = .default
) -> Observable<Response>
複製程式碼
實際上是對
Moya
請求後的Response
進行快取。 其實與Moya
自帶的方法相比較只多了一個引數cacheType: MMCache.CacheKeyType
,定義著快取中的key
,預設為default
下面是 MMCache.CacheKeyType
的定義
/**
let cacheKey = [method]baseURL/path
- default : cacheKey + "?" + parameters
- base : cacheKey
- custom : cacheKey + "?" + customKey
*/
public enum CacheKeyType {
case `default`
case base
case custom(String)
}
複製程式碼
如果你想快取
多頁
列表資料的最新一頁
資料,此時使用default
是不合適的,因為default
使用的key
包含了pageIndex
,這樣就達不到只快取最新一頁資料
的目的, 所以這裡應該使用base
或者custom(String)
我們可以來試一下帶快取的請求
/*
* APP第一次啟動並進行網路請求,網路資料將快取起來
* APP再次啟動並進行網路請求時,會先載入快取,再載入網路資料
* 其它情況只會載入網路資料
* 每次成功請求到資料都會進行資料更新
*/
lxfNetTool.rx.cacheRequest(.data(type: .all, size: 10, index: 1))
.subscribe(onNext: { response in
log.debug("statusCode -- \(response.statusCode)")
}).disposed(by: disposeBag)
// 傳統方式
/*
let _ = lxfNetTool.cacheRequest(.data(type: .all, size: 10, index: 1)) { result in
guard let resp = result.value else { return }
log.debug("statusCode -- \(resp.statusCode)")
}
*/
複製程式碼
列印結果
// 首次使用APP
statusCode -- 200
// 關閉並重新開啟APP,再請求一下
statusCode -- 230
statusCode -- 200
// 然後再請求一下
statusCode -- 200
複製程式碼
這裡的 230
就是 MMStatusCode.cache.rawValue
CocoaPods
- 預設安裝
MoyaMapper預設只安裝Core下的檔案
pod 'MoyaMapper'
複製程式碼
- RxSwift擴充
pod 'MoyaMapper/Rx'
複製程式碼
- 快取擴充
pod 'MoyaMapper/MMCache'
複製程式碼
- Rx快取
pod 'MoyaMapper/RxCache'
複製程式碼