简体   繁体   中英

Swift “namespaces” in mixed objective-c project not working

I have a custom framework MyFramework written in objective-c, where I, in an objective-c category on UIColor , define a method thus:

+ (instancetype)my_colorWithRed:(uint8_t)red 
                          green:(uint8_t)green 
                           blue:(uint8_t)blue 
                          alpha:(uint8_t)alpha NS_SWIFT_NAME(init(red:green:blue:alpha:));

I use NS_SWIFT_NAME to give it a proper Swift name. However, the declaration of the Swift name is identical with a method already present on UIColor in UIKit , namely:

init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat)

Now, if I add a Swift file in my main app — a mixed objective-c and swift project that links to MyFramework — and write the following:

UIColor(red: 1, blue: 0, green: 0, alpha: 1)

the UIKit version of the method is called. But if I write:

MyFramework.UIColor(red: 1, blue: 0, green: 0, alpha: 1)

the UIKit version of the method is still called.

So I'm wonder why:

  • building the app without prefixing the method with a module name does not give me any warnings or errors even though the method is ambiguous. I was under the impression that I would get warnings if this happened.
  • specifying MyFramework to scope the method doesn't have effect.

In the Swift file I do not need to import UIKit or MyFramework because they are already imported via the Swift bridging header; not directly, but indirectly through some header files that in turn imports them. Could that be what is causing the issue? Or is it because it's a mixed objective-c and Swift project? Or is it because this is a method on a category?

UPDATE

Answer from Tarun Tyagi made aware of the parameter types. Specifying the types makes it work:

UIColor(red: UInt8(149), green: UInt8(45), blue: UInt8(152))

This calls the method in MyFramework . That means that using Integers in the call as I initially did:

UIColor(red: 1, blue: 0, green: 0, alpha: 1)

matches UIKit 's init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) instead of MyFramework 's convenience init(red: UInt8, green: UInt8, blue: UInt8) .

If I create a new category method that takes Int

+ (instancetype)my_colorWithIntRed:(NSInteger)red green:(NSInteger)green blue:(NSInteger)blue alpha:(NSInteger)alpha NS_SWIFT_NAME(init(red:green:blue:alpha:));

and then call UIColor(red: 1, blue: 0, green: 0, alpha: 1) from the main app, the call matches that method.

Adding UIKit or MyFramework to qualify the call makes no difference. The matching is only based on the argument types, regardless of which module it's defined in. That is, calling:

UIKit.UIColor(red: 1, blue: 0, green: 0, alpha: 1)

does not give an error and the method in MyFramework is still called.

If I create a initializer in the MyFramework category of UIColor that exactly matches the one in UIKit.UIColor :

+ (instancetype)my_colorWithFloatRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha NS_SWIFT_NAME(init(red:green:blue:alpha:));

then there's simply no Swift version generated.

If I create the same method in a swift extension of UIColor instead

@objc public convenience init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
    ...
}

then I get an expected error:

Initializer 'init(red:green:blue:alpha:)' with Objective-C selector 'initWithRed:green:blue:alpha:' conflicts with previous declaration with the same Objective-C selector

But when I remove the @obj to only make the method available in Swift I get no error, which indicates that Swift allows to have colliding method names in extensions defined by different modules.

So I make this call from from my main app:

UIColor(red: CGFloat(0.5), green: CGFloat(0.5), blue: CGFloat(1.0), alpha: CGFloat(1.0))

but I still get no error about ambiguous methods, and the UIKit version is called. Further, qualifying with:

MyFramework.UIColor(red: CGFloat(0.5), green: CGFloat(0.5), blue: CGFloat(1.0), alpha: CGFloat(1.0))

again makes no difference.

So I still don't understand how implicit namespacing in Swift is supposed to work with extensions. It seems that it's not supported. I read through the Swift programing guide on extensions and there's no mention about this.

Building the app without prefixing the method with a module name does not give me any warnings or errors even though the method is ambiguous. I was under the impression that I would get warnings if this happened

This seems like -

A. Either it should be translated in Swift to use UInt8 instead of CGFloat so it is different based on type of input.

B. Or it shouldn't allow to use init(red:green:blue:alpha:) inside NS_SWIFT_NAME since it collides with already existing initializer.

Neither A nor B happens, so it seems to be a bug. You should file a radar to investigate this further.

Specifying MyFramework to scope the method doesn't have effect

UIColor isn't owned by your custom framework and you are only extending it. It's your responsibility to name the methods uniquely. A possible name would be my_color(red:green:blue:alpha:) inside NS_SWIFT_NAME to make it unique.

Clearly your naming init(red:green:blue:alpha:) collides with UIKit standard initializer and in case you don't get a compiler warning and are able to run, UIKit variant wins over your custom one.

Hope it helps.

EDIT 1: Reference https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html

Adding it as a screenshot here since a link might break in a future revision.

Although the reference is for Objective-C and not Swift extension, this is valid for UIColor extension because all UIKit is still Objective-C as of now.

Try prefixing your frameworks classes with something unique. Call it MyFrameworkUIColor

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM