Nick Zaporozhchenko
Nick Zaporozhchenko

Reputation: 93

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)

Upvotes: 1

Views: 3252

Answers (1)

Hexfire
Hexfire

Reputation: 6058

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.

Upvotes: 3

Related Questions