How to handle Protocol Delegate when converting Objective-C to Swift - ios

I'm trying to convert a speech recognition code to Swift, Protocol defined in ViewController.h as:
#interface ViewController : UIViewController<SpeechRecognitionProtocol>
{
NSMutableString* textOnScreen;
DataRecognitionClient* dataClient;
MicrophoneRecognitionClient* micClient;
SpeechRecognitionMode recoMode;
bool isMicrophoneReco;
bool isIntent;
int waitSeconds;
}
I got stuck converting below function at ViewController.h:
micClient = [SpeechRecognitionServiceFactory createMicrophoneClient:(recoMode)
withLanguage:(language)
withKey:(primaryOrSecondaryKey)
withProtocol:(self)];
This function is defined in the SpeechSDK.framework as:
#interface SpeechRecognitionServiceFactory : NSObject
/*
#param delegate The protocol used to perform the callbacks/events upon during speech recognition.
*/
+(MicrophoneRecognitionClient*)createMicrophoneClient:(SpeechRecognitionMode)speechRecognitionMode
withLanguage:(NSString*)language
withKey:(NSString*)primaryOrSecondaryKey
withProtocol:(id<SpeechRecognitionProtocol>)delegate;
#end
this protocol looks like this in my converted ViewController.Swift:
import UIKit
protocol SpeechRecognitionProtocol {
func onIntentReceived(result: IntentResult)
func onPartialResponseReceived(response: String)
func onFinalResponseReceived(response: RecognitionResult)
func onError(errorMessage: String, withErrorCode errorCode: Int)
func onMicrophoneStatus(recording: DarwinBoolean)
func initializeRecoClient()
}
class ViewController: UIViewController, SpeechRecognitionProtocol {
var myDelegate: SpeechRecognitionProtocol?
finally I am calling this function inside ViewController.swift. I am getting following error after withProtocol: cannot convert value of type 'SpeechRecognitionProtocol.Protocol' to expected argument type 'SpeechRecognitionProtocol!' :
func initializeRecoClient() {
let language: String = "en-us"
let path: String = NSBundle.mainBundle().pathForResource("settings", ofType: "plist")!
let settings = NSDictionary(contentsOfFile: path)
let primaryOrSecondaryKey = settings?.objectForKey("primaryKey") as! String
micClient = SpeechRecognitionServiceFactory.createMicrophoneClient(recoMode!,
withLanguage: language,
withKey: primaryOrSecondaryKey,
withProtocol: SpeechRecognitionProtocol)
}

You shouldn't declare the SpeechRecognitionProtocol yourself (not sure you added this just for demonstration purposes or whether your actually have that in your code). SpeechRecognitionProtocol is already declared in SpeechRecognitionService.h and available to Swift - this is the one you need to use.
The object implementing that protocol is ViewController. Assuming your initializeRecoClient is a method of that class, the call would need to look like:
micClient = SpeechRecognitionServiceFactory
.createMicrophoneClient(recoMode!,
withLanguage: language,
withKey: primaryOrSecondaryKey,
withProtocol: self)
The SpeechSDK API didn't choose a particularly good name for that factory method.
The withProtocol argument doesn't take the protocol object itself (as the name suggests), but the object implementing the protocol (obviously).
P.S.: Not sure what SpeechAPI version you use, I had to implement those Swift methods to make ViewController conform to SpeechRecognitionProtocol:
func onPartialResponseReceived(response: String!) {}
func onFinalResponseReceived (response: RecognitionResult) {}
func onError (errorMessage: String!,
withErrorCode errorCode: Int32) {}
func onMicrophoneStatus (recording: Bool) {}
func onIntentReceived (result: IntentResult) {}

Related

Value of Protocol type 'name' cannot conform to protocol 'name' with generic function

I have a function with generic parameters and when it is called I get the error Value of protocol type 'AddableObject' cannot conform to 'AddableObject'; only struct/enum/class types can conform to protocols
I have the following class and protocols
class DataManager{
func updateItem<Item: AddableObject>(itemToUpdate: Item, updateValues: [String : Any], completion: #escaping ((Result<Item, Error>) -> ())){ }
}
protocol AddableObject{ }
This is the code in my VC
class vc: UIViewController{
weak var task: Task?
viewDidLoad(){
super.viewDidLoad()
}
private func uploadTask(){
guard let taskToUpdate = self.task else{return}
//this is where I get the error
DataManager.shared.updateItem(itemToUpdate: taskToUpdate, updateValues: ["index": 1]){result in
}
}
}
This is my task object
class Task: AddableObject{
}
When I try to call the updateItem method from my VC I get the error
Value of protocol type 'AddableObject' cannot conform to 'AddableObject'; only struct/enum/class types can conform to protocols
Why am I getting this error?
One obvious issue: your declaration weak var task: Task? declares an Optional. But the parameter itemToUpdate: is not an Optional. It is a generic type that conforms to AddableObject — and an Optional is not something that conforms to AddableObject.
If we correct for that, and for the other obvious issues in the code you have shown, the resulting code compiles perfectly:
class DataManager{
func updateItem<Item: AddableObject>(itemToUpdate: Item, updateValues: [String : Any], completion: #escaping ((Result<Item, Error>) -> ())){ }
}
protocol AddableObject{ }
class vc: UIViewController{
weak var task: Task?
private func uploadTask(){
guard let taskToUpdate = self.task else {return}
DataManager().updateItem(itemToUpdate: taskToUpdate, updateValues: ["index": 1]){result in
}
}
}
class Task: AddableObject{
}
Thus I have to conclude that the trouble is in code you have not shown us, or that you have misrepresented to us.

optional delegates with struct types - swift

I am getting below error while making protocol as optionals.
Method cannot be marked #objc because the type of the parameter 1
cannot be represented in Objective-C
My code :
#objc protocol PopupDelegate : class {
#objc optional func popupItemSelected(item : PopupItem, identifier : String)
#objc optional func popupItemMultipleSelected(item : [PopupItem], identifier : String)
}
struct PopupItem : Hashable {
var name : String
var id : Int
var isSelected : Bool
init(name: String, id: Int, isSelected : Bool = false) {
self.name = name
self.id = id
self.isSelected = isSelected
}
}
I got one post with same issue in swift 2, but I can't implement this solution as Inheritance not allowed in struct.
I tried to add #objc flag to my struct but got below error
Only classes (and their extensions), protocols, methods, initializers,
properties, and subscript declarations can be declared #objc
Is there any way to implement optional delegates with struct types?
I think the error messages you posted are self explanatory, Struct is not available in Objective-C runtime so when you annotate a protocol with #objc compiler gives you warning that struct can't be passed as an argument to such protocol.
How to achieve optional behaviour in pure swift?
Officially there is no equivalent of objective-C optional in swift. But empty default extensions will help you achieve the same behavior.
protocol PopupDelegate {
func popupItemSelected(item : PopupItem, identifier : String)
func popupItemMultipleSelected(item : [PopupItem], identifier : String)
}
extension PopupDelegate {
func popupItemSelected(item : PopupItem, identifier : String) { }
func popupItemMultipleSelected(item : [PopupItem], identifier : String) { }
}
Now whoever confirms to PopupDelegate need not implement methods as default implementation is already provided and because its empty implementation it's almost same as optional.
One caveat of this approach would be though, if you call respondsToSelector this will return true as there exists a default implementation but with optional you would get appropriate response.

Swift protocol as an init parameter called from Objective C class

I have a swift protocol which i have defined for testing openUrl functionality in iOS. It looks something like this:
protocol URLOpener {
func open(_ url: URL, options: [String : Any], completionHandler completion: ((Bool) -> Void)?)
func canOpenURL(_ url: URL) -> Bool
}
extension UIApplication: URLOpener {}
Note that UIApplication is class is conformed to this protocol.
Now i have a class which takes an object URLOpener type to initilize that class
import Foundation
class GenericHandler : NSObject {
required init(urlOpener: URLOpener) {
super.init()
}
func aaa() {
NBLog("aaaaa")
}
}
I want to use this GenericHandler class from Objective C but i gives me an error that it could not find the initilizer.
GenericHandler *handler = [[GenericHandler alloc] initWithUrlOpener:[UIApplication sharedApplication]];
[handler aaa];
No visible #interface for 'GenericHandler' declares the selector 'initWithUrlOpener:'
However if i change the initizer so that it accepts String parameter then it starts to work fine.
import Foundation
class GenericHandler : NSObject {
required init(urlOpener: String) {
super.init()
}
func aaa() {
NBLog("aaaaa")
}
}
GenericHandler *handler = [[GenericHandler alloc] initWithUrlOpener:#"test"];
[handler aaa];
This just works fine. Can anyone guide me whats the issue withURLOpener protocol or how i can make it working with URLOpener parameter.
A bit late, but I just struggled with the same question.
You have to expose both the protocol and the init to Objective-C.
#objc protocol URLOpener {
func open(_ url: URL, options: [String : Any], completionHandler completion: ((Bool) -> Void)?)
func canOpenURL(_ url: URL) -> Bool
}
extension UIApplication: URLOpener {}
-
import Foundation
class GenericHandler : NSObject {
#objc required init(urlOpener: URLOpener) {
super.init()
}
func aaa() {
NBLog("aaaaa")
}
}
You can always check your generated header file to see if your Swift code is available in Objective-C. See the paragraph "Importing Swift into Objective-C" from the Apple docs.

Passing closures to Private API's

I'm trying to fetch all the available airplay devices from the private API MPAVRoutingController. I'm using a third party perform selector library for swift called performSelector-Swift. The method I am trying to call is fetchAvailableRoutesWithCompletionHandler. This takes one parameter, an objective-c block. - (void)fetchAvailableRoutesWithCompletionHandler:(id /* block */)arg1; When I try and pass in a closure I get a compile error and if I don't pass anything in my app crashes. I'm not releasing this app and thats why I'm using the priv API.
let MPAVRoutingController = NSClassFromString("MPAVRoutingController")! as! NSObject.Type
let routingController = MPAVRoutingController.init()
if let availableRoutes = routingController.swift_performSelector("fetchAvailableRoutesWithCompletionHandler:", withObject: {
object in
}) {
print(availableRoutes)
}
First.. How I found the correct completion block signature: http://i.imgur.com/UGVayPE.png
That shows that it allocates an NSMutableArray as the parameter to the completion block when it invokes it. That's the only parameter. You don't have to do this (disassemble it). Upon an exception being thrown, you can print the signature. Sometimes it will also tell you which kind of block is expected.
Next, my opinion on invoking selectors dynamically..
Your best option is to not perform selectors.. It's a pain especially when the call contains MULTIPLE parameters..
What you can do is invocation through interface/extension pointers.. I do this in C++ (Idea from the Pimpl idiom.. COMM interfaces do this too) all the time and it works with Swift, Objective-C, Java.. etc..
Create a protocol that has the same interface as the object. Create an extension that inherits that protocol. Then cast the object instance to that extension/interface/protocol.
Call whatever function you want via the interface/extension/protocol pointer.
import UIKit
import MediaPlayer
#objc
protocol MPAProtocol { //Functions must be optional. That way you don't implement their body when you create the extension.
optional func availableRoutes() -> NSArray
optional func discoveryMode() -> Int
optional func fetchAvailableRoutesWithCompletionHandler(completion: (routes: NSArray) -> Void)
optional func name() -> NSString
}
extension NSObject : MPAProtocol { //Needed otherwise casting will fail!
//Do NOT implement the body of the functions from the protocol.
}
Usage:
let MPAVRoutingControllerClass: NSObject.Type = NSClassFromString("MPAVRoutingController") as! NSObject.Type
let MPAVRoutingController: MPAProtocol = MPAVRoutingControllerClass.init() as MPAProtocol
MPAVRoutingController.fetchAvailableRoutesWithCompletionHandler! { (routes) in
print(routes);
}
If you were to do it with a Bridging header instead of creating the extension + protocol, you'd just do a single Objective-C category:
#import <Foundation/Foundation.h>
#interface NSObject (MPAVRoutingControllerProtocol)
- (void)fetchAvailableRoutesWithCompletionHandler:(void(^)(NSArray *routes))completion;
#end
#implementation NSObject (MPAVRoutingControllerProtocol)
#end
Then:
let MPAVRoutingControllerClass: NSObject.Type = NSClassFromString("MPAVRoutingController") as! NSObject.Type
let MPAVRoutingController = MPAVRoutingControllerClass.init()
MPAVRoutingController.fetchAvailableRoutesWithCompletionHandler! { (routes) in
print(routes);
}
Finally, if you can use protocol injection, you can do this much easier:
func classFromString(cls: String, interface: Protocol?) -> NSObject.Type? {
guard let interface = interface else {
return NSClassFromString(cls) as? NSObject.Type
}
if let cls = NSClassFromString(cls) {
if class_conformsToProtocol(cls, interface) {
return cls as? NSObject.Type
}
if class_addProtocol(cls, interface) {
return cls as? NSObject.Type
}
}
return nil
}
func instanceFromString<T>(cls: String, interface: Protocol?) -> T? {
return classFromString(cls, interface: interface)?.init() as? T
}
#objc
protocol MPAProtocol {
optional func availableRoutes() -> NSArray
optional func discoveryMode() -> Int
optional func fetchAvailableRoutesWithCompletionHandler(completion: (routes: NSArray) -> Void)
optional func name() -> NSString
}
let MPAVRoutingController: MPAProtocol = instanceFromString("MPAVRoutingController", interface: MPAProtocol.self)!
MPAVRoutingController.fetchAvailableRoutesWithCompletionHandler! { (routes) in
print(routes);
}

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