一、概述
1、相信大家在使用Swift開發時,Moya是首選的網路工具,在模型解析這一塊,Swift版模型解析的相關第三方庫有很多,本人最習慣用的就是SwiftyJSON。
2、下面會開始講解整個主要的開發功能與思想。
3、以下內容是基於大家會使用Moya和SwiftJSON的前提下所著,還不會的同學可以先簡單瞭解後再來閱讀本篇文章哦~
二、功能開發與思想講解
1、嘗試模型解析
Moya請求伺服器返回的資料以Response類返回給我們,那我們就給Response類做一個擴充套件,這裡以解析模型為例
// 需要傳入一個引數,告知我們要轉換出什麼模型
public func mapObject<T: Modelable>(_ type: T.Type) -> T {
// 模型解析過程
。。。
return T
}
複製程式碼
Q: 那中間的解析過程該怎麼寫呢?
A: 可以讓開發者遵守某個協議,實現指定的轉換方法並描述轉換關係。其轉換過程我們不需要知道,交給開發者即可。
那接著我們來定義一個協議Modelable,並宣告轉換方法
public protocol Modelable {
mutating func mapping(_ json: JSON)
}
複製程式碼
開發者建立一個MyMoel
的結構體,遵守協議Modelable
,並實現mapping
,書寫轉換關係
struct MyModel: Modelable {
var _id = ""
mutating func mapping(_ json: JSON) {
self._id = json["_id"].stringValue
}
}
複製程式碼
以目前的現狀來分析一下:mapObject
可以讓開發者傳入模型型別
,而我們的協議方法卻並非是個類方法。那我們需要先得到這個模型型別
的物件,再來呼叫mapping
方法
2、模型解析的驅動開發
Q: 怎麼得到這個物件?
A: 可以在協議中宣告一個初始化方法來建立物件。是的,我們在mapObject中建立對應模型型別的物件,呼叫mapping方法來轉換資料,再把模型物件傳出去即可。
那我們在Modelable
中宣告一個init方法,並傳入一個引數,區別於其它初始化方法
public protocol Modelable {
mutating func mapping(_ json: JSON)
init(_ json: JSON)
}
複製程式碼
OK,現在把mapObject
方法補齊模型解析過程
public func mapObject<T: Modelable>(_ type: T.Type) -> T {
let modelJson = JSON(data)["modelKey"]
// 模型解析過程
var obj = T.init(modelJson)
obj.mapping(modelJson)
return obj
}
複製程式碼
3、自定義解析鍵名
Q: 這樣是搞定解析了,但是網路請求回來的json格式錯綜複雜,有什麼辦法可以讓開發者來自行指定model對應的鍵名呢?
A: 嗯嗯,既然解析過程是在 Response 擴充套件裡操作的,那我們可以通過協議定義鍵名屬性,並且使用 Runtime 給Response動態新增一個屬性,來記錄遵守協議後的相應類名
public protocol ModelableParameterType {
/// 請求成功時狀態碼對應的值
static var successValue: String { get }
/// 狀態碼對應的鍵
static var statusCodeKey: String { get }
/// 請求後的提示語對應的鍵
static var tipStrKey: String { get }
/// 請求後的主要模型資料的鍵
static var modelKey: String { get }
}
複製程式碼
// MARK:- runtime
extension Response {
private struct AssociatedKeys {
static var lxf_modelableParameterKey = "lxf_modelableParameterKey"
}
var lxf_modelableParameter: ModelableParameterType.Type {
get {
let value = objc_getAssociatedObject(self, &AssociatedKeys.lxf_modelableParameterKey) as AnyObject
guard let type = value as? ModelableParameterType.Type else { return NullParameter.self }
return type
} set {
objc_setAssociatedObject(self, &AssociatedKeys.lxf_modelableParameterKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
複製程式碼
這裡有個坑:_SwiftValue問題 (獻上 參考連結)
如果我們儲存的不是OC物件,那麼objc_getAssociatedObject
取出來的值的型別統統為_SwiftValue
,直接as? ModelableParameterType.Type
絕對是nil,需要在取出來後as AnyObject
再轉換為其它型別才會成功~~
現在開發者就可以建立一個類來遵守ModelableParameterType
協議,並自定義解析鍵名
struct NetParameter : ModelableParameterType {
static var successValue: String { return "false" }
static var statusCodeKey: String { return "error" }
static var tipStrKey: String { return "errMsg" }
static var modelKey: String { return "results" }
}
複製程式碼
4、外掛注入
Q: 厲害了,不過要在什麼時機下儲存這個自定義鍵名的NetParameter
?
A: 額,這個~~~ 哦,對了,可以通過Moya提供的外掛機制!
翻出Moya中的Plugin.Swift,找到這個process
方法,看看方法說明。
/// 在結束之前,可以被用來修改請求結果
/// Called to modify a result before completion.
func process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response, MoyaError>
複製程式碼
那好,我們也做一個外掛MoyaMapperPlugin
給開發者使用,在建立MoyaMapperPlugin
時把自定義解析鍵名的型別傳進來
public struct MoyaMapperPlugin: PluginType {
var parameter: ModelableParameterType.Type
public init<T: ModelableParameterType>(_ type: T.Type) {
parameter = type
}
// modify response
public func process(_ result: Result<Response, MoyaError>, target: TargetType) -> Result<Response, MoyaError> {
_ = result.map { (response) -> Response in
// 趁機新增相關資料
response.lxf_modelableParameter = parameter
return response
}
return result
}
}
複製程式碼
使用:開發者在建立MoyaProvider
物件時,順便注入外掛。(OS: 這一步堪稱“注入靈魂”)
MoyaProvider<LXFNetworkTool>(plugins: [MoyaMapperPlugin(NetParameter.self)])
複製程式碼
5、總結
以上就是主要的踩坑過程了。模型陣列解析和指定解析也跟這些差不多的,這裡就不再贅述。本人已經將其封裝成一個開源庫 MoyaMapper,包含了上述已經和未曾說明的功能,下面會講解如何去使用。以上部分可以稱為開胃菜,目的就是平滑過渡到下面MoyaMapper的具體使用。
可能單單使用MoyaMapper
的預設子庫Core
,作用體會上並不會很深。但是,如果你也是使用RxSwift來開發專案的話,請安裝`MoyaMapper/Rx`
吧,絕對一個字:「爽」
二、MoyaMapper的使用
MoyaMapper是基於Moya和SwiftyJSON封裝的工具,以Moya的plugin的方式來實現間接解析,支援RxSwift
1、定義並注入自定義鍵名類
- 定義一個遵守ModelableParameterType協議的結構體
// 各引數返回的內容請參考上面JSON資料對照圖
struct NetParameter : ModelableParameterType {
static var successValue: String { return "false" }
static var statusCodeKey: String { return "error" }
static var tipStrKey: String { return "" }
static var modelKey: String { return "results" }
}
複製程式碼
此外,這裡還可以做簡單的路徑處理,以應付各種情況,以`>`隔開
// 假設返回的json資料關於請求狀態的相關資料如下所示,
error: {
`errorStatus`:false
`errMsg`:`error Argument type`
}
複製程式碼
// 我們指明解析路徑:error物件下的errMsg欄位,一層層表示下去即可
static var tipStrKey: String { return "error>errMsg" }
複製程式碼
- 以plugin的方式傳遞給MoyaProvider
// MoyaMapperPlugin這裡只需要傳入型別
MoyaProvider<LXFNetworkTool>(plugins: [MoyaMapperPlugin(NetParameter.self)])
複製程式碼
2、定義解析模型
建立一個遵守Modelable協議的結構體
struct MyModel: Modelable {
var _id = ""
...
init(_ json: JSON) { }
mutating func mapping(_ json: JSON) {
self._id = json["_id"].stringValue
...
}
}
遵守Modelable協議,實現協議的兩個方法,在`mapping`方法中描述模型欄位的具體解析
複製程式碼
3、解析資料
0x00 請求結果與模型解析
// Result
public func mapResult(params: ModelableParamsBlock? = nil) -> MoyaMapperResult
// Model
public func mapObject<T: Modelable>(_ type: T.Type, modelKey: String? = nil) -> T
// Result+Model
public func mapObjResult<T: Modelable>(_ type: T.Type, params: ModelableParamsBlock? = nil) -> (MoyaMapperResult, T)
// Models
public func mapArray<T: Modelable>(_ type: T.Type, modelKey: String? = nil) -> [T]
// Result+Models
public func mapArrayResult<T: Modelable>(_ type: T.Type, params: ModelableParamsBlock? = nil) -> (MoyaMapperResult, [T])
複製程式碼
上面的五個方法,觀其名,知其意,這裡就不過多解釋了,主要注意兩點:
- result
// 元祖型別
// 引數1:根據statusCodeKey取出的值與successValue是否相等
// 引數2:根據tipStrKey取出的值
result:(Bool, String)
複製程式碼
- params
// params: ModelableParamsBlock? = nil
// 這裡只有在特殊場景下才需要使用到。如:專案中需要在某處使用特定介面,但是返回的json格式跟自己專案的不一樣,並且只有這麼一兩處用得著該額外介面,那就需要我們這個引數了,以Block的方式返回解析引數型別。
複製程式碼
0x01、特定解析
// Model
public func toJSON(modelKey: String? = nil) -> JSON
// 獲取指定路徑的值
public func fetchJSONString(path: String? = nil, keys: [JSONSubscriptType]) -> String
複製程式碼
這兩個方法,如果沒有指定路徑,預設都是針對modelKey的
// fetchJSONString(keys: <[JSONSubscriptType]>)
1、通過 keys 傳遞陣列, 該陣列可傳入的型別為 Int 和 String
2、預設是以 modelKey 所示路徑,來獲取相應的數值。如果modelKey並非是你所想要使用的解析路徑,可以使用下方的過載方法重新指定路徑即可
// response.fetchJSONString(path: <String?>, keys: <[JSONSubscriptType]>)
複製程式碼
MoyaMapper也提供了Rx子庫,為方便RxSwift的流式程式設計下便捷解析資料
MoyaMapper預設只安裝Core下的檔案
pod `MoyaMapper`
RxSwift擴充
pod `MoyaMapper/Rx`
複製程式碼
具體使用還不是很明白的同學可以下載並執行Example
看看
如果MoyaMapper有什麼不足的地方,歡迎提出issues,感謝大家的支援