Overriding Obj-C-methods in Swift using Swift enums - objective-c

I have the following Objective-C class which uses an Objective-C enum:
MyClass.h:
typedef NS_ENUM(NSInteger, MyEnum) {
MyEnumCase1
};
#interface MyClass : NSObject
-(void)method:(MyEnum)param;
#end
MyClass.m:
#implementation MyClass
-(void)method:(MyEnum)param {}
#end
I can subclass MyClass in Swift and override the method like this:
SubClass.swift
class SubClass: MyClass {
override func method(_ param: MyEnum) {}
}
But if I define the enum in Swift instead of in Objective-C, overriding the method fails:
MyClass.h:
// Forward declare the enum in Objective-C
typedef NS_ENUM(NSInteger, MyEnum);
#interface MyClass : NSObject
-(void)method:(MyEnum)param;
#end
SubClass.swift:
#objc enum MyEnum: NSInteger {
case case1
}
class SubClass: MyClass {
override func method(_ param: MyEnum) {} // Error
}
In this case, overriding the method fails with the error
Method does not override any method from its superclass
The enum itself works in Swift, I can add the following method to SubClass, and it compiles:
func useEnum() {
let x = MyEnum.case1
}
Why does the overriding fail?

When I opened the Generated Interface of MyClass.h, Xcode showed something like this:
import Foundation
open class MyClass : NSObject {
}
Nothing more but comments.
Seems Swift cannot import incomplete enums, so methods which use such types are not imported neither.
So, your #objc enum MyEnum just declares a new enum type, and override func method(_ param: MyEnum) is an attempt to override a method which does not exist in its superclass, from the Swift side.
The enum itself works in Swift
Of course. The enum works even if you removed the line of typedef (with all lines using it) from MyClass.h .
The enum works even if you specify some different type than NSInteger:
#objc enum MyEnum: UInt8 {
case case1
}
Seems you cannot write an actual definition of an enum in Swift, which is declared as an incomplete enum in Objective-C.

Related

can't access swift method from objective-c interface

I need to call my swift method from an Objective-C interface, however I keep getting Use of undeclared identifier 'MySwiftClass'.
My swift class works fine on other places. I have #import "myApp-Swift.h" in my objc file and #objc before my swift class. I am not really good with Objective-C, so any help would be really appreciated.
#interface myInterface ()
-(void) logger:(NSArray<MyArray*>*) events withCallback:(void (^)(NSError*)) callback;
#end
#implementation myInterface
-(void) logger:(NSArray<MyArray*>*) events withCallback:(void (^)(NSError*)) callback{
..
[MySwiftClass mySwiftMethod]
..
}
#end
My swift class:
import Foundation
#objcMembers
public class MySwiftClass: NSObject {
#objc
class func mySwiftMethod(){
}
}

Swift functions are not included into autogenerated -Swift.h umbrella file

I'm trying to access Swift class methods from Objective-C, following this guide
https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html
This is my Swift class:
import Foundation
#objc public class MySwiftClass: NSObject {
// modifiers public, open do not help
func Hello()->String {
return "Swift says hello!";
}
}
And here is an excerpt from the autogenerated "umbrella" file MyProductModuleName-Swift.h
SWIFT_CLASS("_TtC25_MyProductModuleName12MySwiftClass")
#interface MySwiftClass : NSObject
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
#end
It seems like XCode completely ignores methods of MySwiftClass, and as a result I can't access them from Objective-C. Xcode version is 9.2.
Is it a bug or I missed anything?
You need to add #objc before each function you want to access from Objective-C:
#objc public class MySwiftClass: NSObject {
// modifiers public, open do not help
#objc func Hello() -> String {
return "Swift says hello!";
}
}

objc_getClass: load swift class inheriting NSObject

I'm trying to dynamically load a Swift class inheriting from NSObject using the objc runtime. (I'm trying to load the class from ObjC, not from Swift)
My Swift class:
#objc public class TestClass : NSObject {
#objc public func testMethod() -> String {
return "String"
}
}
According to Apple's documentation,
The #objc attribute makes your Swift API available in Objective-C and the Objective-C runtime
But the result of objc_getClass("TestClass") is (null).
Am I doing something wrong? Or is it not possible at all to load swift classes inheriting an ObjC class using the objc runtime?
You need to specify an Objective-C name for your class, not just include #objc:
#objc(TestClass) public class TestClass : NSObject {
#objc public func testMethod() -> String {
return "String"
}
}
NSClassFromString("TestClass") // TestClass.Type
objc_getClass("TestClass") // TestClass
Otherwise your class will not be registered with the Objective-C runtime, and calls like objc_getClass or NSClassFromString will return nil.
What if you try objc_getClass("YourAppName.TestClass")? Most likely the module name is prepended. You can verify the exact name which is used behind the scenes by using NSStringFromClass([TestClass class])

No visible #interface for 'MySwiftClass' declares the selector 'addX:andY'

We are trying to reference Swift methods inside an Objective-C implementation.
Swift class:
import Foundation
#objc class MySwiftClass: NSObject {
override init() {
super.init()
}
func sayHello() -> Void {
print("hello");
}
func addX(x:Int, andY y:Int) -> Int {
return x+y
}
}
Objective-C implementation (Objective-c.m):
#import "ProductModuleName-Swift.h"
MySwiftClass* getData = [[MySwiftClass alloc]init];
[getData sayHello] //works
[getData addX:5 addY:5] //No visible #interface for 'MySwiftClass' declares selector 'addX:addY'
The last line of code gives the following error:
No visible #interface for 'MySwiftClass' declares selector 'addX:addY'
If you command-click on "ProductModuleName-Swift.h" in the Xcode
source file editor then you can see how the Swift methods are mapped to Objective-C.
In your case that would be
#interface MySwiftClass : NSObject
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
- (void)sayHello;
- (NSInteger)addXWithX:(NSInteger)x andY:(NSInteger)y;
#end
which is called as
MySwiftClass* getData = [[MySwiftClass alloc]init];
[getData sayHello];
NSInteger result = [getData addXWithX:5 andY:5];
A better Swift 3 method name might be
func add(x: Int, y:Int) -> Int
because x is already the argument (external) name of the first
parameter. You can also add an #objc() attribute to the Swift definition
to control the Objective-C name. For example, with
#objc(addX:andY:)
func add(x: Int, y: Int) -> Int {
return x+y
}
it would be called from Objective-C as
NSInteger result = [getData addX:5 andY:5];
As #ekscrypto pointed out, in Swift 4 and later you need to annotate individual functions with #objc. Prior to that, a single, class-level #objc was enough.
Of course in Objective-C class you must add import of NAME_PROJECT-swift.h.
If your project name is Sample then you must add:
#import Sample-swift.h
And then:
Swift 4 or Less
#objc class MySwiftClass: NSObject {
func sayHello(){
//function
}
func addX(){
//function
}
}
Swift 4 or Greater
#objc class MySwiftClass: NSObject {
#objc func sayHello(){
//function
}
#objc func addX(){
//function
}
}
In my case I had forgotten to add:
#import "MyProject-Swift.h"
Into the obj c file.

How to use NSSet<Class> in Swift (exported as Set<NSObject>)?

I have to fulfill a protocol requirement that is defined in Objective-C like this:
#protocol AProtocol <NSObject>
+ (NSSet<Class> * _Nullable)someClasses;
#end
I want to implement this protocol in a subclass written in Swift. I want to return a Set of classes of another Object. The class I want to return is defined like this:
class B: NSObject {}
The class that conforms to the protocol is defined like this:
class A: NSObject, AProtocol {
static func someClasses() -> Set<NSObject>? {
return [B.self]
}
}
Why is NSSet<Class> bridged to Set<NSObject> instead of Set?
This solution is crashing, how can I solve the problem?
NSSet<Class> is bridged to Set<NSObject> because AnyClass does not conform to Hashable which is a precondition for the ValueType of Set.
It can be solved with the following extension for NSObjectProtocol:
extension NSObjectProtocol where Self: NSObject {
static var objcClass: NSObject {
return (self as AnyObject) as! NSObject
}
}
This returns the class of the object casted as NSObject. It is necessary to cast it first to AnyObject because the type system of Swift is so strong that it would not compile or give a warning when directly casting a type to an instance type. In Objective-C this is fine because Class is also just an object. Since NSObject is implemented in Objective-C and the extension is just for NSObjectProtocol, this is save to use (even with the force cast).
Implementing the extension on NSObjectProtocol and not on NSObject itself brings the positive effect that it is not exported to Objective-C.