What is key-value coding, what can it do that normal property access cannot, and how does it fail?
Predict first
What does this print?
final class Address: NSObject { @objc var city = "Pune" }
final class Person: NSObject {
@objc var age = 30
@objc var address = Address()
}
let p = Person()
print(p.value(forKeyPath: "address.city") as Any)
p.setValue(31, forKey: "age"); print(p.age)
print(#keyPath(Person.address.city))
let people = [Person(), Person()]
people[1].age = 10
print((people as NSArray).value(forKeyPath: "@sum.age") as Any)
- Optional(Pune) / 31 / address.city / Optional(40)
- Pune / 31 / address.city / 40
- nil / 30 / city / nil
Predict first
What happens here?
final class Person: NSObject { var name = "ana" } // no @objc
print(Person().value(forKey: "name") as Any)
- Prints Optional("ana")
- Prints nil
- The process terminates with NSUnknownKeyException
Mental model. KVC is asking the front desk for “the value in drawer address.city” by label instead of opening the cabinet yourself. A wrong label does not return empty, the clerk raises the alarm.
KVC reads and writes properties by string name: value(forKey: "age"), setValue(31, forKey: "age"), and a dotted key path such as "address.city" that walks relationships. The runtime looks for a getter, then a backing ivar, and boxes scalars in NSNumber. It works on collections too: value(forKey:) on an NSArray maps over the elements, and operators such as @sum.age, @avg.age, @max, @count and @distinctUnionOfObjects compute aggregates. Core Data, XIB loading (setValue:forKey: for outlets) and animation properties are all KVC.
It needs a name the runtime knows: in Swift the property must be @objc (an NSObject subclass, or exposed explicitly). A key that does not exist raises NSUnknownKeyException from value(forUndefinedKey:), which Swift cannot catch (see the exception question), so a typo is a crash, not a nil. The compile-time safe forms are #keyPath(Person.address.city), which checks the names and produces the string, and Swift’s typed \Person.age key paths (which do not need the runtime, but are a different feature). For new code prefer Swift key paths and keep KVC for frameworks that demand strings.
The key path walks the relationship, the set goes through the setter, #keyPath yields the checked string, and @sum adds the property over the array.
The property is invisible to the runtime, so KVC raises the unknown-key exception that Swift cannot catch.
Common traps
- Using KVC on a plain Swift property. Without
@objcthe runtime cannot find it and the call raises an unknown-key exception. - Forgetting scalar boxing.
value(forKey:)returnsAny?holding anNSNumber-bridged value, so cast it deliberately.
They might follow up with
- What does
setValue(nil, forKey:)do for anIntproperty? It callssetNilValueForKey:, which raises by default. - How does it differ from a Swift key path? Swift key paths are typed and checked by the compiler; KVC uses strings resolved at run time.
Related questions
- 4.2.2 What are associated objects, and when are they the right (or wrong) tool?
- 4.4.1 What do @objc, @objcMembers, @objc(name), dynamic and @nonobjc each do?
- 2.9.2 Why does NSNumber confuse Bool, Int and Double, and how do you read untyped values safely?
What does KVO do to an observed object at run time, and how can you… →