[swift-evolution] [pitch] Make swift enum string available to Objc
Derrick Ho
wh1pch81n at gmail.com
Sun Feb 26 12:21:58 CST 2017
I updated my proposal
<https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/NNNN-Swift-enum-strings-ported-to-Objective-c.md>
to reflect the community's desire to build on @objc instead of adding a new
attribute @objcstring.
I've included it below for convenience:
Swift Enum strings ported to Objective-c
- Proposal: SE-NNNN
<https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/NNNN-filename.md>
- Authors: Derrick Ho <https://github.com/wh1pch81n>
- Review Manager: TBD
- Status: Awaiting review
*During the review process, add the following fields as needed:*
- Decision Notes: Rationale
<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161114/028950.html>
- Previous Proposal: SE-0033
<https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/0033-import-objc-constants.md>
<https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/NNNN-Swift-enum-strings-ported-to-Objective-c.md#introduction>
Introduction
We should allow swift-enum-strings and swift-struct-strings to be bridged
to objective-c. We can use the following notation:
@objc enum Planets: String { case Mercury }
@objc struct Food: String { public static let hamburger = Food(rawValue:
"hamburger") }
Creating a bridgable version will allow objective-c to enjoy some of the
benefits that swift enjoys.
Swift-evolution thread: Discussion
<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20161114/028950.html>
Discussion
<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170220/032656.html>
<https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/NNNN-Swift-enum-strings-ported-to-Objective-c.md#motivation>
Motivation
NS_STRING_ENUM and NS_EXSTENSIBLE_STRING_ENUM are annotations that you can
add to an objective-c global string. The annotations will make swift
interpret the objective-c global strings as enum and structs respectively
in theory. But it actually doesn't ever create enums
<https://bugs.swift.org/browse/SR-3146>.
The problem seems to stem from the conversion from objc to swift. It might
be more fruitful to make a conversion from swift to objc.
However, what if we take it a step further? Turning a swift-string-enum
into a bunch of global NSStrings really limits its power. There are many
classes written by apple that are structs in swift but become classes in
objective-c (i.e. String becomes NSString, Date becomes NSDate, Array
becomes NSArray, etc). There is a special bridging mechanism that allows
this to be possible. I think we should expand on that.
<https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/NNNN-Swift-enum-strings-ported-to-Objective-c.md#proposed-solution>Proposed
solution
// `@objc` and `String` can be applied to an enum to make it available
to objective-c:
//
@objc
public enum Food: String {
case Calamari
case Fish
}
// This can be ported over to Objective-c as an objective-c class
@interface Food: NSObject
@property (readonly) NSString *_Nonnull rawValue;
- (instancetype _Nullable)initWithRawValue:(NSString *_Nonnull)rawValue;
+ (instanceType _Nonnull)Calamari;
+ (instanceType _Nonnull)Fish;
@end
// `@objc` and `String` can be applied to a struct to make it
available to objective-c:
//
@objc
public struct Planets: String {
public let rawValue: String //<- This should be implicit and the
user should not need to add it
init(rawValue: String) { self.rawValue = rawValue } //<- This should
be implicit and the user should not need to add it
public static let Earth = Planets(rawValue: "Earth") //<- user defines these
public static let Venus = Planets(rawValue: "Venus") //<- user defines these
}
// This can be ported over to objective-c as a class
@interface Planets: NSObject
- (instancetype _Nonnull)initWithRawValue:(NSString *_Nonnull)rawValue;
+ (instancetype)Earth;
+ (instancetype)Venus;
@end
The difference between a swift-enum-string and a swift-struct-string is
that swift-enum-string provides a failable initializer while
swift-struct-string provides a non-failable initializer.
<https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/NNNN-Swift-enum-strings-ported-to-Objective-c.md#detailed-design>Detailed
design
<https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/NNNN-Swift-enum-strings-ported-to-Objective-c.md#swift-string-enum---casestring-translations>swift-string-enum
- case/string translations
A swift-enum-string, is created with cases and it has an implicit string
value based on the name of the case. The user may also add a name that does
not equal the name of the case.
// Swift
@objc
public enum Food: String {
case Calamari
case Fish = "Flounder" //<-- User wants Fish to be Flounder
}
// Objective-c
@interface Food: NSObject
@property (readonly) NSString *_Nonnull rawValue;
+ (instanceType _Nonnull)Calamari;
+ (instanceType _Nonnull)Fish;
@end
@implementation Food
+ (instanceType _Nonnull)Calamari { return [[Food alloc]
initWithRawValue:@"Calimari"]; }
+ (instanceType _Nonnull)Fish { return [[Food alloc]
initWithRawValue:@"Flounder"]; } //<-- Fisher contains Flounder
@end
<https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/NNNN-Swift-enum-strings-ported-to-Objective-c.md#swift-string-enum---failable-initializer>swift-string-enum
- failable initializer
A swift-enum-string has the ability to be initialized with a string. If the
string matches one of the possible cases, then it returns it, otherwise it
will return nil. This feature might be implemented as a dictionary or some
other means that gets the same results; Below is my suggestion.
// Assuming this swift implementation
@objc
public enum Food: String {
case Calamari
case Fish = "Flounder" //<-- User wants Fish to be Flounder
}
// The objective-c failable initializer may look like this.
@implementation Food
- (instancetype _Nullable)initWithRawValue:(NSString *_Nonnull)rawValue {
static NSDictionary <NSString *, NSString *>*states;
if (!states) {
// A dictionary where the KEYs are the acceptable rawValue's and
the VALUE are empty strings
states = @{
@"Calimari" : @"",
@"Flounder" : @""
}
}
if ((self = [super init])) {
if (states[rawValue]) {
_rawValue = rawValue
return self;
}
}
return nil;
}
@end
<https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/NNNN-Swift-enum-strings-ported-to-Objective-c.md#swift-string-enum---methods>swift-string-enum
- methods
swift enums allow methods to be defined. If you mark a method with @objc it
should be made available to objective-c.
// Swift
@objc
public enum Food: String {
case Calamari
case Fish
@objc func price() -> Double {
// ...
}
}
// Objective-c
@interface Food: NSObject
// ...
- (Double)price;
// ...
@end
<https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/NNNN-Swift-enum-strings-ported-to-Objective-c.md#swift-struct-string---string-translations>swift-struct-string
- string translations
A swift-struct-string needs to be marked with @objc and inherit from String to
bridge to objective-c. A property or method must be marked with @objc to be
made available to objective-c.
// Swift
@objc
struct Planet {
@objc public static let Earth = Planet(rawValue: "Earth")
@objc public func distanceFromSun() -> Double { ... }
}
// Objective-c
@interface Planet
+ (instancetype _Nonnull)Earth;
+ (Double)distanceFromSun;
@end
<https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/NNNN-Swift-enum-strings-ported-to-Objective-c.md#swift-struct-string---non-failable-initializer>swift-struct-string
- non-failable initializer
The swift-struct-string initializer should not be failable and will accept
any string value
@implementation Planet
- (instancetype _Nonnull)initWithRawValue:(NSString *)rawValue {
if ((self = [super init])) {
_rawValue = rawValue;
}
return self;
}
@end
<https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/NNNN-Swift-enum-strings-ported-to-Objective-c.md#swift-struct-string---extension>swift-struct-string
- extension
One of the key attributes of an extensible string enum is that it can be
extended. This should produce something available to objective-c. The
original definition of Planet needs to have been marked with @objc.
// Swift
extension Planet {
@objc public static let Pluto = Planet(rawValue: "Pluto")
}
// Objective-c
@interface Planet (extention_1)
- (instancetype _Nonnull)Pluto;
@end
@implementation Planet (extention_1)
- (instancetype _Nonnull)Pluto {
return [[Planet alloc] initWithRawValue:@"Pluto"];
}
@end
<https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/NNNN-Swift-enum-strings-ported-to-Objective-c.md#swift-string-enum--swift-struct-string---equalityhashrawvalue>swift-string-enum
&& swift-struct-string - equality/hash/rawValue
When an enum or struct is marked with @objc and String, the objective-c
class that is produced should have its equality/hash methods and rawValue
property implicitly be implemented. The user should not need to implement
these on his/her own.
@implementation Food
- (instancetype)rawValue { return _rawValue; }
- (NSUInteger)hash {
return [[self rawValue] hash];
}
- (BOOL)isEqual:(id)object {
if (self == object) { return YES }
if (![object isKindOfClass:[Food class]]) { return NO; }
return [self.rawValue isEqualToString:((Food *)object).rawValue];
}
@end
<https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/NNNN-Swift-enum-strings-ported-to-Objective-c.md#objective-c-name>Objective-c
name
In the above examples, the objective-c name of the class and the swift name
of the class were the same. If this causes a naming conflict then the
objective-c name could be Prefixed with ENUM.
// Swift
@objc
enum Planet: String { ... }
// Objective-c
@interface ENUMPlanet
@end
The programmer should still be able to add their own name by specifying it
as an argument.
// Swift
@objc(CustomPlanet)
enum Planet { ... }
// Objective-c
@interface CustomPlanet
@end
<https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/NNNN-Swift-enum-strings-ported-to-Objective-c.md#source-compatibility>Source
compatibility
This will be an additive feature and will not break anything existing.
<https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/NNNN-Swift-enum-strings-ported-to-Objective-c.md#alternatives-considered>Alternatives
considered
-
Implement a swift class that implements the above described behviors.
-
Don't change anything.
On Tue, Feb 21, 2017 at 6:09 PM Derrick Ho <wh1pch81n at gmail.com> wrote:
> True.
> In my proposal I mention how NS_STRING_ENUM doesn't produce an swift enum.
>
> So one solution is to merely "go the other way" which would produce what
> Kevin N. suggested.
>
> Is it not odd that that the objc version is so different from the swift
> version?
>
> Would it not be better to offer a somewhat similar API experience between
> the two languages?
>
> @objcstring would "promote" the swift enum to an objective-c class to make
> the API experience similar.
>
> I understand that maybe @objcstring is too niche to get its own
> annotation. Instead @objc should become more powerful.
>
> I think @objc should make an enum become a class with swift-enum like
> abilities. This would allow enum functions to be bridged over to
> objective-c as well.
> On Tue, Feb 21, 2017 at 1:32 PM Michael Ilseman <milseman at apple.com>
> wrote:
>
> A quick note addressing a misconception that you’ll want to clean up for a
> formal proposal:
>
> NS_[EXTENSIBLE_]STRING_ENUMs both generate Swift structs, the difference
> is only in the explicitness of the rawValue initializer. To use the “other
> direction” analogy, you’d similarly want them to apply for rawValue-ed
> structs alone. The reasons are the same: only the structs are
> layout-compatible because enums are integers.
>
> Once you go down this route, perhaps it doesn’t make sense to annotate the
> struct decls themselves anymore. Maybe you just want more @objc control
> over bridging the types. For example, maybe you want to introduce a feature
> so that static members that are layout-compatible with String are bridged
> as global strings with the supplied name.
>
>
>
>
> On Feb 20, 2017, at 4:07 PM, Derrick Ho via swift-evolution <
> swift-evolution at swift.org> wrote:
>
> Swift should not forsake objective-c. At least not when it comes enum
> strings. Although swift enums are suppose to be swift only, I think we
> should add a new attribute to slightly relax that. I think a good
> attribute would be @objcstring.
>
> By adding @objcstring, an objective-c exclusive class will be generated.
>
> @objcstring
> enum Planet {
> case Jupiter
> }
>
> I have written up a proposal with more details on what it would look for
> objective-c.
>
>
> https://github.com/wh1pch81n/swift-evolution/blob/swift-enum-objc/proposals/NNNN-Swift-enum-strings-ported-to-Objective-c.md
>
> If no one objects to this proposal I'll submit it.
>
> **notes: I am reviving this discussion so that I may submit this for Swift
> 4 stage 2
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170226/6e36944e/attachment.html>
More information about the swift-evolution
mailing list