swiftswift-keypath

Advantages of KVC


I have been trying to figure this out for a while but cannot understand the advantage of KVC other than :

  1. Compiler checks (thus avoiding stringly typed code)
  2. Used with KVO

I am not sure if there is any advantage of using KVC other than the 2 cases said above (i know i might be wrong) but i could not find one !

Like consider the following code :

class Profile: NSObject {
    
    @objc var firstName: String
    var lastName: String
    
    init(firstName: String,lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
        super.init()
    }
    
}

let profile1 = Profile(firstName: "John", lastName: "Doe")

profile1.firstName // returns String "John"

profile1.value(forKey: "firstName") // returns Optional<Any> 

let firstNameKey = \Profile.firstName
profile1[keyPath: firstNameKey] /* returns String "John" */

I mean why would i use :

let firstNameKey = \Profile.firstName

profile1[keyPath: firstNameKey] /* returns String "John" */

instead of :

profile1.firstName // returns String "John"

And if someone has some code sample/examples, then if they can explain it using swift , it would be great (as my Objective-C is not good)


Solution

  • The example that you have used is not the optimal case of using KVC or keyPath itself.

    The true power, IMO, of keyPath is unleashed when you're working with protocols. Let me give you an example -

    Say you have a protocol Identifiable which has only one member - id . Each type that conforms to this, must use id property which uniquely identifies it.

    protocol Identifiable {
        var id: Int { get }
    }
    
    struct Person: Identifiable {
        var id: Int
        var name: String
    }
    

    Here, a Person type having a member id doesn't sound too good. Consider another one -

    struct Book: Identifiable {
        var id: Int
        var title: String
        var author: String 
    }
    

    Here, as well , a book can be uniquely identified by id but it doesn't sound good.

    Here's where keyPath comes into play.

    You can define member inside a protocol with some name and let the conforming types write their own name for that particular protocol member. The conforming types can map or tell the compiler that their particular member is the replacement of the one inside the protocol using keyPath.

    protocol Identifiable {
        associatedtype ID
        static var id: WritableKeyPath<Self,ID> { get }
    }
    
    struct Person: Identifiable {
        static var id = \Person.socialSecurityNumber
    
        var socialSecurityNumber: Int
        var name: String
    }
    
    struct Book: Identifiable {
        static var id = \Book.isbn
    
        var isbn: String
        var title: String
        var author: String
    }
    
    func printID<T: Identifiable>(aType: T) {
        print(aType[keyPath: T.id])
    }
    
    printID(aType: Person(socialSecurityNumber: 1234, name: "Shubham Bakshi"))
    printID(aType: Book(isbn: "qwertyui", title: "The Theory of Everything", author: "Stephen W. Hawking"))
    

    As an added advantage , your conforming type can have id of any type , String, Int rather than Int only (as was in previous case)

    If you want to give only a specific type to id, say Int only, you can replace the definition of our protocol with this one -

    protocol Identifiable {
        static var id: WritableKeyPath<Self,Int> { get }
    }
    

    This will force the conforming types to use Int for their id substitute.

    Source - Swift KeyPath - Paul Hudson