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