[swift-evolution] [Review] SE-0005 Better Translation of Objective-C APIs Into Swift

Marco Masser lists at duckcode.com
Mon Feb 8 04:28:39 CST 2016


I know I’m late to the party and the review period has ended, but I’d like to add a suggestion towards a possible solution to a point that was discussed in this context.

> On 2016-02-05, at 17:54, Michel Fortin via swift-evolution <swift-evolution at swift.org> wrote:
> 
> […]
> 
> Eventually, classes such as NSCountedSet and NSIndexSet will have a proper struct wrapper that will claim the non-prefixed name. But there is no urgency in making those wrappers ready for Swift 3 because the class remains available.
> 
> Anyway, that's what I'd propose. There aren't that many classes in Foundation; cases like this can be sorted out manually.

I wholeheartedly agree with this: I’d take a close look at every class in Foundation that should really be a value type in Swift (all collection types, NSURL, …) and keep the NS prefix for them. Then, at some later point (or with Swift 3 if there’s enough time), add struct wrappers without the NS prefix for these classes.

I want to outline a possible solution to this that allows all developers to annotate their classes and have them automatically imported as structs in Swift.
I think that could actually work with just a couple of annotations in the Objective-C headers and the correct interpretation of them in the Clang Importer. I’d propose something like this:

@interface NS_SWIFT_STRUCT(CountedSet) NSCountedSet<ObjectType> : NSMutableSet<ObjectType> {

@property BOOL foo; // Just for demonstration purposes.

- (NSUInteger)countForObject:(ObjectType)object;
- (void)addObject:(ObjectType)object NS_SWIFT_MUTATING;
- (void)removeObject:(ObjectType)object NS_SWIFT_MUTATING;

…

@end

In Swift, that would come through as the following (in addition to an NSCountedSet class):

struct CountedSet<ObjectType> {
    private let _internal: NSCountedSet

    var foo: Bool

    func countForObject(object: ObjectType) -> Int
    mutating func addObject(object: ObjectType)
    mutating func removeObject(object: ObjectType)

    …

}

That is: NS_SWIFT_STRUCT(_name) specifies that a struct with the given _name should be created that has a private let member of the annotated class. All methods of the class and all its superclasses are automatically imported as methods of the struct. Methods annotated as  NS_SWIFT_MUTATING are imported as mutating methods in the Swift struct. For @property declarations, the importer could automatically infer that the setter is mutating and the getter nonmutating, although an NS_SWIFT_NONMUTATING might be useful to annotate that a property setter should be imported as nonmutating, if applicable.

After a quick glance over all of Foundation’s classes, I think the following could be eligible (probably not a complete list):
NSAttributedString
NSCharacterSet
NSCountedSet
NSData
NSDate
NSDecimalNumber
NSError
NSHashTable
NSIndexPath
NSIndexSet
NSNotification
NSNumber/NSValue
NSOrderedSet
NSURL
NSUUID

Whereas for some classes it would be better to use the mutable variants as the internal storage for the Swift struct:
NSMutableAttributedString (as AttributedString)
NSMutableCharacterSet (as CharacterSet)
NSMutableData (as Data)
NSMutableIndexSet (as IndexSet)
NSMutableOrderedSet (as OrderedSet)


The Clang Importer could also handle the case where passing a Swift URL to an Objective-C method that expects an NSURL could work automatically. Maybe an Objective-C methods like this:

- (void)methodImplementedInObjectiveC:(NSURL *)arg;

… could be imported into Swift like this:

func methodImplementedInObjectiveC(arg: NSURL)
func methodImplementedInObjectiveC(inout arg: URL) // Objective-C code operates on arg._internal


If the struct wraps a class that has a mutable counterpart (this would have to be annotated somehow), the import could do a better job for the nonmutating variant. This would require a variant of the NS_SWIFT_STRUCT(_name) macro that takes another parameter that points to the nonmutating variant. Then, the following methods:

- (void)nonmutatingMethodImplementedInObjectiveC:(NSData *)arg;
- (void)mutatingMethodImplementedInObjectiveC:(NSMutableData *)arg;

… could be imported into Swift like this:

func nonmutatingMethodImplementedInObjectiveC(arg: Data)
func mutatingMethodImplementedInObjectiveC(inout arg: Data) // Objective-C code operates on arg._internal


Annotations like this would be very useful because it would also allow developers to annotate their own classes like this. It’s not uncommon to have model objects that could sensibly be structs in Swift, but can’t because existing Objective-C code requires them to be classes.

When Objective-C classes become Swift structs, they obviously lose their inheritance chain and therefore polymorphic properties. I haven’t done a survey, but my gut feeling tells me that the classes for which all this is relevant – i.e. those that should have value semantics – aren’t usually subclassed and this point could therefore be moot. I may be wrong about that, though.


There are probably a lot of additional things to specify here, but maybe this could be a way forward. It would enable using things like NSCountedSet and NSURL in Swift just like it does now, with the possibility to add value types for them later without breaking existing code. In time, the classes could also be deprecated in favor of the struct, if that is desired.


Cheers,

Marco
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160208/87cbc40f/attachment.html>


More information about the swift-evolution mailing list