Swift protocol as an init parameter called from Objective C class - ios

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.

Related

How to design protocols that can be applied to both Swift value types and Objective-C NSObject subclasses in Swift?

I'm defining a protocol in Swift language, and this protocol will be used in both Objective-C and Swift projects.
Its first version is as follows:
public protocol DataCodable {
func encode() -> Data?
func decode(from data: Data) -> Self?
}
Since it cannot be exported to Objective-C code, I have to rewrite it as:
#objc public protocol NSDataCodable {
func encode() -> NSData?
func decode(from data: NSData) -> Self?
}
Now I have two problems:
DataCodable is only available in Swift.
NSDataCodable can only be applied to NSObject subclasses.
If I keep both protocols, I have to specify Any to the data type I hold:
public class MyClass {
var data: Any? // DataCodable or NSDataCodable
}
Is there any best practices to solve problems like this?
I don't claim it to be any of the best practices, but...
I had a similar problem some years ago and ended up using two protocols and a wrapper for the Objective-C one: https://lazarevzubov.medium.com/compatible-with-objective-c-swift-code-e7c3239d949
Something like this:
import Foundation
protocol DataCodable {
func encode() -> Data?
func decode(from data: Data) -> Self?
}
#objc(DataCodable) protocol NSDataCodable {
func encode() -> NSData?
func decode(from data: NSData) -> Self?
}
final class NSDataCodableWrapper: DataCodable {
private let wrappee: NSDataCodable
init(_ wrappee: NSDataCodable) {
self.wrappee = wrappee
}
func encode() -> Data? {
wrappee.encode() as Data?
}
func decode(from data: Data) -> NSDataCodableWrapper? {
if let wrapee = wrappee.decode(from: data as NSData) {
return NSDataCodableWrapper(wrapee)
}
return nil
}
}
public class MyClass {
var data: DataCodable?
}
MyClass.data can have either a Swift implementation of DataCodable or an Objective-C implementation of NSDataCodable wrapped with NSDataCodableWrapper.

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.

Calling Swift class method from Objective-C

I have this function in Swift
class func someFunction(idValue: Int, completionHandler: #escaping (_ jsonData: JSON) -> ()) {
(...)
if (some validation) {
completionHandler(jsonData)
} else {
completionHandler(JSON.null)
}
}
Now I want to call that function from Objective-C. What I am doing is this:
[[ClassName new] someFunction:self.sesionId completionHandler:^{
}];
But is throwing "No visible #interface for 'ClassName' declares the selector 'someFunction:completionHandler:'
How can I call this function?
Essentially the NSObject is the base object type in Apple development.
The root class of most Objective-C class hierarchies, from which subclasses inherit a basic interface to the runtime system and the ability to behave as Objective-C objects.
Your class defines like that, using NSObject supper class (related to your requirements).
class ClassName: NSObject {
class func someFunction(idValue: Int, completionHandler: #escaping (_ jsonData: String) -> ()) {
}
}
call this function
[ClassName someFunctionWithIdValue:12 completionHandler:^(NSString * test) {
}];
In addition to inheriting from NSObject and declaring the class with #objc , Seems that #objc must be added to the func declaration for it to be seen in objective-c classes/files.
like so:
#objc class SomeClass:NSObject {
#objc func someFunction(_:Any) -> Bool {
return true
}
}
It then shows up in the autocomplete when calling as normal :
#import "PROJECT_Name-Swift.h"
#implementation SomeObjCClass
...
-(void)someObjCMethod{
SomeClass* instance = [[SomeClass alloc]init];
[instance someFunction:(id _Nonnull)]
}
...
#end
the presence of public keyword didn't seem to affect it either way

Swift protocol defines an init that doesn't work for UIViewController

Here is a simple protocol:
protocol StringsInitiable {
init(strings: [String])
}
Trying to use the initializer in an extension works when constraint to NSObject...
extension StringsInitiable where Self: NSObject {
func test() {
let _ = Self(strings: [])
}
}
...but not when constraint to UIViewController. It then complains that the initializer should be labeled 'coder', referring to the mandatory initializer from NSCoding.
extension StringsInitiable where Self: UIViewController {
func test() {
let _ = Self(strings: []) // error label 'strings:' expected 'coder:'
}
}
Is there a way to use the initializer declared in the protocol even when being a UIViewController subclass?
EDIT
It seems to be working when constraining the extension to a base class (NSObject or any Swift class that doesn't inherit from anything) but not when constraining the extension to a child class.
I'm not entirely convinced, but this smells like a bug. Have a look at this example which doesn't compile:
protocol P {
init(n: Int)
}
class A {}
class B : A {}
extension P where Self : B {
func f() -> Self {
return Self(n: 3) // Error
}
}
But this compiles:
extension P where Self : A {
func f() -> Self {
return Self(n: 3)
}
}
Probably you don't want a protocol for that anyways, since you even named it StringsViewController. You should probably subclass UIViewController:
class StringsViewController : UIViewController {
convenience init(strings: [String]) {
self.init()
}
}
extension StringsViewController {
func test() {
let _ = StringsViewController(strings: [])
}
}
Or if you really want a protocol you can do something like this:
protocol HasView {
var view : UIView! { get }
}
protocol StringsInitable {
init(strings: [String])
}
extension UIViewController : HasView {}
extension HasView where Self : StringsInitable {
func test() {
let n = Self(strings: [])
print(n.view)
}
}
UIViewController doesn't have such an initialiser, because you haven't implemented the StringsViewController protocol. You would not be able to implement this protocol for UIViewController, because you cannot declare a designed initialiser into an extension. On the other hand you need a designated initialiser in order to conform to a init requirement of a protocol.

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

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

Resources