Only classes that inherit from NSObject can be declared #objc - swift

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.

Related

Swift 5: How to make a Set containing Class Types (for NSXPCInterface)

In Apple documentation of NSXPCInterface.setClasses(_:for:argumentIndex:ofReply:), for Swift, the first parameter is described as:
An NSSet containing Class objects —for example, [MyObject class].
Hmmm, it looks as though someone neglected to update this from Objective-C to Swift. For Swift I presume it should be something like
A Set containing Class Types
(Someone please correct my wording.) But how do you make a Set containing Class Types? The compiler will not allow me to declare a Set whose member types are not do not conform to Hashable. This makes sense because hashes are needed to maintain uniqueness among members. But Class Types in Swift do not seem to be hashable. Try this in any Swift file, or a playground…
class Foo {}
let mySet: Set<AnyHashable>
mySet.insert(Foo)
The compiler complains:
Argument type 'Foo.Type' does not conform to expected type 'Hashable'
Or, more broadly, does anyone know how to use NSXPCInterface.setClasses(_:for:argumentIndex:ofReply:) in Swift 5?
The classes you pass need to be #objc classes (and subclasses of NSObject). You'll put them into an NSSet, and then cast that to Set:
let classSet = NSSet(object: Foo.self) as! Set<AnyHashable>
This is almost certainly a bug in NSXPCInterface (it needs some kind of "refine for Swift" or possibly a shim), and I suggest opening a defect at bugreport.apple.com.
As of Swift v5.6 (perhaps earlier), you can create the set without casting:
let classSet = NSSet().adding(Foo.self)
This is because adding() is declared like this:
open func adding(_ anObject: Any) -> Set<AnyHashable>

Why can't I declare dynamic variable inside protocol

I'm currently working on protocols for my objects which inherits from Realm's Object. Inside my objects I have variables and these variables are marked as #objc dynamic
#objc dynamic var title: String = ""
Now imagine situation that I have more similar objects with same variable title. I want to create protocol for them since I want to have just one generics method for changing title of object.
So, I created protocol with title variable marked as #objc dynamic with the expectation that this is how it works
protocol Titleable: class {
#objc dynamic var title: String { get set }
}
... this didn't work out and I received actually two errors.
One about marking variable as #objc
#objc can only be used with members of classes, #objc protocols, and concrete extensions of classes
... this I could solve by marking protocol as #objc.
However I still had error connected with dynamic keyword
Only members of classes may be dynamic
... I thought that when I constrained protocol for classes, it should be alright, but... it wasn't.
I somehow solved it by removing #objc as well as dynamic keywords
protocol Titleable: class {
var title: String { get set }
}
... that works. I'm able to mark variable as #objc dynamic in class where I implement this protocol.
class Item: Object, Titleable {
#objc dynamic var title: String = ""
}
However, I'm not sure why this works and why marking variable as dynamic inside protocol declaration doesn't. I would appreciate any explanation.
Look at what dynamic means:
dynamic
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 must be marked with the objc
attribute.
Particularly consider that first paragraph. It says that something that is marked dynamic cannot be statically dispatched. Now consider the case where I have some class in a module. It's already been compiled, and its methods were statically dispatched. Now consider another module that conforms that class to some protocol that includes a dynamic method. How can that work? The method has already been statically dispatched in some places. It can't retroactively be transformed into dynamic dispatch. (The same can apply to declarations in the same module depending on compiler flags and access levels, but I find it easier to explain cross-module.)
The main reason you'd want to do this in any case is to make sure you can use KVO on that property. (If you have some other reason you need to force conforming types to use a dynamic property, I'm interested to know the use case.) If that is your goal, I would probably require Titleable to conform to NSObjectProtocol.

#objc keyword extension subclass behaviour

Can someone explain why #objc keyword is needed here to compile the code?
As I understood this keyword is used in order to work ObjC message method dispatch. But this is not an NSObject instance.
class MyClass {
}
extension MyClass {
#objc func extensionMethod() { /// THIS LINE
print("A")
}
}
class SubClass: MyClass {
override func extensionMethod() {
print("B")
}
}
Does #objc keyword enable message dispatch as well as dynamic? Or not?
Does #objc keyword enable message dispatch as well as dynamic?
Not usually. Usually, the #objc attribute on its own just exposes a given class member to Objective-C – Swift is still free to dispatch to it either using table or static dispatch. You would need to mark the member as dynamic if you wanted Swift to use message dispatch when calling it.
However, for a non-final #objc class extension member, Swift will automatically infer it to be dynamic. Why? Because for interoperability reasons, Swift allows #objc extension members to override and be overridden (much like how you can override an Obj-C method in a subclass category). In order to achieve this behaviour, Swift relies on Obj-C message dispatch.
Therefore, in an extension, #objc infers dynamic. You cannot override an extension member without exposing it to the Obj-C runtime because extension members cannot currently be added to Swift class vtables (as Swift vtables currently cannot have members dynamically added to them at runtime).
But this is not an NSObject instance.
On Apple platforms (i.e those with Obj-C interop), all Swift classes are exposed to the Obj-C runtime, and all implicitly inherit from a special Obj-C base class called _SwiftObject, which conforms to NSObjectProtocol. So Swift classes are able to take advantage of message dispatch without having to inherit from NSObject.

Why must Protocol defaults be implemented via Extensions in Swift?

In Swift, you cannot define default implementations of functions or properties in the Protocol definition itself, i.e.:
protocol Container {
//These are fine
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get set }
subscript(i: Int) -> Item { get }
//These are not fine
var defaultValue: Int = 10
mutating func messWithCount(){
self.count *= 10
}
}
extension Container {
//This is fine though
mutating func messWithCount(){
self.count *= 10
}
}
But you could do so via the Extensions (although Extensions do not support stored properties, they support functions and computed ones - although the stored property issue can be worked around).
What is the explanation behind this? As an add along, what is the explanation for optional func being only implementable if we mark both the Protocol and the func as #objc and hence rendering it unusable for Structs/Enums (which are Value not Reference based)?
EDIT: Added in Extension example
The #optional directive is an Objective-C only directive and has not been translated to Swift. This doesn't mean that you can't use it in Swift, but that you have to expose your Swift code to Objective-C first with he #objc attribute.
Note that exposing to Obj-C will only make the protocol available to types that are in both Swift and Obj-C, this excludes for example Structs as they are only available in Swift!
To answer your first question, Protocols are here to define and not implement:
A protocol defines a blueprint of methods, properties, and other requirements [...]
And the implementation should thus be supplied by the class/stuct/enum that conforms to it:
The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements
This definition really applies to Protocols we use in our daily lives as well. Take for example the protocol to write a Paper:
The PaperProtocol defines a paper as a document with the following non-nil variables:
Introduction
Chapters
Conclusion
What the introduction, chapters and conclusion contain are up to the one implementing them (the writer) and not the protocol.
When we look at the definition of Extensions, we can see that they are here to add (implement) new functionalities:
Extensions add new functionality to an existing class, structure, enumeration, or protocol type. This includes the ability to extend types for which you do not have access to the original source code.
So extending a protocol (which is allowed) gives you the possibility to add new functionality and hereby give a default implementation of a defined method. Doing so will work as a Swift only alternative to the #optional directive discussed above.
UPDATE:
While giving a default implementation to a protocol function in Switch does make it "optional", it is fundamentally not the same as using the #optional directive in Objective-C.
In Objective-C, an optional method that is not implemented has no implementation at all so calling it would result in a crash as it does not exist. One would thus have to check if it exists before calling it, in opposition to Swift with an extension default where you can call it safely as a default implementation exists.
An optional in Obj-C would be used like this:
NSString *thisSegmentTitle;
// Verify that the optional method has been implemented
if ([self.dataSource respondsToSelector:#selector(titleForSegmentAtIndex:)]) {
// Call it
thisSegmentTitle = [self.dataSource titleForSegmentAtIndex:index];
} else {
// Do something as fallback
}
Where it's Swift counterpart with extension default would be:
let thisSegmentTitle = self.dataSource.titleForSegmentAtIndex(index)

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

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.