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:
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.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
How to Pass Closure as a Parameter in Perform(Selector, Withobject)
Core Data Update in Swift While Selecting Any Row in List Table View Not Working
Thread 1: Signal Sigabrt Error When Using Ibactions
How to Figure Out the Day Difference in the Following Example
Swift: How to Handle Video and Photo Results from a PHPicker
Xcode Firebase | Cannot Convert Value of Type'Authdataresult' to Expected Argument Type 'User'
Firebase Username Uniqueness in Swift
How to Store the Progress of Progressview into Arraylist as One Element
Getting Wrong Date from Dateformat
Swift Updating Screen in Between Steps of While Loop
Inserting Data into Realm Db with Progress
Ibeacon: Get Advertisement Package Faster
How to Use Dispatchgroup/Gcd to Execute Functions Sequentially in Swift
Xcode 8.0 Cbcentralmanager Issue
Uicollectionviewcell Calling Functions Over and Over