6

I'm using Xcode 10.0 with swift 4.2 to learn about Key Value Coding from "Cocoa programming for OSX"

I'm asked to create a simple class, which is a subclass of NSObject. The codes below:

import Cocoa

class Student: NSObject {
    var name: String = ""
    var gradeLevel: Int = 0
}

let student1 = Student()


student1.setValue("Benny", forKeyPath: "name")

student1.setValue("Benny", forKeyPath: "name")

Generates the following error message:

Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).

I've looked online and seem some issues regarding KVC such as : https://bugs.swift.org/browse/SR-5139

What am I doing wrong? The book was published in 2015.

hoboBob
  • 832
  • 1
  • 17
  • 37
  • 1
    Did you read the full bug report? *"Swift 4 no longer exposes properties to Objective-C by default unless they're overriding something or satisfying a protocol requirement. **Use the objc attribute** to expose them explicitly. You can read more about this in the Xcode 9 release notes."* – Martin R Oct 03 '18 at 20:44
  • Hi, Thanks for the reply. Yes I did read the full report, and thought of reading the Xcode 9 release notes. The report was from 2017. I just didn't want to go down the wrong rabbit hole chasing things and though it best to ask. – hoboBob Oct 03 '18 at 20:58
  • See [Key-path Expression](https://docs.swift.org/swift-book/ReferenceManual/Expressions.html#ID563) in the Swift book. – rmaddy Oct 03 '18 at 21:50

2 Answers2

12

In Swift 4 exposing code to the Objective-C runtime is no longer inferred for performance reasons.
To avoid the crash you have to add the @objc attribute explicitly.

@objc var name: String = ""

But from the strong type perspective of Swift there are two better ways to get values with KVC:

  1. The #keyPath directive which uses the ObjC runtime, too, but checks the key path at compile time

    let keyPath = #keyPath(Student.name)
    student1.setValue("Benny", forKeyPath: keyPath)
    

    In this case you get a very descriptive compiler warning

    Argument of '#keyPath' refers to property 'name' in 'Student' that depends on '@objc' inference deprecated in Swift 4

  2. The (recommended) native way: Swift 4+ provides its own KVC pattern where subclassing NSObject is not required.
    Key paths are indicated by a leading backslash followed by the type and the property (or properties):

    class Student {
        var name: String = ""
        var gradeLevel: Int = 0
    }
    
    let student1 = Student()
    student1[keyPath: \Student.name] = "Benny"
    
vadian
  • 274,689
  • 30
  • 353
  • 361
  • This is really interesting and a perfect answer.... i will take @Martin_R suggestion to and read XC9 release notes... soon ! thanks – hoboBob Oct 03 '18 at 21:01
  • There must be another way to do this that falls in with swift. The syntax doesn't look right the back slash is out of place and uneeded – hoboBob Oct 03 '18 at 21:13
  • 1
    Please watch [WWDC 2017 : What's new in Foundation](https://developer.apple.com/videos/play/wwdc2017/212/) from 4:30 – vadian Oct 03 '18 at 21:15
  • 1
    OK here is 57 minutes of my life ;) – hoboBob Oct 03 '18 at 21:18
  • There are worse ways to waste life time Watch the video and you will understand the syntax – vadian Oct 03 '18 at 21:21
  • 21 minutes in and the I'm listening about KVO which I really wanted to ask but thought I'd get the "Off topic" beat down. Thanks man :) really good video. I wish I got 30% of what they are talking about but will watch again in 1 month. Do you have a link to more ? – hoboBob Oct 03 '18 at 21:47
1

Simple Example of KVC

Here we access the name property of SimpleClass directly through the instance.

class SimpleClass {
var name: String
init(name: String) {
    self.name = name

   }
 }

let instance = SimpleClass(name: "Mike")
let name = instance.name
print("name: \(name)")

But in KVC Here we access the name property of the class through forKey.

class KVCClass: NSObject {
    @objc dynamic var name: String
    init(name: String) {
        self.name = name
    }
}

let instance = KVCClass(name: "Mike")
if let name = instance.value(forKey: "name") as? String {
    print("name: \(name)")
}
MannaICT13
  • 189
  • 2
  • 9