Swift 掌控Moya的網絡請求、數據解析與緩存

  • Moya 在Swift開發中起着重要的網絡交互做用,可是還有不如之處,好比網絡不可用時,返回的 Responsenil,這時還得去解析相應的 Error
  • Codable 能夠幫助咱們快速的解析數據,可是一旦聲明的屬性類型與json中的不一致,將沒法正常解析; 並且對於模型中自定義屬性名的處理也十分繁瑣

解決的方案有不少,不過我比較習慣使用 MoyaMapper ,不只能夠解決上述問題,還提供了多種模型轉換數據互轉多種數據類型任意存儲的便捷方法。掌控Moya的網絡請求、數據解析與緩存簡直易如反掌。git

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

GitHub: MoyaMapperjson

📖 詳細的使用請查看手冊 MoyaMapper.github.ioswift

特色

  • 支持jsonModel 自動映射 與 自定義映射
  • 無視 json 中值的類型,Model 中屬性聲明的是什麼類型,它就是什麼類型
  • 支持Data 字典 JSON json字符串 Model 互轉
  • 插件方式,全方位保障Moya.Response,拒絕各類網絡問題致使 Responsenil,將各式各樣的緣由致使的數據加載失敗進行統一處理,開發者只須要關注 Response
  • 可選 - 支持數據隨意緩存( JSONNumberStringBoolMoya.Response )
  • 可選 - 支持網絡請求緩存

數據解析

1、插件注入

附:插件 MoyaMapperPlugin 的詳細使用api

一、定義適用於項目接口的 ModelableParameterType數組

// statusCodeKey、tipStrKey、 modelKey 能夠任意指定級別的路徑,如: "error>used"
struct NetParameter : ModelableParameterType {
    var successValue = "000"
    var statusCodeKey = "retStatus"
    var tipStrKey = "retMsg"
    var modelKey = "retBody"
}
複製代碼

二、在 MoyaProvider 中使用 MoyaMapperPlugin 插件,並指定 ModelableParameterType緩存

let lxfNetTool = MoyaProvider<LXFNetworkTool>(plugins: [MoyaMapperPlugin(NetParameter())])
複製代碼

❗ 使用 MoyaMapperPlugin 插件是整個 MoyaMapper 的核心所在!ruby

2、Model聲明

Model 需遵照 Modelable 協議bash

  • MoyaMapper 支持模型自動映射 和 自定義映射
  • 不須要考慮源json數據的真實類型,這裏統一按 Model 中屬性聲明的類型進行轉換

一、通常狀況下以下寫法便可服務器

struct CompanyModel: Modelable {
    
    var name : String = ""
    var catchPhrase : String = ""
    
    init() { }
}
複製代碼

二、若是自定義映射,則能夠實現方法 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
    }
}
複製代碼

三、支持模型嵌套

struct UserModel: Modelable {
    
    var id : String = ""
    var name : String = ""
    var company : CompanyModel = CompanyModel()
    
    init() { }
}
複製代碼
3、Response 解析

一、如下示例皆使用了 MoyaMapperPlugin ,因此不須要指定 解析路徑

二、若是沒有使用 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)
複製代碼

附: fetchJSONString的詳細使用

// 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的詳細使用說明

// 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 是在何時出來呢?不急,看下一節就知道了。

數據緩存

1、基本使用
// 緩存
@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重啓後也能夠獲取到數據
2、緩存網絡請求

內部緩存過程:

  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'
複製代碼

微信公衆號
相關文章
相關標籤/搜索