【Swift】KVCを用いたJSONパーサー
REST-APIやWebSocketで通信するアプリを作っているとどうしても通らないといけないのがJSONのパース。
普通に実装するとこのようになると思います
class Object: NSObject { dynamic var str0: String = "a" dynamic var str1: String = "b" dynamic var str2: String = "c" dynamic var num0: Int = 0 dynamic var num1: Int = 1 dynamic var num2: Int = 2 init(dictionary: [String : AnyObject]) { super.init() str0 = dictionary["str0"] as! String str1 = dictionary["str1"] as! String str2 = dictionary["str2"] as! String num0 = dictionary["num0"] as! Int num1 = dictionary["num1"] as! Int num2 = dictionary["num2"] as! Int } }
しかし、APIが増えるごとにそれぞれのモデルごとにinitを書いているのは正直疲れます。 今回はKVC(Key Value Coding)を用いて、initを書かずにJSON⇔モデルクラスを実現する方法をSwiftで紹介します。
KVCとは?
キー値コーディングとは、オブジェクトのプロパティに間接的にアクセスするための仕組みです。ア クセサメソッドを呼び出してアクセスしたり、インスタンス変数としてアクセスするのではなく、プ ロパティの識別に文字列を使用してアクセスします。 引用元: https://developer.apple.com/jp/documentation/KeyValueCoding.pdf
要は、プロパティに対して.str1のようにアクセサメソッドやインスタンス変数でアクセスするのではなく、 "str1"といった文字列でプロパティに対してアクセスする手法です。 Objective-CではこのKVCを使用してプライベートなAPIを無理やり叩くこともできました。(Swiftではやったことないけど...)
プロパティ名の取得
オブジェクトからJSONにマッピングするためにはKeyとなる物が必要なので、 JSONのKeyに対応するプロパティ名をすべて取得します。
class KVCModel: NSObject { class func allKeys() -> [String] { var names: [String] = [] var count: UInt32 = 0 self.classForCoder() let properties:UnsafeMutablePointer<objc_property_t> = class_copyPropertyList(self.classForCoder(), &count) for i: UInt32 in 0..<count { let property = properties[Int(i)]; let cname = property_getName(property) let name = String.fromCString(cname) names.append(name!) } free(properties) return names } }
self.classForCoder()
では自身のクラスを取得します。
Objective-Cでいうところの
+ (Class)class;
に対応しています。
イニシャライザでマッピングする JSON->モデル
イニシャライザにJSONから取り出したDictionaryを渡すことで自動的にプロパティに値が設定されるようにします。
class KVCModel: NSObject { init(dictionary: [String : AnyObject]) { super.init() if dictionary.count == 0 { return } let allPropertyKeys = self.dynamicType.allKeys() for key: String in allPropertyKeys { let val = dictionary[key] if val != nil { self.setValue(val, forKey: key) } } } }
self.dynamicType
でも自身のクラスを取得していますが、 こちらはObjective-Cでは
- (Class)class;
に対応しています。
Dictionaryで取り出す モデル->JSON
最後に以下のメソッドを追加し、モデルのプロパティをDictionaryとして取り出せるようにします。
func dictionaryObject() -> [String : AnyObject] { let allKeys = self.dynamicType.allKeys() return self.dictionaryWithValuesForKeys(allKeys) }
動作
以上までで作成したクラスを継承し、モデルクラスを作成しましょう。
class Object: KVCModel { dynamic var str0: String = "" dynamic var str1: String = "" dynamic var str2: String = "" dynamic var num0: Int = 0 dynamic var num1: Int = 0 dynamic var num2: Int = 0 }
テストコード
let dic = ["str1" : "str", "str2" : "strstr", "num1": 1, "num2" : 2] var jsonData: NSData = NSData() var jsonDic: [String : AnyObject] = [:] // Dictionary => JSON do { jsonData = try NSJSONSerialization.dataWithJSONObject(dic, options: .PrettyPrinted) } catch { // なんかやる } // JSON => Dictionary do { jsonDic = try NSJSONSerialization.JSONObjectWithData(jsonData, options: .AllowFragments) as! Dictionary } catch { // なんかやる } // Dictionary => Model let model: Model = Model(dictionary: jsonDic) print("str1 : \(model.str1)") print("str2 : \(model.str2)") print("num1 : \(model.num1)") print("num2 : \(model.num2)") print("dic : \(model.dictionaryObject())")
ログ
str1 : str str2 : strstr num1 : 1 num2 : 2 dic : ["num2": 2, "str2": strstr, "num1": 1, "str1": str]