5

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.

pkamb
  • 33,281
  • 23
  • 160
  • 191
RonLugge
  • 5,086
  • 5
  • 33
  • 61
  • 1
    I know, it's feels like going backward. Any observable needs to inherit from NSObject. – Rikesh Subedi Nov 18 '17 at 02:18
  • 3
    KVO requires the Obj-C runtime to do isa swizzling (and it has *always* required this), so if you want to observe a property, that property needs to be `@objc dynamic`. The only thing that Swift 4 has changed here is that `@objc` is no longer *inferred* in many places (compare https://stackoverflow.com/q/44390378/2976878). You may want to consider using `willSet`/`didSet` over KVO. – Hamish Nov 18 '17 at 16:26
  • @Hamish the big reason I wanted to go with KVO is because my goal was to allow me to observe the value of a property in my app delegate ('is logged in') in a view controller and trigger certain events when login status changes. ViewController in question is stuck being close to the root of my tree, below my login view, so I can't rely on didLoad, and I don't want to trigger the code in question on willAppear. I didn't want to give the delegate a delegate, so this seemed easiest. In this case, it looks like I'm going to have to hook in through notification center instead. – RonLugge Nov 19 '17 at 19:03

2 Answers2

8

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.

Mike Taverne
  • 9,156
  • 2
  • 42
  • 58
3

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

Usage:

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

//: Retrieving...
let biometricState = AppSettings.biometricEnabled  // true
General Grievance
  • 4,555
  • 31
  • 31
  • 45
EK Chhuon
  • 1,105
  • 9
  • 15
  • 3
    What's the reason behind using all that `#keyPath()` stuffs here? – nayem Dec 20 '18 at 08:46
  • Hi @nayem! It's much more Safer and Cleaner! If we're touching with UserDefaults directly, we got to remember its key otherwise things will go wrong! Moreover, we will be repeating ourself to write the same line of code just for saving and retrieving. With #keyPath() we just pick and choose! – EK Chhuon Dec 20 '18 at 08:58