9

Trying out Swift 4 in Xcode 9 Beta.

For some reason I'm getting a crash when using key value accessors on an NSObject.

Any ideas?

import Cocoa

class Person: NSObject {
    var name = ""
    var age = 0
}

let alpha = Person()
alpha.name = "Robert"
alpha.age = 53

alpha.value(forKey: "name")
// error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
closetCoder
  • 1,064
  • 10
  • 21
  • Related: [How can I deal with @objc inference deprecation with #selector() in Swift 4?](https://stackoverflow.com/q/44390378/2976878) – Hamish Jun 09 '17 at 15:38
  • If you call `value(forKey:)` with `#keyPath(Person.name)` instead of a string then the compiler will show a proper error message and Fix-it! – Martin R Jun 09 '17 at 15:39
  • 1
    Or even, now that you're in Swift 4, use Swift's key-value coding mechanism `print(alpha[keyPath: \Person.name])` :) Then you don't have to inherit from `NSObject` and can even make `Person` a `struct`. – Hamish Jun 09 '17 at 15:50
  • I'm using key-value coding in order to dynamically select the property to access from a string variable. Is there any way to do this using the new Swift 4 mechanism? – closetCoder Jun 09 '17 at 15:56
  • @closetCoder You can store and pass about key paths, e.g `let name = \Person.name`, but they're not strings (they're strongly typed instead). Strings are such weak types to represent key-paths, and should really be avoided unless absolutely necessary. What's your use case for using a string key path? – Hamish Jun 09 '17 at 16:01
  • Data source for an NSTableView. I use the identifier of the column to select which property to return as the value. – closetCoder Jun 09 '17 at 16:02
  • @closetCoder When and where do the identifiers get set? I don't have much experience with `NSTableView`, but how about just having your data source hold an array of `PartialKeyPath` (key paths that start at `Person`, but can go to any sub-property in the object graph), with each corresponding to the property to display for a given column index? Then just subscript your model with the key path for a given column index. – Hamish Jun 09 '17 at 16:23
  • @Hamish The identifiers are set in Interface Builder. It is not a problem to use a switch statement. It just seems a bit inelegant to do stuff like case "name": return data.name case "age": return data.age etc. – closetCoder Jun 09 '17 at 16:28
  • @closetCoder Oh I agree (you wouldn't have to switch with the strongly-typed key-paths either) – but the problem with using strings as key paths is that they're really error-prone (e.g typos, rename/remove a model property without changing the key-path) as well as the fact that they don't carry any type information, so the compiler cannot enforce you're using them correctly (e.g using them on the correct type of object). – Hamish Jun 09 '17 at 16:34

2 Answers2

25

Key-value coding requires Objective-C. In Swift 4 you have to mark members accessible from Obj-C explicitly:

@objcMembers
class Person: NSObject {

or

class Person: NSObject {
    @objc var name = ""
    var age = 0
}

See Swift Evolution 160

Sulthan
  • 128,090
  • 22
  • 218
  • 270
7

ObjC inference has been limited in Swift 4. You can mark your class with objcMembers for the compiler to generate ObjC-compliant accessors:

@objcMembers
class Person: NSObject {
    var name = ""
    var age = 0
}

Or get back the old behavior by setting Swift 3 Objc Inference to On in Build Settings:

enter image description here

Code Different
  • 90,614
  • 16
  • 144
  • 163
  • Note that the "Swift 3 @objc Inference" build setting is only really there to assist with the migration, and therefore isn't a permanent solution. – Hamish Jun 09 '17 at 15:41