KVO和KVC的區(qū)別

KVC 與 KVO 無疑是 Cocoa 提供給我們的一個非常強(qiáng)大的特性传惠,使用熟練可以讓我們的代碼變得非常簡潔并且易讀。但 KVC 與 KVO 提供的 API 又是比較復(fù)雜的肮帐,絕對超出我們不經(jīng)深究之前所理解到的復(fù)雜度,這次大家就來跟我一起深入認(rèn)識這兩個特性吧。

基礎(chǔ)使用

首先关串,咱們要說的是 KVC (Key-Value Coding), 它是一種用間接方式訪問類的屬性的機(jī)制监徘。在 Swift 中為一個類實現(xiàn) KVC 的話晋修,需要讓它繼承自 NSObject:

 class Person: NSObject
{  
   var firstName: String
   var lastName: String 

   init(firstName: String, lastName: String) 
  {   self.firstName = firstName
   self.lastName = lastName
  }
}

這樣,我們就可以使用 KVC 的方式訪問 Person 類的屬性了:

   let peter = Person(firstName: "Cook",lastName: "Peter")
    print(peter.lastName)
    print(peter.valueForKey("lastName")!)

注意我們的兩個 print 語句凰盔,第一個是使用直接引用屬性的方式墓卦,第二個就是使用 KVC 機(jī)制訪問的方式。 valueForKey 是 KVC 協(xié)議中定義的方法户敬,它接受一個參數(shù)落剪,我們把它叫做 key睁本,這個 key 表示要訪問的屬性名稱,KVC 就會根據(jù)我們傳入的 key 幫助我們找到對應(yīng)的屬性忠怖。

不同之處

在 Swift 中處理 KVC和 Objective-C 中還是有些細(xì)微的差別呢堰。比如,Objective-C 中所有的類都繼承自 NSObject凡泣,而 Swift 中卻不是枉疼,所以我們在 Swift 中需要顯式的聲明繼承自 NSObject。

可為什么要繼承自 NSObject 呢鞋拟?我們在蘋果官方的 KVC 文檔中找到了答案骂维。其實 KVC 機(jī)制是由一個協(xié)議NSKeyValueCoding定義的。NSObject 幫我們實現(xiàn)了這個協(xié)議贺纲,所以 KVC 核心的邏輯都在 NSObject 中航闺,我們繼承 NSObject 才能讓我們的類獲得 KVC 的能力。(理論上說哮笆,如果你遵循NSKeyValueCoding協(xié)議的接口来颤,其實也可以自己實現(xiàn) KVC 的細(xì)節(jié),完全行得通稠肘。但在實踐上福铅,這么做就太費(fèi)時間了~)。

另外项阴,因為 Swift 中的 Optional 機(jī)制滑黔,所以 valueForKey 方法返回的是一個 Optional 值,我們還需要對返回值做一次解包處理环揽,才能得到實際的屬性值略荡。
關(guān)于 Optional 特性的內(nèi)容,可以參考這兩篇文章

淺談 Swift 中的 Optionals
關(guān)于 Optional 的一點(diǎn)嘮叨

那么書歸正傳歉胶,KVC 最主要的好處是什么呢汛兜,簡單來說就是我們可以不用過多的依賴編譯時的限制,而是為我們提供了更多的運(yùn)行時的能力通今。
valueForUndefinedKey
還是繼續(xù)咱們上面的例子粥谬,假如我們又寫了這樣一個語句會怎么樣呢:

peter.valueForKey("noExist")

因為我們定義的 Person 類中是沒有 noExist 這個屬性的,所以 KVC 也無法找到這個屬性值辫塌,這時候 KVC 協(xié)議其實會調(diào)用 valueForUndefinedKey 方法漏策,NSObject 對這個方法的默認(rèn)實現(xiàn)是拋出一個 NSUndefinedKeyException 異常。所以如果我們沒有自己重寫 valueForUndefinedKey 方法的話臼氨,這時應(yīng)用就會因為異常崩潰掺喻。
我們也可以在 Person 類中實現(xiàn)我們自己的 valueForUndefinedKey 方法:

class PersonHandleUndefinedKey: NSObject
{ var firstName: String 
  var lastName: String 

  init(firstName: String, lastName: String) 
 { self.firstName = firstName
   self.lastName = lastName
 }
  override func valueForUndefinedKey(key: String) -> AnyObject?
 {
 return ""
  }
}
 let peter2 = PersonHandleUndefinedKey(firstName: "Cook", lastName: "Peter")
 print(peter2.valueForKey("noExist"))

這次定義了 valueForUndefinedKey 對于未定義的 key 返回一個空字符串,這樣我們的 KVC 調(diào)用就能以更加優(yōu)雅的方式處理這個異常行為了。
valueForKeyPath
KVC 除了可以用單個的 key 來訪問單個屬性感耙,還提供了一個叫做 keyPath 的東西褂乍。所謂 keyPath,就比如你的屬性本身也有自己的屬性即硼,那么想引用這個屬性树叽,就需要用到 keyPath。咱們用一個示例來說明:

class Address: NSObject
{ var firstLine: String
  var secondLine: String
  init(firstLine: String, secondLine: String)
  { 
     self.firstLine = firstLine
     self.secondLine = secondLine 
  }
}
 class PersonHandleKeyPath: NSObject 
{ 
    var firstName: String
    var lastName: String 
    var address: Address 
    init(firstName: String, lastName: String, address: Address)
   { 
    self.firstName = firstName 
    self.lastName = lastName
    self.address = address
   }
}
    var peter3 = PersonHandleKeyPath(firstName: "Cook", lastName: "Peter", address: Address(firstLine: "Beijing", secondLine: "Haidian"))
    print(peter3.valueForKeyPath("address.firstLine")!)

PersonHandleKeyPath 類定義了一個屬性address谦絮, 這個 address 本身又是一個類,它也有兩個屬性firstLine和lastLine, 那么我們?nèi)绻胍?address 的 firstLine 屬性洁仗,就可以使用 KVC 的 keyPath 機(jī)制:

print(peter3.valueForKeyPath("address.firstLine")!)

通過 keyPath层皱,我們可以使用 KVC 將屬性引用范圍擴(kuò)大很多。這個規(guī)則對 Cocoa 系統(tǒng)類也適用赠潦,比如:

let view = UIView()print(view.valueForKeyPath("superview.superview"))

我們可以通過 KVC 的這個機(jī)制遍歷 UIView 層級叫胖。同樣的,如果 keyPath 中引用的任何一級屬性不存在或者不符合 KVC 規(guī)范她奥, valueForUndefinedKey 方法就會被調(diào)用瓮增。

SetValueForKey

KVC 定義了使用valueForKey方法獲取屬性的值,同樣也提供了設(shè)置屬性值的方法哩俭,就是setValue:forKey, 還是接著上面的例子:

peter3.setValue("swift", forKey: "firstName")
print(peter3.valueForKey("firstName")!)

setValue:forKey 方法接受兩個參數(shù)绷跑,第一個參數(shù)是我們要設(shè)置的屬性的值,第二個參數(shù)是屬性的 key凡资。這個接口很簡單明了砸捏,就不多贅述了。
和 valueForKey 一樣隙赁,如果我們給 setValue 傳遞一個不存在的 key 值垦藏,KVC 就會去調(diào)用setValue: forUndefinedKey方法,NSObject 對這個方法的默認(rèn)實現(xiàn)依然是拋出一個 NSUndefinedKeyException 異常伞访。

關(guān)于標(biāo)量值

所謂標(biāo)量值(Scalar Type)掂骏,指的是簡單類型的屬性,比如 int厚掷,float 這些非對象的屬性弟灼。關(guān)于標(biāo)量值的在 KVC 中的處理有有些地方需要我們注意,我們把 Person 類再重寫一下:

class PersonForScalar : NSObject 
{  
   var firstName: String 
   var lastName: String 
   var age: Int init(firstName: String, lastName: String, age: Int)
   { 
     self.firstName = firstName
      self.lastName = lastName
      self.age = age
   }
 }

那么現(xiàn)在可以使用 KVC 來操作它的各個屬性:

  var person4 = PersonForScalar(firstName: "peter", lastName: "cook", age: 32)person4.setValue(55, forKey: "age")
  print(person4.valueForKey("age")!)

通過 setValue 方法蝗肪,我們將 age 設(shè)置為 55袜爪,并在下一行代碼中使用 valueForKey 將這個值打印出來。一切看似沒什么不同薛闪。
那么假如我們又寫了這一行語句呢:

  person4.setValue(nil, forKey: "age")

額辛馆,你可以自己嘗試一下,這時候程序會崩潰。原因嘛昙篙,很簡單腊状。 我們先來看 age 的定義:

  var age: Int

age 是一個簡單標(biāo)量值(Int 整型變量),而標(biāo)量值是不能夠設(shè)置成 nil 的苔可。雖然 KVC 提供給我們的 setValue 方法可以接受任何類型的參數(shù)作為值的設(shè)置缴挖,但 age 的底層存儲確實標(biāo)量值,因此我們執(zhí)行上面那條 setValue 語句的時候必然會造成程序的崩潰焚辅。(這點(diǎn)在開發(fā)程序的時候確實需要格外留意映屋,稍不留神可能就會浪費(fèi)很多時間去調(diào)試錯誤)。
那么我們除了注意避免將 nil 傳遞給底層存儲是標(biāo)量類型的屬性之外同蜻,還有沒有其他方法呢棚点? 答案是有的。
KVC 為我們提供了一個 setNilValueForKey 方法湾蔓,每當(dāng)我們要將 nil 設(shè)置給一個 key 的時候瘫析,這個方法就會被調(diào)用,所以我們可以修改一下 Person 類的定義:

  class PersonForScalar : NSObject
  { 
     //... override func setNilValueForKey(key: String)
     { 
       if key == "age" 
       { 
         self.setValue(18, forKey: "age") 
       } 
     } //...
  }

我們在 setNilValueForKey 方法中默责,判斷如果當(dāng)前的 key 是 age 的話贬循,就給它設(shè)置一個默認(rèn)值 18。這次我們再次傳入 nil 的時候桃序,程序就不會因為拋出異常而崩潰杖虾,而是為這個 age 屬性設(shè)置一個默認(rèn)值。

集合屬性

KVC 還提供了對集合屬性的處理葡缰,簡單來說就是這樣亏掀,我們?yōu)?Person 類再添加一個 friends 屬性,用于表示這個人的朋友:

   class PersonForCollection : NSObject 
  { 
      var firstName: String 
      var lastName: String 
      var friends: NSMutableArray
 }

如果我們要為某一個 Person 的實例添加一個新朋友泛释,或者獲取它現(xiàn)有的朋友該怎么做呢滤愕? 大家可能會直接想到這樣:

  person5.friends.addObject(person6)

通過直接的屬性引用,我們可以完成這樣的需求怜校。不過嘛间影,KVC 還給我們提供了專屬的集合操作協(xié)議,這樣我們就可以通過 KVC 的方式操作集合中的內(nèi)容了茄茁,我們將 Person 類改寫一下:

   class PersonForCollection : NSObject 
   { 
      var firstName: String
      var lastName: String 
      var friends: NSMutableArray              
      init(firstName: String, lastName: String) 
     { 
         self.firstName = firstName
         self.lastName = lastName 
         self.friends = NSMutableArray()
     } 
    func countOfFriends() -> Int 
    { 
       return self.friends.count
    } 
    func objectInFriendsAtIndex(index: Int) -> AnyObject? 
    {
       return self.friends[index]
     }
   }

這次我們新添加了兩個方法魂贬,countOfFriends和objectInFriendsAtIndex,這兩個方法是 KVC 預(yù)定義的協(xié)議方法裙顽,用于集合類型的操作付燥。注意這兩個協(xié)議更明確的定義是這樣countOf<Key>和objectIn<Key>AtIndex。 其中的 Key 代表集合操作的應(yīng)的屬性 key 的名字愈犹。比如countOfFriends,countOfAddress,countOfBooks這些都是合法的集合操作協(xié)議方法键科,前提是只要相應(yīng) key 值對應(yīng)的屬性存在闻丑。
那么集合操作方法定義好了,我們來看看如何使用 KVC 來操作集合屬性吧:

      person5.mutableArrayValueForKey("friends").count

這個調(diào)用取得當(dāng)前的 friends 集合的 count 屬性勋颖,這時候?qū)嶋H上調(diào)用了countOfFriends
方法嗦嗡。自然,我們剛才還實現(xiàn)了objectInFriendsAtIndex
方法饭玲,大家也能推理出這個方法如何使用了吧:

let friend = person5.mutableArrayValueForKey("friends")[0]

就是這樣了侥祭,實際上 KVC 對于我們這個集合屬性friends
的操作都會通過 mutableArrayValueForKey 方法來進(jìn)行,它會用我們傳入的 key 值在當(dāng)前實例中進(jìn)行解析茄厘,如果接續(xù)成功會返回一個 NSMutableArray 類型的對象矮冬,我們就可以直接使用 NSMutableArray 的接口對集合類的屬性進(jìn)行操作了,不論他的底層存儲是不是 NSMutableArray次哈,它也是 NSKeyValueCoding 協(xié)議中定義的方法(這個協(xié)議定義我們在前面提到過欢伏,大家還記得吧~)。
我們剛才實現(xiàn)了集合相關(guān)的兩個方法還缺了些什么呢 — 我們只實現(xiàn)了集合操作的 getter 方法亿乳,并沒有實現(xiàn) setter 方法。到目前径筏,我們還不能通過 KVC 機(jī)制來給 firends 數(shù)組添加元素葛假。
我們還需要添加兩個方法:

    class PersonForCollection : NSObject 
  {  
      func insertObjectInFriendsAtIndex(friend: PersonForCollection, index: Int)
    { 
        self.friends.insertObject(friend, atIndex: index) 
     } 
    func removeObjectFromFriendsAtIndex(index: Int)
     { 
      self.friends.removeObjectAtIndex(index) 
    }
  }

insertObjectInFriendsAtIndexremoveObjectFromFriendsAtIndex
分別用于向 friends 屬性中插入元素和刪除元素。現(xiàn)在我們也可以用 KVC 來操作集合內(nèi)容了:

 person5.mutableArrayValueForKey("friends").addObject(person6)
 person5.mutableArrayValueForKey("friends").count
 person5.mutableArrayValueForKey("friends").removeObjectAtIndex(0)

通過 KVC 的集合操作協(xié)議滋恬,我們實現(xiàn)了直接用 KVC 接口來操作集合屬性的內(nèi)容聊训。 KVC 集合操作會更加靈活,friends 屬性不一定是 NSMutableArray 類型恢氯, 它的底層存儲可以是任何形式带斑,只要我們實現(xiàn)了 KVC 集合操作接口,我們就能通過 KVC 像使用 NSMutableArray 一樣來操作底層的集合了勋拟。

總結(jié)

善用 KVC 肯定會對我們的開發(fā)有很大的幫助勋磕。關(guān)于 KVC 如果大家想了解更多,推薦大家看一看蘋果官方的文檔 Key-Value Coding Programming Guide敢靡。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末挂滓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子啸胧,更是在濱河造成了極大的恐慌赶站,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纺念,死亡現(xiàn)場離奇詭異贝椿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)陷谱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門烙博,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事习勤∽俣埃” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵图毕,是天一觀的道長夷都。 經(jīng)常有香客問我,道長予颤,這世上最難降的妖魔是什么囤官? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮蛤虐,結(jié)果婚禮上党饮,老公的妹妹穿的比我還像新娘。我一直安慰自己驳庭,他們只是感情好刑顺,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著饲常,像睡著了一般蹲堂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贝淤,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天柒竞,我揣著相機(jī)與錄音,去河邊找鬼播聪。 笑死朽基,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的离陶。 我是一名探鬼主播稼虎,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼招刨!你這毒婦竟也來了渡蜻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤计济,失蹤者是張志新(化名)和其女友劉穎茸苇,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沦寂,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡学密,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了传藏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腻暮。...
    茶點(diǎn)故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡彤守,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出哭靖,到底是詐尸還是另有隱情具垫,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布试幽,位于F島的核電站筝蚕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏铺坞。R本人自食惡果不足惜起宽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望济榨。 院中可真熱鬧坯沪,春花似錦、人聲如沸擒滑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丐一。三九已至赴魁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钝诚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工榄棵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凝颇,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓疹鳄,卻偏偏與公主長得像拧略,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瘪弓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容

  • KVC(Key-value coding)鍵值編碼垫蛆,單看這個名字可能不太好理解。其實翻譯一下就很簡單了腺怯,就是指iO...
    黑暗中的孤影閱讀 49,675評論 74 441
  • KVC 與 KVO 無疑是 Cocoa 提供給我們的一個非常強(qiáng)大的特性袱饭,使用熟練可以讓我們的代碼變得非常簡潔并且易...
    SwiftCafe閱讀 1,859評論 3 14
  • KVC(Key-value coding)鍵值編碼,單看這個名字可能不太好理解呛占。其實翻譯一下就很簡單了虑乖,就是指iO...
    朽木自雕也閱讀 1,552評論 6 1
  • KVC(Key-value coding)鍵值編碼,單看這個名字可能不太好理解晾虑。其實翻譯一下就很簡單了疹味,就是指iO...
    Fendouzhe閱讀 669評論 0 6
  • 文 沁藍(lán) 這是一個沒有投資就沒有進(jìn)步的社會糙捺。 這是一個投資激發(fā)爆炸式增長的時代诫咱。 投資讓我們的生活更美好,我們用的...
    沁藍(lán)說閱讀 413評論 2 0