Swift: List all objects that have the same protocol implemented - swift

Hello in my project I have 3 objects that receive the same protocol, is there a way to list all the classes having the same protocol to print it?

By doing
if let _ = someObject as? SomeProtocol {
///
}
you can check if this object conforms to SomeProtocol.
An array of object can be compactMapped like this
let objectsThatConform = arrayOfObjects.compactMap { $0 as? SomeProtocol }

Related

Unwrapping Struct vs Class within Swift Combine Publisher [duplicate]

FYI: Swift bug raised here: https://bugs.swift.org/browse/SR-3871
I'm having an odd problem where a cast isn't working, but the console shows it as the correct type.
I have a public protocol
public protocol MyProtocol { }
And I implement this in a module, with a public method which return an instance.
internal struct MyStruct: MyProtocol { }
public func make() -> MyProtocol { return MyStruct() }
Then, in my view controller, I trigger a segue with that object as the sender
let myStruct = make()
self.performSegue(withIdentifier: "Bob", sender: myStruct)
All good so far.
The problem is in my prepare(for:sender:) method.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Bob" {
if let instance = sender as? MyProtocol {
print("Yay")
}
}
}
However, the cast of instance to MyProtocol always returns nil.
When I run po sender as! MyProtocol in the console, it gives me the error Could not cast value of type '_SwiftValue' (0x1107c4c70) to 'MyProtocol' (0x1107c51c8). However, po sender will output a valid Module.MyStruct instance.
Why doesn't this cast work?
(I've managed to solve it by boxing my protocol in a struct, but I'd like to know why it's not working as is, and if there is a better way to fix it)
This is pretty weird bug – it looks like it happens when an instance has been bridged to Obj-C by being boxed in a _SwiftValue and is statically typed as Any(?). That instance then cannot be cast to a given protocol that it conforms to.
According to Joe Groff in the comments of the bug report you filed:
This is an instance of the general "runtime dynamic casting doesn't bridge if necessary to bridge to a protocol" bug. Since sender is seen as _SwiftValue object type, and we're trying to get to a protocol it doesn't conform to, we give up without also trying the bridged type.
A more minimal example would be:
protocol P {}
struct S : P {}
let s = S()
let val : Any = s as AnyObject // bridge to Obj-C as a _SwiftValue.
print(val as? P) // nil
Weirdly enough, first casting to AnyObject and then casting to the protocol appears to work:
print(val as AnyObject as! P) // S()
So it appears that statically typing it as AnyObject makes Swift also check the bridged type for protocol conformance, allowing the cast to succeed. The reasoning for this, as explained in another comment by Joe Groff, is:
The runtime has had a number of bugs where it only attempts certain conversions to one level of depth, but not after performing other conversions (so AnyObject -> bridge -> Protocol might work, but Any -> AnyObject -> bridge -> Protocol doesn't). It ought to work, at any rate.
The problem is that the sender must pass through the Objective-C world, but Objective-C is unaware of this protocol / struct relationship, since both Swift protocols and Swift structs are invisible to it. Instead of a struct, use a class:
protocol MyProtocol {}
class MyClass: MyProtocol { }
func make() -> MyProtocol { return MyClass() }
Now everything works as you expect, because the sender can live and breathe coherently in the Objective-C world.
Still not fixed. My favorite and easiest workaround is by far chain casting:
if let instance = sender as AnyObject as? MyProtocol {
}
I came across this issue on macOS 10.14.
I have an _NSXPCDistantObject coming from Objc for which
guard let obj = remoteObj as? MyProtocol else { return }
returns
My solution was to define a c function in a separate header like this:
static inline id<MyProtocol> castObject(id object) {
return object
}
And then use like this:
guard let obj: MyProtocol = castObject(remoteObject) else { return }
Here's my solution. I didn't want to just make it into a class (re: this answer) because my protocol is being implemented by multiple libraries and they would all have to remember to do that.
I went for boxing my protocol into a struct.
public struct BoxedMyProtocol: MyProtocol {
private let boxed: MyProtocol
// Just forward methods in MyProtocol onto the boxed value
public func myProtocolMethod(someInput: String) -> String {
return self.boxed.myProtocolMethod(someInput)
}
}
Now, I just pass around instances of BoxedMyProtocol.
I know this issue was resolved with swift 5.3 but sometimes you have to support older versions of iOS.
You can cast to the protocol if you first cast it to AnyObject.
if let value = (sender as? AnyObject) as? MyProtocol {
print("Yay")
}

Downcast enum with Error protocol from Any type in Swift 4 [duplicate]

FYI: Swift bug raised here: https://bugs.swift.org/browse/SR-3871
I'm having an odd problem where a cast isn't working, but the console shows it as the correct type.
I have a public protocol
public protocol MyProtocol { }
And I implement this in a module, with a public method which return an instance.
internal struct MyStruct: MyProtocol { }
public func make() -> MyProtocol { return MyStruct() }
Then, in my view controller, I trigger a segue with that object as the sender
let myStruct = make()
self.performSegue(withIdentifier: "Bob", sender: myStruct)
All good so far.
The problem is in my prepare(for:sender:) method.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Bob" {
if let instance = sender as? MyProtocol {
print("Yay")
}
}
}
However, the cast of instance to MyProtocol always returns nil.
When I run po sender as! MyProtocol in the console, it gives me the error Could not cast value of type '_SwiftValue' (0x1107c4c70) to 'MyProtocol' (0x1107c51c8). However, po sender will output a valid Module.MyStruct instance.
Why doesn't this cast work?
(I've managed to solve it by boxing my protocol in a struct, but I'd like to know why it's not working as is, and if there is a better way to fix it)
This is pretty weird bug – it looks like it happens when an instance has been bridged to Obj-C by being boxed in a _SwiftValue and is statically typed as Any(?). That instance then cannot be cast to a given protocol that it conforms to.
According to Joe Groff in the comments of the bug report you filed:
This is an instance of the general "runtime dynamic casting doesn't bridge if necessary to bridge to a protocol" bug. Since sender is seen as _SwiftValue object type, and we're trying to get to a protocol it doesn't conform to, we give up without also trying the bridged type.
A more minimal example would be:
protocol P {}
struct S : P {}
let s = S()
let val : Any = s as AnyObject // bridge to Obj-C as a _SwiftValue.
print(val as? P) // nil
Weirdly enough, first casting to AnyObject and then casting to the protocol appears to work:
print(val as AnyObject as! P) // S()
So it appears that statically typing it as AnyObject makes Swift also check the bridged type for protocol conformance, allowing the cast to succeed. The reasoning for this, as explained in another comment by Joe Groff, is:
The runtime has had a number of bugs where it only attempts certain conversions to one level of depth, but not after performing other conversions (so AnyObject -> bridge -> Protocol might work, but Any -> AnyObject -> bridge -> Protocol doesn't). It ought to work, at any rate.
The problem is that the sender must pass through the Objective-C world, but Objective-C is unaware of this protocol / struct relationship, since both Swift protocols and Swift structs are invisible to it. Instead of a struct, use a class:
protocol MyProtocol {}
class MyClass: MyProtocol { }
func make() -> MyProtocol { return MyClass() }
Now everything works as you expect, because the sender can live and breathe coherently in the Objective-C world.
Still not fixed. My favorite and easiest workaround is by far chain casting:
if let instance = sender as AnyObject as? MyProtocol {
}
I came across this issue on macOS 10.14.
I have an _NSXPCDistantObject coming from Objc for which
guard let obj = remoteObj as? MyProtocol else { return }
returns
My solution was to define a c function in a separate header like this:
static inline id<MyProtocol> castObject(id object) {
return object
}
And then use like this:
guard let obj: MyProtocol = castObject(remoteObject) else { return }
Here's my solution. I didn't want to just make it into a class (re: this answer) because my protocol is being implemented by multiple libraries and they would all have to remember to do that.
I went for boxing my protocol into a struct.
public struct BoxedMyProtocol: MyProtocol {
private let boxed: MyProtocol
// Just forward methods in MyProtocol onto the boxed value
public func myProtocolMethod(someInput: String) -> String {
return self.boxed.myProtocolMethod(someInput)
}
}
Now, I just pass around instances of BoxedMyProtocol.
I know this issue was resolved with swift 5.3 but sometimes you have to support older versions of iOS.
You can cast to the protocol if you first cast it to AnyObject.
if let value = (sender as? AnyObject) as? MyProtocol {
print("Yay")
}

Why do I need a force downcast to work with protocol arrays with a constraint

This is my code:
import UIKit
protocol Test where Self: UIView {}
class MyView: UIView, Test {
}
let array: [Test] = [MyView()]
let view = array[0]
let myAlpha = (view as UIView).alpha //ERROR
The error is:
error: 'Test' is not convertible to 'UIView'; did you mean to use
'as!' to force downcast?
Why the force downcast? The protocol can only be adopted by UIView, therefore every element in the array array IS-A UIView, right? Is there any case it is not a UIView, hence needing the downcast?
I scratched my head and I know the answer to my own question. With only classes, this situation would never happen, but with protocols (and interfaces in Java), I could actually put in an other class inside the array, this is how:
import UIKit
protocol Test where Self: UIView {}
class MyView: UIView, Test {}
var array: [Test] = [MyView()]
let view = array[0]
class NotAUIView {}
let myFakeTestInstance = NotAUIView() as! Test
array.append(myFakeTestInstance) // Auch! Compiles but it does not conform to the protocol Test!!
It compiles, but crashes at runtime. Subclasses can adopt ofcourse the protocol Test, I did not think about it. Force-downcasting to protocols always compiles to any class without errors, but force-downcasting to other classes which will never work, will throw an warning from the compiler saying it will always fail.
Since you define your array as containing Test objects with
let array: [Test] // <-- This
Then getting any object in that array will give you an instance of Test
let view = array[0] // <-- view is of type `Test
So if you know that this object will be of type UIView (which again, the compiler does not since you told it was a Test object), then you need to force cast.
I'm guessing the compiler does not recognize the type constraint (strict as it may be) as an actual inheritance.
You could work around this by extending your protocol to make it do the type cast itself, given that within the protocol, the identity of the class is known.
#objc protocol Test where Self:UIView {}
extension Test
{
var asUIView: UIView { return self }
}
view.asUIView.alpha // will work
Note that you need to add #objc to your protocol declaration for this to work with a bridged class (UIView) which would not be necessary with a native Swift class.
If you change the type of array to [MyView] you do not need the forced cast:
import UIKit
protocol Test where Self: UIView {}
class MyView: UIView, Test {}
let array: [MyView] = [MyView()]
let view = array[0]
let myAlpha = view.alpha
Update - fixed in Swift5 (Xcode11.1)
protocol TestProtocol where Self: UIView {
func testCall()
}
extension TestProtocol {
func testCall() {
print("protocol #function")
}
}
class MyView: UIView, TestProtocol {
// if commented out then imnplementation in extension of TestProtocol
func testCall() {
print("instance #function")
}
}
let array: [TestProtocol] = [MyView()]
let view = array[0]
let myAlpha = view.alpha
view.testCall()

KVObserving a protocol in Swift 4

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.

Can swift generics be used inside class with generics

I have a protocol call Decodable:
public protocol Decodable {
typealias DecodedType = Self
static func decode(e: Extractor) -> DecodedType?
}
I have a function decode:
public func decode<T: Decodable where T.DecodedType == T>(object: AnyObject) -> T? {
Also, I have a class RestApiResponseParser
class RestApiResponseParser<T where T:Decodable> {
private func createServerModels(response: ServerResponse) -> Result<T> {
var parsingError:NSError?
let link: T? = decode(response)
...
}
...
}
and typealias ServerResponse = [String: AnyObject]
which I try to call as:
let responseParser = RestApiResponseParser<MyModel>(responseData: inputObject as! NSData)
let processError = responseParser.processResponseData()
Unfortunately, I get compile time error:
Does it mean, I can't use generic function in generic class?
Yes, you can use a generic function in a generic class. Your problem is that you are passing a [String: AnyObject] dictionary (which is a struct, and not an AnyObject) into a function that is expecting an AnyObject. It looks like you need to extract the AnyObject from the dictionary using its key:
if let object = response["<insert your key here>"] {
decode(object)
}
Another approach would be to cast your ServerResponse to an NSDictionary which is a class, and can therefore be passed as an AnyObject:
let link: T? = decode(response as NSDictionary)
And btw, the bridging between Dictionary and NSDictionary is not as "seemless" as Apple claims - I have run into instances like this where I can't figure out why Swift isn't able to automatically bridge them for me, and when I have to be explicit. There are inconsistencies like the one that you noted. Your function will take a Dictionary as an argument in once place, but not another, without a clear rationale for why. Perhaps it has something to do with the fact that you are using a typealias, but I'm not sure.