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:
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?
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 ofCGFloat
so it is different based on type of input.B. Or it shouldn't allow to use
init(red:green:blue:alpha:)
insideNS_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 bemy_color(red:green:blue:alpha:)
insideNS_SWIFT_NAME
to make it unique.Clearly your naming
init(red:green:blue:alpha:)
collides withUIKit
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.