Swift 掌控Moya的網路請求、資料解析與快取

LinXunFeng發表於2018-10-27
  • Moya 在Swift開發中起著重要的網路互動作用,但是還有不如之處,比如網路不可用時,返回的 Responsenil,這時還得去解析相應的 Error
  • Codable 可以幫助我們快速的解析資料,但是一旦宣告的屬性型別與json中的不一致,將無法正常解析; 而且對於模型中自定義屬性名的處理也十分繁瑣

解決的方案有很多,不過我比較習慣使用 MoyaMapper ,不僅可以解決上述問題,還提供了多種模型轉換資料互轉多種資料型別任意儲存的便捷方法。掌控Moya的網路請求、資料解析與快取簡直易如反掌。

MoyaMapper是基於Moya和SwiftyJSON封裝的工具,以Moya的plugin的方式來實現間接解析,支援RxSwift

GitHub: MoyaMapper

? 詳細的使用請檢視手冊 MoyaMapper.github.io

特點

  • 支援jsonModel 自動對映 與 自定義對映
  • 無視 json 中值的型別,Model 中屬性宣告的是什麼型別,它就是什麼型別
  • 支援Data 字典 JSON json字串 Model 互轉
  • 外掛方式,全方位保障Moya.Response,拒絕各種網路問題導致 Responsenil,將各式各樣的原因導致的資料載入失敗進行統一處理,開發者只需要關注 Response
  • 可選 - 支援資料隨意快取( JSONNumberStringBoolMoya.Response )
  • 可選 - 支援網路請求快取

資料解析

一、外掛注入

附:外掛 MoyaMapperPlugin 的詳細使用

Swift 掌控Moya的網路請求、資料解析與快取

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 是最合適不過了

Swift 掌控Moya的網路請求、資料解析與快取

// 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)
複製程式碼

附: fetchJSONString的詳細使用

Swift 掌控Moya的網路請求、資料解析與快取

// 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)
複製程式碼

附:mapArray的詳細使用說明

Swift 掌控Moya的網路請求、資料解析與快取

// 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)
複製程式碼

附:mapResult的詳細使用說明

統一處理網路請求結果

在APP的實際使用過程中,會遇到各種各樣的網路請求結果,如:伺服器掛了、手機無網路,此時 Moya 返回的 Response 為 nil,這樣我們就不得不去判斷 Error。但是使用 MoyaMapperPlugin 就可以讓我們只關注 Response

// MoyaMapperPlugin 的初始化方法
public init<T: ModelableParameterType>(
    _ type: T,
    transformError: Bool = true
)

type : ModelableParameterType  用於定義欄位路徑,做為全域性解析資料的依據
transformError : Bool  是否當網路請求失敗時,自動轉換請求結果,預設為 true
複製程式碼
  • 當請求失敗的時候,此時的 result.responsenil,根據transformError是否為true 判斷是否建立一個自定義的 response 並返回出去。

➡ 本來可以請求到的資料內容

success-obj

➡ 現在關閉網路,再請求資料

  • 正常情況下,即不做任何不處理的時候, Responsenil

  • 經過 MoyaMapperPlugin 處理的後可得到轉換後的 Response ,如圖

plugin

這裡將請求失敗進行了統一處理,無論是伺服器還是自身網路的問題,retStatus 都為 MMStatusCode.loadFail,但是 errorDescription 會保持原來的樣子並賦值給 retMsg

  • retStatus 值會從列舉 MMStatusCode 中取 loadFail.rawValue ,即 700
  • 取 型別為 ModelableParameterTypetypestatusCodeKey 所指定的值 為鍵名,retMsg也同理

ps: 這個時候可以通過判斷 retStatusresponse.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重啟後也可以獲取到資料
二、快取網路請求

內部快取過程:

  1. APP首次啟動並進行網路請求,網路資料將快取起來
  2. APP再次啟動並進行網路請求時,會先返回快取的資料,等請求成功後再返回網路資料
  3. 其它情況只會載入網路資料
  4. 每次成功請求到資料後,都會對快取的資料進行更新
// 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'
複製程式碼

相關文章