Reputation: 57173
My Swift library API has a public property of type enum
:
public enum Animal : String, Codable {
case Dog
case Cat
}
public var pet: Animal
The enum
is not exported as @objc
, but I need to access this property from Objective-C. So, I add a computed property
@objc public var petAsString: String {
get { return String(reflecting: pet) }
set { pet = newValue == "Cat" ? Animal.Cat : Animal.Dog }
}
Can I hide this property from the Swift consumers of my library? Or, maybe, there is a better way to let it be used from Objective-C, without the ugly 'AsString' suffix?
Update: Sorry for misrepresenting my root problem. I chose to go through all this trouble because I did not understand then that Swift typealias
lets me export to Objective-C a long and ugly but unambiguous enum AlexPetLib_Animal
, while all my and third party Swift code can still use properly namespaced Animal
alias:
public typealias Animal = AlexPetLib_Animal
@objc public enum AlexPetLib_Animal : Int, Codable {
case Dog
case Cat
}
No extra changes were required for my library code; the Swift app that uses the library only had to be recompiled.
Upvotes: 0
Views: 353
Reputation: 57173
To sum it up: there is no trick that will hide a Swift var or func from Swift consumers, but it is possible not only to hide it from Obj-C consumers, but also expose it to them under a different name, using @objc(AnotherName)
pattern.
For my specific use case, this allowed me to expose the enum to Obj-C under a library-prefixed name, which is definitely a better choice, compared to using unprotected string-based API.
Upvotes: 0
Reputation: 271175
An enum that has an integral raw value type can be @objc
, so you can just use Int
as Animal
's raw value type instead:
@objc
public enum Animal : Int, Codable {
case dog
case cat
}
To replicate the behaviour of the String
raw value type in Swift, add these members:
var stringValue: String {
switch self {
case .cat: return "Cat"
case .dog: return "Dog"
}
}
init?(stringValue: String) {
switch stringValue {
case "Cat": self = .cat
case "Dog": self = .dog
default: return nil
}
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
if let animal = Animal(stringValue: string) {
self = animal
} else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Expected cat or dog!")
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(stringValue)
}
Unlike the "writing two versions of each thing that uses Animal
approach", those four members are the only extra things you need. After that, you will be able to mark everything that uses Animal
as @objc
(assuming it doesn't use other non @objc
stuff of course).
An edge case is when passing Animal
to methods that accept a generic RawRepresentable
, it will use the Int
raw value, but I don't that happens often...
Upvotes: 1