Alex Cohn
Alex Cohn

Reputation: 57173

export Swift var for Objective-C only

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

Answers (2)

Alex Cohn
Alex Cohn

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

Sweeper
Sweeper

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

Related Questions