使用Swift 字典模型互轉 超級簡單

hejunm發表於2019-03-03

寫在前面的話

現在很多iOS專案的開發開始轉向Swift語言。 相信 Swift語言很快會成為iOS工程師 必備技能。 字典轉模型, 模型轉轉字典在開發過程中扮演非常重要的角色。 今天就和大家分享一下使用Swift,如何進行字典模型互轉。 **

Demo在這裡

為了讓工作做到極致,這裡先提供一個工具 JSONExport。該工具能夠使用json資料生成對應的模型類檔案,支援oc和Swift,非常不錯。


功能:

1,字典–>模型 :最簡單的形式

class User: NSObject {  //模型類
    var name:String?
    var icon:String?
    
   // print時會呼叫。相當於java中的 toString()。為了程式碼整潔下面的模型去了這個計算屬性。測試時請下載demo
    override internal var description: String {  
        return "name: (name) 
 icon:(icon) 
"
    }
}


 func func1(){
        let dict = ["name":"Jack","icon":"lufy.png"]
        if let user = User.objectWithKeyValues(dict) as? User{
             print("(user)")
        }
  }
  輸出: name: Optional("Jack") 
        icon: Optional("lufy.png") 
複製程式碼

2,字典–>模型 :模型中包裹模型

//模型類
class Status :NSObject {  
    var text:String?
    var user:User?        //與 1 中的模型相同
    var retweetedStatus:Status?
}

 func func2(){
      let dict = ["text":"Agree!Nice weather!",
                  "user":["name":"Jack","icon":"lufy.png"],
                  "retweetedStatus":["text":"Nice weather!",
					                 "user":["name":"Rose","icon":"nami.png"]]                                      
                 ]
                 
      if let status = Status.objectWithKeyValues(dict) as? Status{
          print("(status)")
      }
 }
輸出: 
    text:Optional("Agree!Nice weather!")
    user:Optional(name: Optional("Jack")  icon:Optional("lufy.png"))
    retweetedStatus:Optional(text:Optional("Nice weather!")
                             user:Optional(name: Optional("Rose")icon:Optional("nami.png"))
                             retweetedStatus:nil)

複製程式碼

3,字典–>模型: 字典中包裹陣列, 陣列中的元素是 一個模型對應的字典

//模型類, 必須遵守DictModelProtocol協議, 並實現customClassMapping方法。
class UserGroup: NSObject,DictModelProtocol {
    var groupName:String?;            //團隊名稱
    var numbers:NSArray?              //成員,儲存User例項
    static func customClassMapping() -> [String: String]?{
        return ["numbers":"User"];   //指定numbers陣列中的元素型別是User
    }
}

func func3(){
   let dict = ["groupName":"Dream Team",
                 "numbers":[["name":"Jack","icon":"lufy.png"],
                            ["name":"Rose","icon":"nami.png"]]
               ]
   if let group = UserGroup.objectWithKeyValues(dict){
       print("(group)")
   }
}

輸出: groupName:Optional("Dream Team")
      numbers:Optional((
                        "name: Optional("Jack") 
 icon:Optional("lufy.png") 
",
                        "name: Optional("Rose") 
 icon:Optional("nami.png") 
"
    ))
複製程式碼

4,字典–>模型: 將一個字典陣列轉成模型陣列

func func4(){
        let arrayOfStatus = [["text":"Agree!Nice weather!",
                             "user":["name":"Jack",
                                     "icon":"lufy.png"
                                    ],
                            "retweetedStatus":["text":"Nice weather!",
                                                "user":["name":"Rose",
                                                        "icon":"nami.png"
                                                       ]
                                               ]
                            ],
                            ["text":"2___Agree!Nice weather!",
                              "user":["name":"2___Jack",
                                       "icon":"2___lufy.png"
                                     ],
                            "retweetedStatus":["text":"2___Nice weather!",
                                                "user":["name":"2___Rose",
                                                        "icon":"2___nami.png"
                                                       ]
                                               ]
                            ]]

        if let status = Status.objectArrayWithKeyValuesArray(arrayOfStatus){
            for item in status{ //列印出陣列的元素
                print(item)
            }
        }
    }
輸出: 
    text:Optional("Agree!Nice weather!")
    user:Optional(name: Optional("Jack")icon:Optional("lufy.png"))
    retweetedStatus:Optional(text:Optional("Nice weather!")
                             user:Optional(name: Optional("Rose") icon:Optional("nami.png"))
                             retweetedStatus:nil
    )
    
    text:Optional("2___Agree!Nice weather!")
    user:Optional(name: Optional("2___Jack")icon:Optional("2___lufy.png"))
    retweetedStatus:Optional(text:Optional("2___Nice weather!")
                             user:Optional(name: Optional("2___Rose")icon:Optional("2___nami.png"))
                             retweetedStatus:nil
    )

複製程式碼

5 模型–>字典: 最簡單形式

func func5(){
        let user = User()
        user.name = "hejunm"
        user.icon = "my.png"
        if let dict = user.keyValues{
            do{ //轉化為JSON 字串,列印出來更直觀
                let data = try NSJSONSerialization.dataWithJSONObject(dict, options: .PrettyPrinted)
                print(NSString(data: data, encoding: NSUTF8StringEncoding))
            }catch{}
        }
    }
輸出:    
 Optional({
  "icon" : "my.png",
  "name" : "hejunm"
})
複製程式碼

6 模型–>字典: 模型中還有模型

func func6(){
	let user = User()
       user.name = "retweeted user hejunm"
       user.icon = "my.png"

       let retweetedStatus = Status();  //轉發微博
       retweetedStatus.text = "this is retweeted status";
       retweetedStatus.user = user


       let oriUser = User()
       oriUser.name = "original user"
       oriUser.icon = "my.png"

       let oriStatus = Status(); //原微博
       oriStatus.text = "this is original status"
       oriStatus.user = oriUser
       oriStatus.retweetedStatus = retweetedStatus

       let dict =  oriStatus.keyValues
       do{ //轉化為JSON 字串
           var data = try NSJSONSerialization.dataWithJSONObject(dict!, options: .PrettyPrinted)
            print(NSString(data: data, encoding: NSUTF8StringEncoding))
        
        }catch{
            
        }
}

輸出:  
 Optional({
 "text" : "this is original status",
 "user" : {
		   "icon" : "my.png",
		   "name" : "original user"
		  },
  "retweetedStatus" : {
    "text" : "this is retweeted status",
    "user" : {
      "icon" : "my.png",
      "name" : "retweeted user hejunm"
    }
  }
})
複製程式碼

7,模型–>字典 : 模型陣列轉字典陣列

func func7(){
        
        let user1 = User()
        user1.name = "hejunm_1"
        user1.icon = "my.png_1"

        let user2 = User()
        user2.name = "hejunm_2"
        user2.icon = "my.png_2"

        let userArray = [user1,user2] as NSArray
       
        if let dicts = userArray.keyValuesArray{
            do{
                let data = try NSJSONSerialization.dataWithJSONObject(dicts, options: .PrettyPrinted) //轉成json字串
                print(NSString(data: data, encoding: NSUTF8StringEncoding))
            }catch{
                
            }
        }
    }

輸出: 
Optional([
  {
    "icon" : "my.png_1",
    "name" : "hejunm_1"
  },
  {
    "icon" : "my.png_2",
    "name" : "hejunm_2"
  }
])
複製程式碼

原始碼

字典–>模型

//
//  HE_Dict2Model.swift
//  HEExtention
//
//  Created by 賀俊孟 on 16/4/27.
//  Copyright © 2016年 賀俊孟. All rights reserved.
//  字典傳模型


import Foundation

/** 當字典中存在陣列, 並且陣列中儲存的值得型別是字典, 那麼就需要指定陣列中的字典對應的類型別。
 這裡以鍵值對的形式儲存
 eg 字典如下:
 key: [[key1:value1, key2:value2],[key1:value3, key2:value4],[key1:value5, key2:value6]]
 
 key:  key值
 value: 字典[key1:value1, key2:value2] 對應的模型
*/

@objc public protocol DictModelProtocol{
    static func customClassMapping() -> [String: String]?
}


extension NSObject{
 
    //dict: 要進行轉換的字典
    class func objectWithKeyValues(dict: NSDictionary)->AnyObject?{
        if HEFoundation.isClassFromFoundation(self) {
            print("只有自定義模型類才可以字典轉模型")
            assert(true)
            return nil
        }
        
        let obj:AnyObject = self.init()
        var cls:AnyClass = self.classForCoder()                                           //當前類的型別
        
        while("NSObject" !=  "(cls)"){
            var count:UInt32 = 0
            let properties =  class_copyPropertyList(cls, &count)                         //獲取屬性列表
            for i in 0..<count{
                
                let property = properties[Int(i)]                                         //獲取模型中的某一個屬性
                
                let propertyType = String.fromCString(property_getAttributes(property))!  //屬性型別
                
                let propertyKey = String.fromCString(property_getName(property))!         //屬性名稱
                if propertyKey == "description"{ continue  }                              //description是Foundation中的計算型屬性,是例項的描述資訊
                
                
                var value:AnyObject! = dict[propertyKey]      //取得字典中的值
                if value == nil {continue}
                
                let valueType =  "(value.classForCoder)"     //字典中儲存的值得型別
                if valueType == "NSDictionary"{               //1,值是字典。 這個字典要對應一個自定義的模型類並且這個類不是Foundation中定義的型別。
                    let subModelStr:String! = HEFoundation.getType(propertyType)
                    if subModelStr == nil{
                        print("你定義的模型與字典不匹配。 字典中的鍵(propertyKey)  對應一個自定義的 模型")
                        assert(true)
                    }
                    if let subModelClass = NSClassFromString(subModelStr){
                        value = subModelClass.objectWithKeyValues(value as! NSDictionary) //遞迴
                    }
                }else if valueType == "NSArray"{              //值是陣列。 陣列中存放字典。 將字典轉換成模型。 如果協議中沒有定義對映關係,就不做處理
                    
                    if self.respondsToSelector("customClassMapping") {
                        if var subModelClassName = cls.customClassMapping()?[propertyKey]{   //子模型的類名稱
                            subModelClassName =  HEFoundation.bundlePath+"."+subModelClassName
                            if let subModelClass = NSClassFromString(subModelClassName){
                                value = subModelClass.objectArrayWithKeyValuesArray(value as! NSArray);
                            }
                        }
                    }
                    
                }
                
              obj.setValue(value, forKey: propertyKey)
            }
            free(properties)                            //釋放記憶體
            cls = cls.superclass()!                     //處理父類
        }
        return obj
    }
    
    /**
     將字典陣列轉換成模型陣列
     array: 要轉換的陣列, 陣列中包含的字典所對應的模型類就是 呼叫這個類方法的類
     
     當陣列中巢狀陣列, 內部的陣列包含字典,cls就是內部陣列中的字典對應的模型
     */
    class func objectArrayWithKeyValuesArray(array: NSArray)->NSArray?{
        if array.count == 0{
            return nil
        }
        var result = [AnyObject]()
        for item in array{
            let type = "(item.classForCoder)"
            if type == "NSDictionary"{
                if let model = objectWithKeyValues(item as! NSDictionary){
                    result.append(model)
                }
            }else if type == "NSArray"{
                if let model =  objectArrayWithKeyValuesArray(item as! NSArray){
                    result.append(model)
                }
            }else{
                result.append(item)
            }
        }
        if result.count==0{
            return nil
        }else{
            return result
        }
    }
}

複製程式碼

模型–>字典

//
//  HE_Model2Dict.swift
//  HEExtention
//
//  Created by 賀俊孟 on 16/4/27.
//  Copyright © 2016年 賀俊孟. All rights reserved.
//  模型傳字典

import Foundation

extension NSObject{
    var keyValues:[String:AnyObject]?{                   //獲取一個模型對應的字典
        get{
            var result = [String: AnyObject]()           //儲存結果
            var classType:AnyClass = self.classForCoder
            while("NSObject" !=  "(classType)" ){
                var count:UInt32 = 0
                let properties = class_copyPropertyList(classType, &count)
                for i in 0..<count{
                    let property = properties[Int(i)]
                    let propertyKey = String.fromCString(property_getName(property))!         //模型中屬性名稱
                    let propertyType = String.fromCString(property_getAttributes(property))!  //模型中屬性型別
                    
                    if "description" == propertyKey{ continue }   //描述,不是屬性
                    
                    let tempValue:AnyObject!  = self.valueForKey(propertyKey)
                    if  tempValue == nil { continue }
                    
                    if let _ =  HEFoundation.getType(propertyType) {         //1,自定義的類
                        result[propertyKey] = tempValue.keyValues
                    }else if (propertyType.containsString("NSArray")){       //2, 陣列, 將陣列中的模型轉成字典
                        result[propertyKey] = tempValue.keyValuesArray       //3, 基本資料
                    }else{
                        result[propertyKey] = tempValue
                    }
                }
                free(properties)
                classType = classType.superclass()!
            }
            if result.count == 0{
                return nil
            }else{
                return result
            }
            
        }
    }
}

extension NSArray{  //陣列的擴充
    var keyValuesArray:[AnyObject]?{
        get{
            var result = [AnyObject]()
            for item in self{
                if !HEFoundation.isClassFromFoundation(item.classForCoder){ //1,自定義的類
                    let subKeyValues:[String:AnyObject]! = item.keyValues
                    if  subKeyValues == nil {continue}
                    result.append(subKeyValues)
                }else if item.classForCoder == NSArray.classForCoder(){    //2, 如果item 是陣列
                    let subKeyValues:[AnyObject]! = item.keyValuesArray
                    if  subKeyValues == nil {continue}
                    result.append(subKeyValues)
                }else{                                                     //3, 基本資料型別
                    result.append(item)
                }
            }
            if result.count == 0{
                return nil
            }else{
                return result
            }
            
        }
    }
}
複製程式碼

輔助類

//
//  HEFoundation.swift
//  HEExtention
//
//  Created by 賀俊孟 on 16/4/27.
//  Copyright © 2016年 賀俊孟. All rights reserved.


import Foundation

class HEFoundation {
    
    static let set = NSSet(array: [
                                    NSURL.classForCoder(),
                                    NSDate.classForCoder(),
                                    NSValue.classForCoder(),
                                    NSData.classForCoder(),
                                    NSError.classForCoder(),
                                    NSArray.classForCoder(),
                                    NSDictionary.classForCoder(),
                                    NSString.classForCoder(),
                                    NSAttributedString.classForCoder()
                                  ])
    static let  bundlePath = NSBundle.mainBundle().infoDictionary!["CFBundleExecutable"] as! String
    
    /*** 判斷某個類是否是 Foundation中自帶的類 */
    class func isClassFromFoundation(c:AnyClass)->Bool {
        var  result = false
        if c == NSObject.classForCoder(){
            result = true
        }else{
            set.enumerateObjectsUsingBlock({ (foundation,  stop) -> Void in
                if  c.isSubclassOfClass(foundation as! AnyClass) {
                    result = true
                    stop.initialize(true)
                }
            })
        }
        return result
    }
    
    /** 很據屬性資訊, 獲得自定義類的 類名*/
     /**
     let propertyType = String.fromCString(property_getAttributes(property))! 獲取屬性型別
     到這個屬性的型別是自定義的類時, 會得到下面的格式: T+@+"+..+工程的名字+數字+類名+"+,+其他,
     而我們想要的只是類名,所以要修改這個字串
     */
    class func getType(var code:String)->String?{
        
        if !code.containsString(bundlePath){ //不是自定義類
            return nil
        }
        code = code.componentsSeparatedByString(""")[1]
        if let range = code.rangeOfString(bundlePath){
            code = code.substringFromIndex(range.endIndex)
            var numStr = "" //類名前面的數字
            for c:Character in code.characters{
                if c <= "9" && c >= "0"{
                    numStr+=String(c)
                }
            }
            if let numRange = code.rangeOfString(numStr){
                code = code.substringFromIndex(numRange.endIndex)
            }
            return bundlePath+"."+code
        }
        return nil
    }
}

複製程式碼

相關文章