Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 4 (BETA 2) KVO crashing, based upon WWDC talk

I am trying to get something very similar to the example in the WWDC 2017 Foundation talk working for KVO observing. The only differences that I see that are different from that talk are, I had to call super.init(), and I had to make the "kvo" token implicitly unwrapped.

The following is used in a playground:

struct Node  {
    let title: String
    let leaf: Bool
    var children: [String: Node] = [:]
}

let t = Node(title:"hello", leaf:false, children:[:])
let k1 = \Node.leaf
let k2 = \Node.children
t[keyPath: k1] // returns "false" works
t[keyPath: k2] // returns "[:]" works

@objcMembers class MyController : NSObject {
    dynamic var tr: Node
    var kvo : NSKeyValueObservation!
    init(t: Node) {
        tr = t
        super.init()
        kvo = observe(\.tr) { object, change in
            print("\(object)  \(change)")
        }
    }
}


let x = MyController(t: t)
x.tr = Node(title:"f", leaf:false, children:[:])
x

This error:

fatal error: Could not extract a String from KeyPath Swift.ReferenceWritableKeyPath<__lldb_expr_3.MyController, __lldb_expr_3.Node>: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-900.0.45.6/src/swift/stdlib/public/SDK/Foundation/NSObject.swift, line 85

Also, see this error:

error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0). The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.

Is anyone else able to get something like this working, or is this a bug I need to report?

like image 789
possen Avatar asked Jun 22 '17 18:06

possen


2 Answers

The bug here is that the compiler lets you say:

@objcMembers class MyController : NSObject {
    dynamic var tr: Node
    // ...

Node is a struct, so cannot be directly represented in Obj-C. However, the compiler still allows you to mark tr as dynamic – which requires @objc. While @objcMembers infers @objc for members of the class, it only does so for members that are directly representable in Obj-C, which tr is not.

So really, the compiler shouldn't let you mark tr as dynamic – I went ahead and filed a bug here, which has now been fixed and will be ready for Swift 5.

tr needs to be @objc & dynamic for you to use KVO on it, because KVO requires method swizzling, which the Obj-C runtime provides, and Swift runtime doesn't. So to use KVO here you'll need to make Node a class, and inherit from NSObject in order to expose tr to Obj-C:

class Node : NSObject {

    let title: String
    let leaf: Bool
    var children: [String: Node] = [:]

    init(title: String, leaf: Bool, children: [String: Node]) {
        self.title = title
        self.leaf = leaf
        self.children = children
    }
}

(and if you take a look at the WWDC video again, you'll see the property they're observing is in fact of type a class that inherits from NSObject)

However, in the example you give, you don't really need KVO – you can just keep Node as a struct, and instead use a property observer:

struct Node  {
    let title: String
    let leaf: Bool
    var children: [String: Node] = [:]
}

class MyController : NSObject {

    var tr: Node {
        didSet {
            print("didChange: \(tr)")
        }
    }

    init(t: Node) {
        tr = t
    }
}
let x = MyController(t: Node(title:"hello", leaf:false, children: [:]))
x.tr = Node(title:"f", leaf: false, children: [:])
// didChange: Node(title: "f", leaf: false, children: [:])

And because Node is a value type, didSet will also trigger for any changes to its properties too:

x.tr.children["foo"] = Node(title: "bar", leaf: false, children: [:])
// didChange: Node(title: "f", leaf: false, children: [
//  "foo": kvc_in_playground.Node(title: "bar", leaf: false, children: [:])
// ])
like image 171
Hamish Avatar answered Nov 07 '22 21:11

Hamish


According to Apple, this is the intended behavior at this time as it depends on the Objective-C runtime. This was their response to my bug report and it further confirms what the accepted answer poster said.

like image 1
possen Avatar answered Nov 07 '22 21:11

possen