Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Name of Swift class when exposed to Objective-C code does not respect the @objc renaming

Tags:

Given a declaration of a Swift class like

@objc(NSFoo) public class Foo {
   public func bar() -> () {}
}

I would expect, from my reading of the documentation, that on the Objective-C side of things we would be able to refer to this class using the identifier NSFoo. This is not what seems to be happening for me. The generated definition in ProjectName-Swift.h is:

SWIFT_CLASS("NSFoo")
@interface Foo
- (void)bar;
- (instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end

whereas what I would expect is

SWIFT_CLASS("Foo")
@interface NSFoo
...

I am using Xcode 6.0.1.

I missing something, or is this just a Xcode bug?

like image 444
Manav Avatar asked Oct 01 '14 01:10

Manav


People also ask

What does @objc mean in Swift?

There are two keywords to keep in mind when dealing with interoperability: @objc means you want your Swift code (class, method, property, etc.) to be visible from Objective-C. dynamic means you want to use Objective-C dynamic dispatch.

Can we use Swift class in Objective-C?

You can work with types declared in Swift from within the Objective-C code in your project by importing an Xcode-generated header file. This file is an Objective-C header that declares the Swift interfaces in your target, and you can think of it as an umbrella header for your Swift code.

Can you call Swift from Objective-C?

The Swift library cannot be directly called from Objective-C, since it is missing the required annotations in the code, and in many cases, modules do not inherit from NSObject, rather they use the native Swift data types.

Can Objective-C class inherit from Swift class?

Unfortunately, it's not possible to subclass a Swift class in Objective-C. Straight from the docs: You cannot subclass a Swift class in Objective-C.


2 Answers

Note: Things have change since this answer was first written - see updates at the end!

Yeah, this does seam to be a bug... though, controlling Obj-C runtime names of methods does work:

Say we define a pair of minimal Obj-C and Swift classes that interact with each other:

Foo.swift

import Foundation

@objc(SwiftFoo)
class Foo { // inheriting from NSObject makes no difference in this case

    @objc(postcardFromSwift)
    class func postcard() -> String {
        return "Postcard from Swift!"
    }

    @objc(getMailInSwift)
    class func getMail() {
        if let hello = NSBar.postcard() { // NSBar is an Obj-C class (see below)
            println("Printed in Swift: \(hello)")
        }
    }
}

NSBar.h

#import <Foundation/Foundation.h>

@interface NSBar : NSObject
+ (NSString *)postcard;
+ (void)getMail;
@end

NSBar.m

#import "NSBar.h"
#import "ObjS-Swift.h"

@implementation NSBar
+ (void)getMail {
    // notice that I am not referring to SwiftFoo in spite of @objc(SwiftFoo)
    printf("Printed in Objective C: %s", [[Foo postcardFromSwift] UTF8String]);
}
+ (NSString *)postcard {
    return @"Postcard from Objective C!";
}
@end

If we now call their class methods, say, from main.m:

#import <Cocoa/Cocoa.h>
#import "NSBar.h"
#import "ObjS-Swift.h"

int main(int argc, const char * argv[]) {

    // notice that I am not referring to SwiftFoo in spite of @objc(SwiftFoo)
    [Foo getMailInSwift]; 
    [NSBar getMail];

    return NSApplicationMain(argc, argv);
}

This prints the following:

// --> Printed in Swift: Postcard from Objective C!
// --> Printed in Objective C: Postcard from Swift!

But it shouldn't have! Foo should only be visible to Obj-C as SwiftFoo since that is what @objc(SwiftFoo) is promising to do. Indeed, using SwiftFoo triggers the Use of undeclared identifier compiler error instead. The fact that this did work for method names, leaves little doubt that this is a bug. I am just amazed that you seem to be the first to ask about it! Plus one for that!

And yes:

// <#ModuleName#>-Swift.h
SWIFT_CLASS("SwiftFoo")
@interface Foo
+ (NSString *)postcardFromSwift;
+ (void)getMailInSwift;

... does seam to be inverted for the class name, yet this is how that macro works – see WWDC video Swift Interoperability In Depth (c. 45 min and c. 48 min into the video). The relevant documentation is Exposing Swift Interfaces in Objective-C.

Xcode 7 beta 4

The issue is now fixed (thanks to @ScottBerrevoets for the comment).

Xcode 7.1

(thanks to @Pang for the comment)

@objc class C { } // error: only classes that inherit from NSObject can be declared @objc
like image 160
Milos Avatar answered Nov 08 '22 15:11

Milos


Currently (in XCode8) this seems to have been addressed.

Defined in XYZLogger.h and XYZLogger.m

NS_ASSUME_NONNULL_BEGIN

NS_SWIFT_NAME(Logger)
@interface XYZLogger : NSObject

+ (void)verbose:(NSString *)logString;
+ (void)debug:(NSString *)logString;
+ (void)info:(NSString *)logString;
+ (void)warn:(NSString *)logString;
+ (void)error:(NSString *)logString;

@end

NS_ASSUME_NONNULL_END

Used in objc like this:

[XYZLogger debug:@"Hi from objective C"];

Used in Swift like this:

Logger.debug("Hi from swift");
like image 40
mkirk Avatar answered Nov 08 '22 16:11

mkirk