For most of Cocoa, Apple has done a nice job of wrapping the APIs in native Swift entities. In particular, sets of constants that exist in NS_ENUM
enumerations are imported as Swift enum
s with associated raw values. The situation is a little different when it comes to Foundation. In Foundation, many of the constants are not gathered into C enums and so they are not imported into Swift as nicely.
In this post I will look at what code is necessary to wrap up Foundation constants as a Swift enum and then I will introduce a simple tool to automate the code generation.
A Naïve First Attempt
I first became aware of this issue when I was writing some code to try to make the Keychain a little more palatable to use in Swift. I’ll use as an example throughout this post the manifest constants for the kSecAttrAccessible
attribute in a keychain item. These are defined in SecItem.h
as:
extern CFTypeRef kSecAttrAccessibleWhenUnlocked extern CFTypeRef kSecAttrAccessibleAfterFirstUnlock extern CFTypeRef kSecAttrAccessibleAlways extern CFTypeRef kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly extern CFTypeRef kSecAttrAccessibleWhenUnlockedThisDeviceOnly extern CFTypeRef kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly extern CFTypeRef kSecAttrAccessibleAlwaysThisDeviceOnly
Although it is not apparent from these declarations, the underlying type of these constants is CFStringRef
.
A first attempt to wrap these values in a Swift enum
might look something like this.
enum Accessible : String { case WhenUnlocked = kSecAttrAccessibleWhenUnlocked, AfterFirstUnlock = kSecAttrAccessibleAfterFirstUnlock, Always = kSecAttrAccessibleAlways, WhenPosscodeSetThisDeviceOnly = kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, WhenInlockedThisDeviceOnly = kSecAttrAccessibleWhenUnlockedThisDeviceOnly, AfterFirstUnlockThisDeviceOnly = kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, AlwaysThisDeviceOnly = kSecAttrAccessibleAlwaysThisDeviceOnly }
Unfortunately this does not work, because the raw values of enum case
s must be literals. I could have determined the values of the constants and explicitly equated these with the case
values, but that would break in the (albeit unlikely) even of the value of a constant changing in some future version of the OS.
Conforming to RawRepresentable
A better solution is to have the enum
conform to RawRepresentable
. For Swift 1.1, the RawRepresentable
protocol is defined as:
protocol RawRepresentable { typealias RawValue init?(rawValue: RawValue) var rawValue: RawValue { get } }
An initializer must be provided to create a enum
from a rawValue
and a computed property to perform the opposite conversion. In our example enum
this would look something like this.
public enum Accessible : RawRepresentable { case WhenUnlocked, AfterFirstUnlock, Always, WhenPasscodeSetThisDeviceOnly, WhenUnlockedThisDeviceOnly, AfterFirstUnlockThisDeviceOnly, AlwaysThisDeviceOnly public init?(rawValue: String) { if rawValue == String(kSecAttrAccessibleWhenUnlocked) { self = WhenUnlocked } else if rawValue == String(kSecAttrAccessibleAfterFirstUnlock) { self = AfterFirstUnlock } else if rawValue == String(kSecAttrAccessibleAlways) { self = Always } else if rawValue == String(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly) { self = WhenPasscodeSetThisDeviceOnly } else if rawValue == String(kSecAttrAccessibleWhenUnlockedThisDeviceOnly) { self = WhenUnlockedThisDeviceOnly } else if rawValue == String(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) { self = AfterFirstUnlockThisDeviceOnly } else if rawValue == String(kSecAttrAccessibleAlwaysThisDeviceOnly) { self = AlwaysThisDeviceOnly } else { return nil } } public var rawValue: String { switch self { case WhenUnlocked: return String(kSecAttrAccessibleWhenUnlocked) case AfterFirstUnlock: return String(kSecAttrAccessibleAfterFirstUnlock) case Always: return String(kSecAttrAccessibleAlways) case WhenPasscodeSetThisDeviceOnly: return String(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly) case WhenUnlockedThisDeviceOnly: return String(kSecAttrAccessibleWhenUnlockedThisDeviceOnly) case AfterFirstUnlockThisDeviceOnly: return String(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) case AlwaysThisDeviceOnly: return String(kSecAttrAccessibleAlwaysThisDeviceOnly) } } }
Other Nice Extensions
It can often be useful to be able to print the symbol for an enum
(not its raw value) both during debugging and when presenting error information to the user. This can be achieved by extending the enum to conform to Printable
.
extension Accessible : Printable { public var description : String { switch self { case WhenUnlocked: return "WhenUnlocked" case AfterFirstUnlock: return "AfterFirstUnlock" case Always: return "Always" case WhenPasscodeSetThisDeviceOnly: return "WhenPasscodeSetThisDeviceOnly" case WhenUnlockedThisDeviceOnly: return "WhenUnlockedThisDeviceOnly" case AfterFirstUnlockThisDeviceOnly: return "AfterFirstUnlockThisDeviceOnly" case AlwaysThisDeviceOnly: return "AlwaysThisDeviceOnly" } } }
Finally it would also be nice to be able to enumerate all the values of the enum. This can be done the extending the enum with an allValues
array.
extension Accessible { public static let allValues: [Accessible] = [WhenUnlocked, AfterFirstUnlock, Always, WhenPasscodeSetThisDeviceOnly, WhenUnlockedThisDeviceOnly, AfterFirstUnlockThisDeviceOnly, AlwaysThisDeviceOnly] }
Automating the Procedure: enumtool
At this point we have a really nice Swift enum
that wraps the Foundation constants, but what a lot of boilerplate code! Let’s face there is nothing more soul-destroying then spending hours cranking out boilerplate.
To automate this process, I created a little command line tool, written in swift and I have made it available on GitHub.
If the constants in the above example had been extracted, one per line, into a file called SecAttrAccessible.txt running enumtool
as follows would create all the code in this article.
enumtool -i SecAttrAccessible.txt -n Accessible -r String -o Accessible.swift
More information about enumtool
can be found at the enumtool GitHub repository. I hope it may be of some use to you.
Pingback: EVERYTHING YOU WANT – Swift resources | swiftioscodetutorial