如何使 Swift 字符串枚举在 Objective-C 中可用?

How to make a Swift String enum available in Objective-C?

我有这个带有 String 值的枚举,它将用于告诉一个 API 方法,该方法记录到服务器一条消息具有什么样的服务器性。我正在使用 Swift 1.2,因此枚举可以映射到 Objective-C

@objc enum LogSeverity : String {
    case Debug = "DEBUG"
    case Info = "INFO"
    case Warn = "WARN"
    case Error = "ERROR"
}

我收到错误

@objc enum raw type String is not an integer type

我还没找到任何地方说只能将整数从 Swift 转换为 Objective-C。是这样吗?如果是这样,有人对如何在 Objective-C 中提供此类内容有任何最佳实践建议吗?

来自Xcode 6.3 release notes(强调已添加):

Swift Language Enhancements

...
Swift enums can now be exported to Objective-C using the @objc attribute. @objc enums must declare an integer raw type, and cannot be generic or use associated values. Because Objective-C enums are not namespaced, enum cases are imported into Objective-C as the concatenation of the enum name and case name.

如果您真的想实现目标,这里有变通办法。但是,您可以访问 Objective C 接受的对象中的枚举值,而不是实际的枚举值。

enum LogSeverity : String {

    case Debug = "DEBUG"
    case Info = "INFO"
    case Warn = "WARN"
    case Error = "ERROR"

    private func string() -> String {
        return self.rawValue
    }
}

@objc
class LogSeverityBridge: NSObject {

    class func Debug() -> NSString {
        return LogSeverity.Debug.string()
    }

    class func Info() -> NSString {
        return LogSeverity.Info.string()
    }

    class func Warn() -> NSString {
        return LogSeverity.Warn.string()
    }

    class func Error() -> NSString {
        return LogSeverity.Error.string()
    }
}

致电:

NSString *debugRawValue = [LogSeverityBridge Debug]

这是一个有效的解决方案。

@objc public enum ConnectivityStatus: Int {
    case Wifi
    case Mobile
    case Ethernet
    case Off

    func name() -> String {
        switch self {
        case .Wifi: return "wifi"
        case .Mobile: return "mobile"
        case .Ethernet: return "ethernet"
        case .Off: return "off"
        }
    }
}

这是我想出的。在我的例子中,这个枚举是在上下文中为特定的 class、ServiceProvider.

提供信息
class ServiceProvider {
    @objc enum FieldName : Int {
        case CITY
        case LATITUDE
        case LONGITUDE
        case NAME
        case GRADE
        case POSTAL_CODE
        case STATE
        case REVIEW_COUNT
        case COORDINATES

        var string: String {
            return ServiceProvider.FieldNameToString(self)
        }
    }

    class func FieldNameToString(fieldName:FieldName) -> String {
        switch fieldName {
        case .CITY:         return "city"
        case .LATITUDE:     return "latitude"
        case .LONGITUDE:    return "longitude"
        case .NAME:         return "name"
        case .GRADE:        return "overallGrade"
        case .POSTAL_CODE:  return "postalCode"
        case .STATE:        return "state"
        case .REVIEW_COUNT: return "reviewCount"
        case .COORDINATES:  return "coordinates"
        }
    }
}

从 Swift 开始,您可以在枚举上使用 .string(类似于 .rawValue)。 从 Objective-C 开始,您可以使用 [ServiceProvider FieldNameToString:enumValue];

解决方案之一是使用 RawRepresentable 协议。

必须编写 init 和 rawValue 方法并不理想,但这允许您像往常一样在 Swift 和 Objective-C 中使用此枚举。

@objc public enum LogSeverity: Int, RawRepresentable {
    case debug
    case info
    case warn
    case error

    public typealias RawValue = String

    public var rawValue: RawValue {
        switch self {
            case .debug:
                return "DEBUG"
            case .info:
                return "INFO"
            case .warn:
                return "WARN"
            case .error:
                return "ERROR"
        }
    }

    public init?(rawValue: RawValue) {
        switch rawValue {
            case "DEBUG":
                self = .debug
            case "INFO":
                self = .info
            case "WARN":
                self = .warn
            case "ERROR":
                self = .error
            default:
                return nil
        }
    }
}

Xcode 8 的代码,使用 Int 有效但其他方法未公开给 Objective-C 的事实。就目前而言,这非常可怕......

class EnumSupport : NSObject {
    class func textFor(logSeverity severity: LogSeverity) -> String {
        return severity.text()
    }
}

@objc public enum LogSeverity: Int {
    case Debug
    case Info
    case Warn
    case Error

    func text() -> String {
        switch self {
            case .Debug: return "debug"
            case .Info: return "info"
            case .Warn: return "warn"
            case .Error: return "error"
        }
    }
}

如果您不介意在 (Objective) C 中定义值,您可以使用 NS_TYPED_ENUM 宏在 Swift.

中导入常量

例如:

.h 文件

typedef NSString *const ProgrammingLanguage NS_TYPED_ENUM;

FOUNDATION_EXPORT ProgrammingLanguage ProgrammingLanguageSwift;
FOUNDATION_EXPORT ProgrammingLanguage ProgrammingLanguageObjectiveC;

.m 文件

ProgrammingLanguage ProgrammingLanguageSwift = "Swift";
ProgrammingLanguage ProgrammingLanguageObjectiveC = "ObjectiveC";

在 Swift 中,这是作为 struct 导入的:

struct ProgrammingLanguage: RawRepresentable, Equatable, Hashable {
    typealias RawValue = String

    init(rawValue: RawValue)
    var rawValue: RawValue { get }

    static var swift: ProgrammingLanguage { get }
    static var objectiveC: ProgrammingLanguage { get }
}

虽然该类型没有桥接为 enum,但在 Swift 代码中使用它时感觉非常相似。

您可以在 Using Swift with Cocoa and Objective-C documentation

的 "Interacting with C APIs" 中阅读有关此技术的更多信息

您可以创建私有 Inner 枚举。实现有点可重复,但清晰易行。 1 行 rawValue、2 行 init,它们看起来总是一样的。 Inner 有一个返回 "outer" 等效项和 vice-versa 的方法。

与此处的其他答案不同,还有一个额外的好处是您可以将枚举大小写直接映射到 String

如果您知道如何使用模板解决可重复性问题,欢迎在此答案的基础上进行构建,我现在没有时间讨论它。

@objc enum MyEnum: NSInteger, RawRepresentable, Equatable {
    case
    option1,
    option2,
    option3

    // MARK: RawRepresentable

    var rawValue: String {
        return toInner().rawValue
    }

    init?(rawValue: String) {
        guard let value = Inner(rawValue: rawValue)?.toOuter() else { return nil }
        self = value
    }

    // MARK: Obj-C support

    private func toInner() -> Inner {
        switch self {
        case .option1: return .option1
        case .option3: return .option3
        case .option2: return .option2
        }
    }

    private enum Inner: String {
        case
        option1 = "option_1",
        option2 = "option_2",
        option3 = "option_3"

        func toOuter() -> MyEnum {
            switch self {
            case .option1: return .option1
            case .option3: return .option3
            case .option2: return .option2
            }
        }
    }
}

这是我的用例:

  • 我尽可能避免硬编码字符串,这样当我更改某些内容时会收到编译警告
  • 我有一个来自后端的固定字符串值列表,它也可以是 nil

这是我的解决方案,完全不涉及硬编码字符串,支持缺失值,并且可以在 Swift 和 Obj-C 中优雅地使用:

@objc enum InventoryItemType: Int {
    private enum StringInventoryItemType: String {
        case vial
        case syringe
        case crystalloid
        case bloodProduct
        case supplies
    }

    case vial
    case syringe
    case crystalloid
    case bloodProduct
    case supplies
    case unknown

    static func fromString(_ string: String?) -> InventoryItemType {
        guard let string = string else {
            return .unknown
        }
        guard let stringType = StringInventoryItemType(rawValue: string) else {
            return .unknown
        }
        switch stringType {
        case .vial:
            return .vial
        case .syringe:
            return .syringe
        case .crystalloid:
            return .crystalloid
        case .bloodProduct:
            return .bloodProduct
        case .supplies:
            return .supplies
        }
    }

    var stringValue: String? {
        switch self {
        case .vial:
            return StringInventoryItemType.vial.rawValue
        case .syringe:
            return StringInventoryItemType.syringe.rawValue
        case .crystalloid:
            return StringInventoryItemType.crystalloid.rawValue
        case .bloodProduct:
            return StringInventoryItemType.bloodProduct.rawValue
        case .supplies:
            return StringInventoryItemType.supplies.rawValue
        case .unknown:
            return nil
        }
    }
}

我认为@Remi 的回答在某些情况下会崩溃,因为我有这个:

My error's screesshot。所以我 post 我对 @Remi 的回答的版本:

@objc public enum LogSeverity: Int, RawRepresentable {
    case debug
    case info
    case warn
    case error

    public typealias RawValue = String

    public var rawValue: RawValue {
        switch self {
            case .debug:
                return "DEBUG"
            case .info:
                return "INFO"
            case .warn:
                return "WARN"
            case .error:
                return "ERROR"
        }
    }

    public init?(rawValue: RawValue) {
        switch rawValue {
            case "DEBUG":
                self = .debug
            case "INFO":
                self = .info
            case "WARN":
                self = .warn
            case "ERROR":
                self = .error
            default:
                return nil
        }
    }
}