Realm: Swift `let` property cannot be marked as dynamic - swift

I am using Xcode 7.2, Swift 2.1.1. I have a Realm model object below
class B: Object {
dynamic let lists = List<A>()
}
But the Swift compiler gives me an error saying:
Property cannot be marked as dynamic because its type cannot be represented in Objective-C
I saw Realm's documentation that says:
Realm model properties need the dynamic var attribute in order for these properties to become accessors for the underlying database data.
There are two exceptions to this: List and RealmOptional properties
cannot be declared as dynamic because generic properties cannot be
represented in the Objective-C runtime, which is used for dynamic
dispatch of dynamic properties, and should always be declared with let
But declaring let doesn't seem to solve this case now. What am I missing?

The documentation you quoted includes the following (emphasis mine):
List and RealmOptional properties cannot be declared as dynamic because generic properties cannot be represented in the Objective-C runtime, […], and should always be declared with let.
This means your property should be declared like so:
let lists = List<A>()
The Realm Swift documentation recently gained a property declaration cheatsheet which hopefully clarifies the requirements for the different types of declarations.

Related

Only classes that inherit from NSObject can be declared #objc

I have to set value to property by string name representation.
import Foundation
#objc class A:NSObject {
var x:String = ""
}
var a = A()
a.x = "ddd"
print(a.x)
a.setValue("zzz", forKey:"x")
print(a.x)
And getting strange errors during compilation:
main.swift:4:2: error: only classes that inherit from NSObject can be declared #objc
#objc class A:NSObject {
~^~~~~
main.swift:13:1: error: value of type 'A' has no member 'setValue'
a.setValue("zzz", forKey:"x")
^ ~~~~~~~~
Does anyone know what is happening?
PS: reproducible on Swift 4.0 & 3.1.1 (Ubuntu 16.04.3 LTS)
Edited:
import Foundation
#objc class A:NSObject {
#objc dynamic var x:String = ""
}
var a = A()
a.x = "ddd"
print(a.x)
a.setValue("zzz", forKeyPath:"x")
print(a.x)
Output:
error: only classes that inherit from NSObject can be declared #objc
#objc class A:NSObject {
error: property cannot be marked #objc because its type cannot be represented in Objective-C
#objc dynamic var x:String = ""
note: Swift structs cannot be represented in Objective-C
#objc dynamic var x:String = ""
error: value of type 'A' has no member 'setValue'
a.setValue("zzz", forKeyPath:"x")
EDIT 2:
Just trying like "c-style":
func set<T>(_ val:T, forKey key:String) {
print("SET:\(self) \(key) to \(val)")
let ivar: Ivar = class_getInstanceVariable(type(of: self), key)!
let pointerToInstanceField:UnsafeMutableRawPointer = Unmanaged.passRetained(self).toOpaque().advanced(by: ivar_getOffset(ivar))
let pointer = pointerToInstanceField.assumingMemoryBound(to: T.self)
pointer.pointee = val
}
It works well, but causes bad access in the recursive calls. Probably some retain/release issues. Will dig dipper. Also does not work on Linux (as mentioned in answers)
Documentation
Swift without the Objective-C Runtime: Swift on Linux does not depend
on the Objective-C runtime nor includes it. While Swift was designed
to interoperate closely with Objective-C when it is present, it was
also designed to work in environments where the Objective-C runtime
does not exist.
https://swift.org/blog/swift-linux-port/
Which is clear, provided that it states:
value of type 'A' has no member 'setValue'
It basically tells that there is no KVC mechanism underneath. setValue method comes from Objective-C runtime, which is absent on Linux. Thus, it's a no-go and what you're trying to accomplish is simply not possible.
Other than that, the following rule is applied on systems with Obj-C runtime environment:
Key-Value Coding with Swift
Swift objects that inherit from NSObject or one of its subclasses are
key-value coding compliant for their properties by default. Whereas in
Objective-C, a property’s accessors and instance variables must follow
certain patterns, a standard property declaration in Swift
automatically guarantees this. On the other hand, many of the
protocol’s features are either not relevant or are better handled
using native Swift constructs or techniques that do not exist in
Objective-C. For example, because all Swift properties are objects,
you never exercise the default implementation’s special handling of
non-object properties.
Also: Requiring Dynamic Dispatch
Swift APIs that are callable from Objective-C must be available
through dynamic dispatch. However, the availability of dynamic
dispatch doesn’t prevent the Swift compiler from selecting a more
efficient dispatch approach when those APIs are called from Swift
code.
You use the #objc attribute along with the dynamic modifier to require
that access to members be dynamically dispatched through the
Objective-C runtime. Requiring this kind of dynamic dispatch is rarely
necessary. However, it is necessary when using APIs like key–value
observing or the method_exchangeImplementations function in the
Objective-C runtime, which dynamically replace the implementation of a
method at runtime.
Declarations marked with the dynamic modifier must also be explicitly
marked with the #objc attribute unless the #objc attribute is
implicitly added by the declaration’s context. For information about
when the #objc attribute is implicitly added, see Declaration
Attributes in The Swift Programming Language (Swift 4).
Elements must also be declared dynamic in order to be KVO-compatible (for KVC, inheriting from NSObject is enough):
#objc dynamic var x:String = ""
If String doesn't work out, then try going with NSString.
If neither helps, this seems to be a Linux-specific issue, which doesn't appear to support KVC/KVO mechanism (which is also understandable).
P.S. With the code provided, your issue reproduced in Xcode on Mac, too.

Swift 4 statically get value of model property name

When using Swift 3, I was defining my model like so
class Model: NSObject {
var prop1: String
}
When I wanted to access the static string value of the property name prop1, I would use let sad = #keyPath(Model.prop1) and it would give me "prop1" printed out. Happy days.
The problem is, that since upgrading to Swift 4, I am having trouble doing the above. I see in other posts that we can use the new \Model.prop1 syntax but that seems to be providing the value of property rather than the string representation of the name.
I am also refactoring out the need for NSObject on my Swift models, but I would have thought I can still get this functionality.
Any help here would be appreciated!
Swift properties do not necessarily retain the strings of the property names at runtime. Therefore, if the Swift key path syntax were able to give you this string value, it would only be able to be used on NSObject-derived classes. The Swift key path syntax doesn't only work with those, though; it can also be used to refer to properties of non-#objc classes and structs. Therefore, this is not possible. The #keyPath syntax remains available, however, to get the string key path of an Objective-C property.

How Realm handles dynamic dispatch with List<T> and RealmOptional<T>?

Realm relies on dynamic dispatch to access ObjectiveC runtime for some KVC mechanisms. Model properties should be marked with dynamic keyword to enable KVC that is used by Realm objects to fill them with exact values. So you can have model defined like this:
class Car: Object {
dynamic var color = ""
dynamic var age = 0
}
At some level you would be able to set properties like this:
var car = Car()
car["color"] = "white"
car["age"] = 20
This works for all basic types like Int or String but doesn't work with generic classes and structures since they can't be represented in Objective C. Realm uses two of those types:
List<T>
RealmOptional<T>
When adding properties of these types to the Realm models, you add them without dynamic keyword:
let cars = List<Car>
let registration = RealmOptional<String>
What mechanism Realm uses to set and read data of those types without using dynamic dispatch?
Update 1
Upon some inspection, I've found that Realm uses some Objective C runtime methods like class_copyPropertyList() and property_getName() for introspection of property names. What I haven't found yet is how properties are populated with correct data when you read from Realm? Is that part of the ObjectStore C++ framework?
Generic properties such like List<T> or RealmOptional<T> cannot be appeared from Objective-C. Realm uses Swift's reflection (Mirror()) to inspect those.
(See https://github.com/realm/realm-cocoa/blob/b71daecd0f4cf7a89fcb30178be02f506d9b3124/RealmSwift/Object.swift#L310-L316)
Then directly access their ivar: https://github.com/realm/realm-cocoa/blob/76d1018b32ba98babce31afbefd31863075cde8c/Realm/RLMObjectSchema.mm#L217-L223
These generic properties are dispatched statically. Realm cannot hook accessing those. That's why those properties must be declared as let. Realm cannot see re-assign the properties.

How to iterate over Results in Realm with Swift

I am trying to iterate over Results from a Realm query in Swift 2. There are two PersonClass objects stored.
The results var from the query is valid and contains two PersonClass objects but when iterating over results, then name property are empty strings.
class PersonClass: Object {
var name = ""
}
let realm = try! Realm()
#IBAction func button0Action(sender: AnyObject) {
let results = realm.objects(PersonClass)
print(results) //prints two PersonClass object with the name property populated
for person in results {
let name = person.name
print(name) //prints and empty string
}
}
The problem is that you have omitted the dynamic modifier from the property declaration in your model class. The dynamic modifier is necessary to ensure that Realm has an opportunity to intercept access to the properties, giving Realm an opportunity to read / write the data from the file on disk. Omitting this modifier results in the Swift compiler accessing the instance variables directly, cutting Realm out of the loop.
Dynamic
Tells the runtime to use dynamic dispatch over static dispatch for the function or variables modified
Implicitly adds the #objc attribute to the variable or function declaration.
Anything using the dynamic keyword uses the Objective-C runtime instead of the Swift runtime to dispatch messages to it.
Dynamic is useful for app analytics situations but sacrifices optimizations provided by static dispatch.
Dynamic dispatch add better interoperability with Objective-C runtime functions like Core Data which relies on KVC/KVO.
And from the Swift Language Reference
Apply this modifier to any member of a class that can be represented
by Objective-C. When you mark a member declaration with the dynamic
modifier, access to that member is always dynamically dispatched using
the Objective-C runtime. Access to that member is never inlined or
devirtualized by the compiler.

Why does adding 'dynamic' fix my bad access issues?

I'm having a strange issue that appeared with iOS 8 Beta 5 (this issue did not occur with previous versions).
I tried to create an empty project and try to replicate the issue, but I'm unable to do so, so I'm not quite sure where the issue lies.
What I'm seeing is that attempting to access methods of a custom NSManagedObject subclass results in a strange EXC_BAD_ACCESS error.
For example:
var titleWithComma: String {
return "\(self.title),"
}
This method, out of many others, causes this issue when called. However, adding a dynamic keyword before it makes the issue go away:
dynamic var titleWithComma: String {
return "\(self.title),"
}
I know I'm not giving enough info, because I honestly don't know how to pinpoint the actual issue, but can anyone explain what is possibly happening, and why adding dynamic might resolve this issue?
From Swift Language Reference (Language Reference > Declarations > Declaration Modifier)
Apply this modifier to any member of a class that can be represented
by Objective-C. When you mark a member declaration with the dynamic
modifier, access to that member is always dynamically dispatched using
the Objective-C runtime. Access to that member is never inlined or
devirtualized by the compiler.
Because declarations marked with the dynamic modifier are dispatched
using the Objective-C runtime, they’re implicitly marked with the objc
attribute.
It means that your property/method can be accessed by Objective-C code or class. Normally it happens when you sub-classing a Swift class of Objective-C base class.
This is from the prerelease Swift / Objective-C interoperability documentation:
Implementing Core Data Managed Object Subclasses
Core Data provides the underlying storage and implementation of properties in subclasses of the NSManagedObject class. Add the #NSManaged attribute before each property definition in your managed object subclass that corresponds to an attribute or relationship in your Core Data model. Like the #dynamic attribute in Objective-C, the #NSManaged attribute informs the Swift compiler that the storage and implementation of a property will be provided at runtime. However, unlike #dynamic, the #NSManaged attribute is available only for Core Data support.
So, because of some of the Objective-C runtime features that Core Data uses under the covers, Swift properties need to be specially annotated.