enum StringEnum: String {
case one
case two
case three
}
let v: StringEnum = ...
v.rawValue // string here
in some cases i have to use a different raw type for some reason (e.g. Int), but still want to have names for case values:
enum IntEnum: Int {
case one
case two
case three
}
let v: IntEnum = ...
v.rawValue // int value here
v.stringValue // a hypothetical new method that would return
// "one", "two" or "three" in this particular case
// will also work if there is no raw type
i can do this manually but would rather avoid that boilerplate:
extension IntEnum {
var stringValue: String {
case .one: return "one"
case .two: return "two"
case .three: return "three"
}
}
import Runtime // https://github.com/wickwirew/Runtime
struct Person: Codable {
var name: String
var age: Int
static var codingKeys: [String] {
let metadata = try! typeInfo(of: CodingKeys.self)
return metadata.cases.map(\.name)
}
}
print(Person.codingKeys) // → ["name", "age"]
The library inspects the type metadata (which contains the case names) that is embedded in the binary. If I understand correctly, this should be safe on ABI-stable platforms (Apple) because the metadata layout will not change in a breaking manner. On other platforms the metadata layout might change, which means the library would have to be adapted for the new compiler version.
Here is a dump of the metadata value from the first example. It should give you a good idea of the kind of data the library makes available:
Not with this library. And I don’t think the binary contains the case names for enums imported from C/Obj-C. C-based enum cases are just integer constants from the perspective of the compiler.
import Foundation
import Runtime
dump(try typeInfo(of: URLRequest.CachePolicy.self))
but compiler knows what i mean when i write .useProtocolCachePolicy so in theory it can translate this to a "useProtocolCachePolicy" string accessible in runtime somehow.
extension URLRequest.CachePolicy {
var stringValue: String {
switch self {
case .useProtocolCachePolicy:
return "useProtocolCachePolicy"
case .reloadIgnoringLocalCacheData:
return "reloadIgnoringLocalCacheData"
case .reloadIgnoringLocalAndRemoteCacheData:
return "reloadIgnoringLocalAndRemoteCacheData"
case .returnCacheDataElseLoad:
return "returnCacheDataElseLoad"
case .returnCacheDataDontLoad:
return "returnCacheDataDontLoad"
case .reloadRevalidatingCacheData:
return "reloadRevalidatingCacheData"
}
}
}
so shall be able the compiler... if nothing simpler exists then by just automatically and invisibly generating one of those fragments for me when i merely using "cachePolicy.stringValue" somewhere in the app, or when i'm expressing my intent more explicitly via some marker:
Yes, I believe the compiler could do this, but the Swift team decided not to implement imported enums this way, and I'm assuming they had good reasons. I'm not a compiler developer, so I can only speculate.
For one, every piece of metadata you include increases the size of the compiled binary.
Perhaps more importantly, C/Objective-C binaries don't contain this metadata, so Swift's Clang importer would have to somehow include extra metadata (parsed from the C headers) for imported types in the binary that links with the imported libraries. And then the imported type would somehow have to include a reference to this extra metadata so that the runtime can find it later. I assume this extra field would make the type layout of the imported type incompatible with C/Obj-C, requiring some kind of automatic bridging every time a value is passed between Swift and C/Obj-C code or vice versa.
this is one of those things when you only pay this [increased binary size] tax if you opt-in to use that feature, be it using the manual code above or the autogenerated code.
then maybe it's easier to do the autogeneration (like above) separate to those existing mechanisms if it is indeed a major challenge to enhance those existing mechanisms to support this case. and should those existing mechanisms enhance in the future to support this case as well - the auto generator can be dropped without breaking source compatibility or with some minor deprecations (e.g. "Warning: HasStringValue protocol conformance is no longer needed").
This is pretty much it: C binaries don't contain this info, so every Swift library that might inspect an enum's name would have to emit their own copy of the metadata. Furthermore, we generally didn't want to have Swift emit additional strings that come from C code out of a concern for secrecy: people who write enums in C might pick names like "CurrentProcessorARM128" and trust that that name doesn't leak the existence of ARM128 processors into the final binary.
I think it'd be reasonable to have an opt-in feature in Swift for this—for instance, another magic protocol you could conform to and have the compiler synthesize the implementation of. It's kind of a niche feature, but there's not another way to do it without the boilerplate Mike's gone through.
This is essentially what I wound up with using sourcery... a CaseNamable protocol which produced a caseName property for all conforming enums, patterned after the existing CaseIterable