Why can't I declare dynamic variable inside protocol - swift

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.

Related

Swift protocols mutability [duplicate]

This question already has answers here:
Read-only properties of protocols in Swift
(3 answers)
Why am I allowed to set a read only property of a protocol using a struct that inherits said protocol?
(3 answers)
Closed 2 years ago.
I'm currently a bit confused about gettable properties in protocols. Consider this example:
protocol Person {
var name: String { get }
}
I expected the name property to be read-only, but I found that you could change the value without compiler complaints:
struct Driver: Person {
var name: String
}
var driver = Driver(name: "Ryan")
driver.name = "Changed!"
If we define driver with let keyword, then compiler raises the error, but if I understand correctly, it has nothing to do with protocols, as constant structs are immutable by design in Swift.
Method interactions behave as I would've expected:
extension Person {
mutating func changeName(_ newName: String) {
self.name = newName // Error: 'name' is a get-only property
}
}
I'm new to Swift, and the nuance mentioned may not have any practical use, but this behavior made me ask myself if I lack some basic understanding of how structs work.
The protocol requirement is
a variable name which can be read
which doesn't mean that the variable in a struct adopting this protocol is necessarily read-only.
In the code you are changing the variable directly in the Driver type, the protocol is not involved.
On the other hand if you annotate the protocol type you get the expected error
var driver : Person = Driver(name: "Ryan")
driver.name = "Changed!" // Cannot assign to property: 'name' is a get-only property
A protocol only declares the required interface, but not the full interface of conforming types. Your conforming types can add extra properties/methods that aren't required by the protocol.
The same is true for getters and setter. If a protocol property requirement is get, that means the conformant type must have a getter for the property, but doesn't mean it cannot have a setter for it as well.
However, the same doesn't work the other way around. If a protocol declares a property as { get set }, that property must have a setter (be mutable).
The apple documentation explains this very well.
The getter and setter requirements can be satisfied by a conforming type in a variety of ways. If a property declaration includes both the get and set keywords, a conforming type can implement it with a stored variable property or a computed property that is both readable and writeable (that is, one that implements both a getter and a setter). However, that property declaration can’t be implemented as a constant property or a read-only computed property. If a property declaration includes only the get keyword, it can be implemented as any kind of property.

What's the mechanics behind extension's methods overriding with '#objc' attribute?

Kind of nerd question. It's unclear to me, what exactly makes this code works:
class Shape { }
extension Shape {
#objc func redraw() {
print("from ext")
}
}
class Circle: Shape { }
class Line: Shape {
override func redraw() { // Compiler error: Declarations from extensions cannot be overridden yet
print("from subclass")
}
}
let line = Line()
let shape:Shape = line
let circle = Circle()
line.redraw() //from subclass
circle.redraw() //from ext
shape.redraw() //from subclass
If I omit #objc keyword in extension, the code won't compile - it's expected behaviour since methods in extension use static method dispatch -> cannot be overridden.
But why adding #objc makes it work? According to documentation and most articles, all is #objc doing is making things visible to Objective-c runtime. To change method dispatch type there is a special keyword - dynamic. But seems it is not necessary here!
Help me figure out, why is adding #objc (and omitting dynamic) makes such things possible.
Extensions,
as the name already says, are supposed to extend/add/include methods
to an existing implementation, making them one of the most beautiful
things about Objective-C, and now Swift, since you can add code to a
class or framework you do not own. Therefore, it makes sense that
you’re not supposed to “replace” code in extensions, conceptually
speaking.
That’s why the compiler complains when you try to do it.
Also Check out this answer.
however this seems to be a support issue too, as swift compiler simply throw this error:
overriding non-#objc declarations from extensions is not supported.
According to Apple,
Extensions can add new functionality to a type, but they cannot
override existing functionality.
But that is not the case, as we are overriding from the extension not vice versa,
which takes us back to the declaration of extension.
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 (known as retroactive modeling). Extensions are similar to categories in Objective-C. (Unlike Objective-C categories, Swift extensions do not have names.) Here.
Going back to the legacy topic swift compiler vs Objc compiler,
Dynamic dispatch vs. Static dispatch .
And there is no official documentation from apple on why this is not supported from the swift compiler or if they have any future plans to fix this or consider it an issue at all.
However, there’s no such thing as Swift dynamic dispatch; we only have
the Objective-C runtime’s dynamic dispatch. That means you can’t have
just dynamic and you must write #objc dynamic. So this is effectively
the same situation as before, just made explicit.
And here is a great article talking about this topic deeply.

#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 do I have to use public to expose func with Swift 4 #objc inference

Using Swift 4, I have a simple class I wish to expose to Objective-C like this:
#objc class SomeClass : NSObject {
#objc class func foo(someDate : Date) -> Bool {
return true
}
}
After compile the autogenerated Obj-C bridging header does not contain the class. However, if I add the public modifier in front of the class and func and rebuild it will be in the bridging header. This seems to contradict the statement from Apple's doc that states (emphasis mine):
Private declarations are not exposed to Objective-C unless they are explicitly marked with #IBAction, #IBOutlet, or #objc
Why do I need to add the public modifier? Even the Swift docs show examples that leave 'public' off and simply use #objc to expose to Objective-C
I think these are two different things.
"Swift declarations marked with the private or fileprivate modifier do
not appear in the generated header.
is Followed by
Private declarations are not exposed to Objective-C unless they are explicitly marked with #IBAction, #IBOutlet, or #objc.
This can be read as saying a function must have the explicit marking to be exposed to Objective-C, and (separately) if marked private it will not be included in the header.
I'm not sure why something would need to be exposed without being visible, (I'm not familiar enough with the runtime to speak to that). However the closest Objective-C analogue to private is to not be included in the header, so it makes sense to have that behavior match.

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.