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

时间:2023-01-15 14:16:20

On my Objective-C header, I have the following class method declaration:

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

@interface WXMediaMessage : NSObject

    +(WXMediaMessage *) message;

I tried to call this method like the following:

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

var message : WXMediaMessage = WXMediaMessage.message()

But it doesn't work. I have set up bridging-header.

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

EDIT:

编辑:

If I call the method as described, it will show an error says 'message()' is unavailable: use object construction 'WXMediaMessage()'.

如果我按照描述调用方法,它将显示错误,说'message()'不可用:使用对象构造'WXMediaMessage()'。

If I use WXmediaMessage(), error does go away. However, will it return the same result as [WXMediaMessage message] would if called in Objective C?

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

MYAPP_bridging_header.h

MYAPP_bridging_header.h

#ifndef MYAPP_bridging_header_h
#define MYAPP_bridging_header_h

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

#endif

WXApiObject.h snippet

WXApiObject.h片段

@interface WXMediaMessage : NSObject

   +(WXMediaMessage *) message;

@end

WXApiObject.m

WXApiObject.m

(It is an external api, so I can't see the content)

2 个解决方案

#1


25  

I mean: BUT WHY?

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

That's what we're left asking. Matt's answer is correct in that renaming the method will fix the problem, but we're still left asking why.

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

The answer is because Swift actually remaps convenience methods to constructors.

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

So while we'd have to call the method like this in Objective-C:

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

[WXMediaMessage message];

In Swift, we should instead be simply calling WXMediaMessage(). In Objective-C, this is the equivalent to calling:

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

[[WXMediaMessage alloc] init];

It's important to note that the actual factory method doesn't get called when we use the Swift default initializer. This will go straight to init without calling the factory wrapper. But best practice would suggest that our factory method should be nothing more than [[self alloc] init];. The one notable suggestion might be a singleton, but if we have a singleton factory method, we should follow the pattern Apple has set, and name our factory method with the "shared" prefix:

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

+ (instancetype)sharedMessage;

And the following Swift code would be perfectly valid:

以下Swift代码完全有效:

let message = WXMediaMessage.sharedMessage()

But if message isn't a singleton, then it should be no more than return [[self alloc] init];, which is what we'd get with the following Swift code:

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

let message = WXMediaMessage()

And this is what the error message is telling us to do:

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

'message()' is unavailable: use object construction 'WXMediaMessage()'

The error message tells us to use the default Swift initializer rather than the Objective-C factory method. And this makes sense. The only reason anyone ever wanted factory methods in Objective-C was because [[MyClass alloc] init] looks ugly. All of our initialization should still be done in the init method however... not in the factory method we create because we'd rather not look at alloc] init]...

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


Consider the following Objective-C class:

考虑以下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

Now using the following Swift code:

现在使用以下Swift代码:

let var1 = FooBar()
var1.test()

We get the following output:

我们得到以下输出:

2014-11-08 10:48:30.980 FooBar[5539:279057] initialized
2014-11-08 10:48:30.981 FooBar[5539:279057] Testing FooBar

As we can see, the method fooBar is never called. But truly, if you're building your Objective-C classes correct with good practice in mind, a class method named as such should never be any more than:

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

return [[self alloc] init];

And your init method should be handling all the set up.

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

What's happening becomes more obvious when we use less simply initializers and factory methods.

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

Consider if we add a way to initialize our class with a number:

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

@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

At this point, note that our .h exposes the fooBarWithNumber: method, but not the initWithNumber: method.

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

Now let's go back to the Swift code:

现在让我们回到Swift代码:

let var1 = FooBar(number: 3)
var1.test()

Swift has turned our fooBarWithNumber: method into an initializer: FooBar(number:Int32)

Swift已将我们的fooBarWithNumber:方法转换为初始值设定项:FooBar(数字:Int32)

And here's the output:

这是输出:

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)

Our fooBarWithNumber: method is called, which calls initWithNumber:, which calls init.

调用我们的fooBarWithNumber:方法,调用initWithNumber:,调用init。


So the answer to the question, "WHY" is because Swift translates our Objective-C initializers and factory methods into Swift style initializers.

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

To expand on this further, the problem isn't even just with the method name itself. It's a combination of the method name, class name, AND return type. Here, we've used instancetype as a return type. If we use instancetype, or our specific class type, we run into the problem. But however consider this:

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

@interface FooBar

+ (NSArray *)fooBar;

@end

And presuming some valid implementation of this class method which matches the class name, but the return type does NOT match our class.

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

Now the following method is perfectly valid Swift code:

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

let fooBarArray = FooBar.fooBar()

Because the return type doesn't match the class, Swift can't translate this into a class initializer, so our method is fine.

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

It's also worth noting that if we choose id as the return type of our method, as such:

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

+ (id)fooBar;

Swift will let us get away with it... but it will warn us that our variable is inferred to have the type 'AnyObject!' and recommends we add an explicit type, so it'd recommend this:

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

let var1: FooBar = FooBar.fooBar()

And it'd allow us to do that... but best practice almost certainly recommends against using id as your return type. Apple just recently changed almost all of its id return types over to instancetype.

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

#2


7  

The problem is the name. Swift does not import factory constructors. It detects these by noticing that the name is the same as the end of the class name.

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

Change the message method name to something like singletonMessage and all will be well.

将消息方法名称更改为类似singletonMessage的内容,一切都会好的。

If the name is message, you will see this:

如果名称是消息,您将看到:

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

But if I change the name to, say, messager, it compiles just fine.

但是,如果我将名称更改为,例如,messager,它编译就好了。

For an analogy, try this:

打个比方,试试这个:

var thing : HeyHo = HeyHo.ho()

You will get the same error message.

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

#1


25  

I mean: BUT WHY?

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

That's what we're left asking. Matt's answer is correct in that renaming the method will fix the problem, but we're still left asking why.

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

The answer is because Swift actually remaps convenience methods to constructors.

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

So while we'd have to call the method like this in Objective-C:

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

[WXMediaMessage message];

In Swift, we should instead be simply calling WXMediaMessage(). In Objective-C, this is the equivalent to calling:

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

[[WXMediaMessage alloc] init];

It's important to note that the actual factory method doesn't get called when we use the Swift default initializer. This will go straight to init without calling the factory wrapper. But best practice would suggest that our factory method should be nothing more than [[self alloc] init];. The one notable suggestion might be a singleton, but if we have a singleton factory method, we should follow the pattern Apple has set, and name our factory method with the "shared" prefix:

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

+ (instancetype)sharedMessage;

And the following Swift code would be perfectly valid:

以下Swift代码完全有效:

let message = WXMediaMessage.sharedMessage()

But if message isn't a singleton, then it should be no more than return [[self alloc] init];, which is what we'd get with the following Swift code:

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

let message = WXMediaMessage()

And this is what the error message is telling us to do:

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

'message()' is unavailable: use object construction 'WXMediaMessage()'

The error message tells us to use the default Swift initializer rather than the Objective-C factory method. And this makes sense. The only reason anyone ever wanted factory methods in Objective-C was because [[MyClass alloc] init] looks ugly. All of our initialization should still be done in the init method however... not in the factory method we create because we'd rather not look at alloc] init]...

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


Consider the following Objective-C class:

考虑以下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

Now using the following Swift code:

现在使用以下Swift代码:

let var1 = FooBar()
var1.test()

We get the following output:

我们得到以下输出:

2014-11-08 10:48:30.980 FooBar[5539:279057] initialized
2014-11-08 10:48:30.981 FooBar[5539:279057] Testing FooBar

As we can see, the method fooBar is never called. But truly, if you're building your Objective-C classes correct with good practice in mind, a class method named as such should never be any more than:

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

return [[self alloc] init];

And your init method should be handling all the set up.

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

What's happening becomes more obvious when we use less simply initializers and factory methods.

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

Consider if we add a way to initialize our class with a number:

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

@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

At this point, note that our .h exposes the fooBarWithNumber: method, but not the initWithNumber: method.

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

Now let's go back to the Swift code:

现在让我们回到Swift代码:

let var1 = FooBar(number: 3)
var1.test()

Swift has turned our fooBarWithNumber: method into an initializer: FooBar(number:Int32)

Swift已将我们的fooBarWithNumber:方法转换为初始值设定项:FooBar(数字:Int32)

And here's the output:

这是输出:

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)

Our fooBarWithNumber: method is called, which calls initWithNumber:, which calls init.

调用我们的fooBarWithNumber:方法,调用initWithNumber:,调用init。


So the answer to the question, "WHY" is because Swift translates our Objective-C initializers and factory methods into Swift style initializers.

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

To expand on this further, the problem isn't even just with the method name itself. It's a combination of the method name, class name, AND return type. Here, we've used instancetype as a return type. If we use instancetype, or our specific class type, we run into the problem. But however consider this:

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

@interface FooBar

+ (NSArray *)fooBar;

@end

And presuming some valid implementation of this class method which matches the class name, but the return type does NOT match our class.

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

Now the following method is perfectly valid Swift code:

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

let fooBarArray = FooBar.fooBar()

Because the return type doesn't match the class, Swift can't translate this into a class initializer, so our method is fine.

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

It's also worth noting that if we choose id as the return type of our method, as such:

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

+ (id)fooBar;

Swift will let us get away with it... but it will warn us that our variable is inferred to have the type 'AnyObject!' and recommends we add an explicit type, so it'd recommend this:

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

let var1: FooBar = FooBar.fooBar()

And it'd allow us to do that... but best practice almost certainly recommends against using id as your return type. Apple just recently changed almost all of its id return types over to instancetype.

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

#2


7  

The problem is the name. Swift does not import factory constructors. It detects these by noticing that the name is the same as the end of the class name.

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

Change the message method name to something like singletonMessage and all will be well.

将消息方法名称更改为类似singletonMessage的内容,一切都会好的。

If the name is message, you will see this:

如果名称是消息,您将看到:

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

But if I change the name to, say, messager, it compiles just fine.

但是,如果我将名称更改为,例如,messager,它编译就好了。

For an analogy, try this:

打个比方,试试这个:

var thing : HeyHo = HeyHo.ho()

You will get the same error message.

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