KVObserving a protocol in Swift 4 - swift4

I am struggling to use the new strongly-typed KVO syntax in Swift 4 to observe properties that are only visible through a protocol:
import Cocoa
#objc protocol Observable: class {
var bar: Int { get }
}
#objc class Foo: NSObject, Observable {
#objc dynamic var bar = 42
}
let implementation = Foo()
let observable: Observable = implementation
let observation = observable.observe(\.bar, options: .new) { _, change in
guard let newValue = change.newValue else { return }
print(newValue)
}
implementation.bar = 50
error: value of type 'Observable' has no member 'observe'
let observation = observable.observe(\.bar, options: .new) { _, change in
Clearly, Observable is not an NSObject. But I cannot simply cast it to NSObject, because the type of the keypath will not match the type of the object.
I tried being more explicit about the type:
let observable: NSObject & Observable = implementation
But:
error: member 'observe' cannot be used on value of protocol type 'NSObject & Observable'; use a generic constraint instead
let observation = observable.observe(\.bar, options: .new) { _, change in
Is what I am trying to do not possible? This seems a common use case. It is easily done with old #keypath syntax. Can you offer any alternatives? Thanks.

This code compiles and runs in Swift 4.1.2 (Xcode 9.4):
import Foundation
#objc protocol Observable: AnyObject {
var bar: Int { get }
}
#objc class Foo: NSObject, Observable {
#objc dynamic var bar = 42
}
let implementation = Foo()
let observable: NSObject & Observable = implementation
func observeWrapper<T: NSObject & Observable>(_ object: T) -> NSKeyValueObservation {
return object.observe(\.bar, options: .new) { _, change in
guard let newValue = change.newValue else { return }
print(newValue)
}
}
let observation = observeWrapper(observable)
implementation.bar = 50
withExtendedLifetime(observation, {})
All I did is wrap the call to observe in a generic function that's constrained to NSObject & Observable.
Despite the introduced generic, it’s still possible to pass the protocol-typed observable to this new function. To be honest, I can't really explain why this works.
Edit: This might explain it: protocols in Swift generally don't conform to themselves, so it wouldn't be allowed to call a generic function with an existential (a protocol type) even if the constraints match. But there's an exception for #objc protocols (without static requirements), which do conform to themselves. I suppose this works because Observable is marked #objc.

Related

How can you get the shared instance from AnyClass using a protocol in Swift?

In the past we've used Objective-C to anonymously get the sharedInstance of a class this way:
+ (nullable NSObject *)sharedInstanceForClass:(nonnull Class)aClass
{
// sharedPropertyProvider
NSObject<KVASharedPropertyProvider> *sharedPropertyProvider = [aClass conformsToProtocol:#protocol(KVASharedPropertyProvider)]
? (NSObject<KVASharedPropertyProvider> *)aClass
: nil;
if (sharedPropertyProvider == nil)
{
return nil;
}
// return
return [sharedPropertyProvider.class sharedInstance];
}
It's protocol based. We put this protocol on every class we have with a shared instance where we need to do this.
#objc (KVASharedPropertyProvider)
public protocol KVASharedPropertyProvider: AnyObject
{
#objc (sharedInstance)
static var sharedInstance: AnyObject { get }
}
The above works fine in Objective-C (and when called from Swift). When attempting to write the same equivalent code in Swift, however, there appears to be no way to do it. If you take this specific line(s) of Objective-C code:
NSObject<KVASharedPropertyProvider> *sharedPropertyProvider = [aClass conformsToProtocol:#protocol(KVASharedPropertyProvider)]
? (NSObject<KVASharedPropertyProvider> *)aClass
: nil;
And attempt to convert it to what should be this line of Swift:
let sharedPropertyProvider = aClass as? KVASharedPropertyProvider
... initially it appears to succeed. The compiler just warns you that sharedPropertyProvider isn't be used. But as soon as you attempt to use it like so:
let sharedInstance = sharedPropertyProvider?.sharedInstance
It gives you the compiler warning back on the previous line where you did the cast:
Cast from 'AnyClass' (aka 'AnyObject.Type') to unrelated type
'KVASharedPropertyProvider' always fails
Any ideas? Is Swift simply not capable of casting AnyClass to a protocol in the same way that it could be in Objective-C?
In case you're wondering why we need to do this, it's because we have multiple xcframeworks that need to operate independently, and one xcframework (a core module) needs to optionally get the shared instance of a higher level framework to provide special processing if present (i.e. if installed) but that processing must be initiated from the lower level.
Edit:
It was asked what this code looked like in Swift (which does not work). It looks like this:
static func shared(forClass aClass: AnyClass) -> AnyObject?
{
guard let sharedPropertyProvider = aClass as? KVASharedPropertyProvider else
{
return nil
}
return type(of: sharedPropertyProvider).sharedInstance
}
The above generates the warning:
Cast from 'AnyClass' (aka 'AnyObject.Type') to unrelated type
'KVASharedPropertyProvider' always fails
It was suggested I may need to use KVASharedPropertyProvider.Protocol. That looks like this:
static func shared(forClass aClass: AnyClass) -> AnyObject?
{
guard let sharedPropertyProvider = aClass as? KVASharedPropertyProvider.Protocol else
{
return nil
}
return type(of: sharedPropertyProvider).sharedInstance
}
And that generates the warning:
Cast from 'AnyClass' (aka 'AnyObject.Type') to unrelated type
'KVASharedPropertyProvider.Protocol' always fails
So, I assume you have something like this
protocol SharedProvider {
static var shared: AnyObject { get }
}
class MySharedProvider: SharedProvider {
static var shared: AnyObject = MySharedProvider()
}
If you want to use AnyObject/AnyClass
func sharedInstanceForClass(_ aClass: AnyClass) -> AnyObject? {
return (aClass as? SharedProvider.Type)?.shared
}
Better approach
func sharedInstanceForClass<T: SharedProvider>(_ aClass: T.Type) -> AnyObject {
return T.shared
}

Use generic struct with protocol

It seems that Swift generics work fine, as long as I don't try to combine them in any practical way. I'm using Swift 4.1, and I would like to create a generic array containing only weak references. I can define this as WeakList<T>. So far so well. But: I would like to use a protocol for T. Swift says nope..
import Foundation
protocol Observer: class {
func stateChanged(sender: SomeClass, newState: Int)
}
struct WeakList<T> where T: AnyObject {
struct Ptr {
weak var p: T?
}
private var storage: [Ptr] = []
var aliveObjects: [T] {
var result: [T] = []
for ptr in storage {
if let p = ptr.p {
result.append(p)
}
}
return result
}
mutating func add(_ obj: T) {
storage.append(Ptr(p: obj))
}
// Let's ignore for a moment that this implementation leaks memory badly.
}
class SomeClass {
var someVar: WeakList<Observer> = WeakList<Observer>()
// Error: WeakList requires that 'Observer' be a class type
var thisIsOk: WeakList<NSObject> = WeakList<NSObject>()
}
(this is not my original code but a minimal verifyable example that contains enough details so that no one can say "just remove the AnyObject constraint from the structure")
I guess what I'm trying to do is just not possible. Or is it? It's just frustrating how 4 out of 5 times when I try to do something with Swift generics, I later learn that what I am trying to do is just not possible. (I can implement the same thing in Objective-C easily, by the way.)
I tried changing the class constraint to an AnyObject constraint => doesn't work either.
I tried to change the AnyObject constraint to a class constraint => doesn't even compile.
And changing it to protocol Observer where Self: NSObject doesn't change anything. NSObject is a class type, Observer is an NSObject. It should follow that Observer is a class type. The "is a" relationship doesn't seem to be transitive here.
With current implementation you cannot inherit the protocol from AnyObject. What you can do is to create a Type Eraser for your protocol and use that instead. Now your type eraser can be inherited from AnyObject.
Something like this:
protocol Observer {
func stateChanged(sender: SomeClass, newState: Int)
}
class AnyObserver: NSObject, Observer {
private let observer: Observer
init(observer: Observer) {
self.observer = observer
}
func stateChanged(sender: SomeClass, newState: Int) {
observer.stateChanged(sender: sender, newState: newState)
}
}
struct WeakList<T> where T: AnyObject {
struct Ptr {
weak var p: T?
}
private var storage: [Ptr] = []
var aliveObjects: [T] {
var result: [T] = []
for ptr in storage {
if let p = ptr.p {
result.append(p)
}
}
return result
}
mutating func add(_ obj: T) {
storage.append(Ptr(p: obj))
}
// Let's ignore for a moment that this implementation leaks memory badly.
}
class SomeClass {
var someVar: WeakList<AnyObserver> = WeakList<AnyObserver>()
var thisIsOk: WeakList<NSObject> = WeakList<NSObject>()
}

Value of type 'X' has no member 'y' - optional func in protocol

I'm trying to get a better understanding of protocols in Swift. Specifically optional protocol methods. I thought the issue might have to do with my protocol being defined / used in a different file, but if you put the following in a playground you'll get the same issue:
import Foundation
#objc protocol MyProtocol {
optional func shouldJump() -> Bool
}
extension NSObject : MyProtocol {}
class Test {
func testJump() {
let object = NSObject()
let jump = object.shouldJump?() ?? true
print("should jump: \(jump)")
}
}
let t = Test()
t.testJump()
Here is the error message:
error: value of type 'NSObject' has no member 'shouldJump'
let jump = object.shouldJump?() ?? true
^~~~~~ ~~~~~~~~~~
For some reason it doesn't accept that the protocol has been defined on NSObject. Code completion finds it, but the compiler doesn't let it pass.
I'm not sure if my ?? true part will work, but I want that to be a default value incase the method isn't defined.
How do I get this to work?
Your NSObject conforms to MyProtocol, but because it doesn't implement the optional protocol method, the compiler knows it does not have the Selector shouldJump:
let object = NSObject()
object.conformsToProtocol(MyProtocol) // true
object.respondsToSelector("shouldJump") // false
One way to solve this is to implement the protocol method in the extension in order for the object to perform that selector:
extension NSObject : MyProtocol {
func shouldJump() -> Bool {
// some logic here
return true
}
}
class Test {
func testJump() {
let object = NSObject()
let jump = object.shouldJump()
print("should jump: \(jump)")
}
}
let t = Test()
t.testJump() // works
If you don't want to implement the optional method in the extension, you have to cast your NSObject as MyProtocol and verify that it responds to the optional Selector:
class Test {
func testJump() {
let object = NSObject()
let obj = object as MyProtocol
if object.respondsToSelector("shouldJump") {
let jump = obj.shouldJump?()
print("should jump: \(jump)")
} else {
print("nope")
}
}
}
You can also skip the respondsToSelector step and use an if let or guard to verify that shouldJump() returns non-nil.
class Test {
func testJump() {
let object = NSObject()
guard let obj: MyProtocol = object else {
return // object does not conform to MyProtocol
}
if let jump = obj.shouldJump?() { // if shouldJump() returns non-nil
print("should jump: \(jump)")
} else {
print("nope")
}
}
}
I think this is because the compiler knowns NSObject doesn't have shouldJump method, so the call object.shouldJump?() makes no sense. You can cast object to your protocol:
let jump = (object as MyProtocol).shouldJump?() ?? true
Swift is a type safe language. In order to be able to use shouldJump?() you first must have an object conformant to MyProtocol. In this case you can simply cast your type:
let jump = (object as MyProtocol).shouldJump?() ?? true
You can also store it in a variable:
let jumper = object as MyProtocol
let jump = jumper?.shouldJump() ?? true

Any ideas on how to have a generic class, implementing a protocol, be cast to that protocol and allow retrieving a property?

I have a simple Result object which should contain an object (class, struct or enum) and a Bool to say whether it was cancelled or not. I need to interrogate this object along its path (before it gets to its destination, where the destination knows what kind of object to expect) to determine whether it was cancelled or not (without worrying about the accompanying object at that moment). My object looks like:
import Foundation
#objc protocol Resultable {
var didCancel: Bool { get }
}
class Result<T>: Resultable {
let didCancel: Bool
let object: T?
init(didCancel: Bool, object: T?) {
self.didCancel = didCancel
self.object = object
}
}
The idea being that my Result object can wrap the didCancel flag and the actual object (which can be of any type), and the fact that it implements the Resultable protocol means that I can interrogate it at any point to see whether it was cancelled by casting it to Resultable.
I understand (while not liking it) that the protocol has to be prefixed with #objc so that we can cast to it (according to the Apple docs). Unfortunately, when I run the code below (in a playground or in a project), I get a nasty "does not implement methodSignatureForSelector:" error message:
let test = Result(didCancel: false, object: NSArray())
println(test.didCancel)
// transform the object into the form it will be received in
let anyTest: AnyObject = test
if let castTest = anyTest as? Resultable {
println(castTest.didCancel)
}
It seems that despite the protocol being prefixed with #objc, it also wants the actual class to inherit from NSObject (and this is not a requirement that Apple makes explicit). This is obviously a problem for a generic class.
Is there anything I am missing here? Any way to get this to work? Failing that, are there any workarounds (although I strongly believe that this kind of thing should be possible - perhaps we can hope that Apple will do away with the #objc protocol casting requirement at some stage)?
UPDATE
It looks like this is solved with Swift 1.2
You can now cast to a non-ObjC protocol, and without the object having to inherit from NSObject.
It seems that despite the protocol being prefixed with #objc, it also wants the actual class to inherit from NSObject
This is not true. For example, the following code works as expected:
#objc protocol MyProtocol {
var flag: Bool { get }
}
class MyClass: MyProtocol {
let flag = true
}
let foo:AnyObject = MyClass()
if let p = foo as? MyProtocol {
println(p.flag)
}
The problems is that: Any methods/properties declared in Swift Generic classes are invisible from Objective-C. Hence, from the perspective of #objc protocol Resultable, didCancel property declared in Result<T> is invisible. That's why methodSignatureForSelector: is called.
The workaround here is very annoying: You have to have non Generic base class for Result that implements didCancel.
#objc protocol Resultable {
var didCancel: Bool { get }
}
class ResultBase: Resultable {
let didCancel: Bool
init(didCancel: Bool) { self.didCancel = didCancel }
}
class Result<T>: ResultBase, Resultable {
// ^^^^^^^^^^^^
// You have to explicitly conforms `Resultable` here as well for some reason :/
let object: T?
init(didCancel: Bool, object: T?) {
self.object = object
super.init(didCancel: didCancel)
}
}
let test = Result(didCancel: false, object: NSArray())
let anyTest: AnyObject = test
if let castTest = anyTest as? Resultable {
println(castTest.didCancel) // -> outputs "false"
}

Distinction in Swift between uppercase "Self" and lowercase "self"

While playing in a Swift playground I noticed that Self, with capital "S", is available along with the lowercase self. Is there any difference between them? If so, what are usages for these two, especially for Self?
Self refers to the type of the current "thing" inside of a protocol (whatever is conforming to the protocol). For an example of its use, see Protocol func returning Self.
The official docs I've found for Self is in Protocol Associated Type Declaration in The Swift Programming Language. It surprisingly is not documented in the sections on protocols or on nested types:
However, now there is a paragraph about Self Type including a code example in the official Swift Programming Language's chapter about Types
Self can also be used in classes, and is useful. Here is an article about it.
Here is an example. You have a class called MyClass. MyClass have methods returning instances of it. Now you add a subclass of MyClass called MySubclass. You want these methods to return instances of MySubclass instead of MyClass. The following code shows how to do it. Note that the methods can be either instance methods or class methods.
class MyClass: CustomStringConvertible {
let text: String
// Use required to ensure subclasses also have this init method
required init(text: String) {
self.text = text
}
class func create() -> Self {
return self.init(text: "Created")
}
func modify() -> Self {
return type(of: self).init(text: "modifid: " + text)
}
var description: String {
return text
}
}
class MySubclass: MyClass {
required init(text: String) {
super.init(text: "MySubclass " + text)
}
}
let myClass = MyClass.create()
let myClassModified = myClass.modify()
let mySubclass = MySubclass.create()
let mySubclassModified = mySubclass.modify()
print(myClass)
print(myClassModified)
print(mySubclass)
print(mySubclassModified)
The following line printed out:
// Created
// modifid: Created
// MySubclass Created
// MySubclass modifid: MySubclass Created
in protocol & Extension declaration use Self else self
extension protocolName where Self: UIView
{
func someFunction()
{
self.layer.shadowColor = UIColor.red.cgColor
}
}
I think this question could use a simpler answer, more focussed on the difference between Self and self, and perhaps aimed at people newer to Swift.
self - explicit reference to the current type or instance of the type in which it occurs.
class MyClass {
func showClass() {
print("\(self)")
}
}
let someClass = MyClass()
someClass.showClass()
// prints "MyClass"
Self - Used specifically in protocol and extension declarations to refer to the eventual type that will conform to the protocol.
protocol MyProtocol {
static func returnSelf() -> Self
}
class MyClass: MyProtocol {
// define class
}
MyClass.returnSelf()
// returns MyClass
The difference is that self is used in types and instances of types to refer to the type that it's in; and Self is used in protocols and extensions where the actual type is not yet known.
In even simpler terms, self is used within an existing type; Self is used to refer to a type that Self is not yet in.
Read more here:
Self - https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#//apple_ref/doc/uid/TP40014097-CH34-XID_543
self - https://docs.swift.org/swift-book/ReferenceManual/Expressions.html
Both -
https://medium.com/the-traveled-ios-developers-guide/swift-keywords-v-3-0-1-f59783bf26c
I understand Self as a type name(class name for example) and self as an instance of a class/struct , for example:
struct Person {
static var documentNumner = "9484930"
var name: String
var computedFullName: String {
return ("\(self.name) with document number: \(Self.documentNumner)")
}
}
You can't use self with a static property but you can use Self
self-refers to the instance or object of a class.
class SampleClassOne {
let name: String
init(name: String) {
self.name = name
}
}
Self-refers to the type of protocol or extension.
class SampleClassTwo {
static let name: String = "Hello"
private func printName() {
print("\(Self.name)")
}
}
protocol SampleProtocol {
func sampleFunc() -> Self
}
Another example to simply understanding together.
extension Int {
var add: Self { //refer to the Type
return self + 100 //refer to the instance
}
}
print(5.add)
Self can also be used as a return type in the protocol extension method body which will return confirming type instance, and for type casting with "as". Please see example below:
extension <Protocol-Name> where Self:<Class-Name> {
static func foo(_ param:Type)-> Self{
guard let abc = method() as? Self else{
return xyz
}
}}
In the nutshell, Self can be used to refer the Type which confirms to the protocol.