Using Swift Protocol Inheritance - ios

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.

Related

Swift protocol extension in Objective-C class

I have a protocol written in Swift that should be conformed by several controllers and some of them are written in Objective-C. Not all of them need all methods from this Swift protocol so at first I decided to provide some methods with default implementation for making them 'optional' but in this case my Objective-C controllers don't recognize my Swift protocol. Did anyone face the same situation and did find a solution? Some sample of my code:
#objc public protocol SwiftProtocol: AnyObject {
func requiredMethod()
func optionalMethod()
}
extension SwiftProtocol {
func optionalMethod() {}
}
#interface ObjClass ()<SwiftProtocol>
And I've got the error : (59, 1) Cannot find protocol declaration for 'SomeProtocol'
Using #objc public in methods instead of extension gave the same result.
TIA for your help!
Objective-C protocols cannot have default implementations.
Objective-C protocols can have real optional methods/properties, unlike Swift protocols, which only have required methods/properties. The workaround for this in Swift is the use of a default implementation, however, sadly those cannot be seen in Objective-C.
I would suggest creating a pure Swift protocol and for all Objective-C classes that want to extend this, write the conformance in Swift, then create #objc wrapper functions in Swift that call the default protocol implementations - if it needs to be called, if it doesn't need to be called, simply ignore it.
Something along the lines of:
protocol SwiftProtocol {
func requiredFunc()
func optionalFunc()
}
extension SwiftProtocol {
func optionalFunc() {}
}
#objc extension ObjcClass: SwiftProtocol {
#objc func requiredFunc() {
print("do something")
}
// This will call the default implementation - can be omitted if you don't need to call the default implementation from Objective-C
#objc func objc_optionalFunc() {
optionalFunc()
}
}

Strange casting behaviour when using performSegue [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")
}

How to call a protocol's default function declared in a protocol extension?

I'm trying to call a protocol's default function that was declared in an extension:
protocol Tester {
func printTest()
}
extension Tester {
func printTest() {
print("XXXXTestXXXX")
}
}
class TestController: UIViewController, Tester {
let testing = Tester()// error here
override func viewDidLoad() {
super.viewDidLoad()
testing.printTest()
}
}
The error ''Tester' cannot be constructed because it has no accessible initializers' keeps appearing when I try to create an instance of the protocol. Whats the best way to use default functions in protocols?
You have to call the implementer, in your case it's TestController so :
self.printTest() will work
protocol Proto {
// func testPrint() <- comment this out or remove it
}
extension Proto {
func testPrint() {
print("This extension called")
}
}
struct Bar: Proto {
func testPrint() {
print("Call from Structure")
(self as Foo).testPrint()
}
}
Bar().testPrint()
// Output: 'Call from Structure',
// 'This extension call'
Protocols cannot be instantiated - ever - lets think of them Contracts
Classes CAN supposed to confrom to 1..n protocols.
The fact that your protocol provides a default implementation, just enables a class that conforms to it to inherit this default functionality if it doesnt need to provide a custom implementation.
a very cheap way in your case would be:
let testing = self
since your TestController class already conforms to Tester.
(Note I create a retain cycle)
===
another way would be to not use testing but self.printTest()
(This might be what you want)
Id reread the apple swift book and check out protocols (what they are and how they work)

Implement delegate using generic protocol in Swift

I'm trying to create a delegate protocol that implements a function which passes an array of a generic type. I've tried several combinations but none of them seem to do the trick.
This is the most approximate thing i've reached to. This is the protocol:
protocol APIControllerProtocol {
typealias T
func didReceiveAPIResults(results: [T])
}
And this is the the delegator object:
class APIController<U:APIControllerProtocol> {
typealias ElementType = U
var delegate: ElementType?
init(delegate: ElementType){
self.delegate = delegate
}
func getAPIResults(){
// Perform some action before delegation
// "results" is an Array of dictionaries got from NSJSONSerialization
self.delegate?.didReceiveAPIResults(results.map{dict in Album(json:dict)})
}
}
However, the last line get this error: "Album is not convertible to U.T"
"Album" is the model object used to return the results.
What am i doing wrong?
EDIT:
Following Mike S advice, i've made the protocol method didReceiveAPIResults a generic function, and specified what T is in the delegate. However, when receiving and assigning the argument of type T to a property in the delegate, i get the error: "T is not identical to T"
class TestDelegate: APIControllerProtocol {
typealias T = Album
var albums:[T] = [T]()
func didReceiveAPIResults<T>(results: [T]) {
// ...
self.albums = results //ERROR: "T is not identical to T"
}
}
Your didReceiveAPIResults declaration in APIControllerProtocol needs to be a generic function so that the generic type T is passed along to it correctly.
protocol APIControllerProtocol {
typealias T
func didReceiveAPIResults<T>(results: [T])
}
Note: This means your delegate definition will need to define what T is:
class TestDelegate: APIControllerProtocol {
typealias T = Album
func didReceiveAPIResults<T>(results: [T]) {
// ...
}
}
Update: While the code above does get rid of the original error, it turns out that it acts more like a workaround and doesn't really address the root of the problem.
The real issue seems to be that the compiler is having trouble reconciling what U.T is with no ambiguity. That's actually easy enough to fix though, we just need to give it a more precise definition (note the where clause in the APIController definition):
protocol APIControllerProtocol {
typealias T
func didReceiveAPIResults(results: [T])
}
class APIController<U:APIControllerProtocol where U.T == Album> {
typealias ElementType = U
// ...
}
Note: I removed the <T> that I added to the function in the protocol previously; that's not needed anymore and will end up causing problems later.
With that, the TestDelegate class works as expected (you don't even need the typealias anymore):
class TestDelegate: APIControllerProtocol {
var albums: [Album]? = nil
func didReceiveAPIResults(results: [Album]) {
albums = results
}
}

AnyObject doesn't recognize proper functions when called

I have a protocol StateMachineDelegate, a class DataSource that conforms to it, and a class StateMachine that has a delegate with such protocol
Both classes implement a function found on the protocol so that if the class has a delegate, let them handle the functions; otherwise the class handles it itself.
StateMachine contains a function like this:
func target() -> AnyObject {
return delegate ?? self
}
My full code goes like this:
import Foundation
#objc protocol StateMachineDelegate {
optional func stateWillChange()
optional func stateDidChange()
optional func missingTransitionFromState(fromState: String?, toState: String?) -> String
}
class StateMachine {
var delegate: StateMachineDelegate?
func target() -> AnyObject {
return delegate ?? self
}
func missingTransitionFromState(fromState: String, toState: String) -> String {
return "Hello"
}
}
class DataSource: StateMachineDelegate {
func missingTransitionFromState(fromState: String?, toState: String?) -> String {
return "Hi"
}
}
When I was running some tests in playground and the StateMachine instance did not possess a delegate the target function returned the same instance as AnyObject. But once I called missingTransitionFromState from target it crashed so I change it to missingTransitionFromState?() with returned nil
Last function line should have returned "Hello"
Once delegate was given the target returned the delegateObject and proceeded to run the function as normal
The playground test are these:
All of your calls to missingTransitionFromState have a ? at the end except for the last one that won't execute. Replacing the ! with a ? fixes the problem. I didn't really understand what the code is doing but the question mark fixes it.
Making the following changes fixes the problem:
Annotating StateMachine's missingTransitionFromState method with dynamic
Changing the parameter types in StateMachine's missingTransitionFromState method from String to String?, to match the signature of the other missingTransitionFromState method.
I believe that the method needs dynamic or #objc in order to be able to be called dynamically using AnyObject. However, after adding that, the compiler will complain that calls to missingTransitionFromState on an AnyObject is ambiguous, because there are two signatures, so you have to fix the signature.

Resources