SwiftオープンソースプロジェクトObjectMapper実践
13996 ワード
最近のプロジェクトは全面的にswiftに移行する予定で、2、3年前にswiftプロジェクトを書いたことがありますが、長い間多くの知識点を開発していません.最近、人気のあるいくつかのサードパーティライブラリの使用方法について調査するつもりです.
今日はまずObjectMapperから始めます.ObjectMapperはswiftが書いたjsonとモデルから変換されたオープンソースライブラリで、現在5950個のstarがあります.
まず公式文書から始めて、簡単な紹介をします.
サポートされる機能 JSONからモデルへの変換 モデルからJSONへの変換 ネスト構造の解析 mapping時のカスタム変換 構造体のサポート 基本的な使い方
ObjectMapperではプロトコルMappableが定義されています
Mappableプロトコルには2つのメソッドが宣言されています
モデルでこのプロトコルに従う必要があります.公式には参考になります.
クラスまたは構造体が上記の例のようにプロトコルを実装すると、JSONとモデル間の変換を容易に行うことができます.
もちろんマッパークラスでの変換も可能です
ネストされたオブジェクトのマッピング
前述したように、ObjectMapperはネストされたオブジェクトのマッピングをサポートします.
列は次のとおりです.
distanceオブジェクトのvalue値を直接取り出すには、次のmappingを設定します.
カスタム変換規則
ObjectMapperでは、開発者がデータマッピング中に変換ルールを指定できます.
ObjectMapperを使用して提供される変換ルールに加えて、TransformTypeプロトコルを実装することで、変換ルールをカスタマイズすることもできます.
ObjectMapperは変換結果を実現するためにTransformOfクラスを提供してくれました.TransformOfは実際にTransformTypeプロトコルを実現しています.TransformOfには2つのタイプのパラメータと2つの閉包パラメータがあり、タイプは変換に参加するデータのタイプを表し、閉包は変換のルールを表しています.
汎用オブジェクト
ObjectMapperは同様に汎用タイプのパラメータを処理できますが、この汎用タイプはMappableプロトコルを実装した上で正常に使用する必要があります.
原理解析
ObjectMapperはMappableプロトコルを宣言し、このプロトコルには2つの方法が宣言されています.
さらにMappableプロトコルの拡張によりJSONとモデル間の4つの変換方法を追加した.
ObjectMapperを用いて変換する場合,まずモデルをMappableプロトコルに従わせ,Mappable宣言を実現する2つの方法が必要である.
ObjectMapperはMappableに4つの変換方法を追加したため、Userもこの4つの変換方法を継承した.
このメソッドの呼び出しは実際には実行に役立ちます
したがって,我々は直接次のような方法で変換することもできる.
次はObjectMapperのコアですね~なぜMapperクラスで変換が完了するのでしょうか?
マッパーの実現原理を徐々に理解していきます
上のコードブロックに示すように,まずMapperクラスは汎用Nを定義し,NはMappableプロトコル,すなわち我々のモデルUserに従って引き続き下を見て,我々は一般的な方法を選択しなければならない.
そう、私たちが前に書いたMapperの変換方法はまさにこの方法を使っています.
では、この方法の内部実装を詳しく分析してみましょう.
先分解
上記のコードの論理により,
最初のステップを理解して、次に第2のステップの実現原理を見てみましょう.
まずmapメソッドの実装コードを全体的に見てみましょう
まず,
解析を続行[Map]((https://github.com/Hearst-DD/ObjectMapper/blob/master/Sources/Mapper.swift)クラス
もともとMapクラスでは
この方法ではJSON辞書からkeyに基づいてvalueを取得し,
ObjectMapper実践
原理を分析し終わって、私達はまた熟練してObjectMapperを運用して私達が解析の機能を完成することを助けることができる必要があって、ObjectMapperは私達のデータを処理する方法を助けることができてとても多くて、ここで私は先に簡単にみんなといくつかプロジェクトの中でよく使う方法を分かち合って、私はすでに関連するDemoをgithubの上でアップロードして、みんなを歓迎しますstar
単一構造のモデルを解析する
単一の構造の模型の解析は比較的に簡単で、みんなはすぐに理解します
上記のようなjsonデータを取得するには、
解析モデルにネストされたモデルの場合
私たちが手に入れたjsonデータにはいくつかの層の構造がネストされていることがありますが、私たちはちょうど層ごとに各モデルのデータを解析する必要があります.次に、2層の構造を通じて処理方法を説明します.
上記のjsonデータに示すように、weatherモデルを作成するとともに、temperatureモデルを含んでjsonデータを解析する必要があります.この場合、汎用型を使用してネストの目的を達成する必要があります.
解析モデルにネストされたモデルですが、サブモデルのプロパティ値を取得するだけです.
一部のjsonデータのネストされた内容は、モデルを分けて取得する必要はありません.属性を1つのモデルに統一して使用する必要があります.
指定した配列の解析
我々が得た戻り結果を上のコードセグメントに示すようにfeaturesは我々が本当に関心を持っているデータであり,それは配列構造であり,我々が望んでいるのは配列であり,いくつかのfeatureモデルが含まれている.
まずFeatureモデルを作成する必要があります
このJSONデータを解析する際には,他の情報を無視して,まずfeatures対応のjsonデータを取得することができる.
そして,Mapperの高度な用法
ObjectMapperの高度な使い方はまだたくさんありますが、上記の使い方をマスターすると、基本的にプロジェクトでObjectMapperを使うことができます.
今日はまずObjectMapperから始めます.ObjectMapperはswiftが書いたjsonとモデルから変換されたオープンソースライブラリで、現在5950個のstarがあります.
まず公式文書から始めて、簡単な紹介をします.
サポートされる機能
ObjectMapperではプロトコルMappableが定義されています
Mappableプロトコルには2つのメソッドが宣言されています
mutation func mapping(map: Map)
init?(map: Map)
モデルでこのプロトコルに従う必要があります.公式には参考になります.
class User: Mappable {
var username: String?
var age: Int?
var weight: Double!
var array: [Any]?
var dictionary: [String : Any] = [:]
var bestFriend: User? // Nested User object
var friends: [User]? // Array of Users
var birthday: Date?
required init?(map: Map) {
}
// Mappable
func mapping(map: Map) {
username
クラスまたは構造体が上記の例のようにプロトコルを実装すると、JSONとモデル間の変換を容易に行うことができます.
let user = User(JSONString: JSONString)
let JSONString = user.toJSONString(prettyPrint: true)
もちろんマッパークラスでの変換も可能です
let user = Mapper().map(JSONString: JSONString)
let JSONString = Mapper().toJSONString(user, prettyPrint: true)
ネストされたオブジェクトのマッピング
前述したように、ObjectMapperはネストされたオブジェクトのマッピングをサポートします.
列は次のとおりです.
{
"distance" : {
"text" : "102",
"value" : 31
}
}
distanceオブジェクトのvalue値を直接取り出すには、次のmappingを設定します.
func mapping(map: Map) {
distance
カスタム変換規則
ObjectMapperでは、開発者がデータマッピング中に変換ルールを指定できます.
class People: Mappable {
var birthday: NSDate?
required init?(_ map: Map) {
}
func mapping(map: Map) {
birthday ().map(JSON)
}
birthday
の変換ルールを指定しているので、上記のコードはJSONデータを解析する際にlongタイプをDateタイプに変換しますObjectMapperを使用して提供される変換ルールに加えて、TransformTypeプロトコルを実装することで、変換ルールをカスタマイズすることもできます.
public protocol TransformType {
typealias Object
typealias JSON
func transformFromJSON(value: AnyObject?) -> Object?
func transformToJSON(value: Object?) -> JSON?
}
ObjectMapperは変換結果を実現するためにTransformOfクラスを提供してくれました.TransformOfは実際にTransformTypeプロトコルを実現しています.TransformOfには2つのタイプのパラメータと2つの閉包パラメータがあり、タイプは変換に参加するデータのタイプを表し、閉包は変換のルールを表しています.
let transform = TransformOf(fromJSON: { (value: String?) -> Int? in
}, toJSON: { (value: Int?) -> String? in
// transform value from Int? to String?
if let value = value {
return String(value)
}
return nil
})
id
汎用オブジェクト
ObjectMapperは同様に汎用タイプのパラメータを処理できますが、この汎用タイプはMappableプロトコルを実装した上で正常に使用する必要があります.
class User: Mappable {
var name: String?
required init?(_ map: Map) {
}
func mapping(_ map: Map) {
name : Mappable {
var result: T?
required init?(_ map: Map) {
}
func mapping(map: Map) {
result >().map(JSON)
原理解析
ObjectMapperはMappableプロトコルを宣言し、このプロトコルには2つの方法が宣言されています.
init?(map: Map)
mutating func mapping(map: Map)
さらにMappableプロトコルの拡張によりJSONとモデル間の4つの変換方法を追加した.
public extension BaseMappable {
/// Initializes object from a JSON String
public init?(JSONString: String, context: MapContext? = nil) {
if let obj: Self = Mapper(context: context).map(JSONString: JSONString) {
self = obj
} else {
return nil
}
}
/// Initializes object from a JSON Dictionary
public init?(JSON: [String: Any], context: MapContext? = nil) {
if let obj: Self = Mapper(context: context).map(JSON: JSON) {
self = obj
} else {
return nil
}
}
/// Returns the JSON Dictionary for the object
public func toJSON() -> [String: Any] {
return Mapper().toJSON(self)
}
/// Returns the JSON String for the object
public func toJSONString(prettyPrint: Bool = false) -> String? {
return Mapper().toJSONString(self, prettyPrint: prettyPrint)
}
}
ObjectMapperを用いて変換する場合,まずモデルをMappableプロトコルに従わせ,Mappable宣言を実現する2つの方法が必要である.
class User: Mappable {
var username: String?
var age: Int?
required init?(map: Map) {
}
// Mappable
func mapping(map: Map) {
username
ObjectMapperはMappableに4つの変換方法を追加したため、Userもこの4つの変換方法を継承した.
let user = User(JSONString: JSONString)
このメソッドの呼び出しは実際には実行に役立ちます
Mapper().map(JSONString: JSONString)
したがって,我々は直接次のような方法で変換することもできる.
let user = Mapper().map(JSONString: JSONString)
次はObjectMapperのコアですね~なぜMapperクラスで変換が完了するのでしょうか?
マッパーの実現原理を徐々に理解していきます
public final class Mapper {
}
上のコードブロックに示すように,まずMapperクラスは汎用Nを定義し,NはMappableプロトコル,すなわち我々のモデルUserに従って引き続き下を見て,我々は一般的な方法を選択しなければならない.
public func map(JSONString: String) -> N? {
if let JSON = Mapper.parseJSONStringIntoDictionary(JSONString: JSONString) {
return map(JSON: JSON)
}
return nil
}
そう、私たちが前に書いたMapperの変換方法はまさにこの方法を使っています.
Mapper().map(JSONString: JSONString)
では、この方法の内部実装を詳しく分析してみましょう.
// 1. Mapper parseJSONStringIntoDictionary JSON
// 2. map
if let JSON = Mapper.parseJSONStringIntoDictionary(JSONString: JSONString) {
return map(JSON: JSON)
}
return nil
先分解
parseJSONStringIntoDictionary
の実現/// Convert a JSON String into a Dictionary using NSJSONSerialization
public static func parseJSONStringIntoDictionary(JSONString: String) -> [String: Any]? {
let parsedJSON: Any? = Mapper.parseJSONString(JSONString: JSONString)
return parsedJSON as? [String: Any]
}
/// Convert a JSON String into an Object using NSJSONSerialization
public static func parseJSONString(JSONString: String) -> Any? {
let data = JSONString.data(using: String.Encoding.utf8, allowLossyConversion: true)
if let data = data {
let parsedJSON: Any?
do {
parsedJSON = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments)
} catch let error {
print(error)
parsedJSON = nil
}
return parsedJSON
}
return nil
}
上記のコードの論理により,
parseJSONStringIntoDictionary
はシステムのJSONSerialization
を利用してJSON文字列を辞書に変換していることが明らかになった.最初のステップを理解して、次に第2のステップの実現原理を見てみましょう.
まずmapメソッドの実装コードを全体的に見てみましょう
/// Maps a JSON dictionary to an object that conforms to Mappable
public func map(JSON: [String: Any]) -> N? {
let map = Map(mappingType: .fromJSON, JSON: JSON, context: context, shouldIncludeNilValues: shouldIncludeNilValues)
if let klass = N.self as? StaticMappable.Type { // Check if object is StaticMappable
if var object = klass.objectForMapping(map: map) as? N {
object.mapping(map: map)
return object
}
} else if let klass = N.self as? Mappable.Type { // Check if object is Mappable
if var object = klass.init(map: map) as? N {
object.mapping(map: map)
return object
}
} else if let klass = N.self as? ImmutableMappable.Type { // Check if object is ImmutableMappable
do {
return try klass.init(map: map) as? N
} catch let error {
#if DEBUG
let exception: NSException
if let mapError = error as? MapError {
exception = NSException(name: .init(rawValue: "MapError"), reason: mapError.description, userInfo: nil)
} else {
exception = NSException(name: .init(rawValue: "ImmutableMappableError"), reason: error.localizedDescription, userInfo: nil)
}
exception.raise()
#else
NSLog("\(error)")
#endif
}
} else {
// Ensure BaseMappable is not implemented directly
assert(false, "BaseMappable should not be implemented directly. Please implement Mappable, StaticMappable or ImmutableMappable")
}
return nil
}
まず,
StaticMappable
とImmutableMappable
の2つのプロトコルの処理ロジックを無視し,最も重要なMappable
プロトコルの実現に直接注目する.// JSON map
let map = Map(mappingType: .fromJSON, JSON: JSON, context: context, shouldIncludeNilValues: shouldIncludeNilValues)
// Mappable
if let klass = N.self as? Mappable.Type
// N
var object = klass.init(map: map) as? N
// ,
object.mapping(map: map)
//
return object
object.mapping(map: map)
を実行すると、モデルが解析を完了する理由についても疑問が残っています.解析を続行[Map]((https://github.com/Hearst-DD/ObjectMapper/blob/master/Sources/Mapper.swift)クラス
もともとMapクラスでは
subscript
を使用していましたが、下付き文字を定義するのと同じように、最も重要なカスタム下付き文字を直接分析する方法です.public subscript(key: String, nested nested: Bool, delimiter delimiter: String, ignoreNil ignoreNil: Bool) -> Map {
// save key and value associated to it
currentKey = key
keyIsNested = nested
nestedKeyDelimiter = delimiter
if mappingType == .fromJSON {
// check if a value exists for the current key
// do this pre-check for performance reasons
if nested == false {
let object = JSON[key]
let isNSNull = object is NSNull
isKeyPresent = isNSNull ? true : object != nil
currentValue = isNSNull ? nil : object
} else {
// break down the components of the key that are separated by .
(isKeyPresent, currentValue) = valueFor(ArraySlice(key.components(separatedBy: delimiter)), dictionary: JSON)
}
// update isKeyPresent if ignoreNil is true
if ignoreNil && currentValue == nil {
isKeyPresent = false
}
}
return self
}
この方法ではJSON辞書からkeyに基づいてvalueを取得し,
object.mapping(map: map)
を呼び出すと,構成したmapping値に基づいて対応するvalueを順次取得することが分かった.ObjectMapper実践
原理を分析し終わって、私達はまた熟練してObjectMapperを運用して私達が解析の機能を完成することを助けることができる必要があって、ObjectMapperは私達のデータを処理する方法を助けることができてとても多くて、ここで私は先に簡単にみんなといくつかプロジェクトの中でよく使う方法を分かち合って、私はすでに関連するDemoをgithubの上でアップロードして、みんなを歓迎しますstar
単一構造のモデルを解析する
単一の構造の模型の解析は比較的に簡単で、みんなはすぐに理解します
{
"name": "objectmapper",
"age": 18,
"nickname": "mapper",
"job": "swifter"
}
上記のようなjsonデータを取得するには、
Mappable
に従うモデルを作成し、解析パスを構成するだけでよい.class User: Mappable {
var name: String?
var age: Int?
var nickname: String?
var job: String?
required init?(map: Map) {
}
func mapping(map: Map) {
name ().map(JSONString: json.rawString()!)
解析モデルにネストされたモデルの場合
私たちが手に入れたjsonデータにはいくつかの層の構造がネストされていることがありますが、私たちはちょうど層ごとに各モデルのデータを解析する必要があります.次に、2層の構造を通じて処理方法を説明します.
{
"weather": "sun",
"temperature": {
"celsius": 70,
"fahrenheit": 34
},
}
上記のjsonデータに示すように、weatherモデルを作成するとともに、temperatureモデルを含んでjsonデータを解析する必要があります.この場合、汎用型を使用してネストの目的を達成する必要があります.
class Weather: Mappable {
var weather: String?
var temperature: T?
required init?(map: Map) {
}
func mapping(map: Map) {
weather >().map(JSONString: json.rawString()!)
解析モデルにネストされたモデルですが、サブモデルのプロパティ値を取得するだけです.
一部のjsonデータのネストされた内容は、モデルを分けて取得する必要はありません.属性を1つのモデルに統一して使用する必要があります.
{
"distance": {
"text": "102 ft",
"value": 31
}
}
class Distance: Mappable {
var text: String?
var value: Int?
required init?(map: Map) {
}
func mapping(map: Map) {
text
指定した配列の解析
{
"status": "200",
"msg": "success",
"features": [
{
"name": "json "
},
{
"name": " "
},
{
"name": " "
},
{
"name": "mapper "
}
]
}
我々が得た戻り結果を上のコードセグメントに示すようにfeaturesは我々が本当に関心を持っているデータであり,それは配列構造であり,我々が望んでいるのは配列であり,いくつかのfeatureモデルが含まれている.
まずFeatureモデルを作成する必要があります
class Feature: Mappable {
var name: String?
required init?(map: Map) {
}
func mapping(map: Map) {
name
このJSONデータを解析する際には,他の情報を無視して,まずfeatures対応のjsonデータを取得することができる.
let featureJson = json["features"];
そして,Mapperの高度な用法
mapArray
手法を用いるだけで配列オブジェクトを直接得ることができる.let features = Mapper().mapArray(JSONString: featureJson.rawString()!)
ObjectMapperの高度な使い方はまだたくさんありますが、上記の使い方をマスターすると、基本的にプロジェクトでObjectMapperを使うことができます.