[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