Strange casting behaviour when using performSegue [duplicate] - ios

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

Using protocol with generic data type to pass data between screens

I am Android Developer that started learning iOS. I am trying to pass data between the master-detail style app.
I got controller1 that has a list of ToDo items, and controller2 that allows to create a new ToDo item and add it to the list on controller1.
I have created a protocol:
protocol ListDataHolder {
associatedtype T
func addItem(item: T)
func reloadData()
}
Assigned self in prepare of controller1:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let controller2 = segue.destination as? Controller2{
controller2.toDoDataHolder = self
}
}
Declared delegate in controller2
// how do I tell to use ToDo class for generic type here
var toDoDataHolder: ListDataHolder? = nil
And use it like this:
#IBAction func onAddClicked(_ sender: Any) {
let toDo = ToDo()
...
toDoDataHolder?.addItem(item: toDo)
toDoDataHolder?.reloadData()
navigationController?.popViewController(animated: true)
}
I got a few errors when going this way:
For delegate declaration:
Protocol 'ListDataHolder' can only be used as a generic constraint because it has Self or associated type requirements
When using addItem() :
Cannot convert value of type 'ToDo' to expected argument type 'ListDataHolder.T'
Insert ' as! ListDataHolder.T'
Member 'addItem' cannot be used on value of protocol type 'ListDataHolder'; use a generic constraint instead
When I remove generic from protocol and just have addItem(item: ToDo), everything works fine. But I want to be able to use ListDataHolder with any data type.
This is just experimentation for me, I am not looking for a correct way to pass data between controllers.
EDIT: you can find complete code in this GitHub repo: github.com/Sermilion/ios_learning
What you need to do is to make the second view controller generic using the protocol and limit the type of objects being used (or held) by the class conforming to ListDataHolder
This can be done in the declaration of the view controller
class SecondViewController<Holder: ListDataHolder>: UIViewController where Holder.T == ToDo
then your method onAddClicked will work as is.
Instead of using associated type in ListDataHolder protocol use ToDo if your are always passing ToDo instance to the addItem method.
If you want this protocol addItem method will work with any genericType use following code.
protocol ListDataHolder {
func addItem<T:Decodable>(item: T)
func reloadData()
}
struct ToDo: Decodable {
}
class A: ListDataHolder {
func addItem<T:Decodable>(item: T) {
print("Add Item called")
}
func reloadData(){
}
}
Here you need to implement Delegation design pattern.
Please check Delegation design pattern in detail.

Extension that makes wrapper that dispenses self typed as self

Sorry for the weird title. Here's a toy sketch of my code:
extension UIControl {
func makeHolder() -> ControlHolder {
ControlHolder(control: self)
}
}
struct ControlHolder {
let control : UIControl
init(control: UIControl) {
self.control = control
}
func retrieve() -> UIControl {
return self.control
}
}
I admit this is a toy reduction, and I don't like when people do that, but it illustrates the syntactical issue perfectly, so let's go with it.
Okay, so we have an extension on UIControl that is a method that returns a wrapper object. Now, the problem is this:
let holder = UISwitch().makeHolder()
let output = holder.retrieve()
The result, output, is typed as UIControl, obviously. But that isn't what I want. I want it to be typed as UISwitch, because I started with a UISwitch. OK, so that sounds like a generic. The problem is, I can't figure out how to make that generic.
It's easy, I think, to make ControlHolder a generic:
struct ControlHolder<T:UIControl> {
let control : T
init(control: T) {
self.control = control
}
func retrieve() -> T {
return self.control
}
}
I'm pretty sure I've got that part right. But then how do I write the extension declaration in such a way as to resolve that generic to the actual type of self, the UIControl on which makeHolder is called?
I tried introducing a generic to the extension, obeying the compiler until I got it to compile:
extension UIControl {
func makeHolder<T>() -> ControlHolder<T> {
ControlHolder<T>(control: self as! T)
}
}
But that's pretty silly, and output is still typed as UIControl.
Obviously I can add another parameter passing the type explicitly into makeHolder and thus resolving it:
extension UIControl {
func makeHolder<T>(ofType: T.Type) -> ControlHolder<T> {
ControlHolder<T>(control: self as! T)
}
}
Now when I call makeHolder I pass in the type:
let holder = UISwitch().makeHolder(ofType: UISwitch.self)
let output = holder.retrieve()
And now of course output is typed as UISwitch. But this is idiotic! I want the extension to just know that the type is UISwitch, because I'm calling makeHolder on a UISwitch.
I feel like I'm coming at this all wrong. Maybe someone can straighten me out? Or am I aiming for something that's just impossible?
The trick to this is to define a protocol, an extension of that protocol, and put the makeHolder method in that extension. That way, you can use Self as the generic type for the returned ControlHolder.
First define a new protocol (let's call it "HoldableControl") and require that conformers must be UIControls. It doesn't need any other requirements because we just care about adding the makeHolder function to an extension.
protocol HoldableControl: UIControl {}
Then, add an extension of HoldableControl and define makeHolder in it, returning ControlHolder<Self>. We are allowed to use Self here because it is allowed in protocol extensions, unlike in an extension to UIControl.
extension HoldableControl {
func makeHolder() -> ControlHolder<Self> {
ControlHolder(control: self)
}
}
Then, we just need to have UIControl conform to this protocol:
extension UIControl: HoldableControl {}
And make your ControlHolder generic, as you've already done:
struct ControlHolder<T: UIControl> {
let control: T
init(control: T) {
self.control = control
}
func retrieve() -> T {
control
}
}
And now it will work:
let holder = UISwitch().makeHolder() // type is ControlHolder<UISwitch>
let output = holder.retrieve() // type is UISwitch

override protocol extension default implementation

Consider following code
// GENERATED PROTOCOL, CANNOT BE MODIFIED
protocol SomeProtocol {
}
// GENERATED CLASS, CANNOT BE MODIFIED
class A {
}
// GENERATED CLASS, CANNOT BE MODIFIED
class B: A, SomeProtocol {
}
// I CAN CHANGE ONLY FROM HERE BELOW
extension SomeProtocol {
func someMethod() {
print("protocol implementation")
}
}
extension B {
func someMethod() {
print("class implementation")
}
}
let some: SomeProtocol = B()
some.someMethod() //this prints "protocol implementation"
I want some.someMethod() to print "class implementation". I know there are ways to achieve this, one would be to add in SomeProtocol someMethod, but, unfortunately, I cannot change none of SomeProtocol, A or B, these are generated. I can only play with extensions. Is there a way to achieve this without touching the 3 mentioned before?
If you declare the variable as the protocol type, it will always take the default implementation of the protocol method, since the method is declared in an extension of the protocol.
Without adding the method to the protocol declaration itself (which you've stated to be not possible for you), the only way to access the specific implementation of your conforming type is to downcast some to B or store it as B in the first place.
let some: SomeProtocol = B()
some.someMethod() //this prints "protocol implementation"
(some as? B)?.someMethod() // this prints "class implementation"
let someB = B()
someB.someMethod() // this prints "class implementation"

Set and protocols in Swift

I would like to initialize a Set with values corresponding to the Hashable protocol and a custom protocol.
I tried :
protocol CustomProtocol: Hashable {}
let set = Set<CustomProtocol>()
But Xcode complains :
Using 'CustomProtocol' as a concrete type conforming to protocol
'Hashable' is not supported
How can I achieve that ?
Thanks in advance.
The immediate reason why you can't do what you want to do is that Hashable is a generic protocol. Thus it — or a protocol that derives from it — cannot be used as a Set's element type. A generic type can used only as a constraint in another generic. You will notice that you can't declare a Set<Hashable> either, even though a set's element type must conform to Hashable.
The simplest approach is to make, not a set of protocols, but a set of some object type. For example, if S is a struct that conforms to CustomProtocol (because it conforms to Hashable plus whatever else CustomProtocol entails), you can declare a set of S.
Example:
protocol CustomProtocol: Hashable {
}
func ==(lhs:S,rhs:S) -> Bool {
return lhs.name == rhs.name
}
struct S : CustomProtocol {
var name : String
var hashValue : Int { return name.hashValue }
}
let set = Set<S>()
If the problem you're trying to solve is that you want a collection of mixed types which are nevertheless in some way equatable to one another, then that is the very same problem solved by protocol extensions, as explained by the discussion in the Protocol-Oriented WWDC 2015 video.
But it would be simpler just to make all your types classes that derive from NSObject. You can still make them adopt some secondary protocol, of course, but the set won't be defined as a set of that protocol but of NSObject.
In Swift 3, one solution is to use the AnyHashable structure.
For instance, to create a Observers/Observable pattern, we could do :
protocol Observer {
func observableDidSomething(_ observable: Observable)
}
class Observable {
private var observersSet: Set<AnyHashable> = []
private var observers: [Observer] {
return observersSet.flatMap { $0 as? Observer }
}
func add<O>(_ observer: O) where O : Observer, O : Hashable {
observersSet.insert(observer)
}
func remove<O>(_ observer: O) where O : Observer, O : Hashable {
observersSet.remove(observer)
}
// ...
private func doSomething() {
// do something ...
observers.forEach { $0.observableDidSomething(self) }
}
}
Notice that I separate the Hashable protocol from my protocol Observer.

Using Swift Protocol Inheritance

I am trying to create a delegate that uses traditional polymorphism to compensate for a device being bluetooth LE, bluetooth, etc and can't seem to get the syntax right for casting.
Here is my parent protocol and class:
#objc protocol DeviceDelegate
{
func didConnectToDevice(name:String)
func didFailToConnectToDevice(name:String)
func didDisconnectFromDevice(name:String)
func didWriteData(data:NSData)
func didReceiveData(data:NSData)
}
class Device: NSObject
{
var delegate: DeviceDelegate?
}
Now here is the child class and protocol simplified down:
protocol BluetoothDelegate : DeviceDelegate
{
func didFindService(name:String)
func didFindCharacteristic(name:String)
}
class BLE: Device
{
func someFunc()
{
let bluetoothDelegate = (delegate as? BluetoothDelegate)
bluetoothDelegate.didFindService(UUIDString)
}
}
It throws the following error on the first line of that function:
Cannot downcast from 'DeviceDelegate?' to non-#objc protocol type 'BluetoothDelegate'
This doesn't make sense to me since it should allow casting to a child like a usual object does.
If I put #objc in front of BluetoothDelegate I get the following error:
#objc protocol 'BluetoothDelegate' cannot refine non-#objc protocol 'DeviceDelegate'
Anybody have any ideas on this?
When I copy your code and paste it directly into a playground and add #objc in front of your BluetoothDelegate definition, I get a message on this line:
bluetoothDelegate.didFindService("asdf")
'BluetoothDelegate?' does not have a member named 'didFindService'
Because you have used as?, there is a chance that bluetoothDelegate is nil. You should be using optional chaining here. Replacing with the following line reports no errors in the playground, indicating that you may have done something else in your code that you're not showing us.
bluetoothDelegate?.didFindService("asdf")
Alternatively, you could use this:
if let bluetoothDelegate = delegate as? BluetoothDelegate {
bluetoothDelegate.didFindService(UUIDString)
}
The message you're seeing about DeviceDelegate not being an objc protocol indicates to me that you have written these in two different files and maybe forward-declared DeviceDelegate incorrectly.

Resources