Unwrapping Struct vs Class within Swift Combine Publisher [duplicate] - swift

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")
}

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
}

Swift protocol to return a dictionary of selector

I'm trying to create a protocol where one of the methods will return a dictionary of selector. But I'm running into an issue...
here is the protocol code:
#objc public protocol MazeProtocol: AnyObject {
#objc static func configurations() -> [String:Selector]
}
and here is the compiler error I'm getting:
MazeTableViewController.swift:12:24: Method cannot be marked #objc because its result type cannot be represented in Objective-C
If I remove the #objc in front of the method, I get a similar error.
Well [String: Selector] is Dictionary<String, Selector> which is a struct and structs cannot be represented in Objective-C, so you would need an NSDictionary
#objc public protocol MazeProtocol: AnyObject {
#objc static func configurations() -> NSDictionary
}
As you can't use Selector in Objective C Dictionary directly, you can change your Swift dictionary's both key and value type to String as like below.
#objc public protocol MazeProtocol: AnyObject {
#objc static func configurations() -> [String:String]
}
So when you want to get your Selector from configurations dictionary, get it as like below.
let selectorString = configurations()["KeyToSelector"]
let selector = NSSelectorFromString(selectorString)
As RX9 suggests, there's no reason (at least that you've explained) to mark this as #objc, at either the function or protocol level. The following is fine:
public protocol MazeProtocol: AnyObject {
static func configurations() -> [String:Selector]
}
The point of #objc is to allow ObjC objects to interact with this protocol. If you have Objective-C that needs to interact with this protocol, I strongly suggest defining this protocol on the ObjC side rather than on the Swift side. (But if you have that case, leave a comment, and we can walk through how to get what you need; as olejnjak notes, you can't put Selector directly in a dictionary that ObjC understands.

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")
}

type ViewController doesn't conform to protocol

i'm trying to implement this solution to handle multiple segue identifiers with protocol Protocol-Oriented Segue Identifiers in Swift , but i got this error:
type 'ViewController', doesn't conform to protocol 'SegueHandlerType'
Here is the code:
protocol SegueHandlerType {
associatedtype SegueIdentifier: RawRepresentable
}
extension SegueHandlerType where Self: UIViewController, SegueIdentifier.RawValue == String {
func performSegueWithIdentifier(segueIdentifier: SegueIdentifier,
sender: AnyObject?) {
performSegueWithIdentifier(segueIdentifier.rawValue, sender: sender)
}
func segueIdentifierForSegue(segue: UIStoryboardSegue) -> SegueIdentifier {
// still have to use guard stuff here, but at least you're
// extracting it this time
guard let identifier = segue.identifier,
segueIdentifier = SegueIdentifier(rawValue: identifier) else {
fatalError("Invalid segue identifier \(segue.identifier).") }
return segueIdentifier
}
}
i copy/pasted the solution but still the same result. and the strangest thing is that when i downloaded the project from the GitHub, it works fine. this drives me nuts.
the error:
The error may be confusingly worded, but what it means is that you need to make sure that you are implementing the methods and variables (only the SegueIdentifier enum in this case) in your ViewController class. Do that and you should be good to go.
The protocol SegueHandlerType contains the line SegueIdentifier: RawRepresentable. That means that the class conforming to the protocol has to define a nested type SegueIdentifier.
The tutorial includes the following for that matter:
// the compiler will now complain if you don't have this implemented
// you need this to conform to SegueHandlerType
enum SegueIdentifier: String {
case TheRedPillExperience
case TheBluePillExperience
}
If you add that code the compiler will not longer complain.
class ViewCtr : UIViewController, SegueHandlerType {
enum SegueIdentifier: String {
case YourSegueIdentifiersGoHere
}
}

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"
}