How do I use #keyPath() in Swift 4?

Viewed 7456

I think part of my problem is because Swift 4 has changed the way things like @objc work.

There are a lot of tutorials floating around, with a lot of different values, and I can't pick my way between what used to work in what version enough to figure out how to make it work in this version.

let delegate = UIApplication.shared.delegate as! AppDelegate
delegate.addObserver(self, forKeyPath: #keyPath(AppDelegate.session), options: [], context: nil)
// Warning: Argument of #keyPath refers to non-'@objc' property 'session'

Adding @objc to the var declaration just informs me that APISession can't be referenced in Objective-C. That seems to lead down the path towards requiring me to expose every class / variable I want to use this tool with to Obj-C, and that just seems backwards -- this is a newer feature, as I understand it, and it's just odd that Apple wouldn't make it work natively in Swift. Which, to me, suggests I'm misunderstanding or misapplying something, somewhere, somehow.

2 Answers

According to the docs:

In Objective-C, a key is a string that identifies a specific property of an object. A key path is a string of dot-separated keys that specifies a sequence of object properties to traverse.

Significantly, the discussion of #keyPath is found in a section titled "Interacting with Objective-C APIs". KVO and KVC are Objective-C features.

All the examples in the docs show Swift classes which inherit from NSObject.

Finally, when you type #keyPath in Xcode, the autocomplete tells you it is expecting an @objc property sequence.

enter image description here

Expressions entered using #keyPath will be checked by the compiler (good!), but this doesn't remove the dependency on Objective-C.

This is how I've applied #keyPath() in real project of mine. I used it to save & retrieve data to and from UserDefaults and I called that feature as AppSettings. Here's how things are going on...

1). I have a protocol called AppSettingsConfigurable It contains a couple of stuffs which are the setting features of my app ...

//: AppSetting Protocol
@objc protocol AppSettingsConfigurable {
    static var rememberMeEnabled : Bool { get set }
    static var notificationEnabled : Bool { get set }
    static var biometricEnabled : Bool { get set }
    static var uiColor: UIColor? { get set }
}

2). I have class and I named it AppSettings . This is where saving and retrieving operation take place with UserDefaults

//: AppSettings
class AppSettings: NSObject {
    fileprivate static func updateDefaults(for key: String, value: Any) {
        // Save value into UserDefaults
        UserDefaults.standard.set(value, forKey: key)
    }

    fileprivate static func value<T>(for key:String) -> T? {
        // Get value from UserDefaults
        return UserDefaults.standard.value(forKey: key) as? T
    }
}

3). Here's where BIG things are happened . Conform AppSettings class to our protocol and lets implement the stuffs using #keyPath().

//: Conform to protocol
extension AppSettings:AppSettingsConfigurable{
    /** get & return remember me state */
    static var rememberMeEnabled: Bool {
        get { return AppSettings.value(for: #keyPath(rememberMeEnabled)) ?? false }
        set { AppSettings.updateDefaults(for: #keyPath(rememberMeEnabled), value: newValue) }
    }

    /** get & return notification state */
    static var notificationEnabled: Bool {
        get { return AppSettings.value(for: #keyPath(notificationEnabled)) ?? true }
        set { AppSettings.updateDefaults(for: #keyPath(notificationEnabled), value: newValue) }
    }

    /** get & return biometric state */
    static var biometricEnabled: Bool {
        get { return AppSettings.value(for: #keyPath(biometricEnabled)) ?? false}
        set { AppSettings.updateDefaults(for: #keyPath(biometricEnabled), value: newValue) }
    }

    /** get & return biometric state */
    static var uiColor: UIColor? {
        get { return AppSettings.value(for: #keyPath(uiColor)) }
        set { AppSettings.updateDefaults(for: #keyPath(uiColor), value: newValue!) }
    }
}

PS: Noticed something different with uiColor from the rest? Nothing wrong with it as it's optional and it's allowed to accept the nil

BOOM..Done! Lets use it..

Usage:

//: Saving...
AppSettings.biometricEnabled = true 

//: Retrieving...
let biometricState = AppSettings.biometricEnabled  // true

That's all...

Related