Swift Optional Property Using Kvc Causes Crash

Adding one read only attribute to Core Data Model in Swift. Which doesn't exist in XCDataModel

To support key-value coding for a type declared in Swift, you have to mark it with the @objc keyword:

@objc public var totalVal: NSNumber {

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

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: [:])
// ])

In Swift, is Key-Value Coding available for custom objects without subclassing NSObject?

Unlike a formal protocol, that any object could technically conform to, NSKeyValueCoding is available to any NSObject via Informal Protocols:

An informal protocol is a category on NSObject, which implicitly makes
almost all objects adopters of the protocol. (A category is a language
feature that enables you to add methods to a class without subclassing
it.) Implementation of the methods in an informal protocol is
optional. Before invoking a method, the calling object checks to see
whether the target object implements it. Until optional protocol
methods were introduced in Objective-C 2.0, informal protocols were
essential to the way Foundation and AppKit classes implemented
delegation.

This is as opposed to simply implementing the KVC directly into NSObject, I think the main benefit of the informal protocol is to split up the functionality of NSObject into separate files. But there may be other benefits of using Informal Protocols

And because NSKeyValueCoding is a category on NSObject, you unfortunately cannot just make any custom object support KVC

How do I use Key-Value Coding in Swfit 4.0?

To implement KVC support for a property in Swift 4, you need two things:

  1. Since the current implementation of KVC is written in Objective-C, you need the @objc annotation on your property so that Objective-C can see it. This also means that the property's type needs to be compatible with Objective-C.

  2. In addition to exposing the property to Objective-C, you will need to set up your notifications in order for observers to be notified when the property changes. There are three ways to do this:

For stored properties, the easiest thing to do is to add the dynamic keyword like so:

@objc dynamic var foo: String

This will allow Cocoa to use Objective-C magic to automagically generate the needed notifications for you, and is usually what you want. However, if you need finer control, you can also write the notification code manually:

@objc private static let automaticallyNotifiesObserversOfFoo = false
@objc var foo: String {
willSet { self.willChangeValue(for: \.foo) }
didSet { self.didChangeValue(for: \.foo) }
}

The automaticallyNotifiesObserversOf<property name> property is there to signify to the KVC/KVO system that we are handling the notifications ourselves and that Cocoa shouldn't try to generate them for us.

Finally, if your property is not stored, but rather depends on some other property or properties, you need to implement a keyPathsForValuesAffecting<your property name here> method like so:

@objc dynamic var foo: Int
@objc dynamic var bar: Int

@objc private static let keyPathsForValuesAffectingBaz: Set<String> = [
#keyPath(foo), #keyPath(bar)
]
@objc var baz: Int { return self.foo + self.bar }

In the example above, an observer of the baz property will be notified when the value for foo or the value for bar changes.



Related Topics



Leave a reply



Submit