SwiftオープンソースプロジェクトObjectMapper実践

13996 ワード

最近のプロジェクトは全面的にswiftに移行する予定で、2、3年前にswiftプロジェクトを書いたことがありますが、長い間多くの知識点を開発していません.最近、人気のあるいくつかのサードパーティライブラリの使用方法について調査するつもりです.
今日はまずObjectMapperから始めます.ObjectMapperはswiftが書いたjsonとモデルから変換されたオープンソースライブラリで、現在5950個のstarがあります.
まず公式文書から始めて、簡単な紹介をします.
サポートされる機能
  • JSONからモデルへの変換
  • モデルからJSONへの変換
  • ネスト構造の解析
  • mapping時のカスタム変換
  • 構造体のサポート
  • 基本的な使い方
    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
        }
    
    

    まず,StaticMappableImmutableMappableの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を使うことができます.