Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 354 Vote(s) - 3.54 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to call an Objective C class method in Swift

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

@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()'`.

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

**MYAPP_bridging_header.h**

#ifndef MYAPP_bridging_header_h
#define MYAPP_bridging_header_h

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

#endif

**WXApiObject.h snippet**

@interface WXMediaMessage : NSObject

+(WXMediaMessage *) message;

@end

**WXApiObject.m**

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

#2
> [I mean: BUT WHY?][1]

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.

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

[WXMediaMessage message];

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

[[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:

+ (instancetype)sharedMessage;

And the following Swift code would be perfectly valid:

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:

let message = WXMediaMessage()

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

<!-- language: lang-none -->

'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]`...

----------


Consider the following Objective-C class:

@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:

let var1 = FooBar()
var1.test()

We get the following output:

<!-- language: lang-none -->

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:

return [[self alloc] init];

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

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.

Now let's go back to the Swift code:

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

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

And here's the output:

<!-- language: lang-none -->

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`.


----------


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

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:

@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:

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.

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

+ (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:

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`.

[1]:

[To see links please register here]

Reply

#3
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.

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

If the name is `message`, you will see this:

![enter image description here][1]

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

For an analogy, try this:

var thing : HeyHo = HeyHo.ho()

You will get the same error message.


[1]:
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through