While implementing nested protocol support, I discovered that when a nested type in Swift is exposed to Objective-C without a custom name, the compiler-chosen name does not mention the parent type. This leads to two issues:
Issue One: Compiler-chosen name may be ambiguous to humans
Swift declaration:
import Foundation
@objc class A: NSObject {
@objc class Nested: NSObject {}
}
Generated Obj-C header (snippet):
SWIFT_CLASS("_TtC4Test1A")
@interface A : NSObject
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
SWIFT_CLASS("_TtCC4Test1A6Nested")
@interface Nested : NSObject
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
The name Nested
is lacking important context, leading to a degraded experience using this type from Objective-C.
In Swift, you would generally need to refer to this type as A.Nested
(except in certain contexts where unqualified lookup permits it).
Issue Two: Compiler-chosen name may be ambiguous to Objective-C
Swift declaration:
import Foundation
@objc class A: NSObject {
@objc class Nested: NSObject {}
}
@objc class B: NSObject {
@objc class Nested: NSObject {}
}
Generated Obj-C header (snippet):
SWIFT_CLASS("_TtC4Test1A")
@interface A : NSObject
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
SWIFT_CLASS("_TtCC4Test1A6Nested")
@interface Nested : NSObject
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
SWIFT_CLASS("_TtC4Test1B")
@interface B : NSObject
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
SWIFT_CLASS("_TtCC4Test1B6Nested")
@interface Nested : NSObject
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end
This header cannot be imported because it contains two interface definitions, both named Nested
. Instead, users need to manually define names for the nested types.
I'd like to suggest that we fix this. Here are two options for what the new default name could be:
-
Simple concatenation (
ANested
andBNested
). The resulting names may feel more idiomatic to Obj-C developers. -
Use an underscore as a delimiter (
A_Nested
andB_Nested
). Clearer but some may say uglier
I have a slight preference for option 2, but I figured it would be good to solicit other opinions. You can always override the default name if it isn't to your liking.
This proposal is limited to fixing the names for nested types, using the export rules that already exist today. Any other changes to Obj-C interfaces are strictly out of scope. If anybody else is planning more sweeping changes to how Obj-C interfaces are generated, I'd be happy to give way to them and they can incorporate this change in to their work.
This would be a source-breaking change, so if people are generally happy about doing this, I'd write up a proposal to use these new names when compiling in Swift 6 mode. As far as I can tell there aren't a huge number of complaints about the existing behaviour, so I don't imagine there will be much breakage in practice. Still, even without complaints I think it's clear that the current behaviour is less than ideal, and given that Swift 6 will break source compatibility to some extent, it may be an opportunity to make this change.
There was a brief discussion about this issue back in December 2015.