with objective interoperability bridging objective-c swift enums interop

objective-c - bridging - swift objective c interoperability



¿Cómo hacer que una enumeración Swift String esté disponible en Objective-C? (9)

Tengo esta enumeración con valores de String , que se utilizará para indicarle a un método API que registra en un servidor qué tipo de servidor tiene un mensaje. Estoy usando Swift 1.2, por lo que las enumeraciones se pueden asignar a Objective-C

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

Me sale el error

@objc enum tipo crudo La cadena no es un tipo entero

No he logrado encontrar ningún sitio que diga que solo los enteros se pueden traducir a Objective-C desde Swift. ¿Es este el caso? Si es así, ¿alguien tiene alguna sugerencia de mejores prácticas sobre cómo hacer que algo como esto esté disponible en Objective-C?


Aquí hay una solución que funciona.

@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" } } }


Aquí hay una solución si realmente quieres lograr el objetivo. Sin embargo, puede acceder a los valores de enumeración en los objetos que el Objetivo C acepta, no como valores de enumeración reales.

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() } }

Llamar :

NSString *debugRawValue = [LogSeverityBridge Debug]


Codifique para Xcode 8, utilizando el hecho de que Int funciona pero otros métodos no están expuestos a Objective-C. Esto es bastante horrible tal como está ...

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" } } }


De las notas de la versión Xcode 6.3 (énfasis agregado):

Mejoras de lenguaje rápido

...
Las enumeraciones rápidas ahora se pueden exportar a Objective-C utilizando el atributo @objc. Las enumeraciones @objc deben declarar un tipo sin formato entero y no pueden ser genéricas ni utilizar valores asociados. Como las enumeraciones de Objective-C no tienen espacios de nombres, los casos de enumeración se importan a Objective-C como la concatenación del nombre de enumeración y el nombre de caso.


Este es mi caso de uso:

  • Evito cadenas codificadas siempre que puedo, de modo que obtengo advertencias de compilación cuando cambio algo
  • Tengo una lista fija de valores de cadena procedentes de un back-end, que también puede ser nulo

Aquí está mi solución que no implica cadenas codificadas, admite valores perdidos y se puede usar con elegancia tanto en Swift como en 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 } } }


Esto es lo que se me ocurrió. En mi caso, esta enumeración estaba en el contexto proporcionando información para una clase específica, 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" } } }

Desde Swift, puede usar .string en una enumeración (similar a .rawValue ). Desde Objective-C, puede usar [ServiceProvider FieldNameToString:enumValue];


Puede crear una enumeración Inner privada. La implementación es un poco repetible, pero clara y fácil. 1 línea rawValue , 2 líneas init , que siempre se ven iguales. El Inner tiene un método que devuelve el equivalente "externo" y viceversa.

Tiene el beneficio adicional de que puede asignar directamente el caso de enumeración a una String , a diferencia de otras respuestas aquí.

No dude en aprovechar esta respuesta si sabe cómo resolver el problema de repetibilidad con plantillas, no tengo tiempo para mezclarme con él en este momento.

@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 } } } }


Si no le importa definir los valores en (Objetivo) C, puede usar la macro NS_TYPED_ENUM para importar constantes en Swift.

Por ejemplo:

archivo .h

typedef NSString *const ProgrammingLanguage NS_TYPED_ENUM; FOUNDATION_EXPORT ProgrammingLanguage ProgrammingLanguageSwift; FOUNDATION_EXPORT ProgrammingLanguage ProgrammingLanguageObjectiveC;

archivo .m

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

En Swift, esto se importa como una struct como tal:

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 } }

Aunque el tipo no se crea como una enum , se siente muy similar a uno cuando se usa en el código Swift.

Puede leer más sobre esta técnica en "Interactuar con las API de C" de la documentación de Uso de Swift con Cocoa y Objective-C


Una de las soluciones es utilizar el protocolo RawRepresentable.

No es ideal tener que escribir los métodos init y rawValue, pero eso le permite usar esta enumeración como de costumbre en Swift y 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: self = .Debug } } }