如何在Swift中调用Objective C类方法

don*_*key 22 objective-c swift

在我的Objective-C标头上,我有以下类方法声明:

@interface WXMediaMessage : NSObject

    +(WXMediaMessage *) message;
Run Code Online (Sandbox Code Playgroud)

我试着像下面这样调用这个方法:

var message : WXMediaMessage = WXMediaMessage.message()
Run Code Online (Sandbox Code Playgroud)

但它不起作用.我已经设置了桥接头.

编辑:

如果我按照描述调用方法,它将显示错误'message()' is unavailable: use object construction 'WXMediaMessage()'.

如果我使用WXmediaMessage(),错误就会消失.但是,如果在Objective C中调用,它会返回与[WXMediaMessage message]相同的结果吗?

MYAPP_bridging_header.h

#ifndef MYAPP_bridging_header_h
#define MYAPP_bridging_header_h

#import "WXApi.h"
#import "WXApiObject.h"

#endif
Run Code Online (Sandbox Code Playgroud)

WXApiObject.h片段

@interface WXMediaMessage : NSObject

   +(WXMediaMessage *) message;

@end
Run Code Online (Sandbox Code Playgroud)

WXApiObject.m

(It is an external api, so I can't see the content)
Run Code Online (Sandbox Code Playgroud)

nhg*_*rif 33

我的意思是:但是为什么?

这就是我们要问的问题.马特的答案是正确的,重命名方法将解决问题,但我们仍然问为什么.

答案是因为Swift实际上将方便方法重新映射到构造函数.

因此,虽然我们必须在Objective-C中调用这样的方法:

[WXMediaMessage message];
Run Code Online (Sandbox Code Playgroud)

在Swift中,我们应该简单地调用WXMediaMessage().在Objective-C中,这相当于调用:

[[WXMediaMessage alloc] init];
Run Code Online (Sandbox Code Playgroud)

重要的是要注意,当我们使用Swift默认初始化程序时,不会调用实际的工厂方法.这将直接进入init而不调用工厂包装器.但最佳实践表明,我们的工厂方法应该只是[[self alloc] init];.一个值得注意的建议可能是单身,但如果我们有单一的工厂方法,我们应该遵循Apple设置的模式,并使用"共享"前缀命名我们的工厂方法:

+ (instancetype)sharedMessage;
Run Code Online (Sandbox Code Playgroud)

以下Swift代码完全有效:

let message = WXMediaMessage.sharedMessage()
Run Code Online (Sandbox Code Playgroud)

但是如果message不是单例,那么它应该不会超过return [[self alloc] init];,这是我们用以下Swift代码得到的:

let message = WXMediaMessage()
Run Code Online (Sandbox Code Playgroud)

这就是错误消息告诉我们要做的事情:

'message()' is unavailable: use object construction 'WXMediaMessage()'
Run Code Online (Sandbox Code Playgroud)

错误消息告诉我们使用默认的Swift初始化程序而不是Objective-C工厂方法.这是有道理的.任何人都想在Objective-C中使用工厂方法的唯一原因是因为[[MyClass alloc] init]看起来很难看.我们所有的初始化仍然应该在init方法中完成...但不是我们创建的工厂方法,因为我们宁愿不看alloc] init]...


考虑以下Objective-C类:

@interface FooBar : NSObject

+ (instancetype)fooBar;

- (void)test;

@end

@implementation FooBar

- (instancetype)init {
    self = [super init];
    if (self) {
        NSLog(@"initialized");
    }
    return self;
}

+ (instancetype)fooBar {
    NSLog(@"initialized with fooBar");
    return [[self alloc] init];
}

- (void)test {
    NSLog(@"Testing FooBar");
}

@end
Run Code Online (Sandbox Code Playgroud)

现在使用以下Swift代码:

let var1 = FooBar()
var1.test()
Run Code Online (Sandbox Code Playgroud)

我们得到以下输出:

2014-11-08 10:48:30.980 FooBar[5539:279057] initialized
2014-11-08 10:48:30.981 FooBar[5539:279057] Testing FooBar
Run Code Online (Sandbox Code Playgroud)

我们可以看到,该方法fooBar永远不会被调用.但实际上,如果你正在构建你的Objective-C类,并且考虑到良好的实践,那么这样命名的类方法永远不应该只是:

return [[self alloc] init];
Run Code Online (Sandbox Code Playgroud)

你的init方法应该处理所有的设置.

当我们使用不那么简单的初始化器和工厂方法时,发生的事情变得更加明显.

考虑一下我们是否添加了一种用数字初始化类的方法:

@interface FooBar : NSObject

+ (instancetype)fooBarWithNumber:(int)number;
- (void)test;

@end

@implementation FooBar {
    int _number;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        NSLog(@"initialized");
    }
    return self;
}

- (instancetype)initWithNumber:(int)number {
    self = [self init];
    if (self) {
        NSLog(@"init with number: %i", number);
        _number = number;
    }
    return self;
}

+ (instancetype)fooBarWithNumber:(int)number {
    NSLog(@"fooBar with number: %i", number);
    return [[self alloc] initWithNumber:number];
}

- (void)test {
    NSLog(@"Testing FooBar (number: %i)", _number);
}

@end
Run Code Online (Sandbox Code Playgroud)

此时,请注意我们.h公开fooBarWithNumber:方法,但不公开initWithNumber:方法.

现在让我们回到Swift代码:

let var1 = FooBar(number: 3)
var1.test()
Run Code Online (Sandbox Code Playgroud)

Swift已将我们的fooBarWithNumber:方法转换为初始化程序:FooBar(number:Int32)

这是输出:

2014-11-08 10:57:01.894 FooBar[5603:282864] fooBar with number: 3
2014-11-08 10:57:01.894 FooBar[5603:282864] initialized
2014-11-08 10:57:01.895 FooBar[5603:282864] init with number: 3
2014-11-08 10:57:01.895 FooBar[5603:282864] Testing FooBar (number: 3)
Run Code Online (Sandbox Code Playgroud)

fooBarWithNumber:调用我们的方法,调用initWithNumber:哪个调用init.


所以问题的答案"为什么"是因为Swift将我们的Objective-C初始化器和工厂方法转换为Swift样式的初始化器.

为了进一步扩展,问题甚至不仅仅是方法名称本身.它是方法名称,类名称和返回类型的组合.在这里,我们使用instancetype了返回类型.如果我们使用instancetype或我们的特定类类型,我们遇到了问题.但是请考虑一下:

@interface FooBar

+ (NSArray *)fooBar;

@end
Run Code Online (Sandbox Code Playgroud)

并假设这个类方法的一些有效实现与类名匹配,但返回类型与我们的类不匹配.

现在,以下方法是完全有效的Swift代码:

let fooBarArray = FooBar.fooBar()
Run Code Online (Sandbox Code Playgroud)

因为返回类型与类不匹配,所以Swift无法将其转换为类初始值设定项,因此我们的方法很好.

值得注意的是,如果我们选择id作为我们方法的返回类型,那么:

+ (id)fooBar;
Run Code Online (Sandbox Code Playgroud)

Swift会让我们侥幸逃脱...但是它会警告我们我们的变量被推断为具有"AnyObject!"类型.并建议我们添加一个显式类型,所以它建议这样:

let var1: FooBar = FooBar.fooBar()
Run Code Online (Sandbox Code Playgroud)

并且它允许我们这样做......但是最佳实践几乎肯定建议不要使用id作为您的返回类型.Apple最近几乎将所有的id返回类型都改为了instancetype.


mat*_*att 11

问题是这个名字.Swift不会导入工厂构造函数.它通过注意名称与类名末尾相同来检测这些.

message方法名称更改为类似的名称singletonMessage,一切都会很好.

如果名称是message,您将看到:

在此输入图像描述

但是,如果我将名称更改为,比如说messager,它编译就好了.

打个比方,试试这个:

var thing : HeyHo = HeyHo.ho()
Run Code Online (Sandbox Code Playgroud)

您将收到相同的错误消息.