How Are Swift Enums Implemented Internally

Swift enum case access level

Swift switch statements must be exhaustive. Suppose a consumer of your public enum Foo tries to use it in a switch. If the .bar case is private, how is the switch supposed to handle it?

Cases are part of the public API of an enum. If you wish to make them private, wrap them in a struct that exposes only public operations.

Update: With the implementation of SE-0192 in Swift 5, the @unknown default: syntax was introduced. This is useful in situations where you have a enum for which you've handled all currently existing cases, but want to protect yourself from future cases being added, by specifying the default behaviour.

How to enumerate an enum with String type?

Swift 4.2+

Starting with Swift 4.2 (with Xcode 10), just add protocol conformance to CaseIterable to benefit from allCases. To add this protocol conformance, you simply need to write somewhere:

extension Suit: CaseIterable {}

If the enum is your own, you may specify the conformance directly in the declaration:

enum Suit: String, CaseIterable { case spades = "♠"; case hearts = "♥"; case diamonds = "♦"; case clubs = "♣" }

Then the following code will print all possible values:

Suit.allCases.forEach {
print($0.rawValue)
}


Compatibility with earlier Swift versions (3.x and 4.x)

If you need to support Swift 3.x or 4.0, you may mimic the Swift 4.2 implementation by adding the following code:

#if !swift(>=4.2)
public protocol CaseIterable {
associatedtype AllCases: Collection where AllCases.Element == Self
static var allCases: AllCases { get }
}
extension CaseIterable where Self: Hashable {
static var allCases: [Self] {
return [Self](AnySequence { () -> AnyIterator<Self> in
var raw = 0
var first: Self?
return AnyIterator {
let current = withUnsafeBytes(of: &raw) { $0.load(as: Self.self) }
if raw == 0 {
first = current
} else if current == first {
return nil
}
raw += 1
return current
}
})
}
}
#endif

How are optional values implemented in Swift?

Optionals are implemented as enum type in Swift.

See Apple's Swift Tour for an example of how this is done:

enum OptionalValue<T> {
case None
case Some(T)
}

How to make an enum conform to a protocol in Swift?

This is my attempt:

protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}

enum ExampleEnum : ExampleProtocol {
case Base, Adjusted

var simpleDescription: String {
return self.getDescription()
}

func getDescription() -> String {
switch self {
case .Base:
return "A simple description of enum"
case .Adjusted:
return "Adjusted description of enum"
}
}

mutating func adjust() {
self = ExampleEnum.Adjusted
}
}

var c = ExampleEnum.Base
c.adjust()
let cDescription = c.simpleDescription

Performance of string-backed enums in Swift

Have a look at this code:

enum A {
case A1, A2
}

enum B : Int {
case B1 = 1
case B2
}

enum C : String {
case C1
case C2
}

A.A1.hashValue // 0
A.A2.hashValue // 1

B.B1.hashValue // 0
B.B2.hashValue // 1

C.C1.hashValue // 0
C.C2.hashValue // 1

As you can see the hashValue of any enum is equal to the number of the case, regardless of the rawValue and if there even is one. This is most certainly because underneath, every enum is just a number and the rawValue is just an array that maps those numbers (indices) to the corresponding value. Therefore

  1. Enum comparison is always just a comparison between two numbers, you can just compare the hashValues -> O(1) complexity (untested, but I'm pretty sure)

  2. I don't know how initializing with a raw value works, most likely it's O(1) though (wrong, it's O(n)!, see edit below), because all the possible RawValue types are Hashable and therefore it's possible to store them in a Hashmap which would mean O(1) indexing.

That said, an enum may be implemented like this:

enum M : RawRepresentable {
case M1 // rawValue = 4, hashValue = 0
case M2 // rawValue = 7, hashValue = 1

typealias RawValue = Int

// Int is Hashable -> Store in Dictionary -> O(1)
static let rawToEnum : [RawValue : M] = [
4 : .M1,
7 : .M2
]

// hashValue is index of this array
static let hashToRaw : [RawValue] = [
4,
7
]

var rawValue : RawValue {
return M.hashToRaw[hashValue]
}

init?(rawValue: RawValue) {
if let m = M.rawToEnum[rawValue] {
self = m
} else {
self = .M1 // Failed, assign a value to let it compile
return nil
}
}
}

EDIT: It seems like initializing an enum is O(n) (where n is the number of cases) complexity!

I made some performance tests, where I created 4 different enums with 128, 256, 512, 1024 cases each. I then made the program choose 128 random rawValues of each of those enums, make an array of them and repeat that array 20 times (to get more accurate times). Then the enum is getting initialized with each of these rawValues. Here are the results (release build):

Default rawValue: 20 repetitions
128 cases -> 0.003 sec (33% StDev)
256 cases -> 0.006 sec (14% StDev)
512 cases -> 0.014 sec (15% StDev)
1024 cases -> 0.025 sec (10% StDev)

You can check out the test project I created here (XCode 7 beta 6)

Another EDIT: I added enums to the test project, which conform to RawRepresentable the way I showed above, again using exactly the same setup with 128, 256, 512 and 1024 cases. Turns out (as expected) it's O(1)! So apparently creating your own enum is faster! I just don't understand why the Swift devs didn't do it like this... Performance btw is this for the custom implementation of RawRepresentable for 200 more repetitions (10 times the amount of enum initilizations than with default rawValues):

Custom rawValue: 200 repetitions
128 cases -> 0.008 sec ( 7% StDev)
256 cases -> 0.008 sec (15% StDev)
512 cases -> 0.010 sec (19% StDev)
1024 cases -> 0.008 sec (26% StDev)

Using Generic swift enum in a protocol

What you're looking for would be a "protected" level, and that doesn't exist in Swift, and can't exist without creating a new protection level, since it would break compiler optimizations. In your example, since getPermission(forFeature:) is promised never to be called outside this scope, the compiler is free to inline it. So this function may not even exist at the point that your extension wants to call it.

It would be possible for Swift to add a "protected" level that is "semi-public," but Swift does not have any such feature. You will need to redesign FeatureDataManager to make this possible. From your example, it's not obvious how to do that, because you provide no public interface for permissions at all, so it's not clear what you mean by "I want to be able to extend it to create more meaningful method name." Currently there is no public method name. If there were one, then making a more convenient syntax like you describe would be easy.

Can you give an example of the calling code that you want this extension to improve?

For more on why the language is this way, see Access Control and protected. It's not an accident.

You note that you can do this in the same file, and that's true. Swift allows this for stylistic reasons (many people use extensions inside a single file for code organization reasons). Swift treats all extensions in the same file as being in the main definition. But that does not extend to other files, and certainly not to other modules.


The generic solution to this looks like:

public class FeatureDataManager<Feature>
where Feature: RawRepresentable, Feature.RawValue == String {

private func getPermission(forFeature feature: String) -> Bool { ... }

public func isPermissionEnable(forFeature feature: Feature) {
self.getPermission(forFeature: feature.rawValue)
}
}

An App would then create a feature set and create a manager for that feature set:

enum AppFeature: String {
case ads = "ads"
case showBanner = "banner"
case showFullScreenPub = "showFullScreenPub"
}

let featureDataManager = FeatureDataManager<AppFeature>()
featureDataManager.isPermissionEnable(forFeature: .ads)

That does prevent the easy creation of a .shared instance. It's arguable whether that's good or bad, but on the assumption that you want it, you would need to wrap it up:

class AppFeatureDataManager {
enum Feature: String {
case ads = "ads"
case showBanner = "banner"
case showFullScreenPub = "showFullScreenPub"
}

static var shared = AppFeatureDataManager()

let manager = FeatureDataManager<Feature>()

public func isPermissionEnable(forFeature feature: Feature) {
manager.isPermissionEnable(forFeature: feature)
}
}

Now that's a bit too much boiler-plate for the app side (especially if there are more methods than isPermissionEnable), so you can remove the boilerplate this way (full code):

public class FeatureDataManager<Feature>
where Feature: RawRepresentable, Feature.RawValue == String {

private var permissionManager: PermissionManager

init() {
self.permissionManager = PermissionManager()
}

private func getPermission(forFeature feature: String) -> Bool {
self.permissionManager.isEnable(feature)
}

public func isPermissionEnable(forFeature feature: Feature) {
self.getPermission(forFeature: feature.rawValue)
}
}

protocol AppFeatureDataManager {
associatedtype Feature: RawRepresentable where Feature.RawValue == String
var manager: FeatureDataManager<Feature> { get }
}

// Here you can write any necessary pass-through functions so the app doesn't have to
extension AppFeatureDataManager {
public func isPermissionEnable(forFeature feature: Feature) {
manager.isPermissionEnable(forFeature: feature)
}
}

//
// Application Developer writes this:
//
class MyGreatAppFeatureDataManager {
enum Feature: String {
case ads = "ads"
case showBanner = "banner"
case showFullScreenPub = "showFullScreenPub"
}

// This is the only thing that's really required
let manager = FeatureDataManager<Feature>()

// They're free make this a shared instance or not as they like.
// That's not something the framework cares about.
static var shared = MyGreatAppFeatureDataManager()
private init() {}
}

All that said, I think this is getting too many layers if FeatureDataManager is really just a front-end for PermissionManager like you've described here. (Maybe your example is highly simplified, so the below doesn't apply.)

If PermissionManager is public, and the real goal is just to have a nicer front-end to it, I would write it this way:

protocol FeatureDataManager {
associatedtype Feature: RawRepresentable where Feature.RawValue == String
var permissionManager: PermissionManager { get }
}

extension FeatureDataManager {
func isPermissionEnable(forFeature feature: Feature) {
permissionManager.isEnable(feature.rawValue)
}
}

//
// App developer writes this
//
class MyGreatAppFeatureDataManager: FeatureDataManager {
enum Feature: String {
case ads = "ads"
case showBanner = "banner"
case showFullScreenPub = "showFullScreenPub"
}

// This is the only required boilerplate; the protocol can't do this for you.
let permissionManager = PermissionManager()

// And the developer can decide to make it a shared instance if they like,
// but it's not the business of the framework
static let shared = MyGreatAppFeatureDataManager()
private init() {}
}


Related Topics



Leave a reply



Submit