Wie kann ich eine Swift String-Aufzählung in Objective-C verfügbar machen?

74

Ich habe diese Aufzählung mit StringWerten, die verwendet werden, um einer API-Methode, die bei einem Server protokolliert, mitzuteilen, welche Art von Serverität eine Nachricht hat. Ich verwende Swift 1.2, damit Aufzählungen Objective-C zugeordnet werden können

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

Ich bekomme den Fehler

@objc enum raw type String ist kein ganzzahliger Typ

Ich habe nirgendwo etwas gefunden, das besagt, dass nur ganze Zahlen von Swift nach Objective-C übersetzt werden können. Ist das der Fall? Wenn ja, hat jemand einen Best-Practice-Vorschlag, wie so etwas in Objective-C verfügbar gemacht werden kann?

bogen
quelle

Antworten:

59

Aus den Versionshinweisen zu Xcode 6.3 (Hervorhebung hinzugefügt):

Schnelle Sprachverbesserungen

...
Schnelle Aufzählungen können jetzt mit dem Attribut @objc nach Objective-C exportiert werden. @objc-Aufzählungen müssen einen ganzzahligen Rohtyp deklarieren und dürfen nicht generisch sein oder zugehörige Werte verwenden. Da Objective-C-Aufzählungen keinen Namespace haben, werden Aufzählungsfälle als Verkettung von Aufzählungsname und Fallname in Objective-C importiert.

Martin R.
quelle
Seite kann nicht auf Link gefunden werden
Zack Shapiro
75

Eine der Lösungen besteht darin, das RawRepresentableProtokoll zu verwenden.

Es ist nicht ideal, die Methoden init und rawValue schreiben zu müssen, aber damit können Sie diese Aufzählung wie gewohnt sowohl in Swift als auch in Objective-C verwenden.

@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
        }
    }
}
Remy Cilia
quelle
9
Ich mag diesen Ansatz wirklich. Um es perfekt zu machen, kann man eine Codeduplizierung vermeiden, indem man ein Wörterbuch vom Typ definiert, [LogSeverity: String]und dann können die Methoden rawValueund init?durch eine einzelne Zeile definiert werden.
Gobe
1
@Gobe können Sie das Beispiel für das Schreiben von rawValue und init teilen? Methoden in einer einzigen Zeile, bitte?
Daniel Sanchez
1
@ DanielSanchez Wenn Sie eine Aufzählung von Typ haben, LogSeveritydie durch Strings roh darstellbar ist , und Sie einmal ein Wörterbuch vom Typ definieren [LogSeverity: String], dann ist der rawValue einfach myDictionary[self]und der Init istself = myDictionary.first(where: { $0.value == rawValue })
Gobe
1
Es scheint eine nette Antwort zu sein, aber ich bekomme seltsame Abstürze bei bad_access, nachdem ich das ausprobiert habe.
Jafar Khoshtabiat
1
@VladimirsMatusevics Ich habe es behoben und um Bearbeitung gebeten, tnx zur Korrektur
Jafar Khoshtabiat
23

Hier ist eine Lösung, die funktioniert.

@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"
        }
    }
}
David
quelle
12
Und wie würde die name()Funktion in Objective-C aufgerufen?
Stephen Paul
@StephenPaul So etwas wie ConnectivityStatusWifi. Wenn Sie dies eingeben, sollte Xcode versuchen, die automatische Vervollständigung zu versuchen.
David
10
@ David, aber Sie können nicht name()in objc anrufen
Cameron Askew
Ich denke, Sie sollten die Funktion als öffentlich deklarieren lassen
i89
@Chuck sogar öffentliche Funktion macht die Methode nicht verfügbar.
Priyal
11

Hier ist eine Lösung, wenn Sie das Ziel wirklich erreichen möchten. Sie können jedoch auf die Aufzählungswerte in Objekten zugreifen, die von Objective C akzeptiert werden, nicht als tatsächliche Aufzählungswerte.

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

Anrufen :

NSString *debugRawValue = [LogSeverityBridge Debug]
BLC
quelle
1
Das Problem, das ich dabei sehe, ist, dass Sie nicht variablesvom Typ LogSeverity haben können, aber ansonsten OK.
Chris Prince
2
Dies funktionierte bei mir mit einigen geringfügigen Änderungen. @objcMembers public class LogSeverityBridge: NSObject { static func debug() -> String { return TravelerProtectionLevel.premium.rawValue }
Hendrix
6

Wenn es Ihnen nichts ausmacht, die Werte in (Ziel) C zu definieren, können Sie das NS_TYPED_ENUMMakro verwenden, um Konstanten in Swift zu importieren.

Zum Beispiel:

.h Datei

typedef NSString *const ProgrammingLanguage NS_TYPED_ENUM;

FOUNDATION_EXPORT ProgrammingLanguage ProgrammingLanguageSwift;
FOUNDATION_EXPORT ProgrammingLanguage ProgrammingLanguageObjectiveC;

.m Datei

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

In Swift wird dies structals solches importiert :

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

Obwohl der Typ nicht als überbrückt ist enum, fühlt er sich einem sehr ähnlich, wenn er in Swift-Code verwendet wird.

Weitere Informationen zu dieser Technik finden Sie in der Dokumentation "Interaktion mit C-APIs" in der Dokumentation " Verwenden von Swift mit Kakao" und "Objective-C"

RvdB
quelle
Dies ist genau der Ansatz, den ich gesucht habe!
Tom Kraina
5

Code für Xcode 8 unter Verwendung der Tatsache, dass dies Intfunktioniert, andere Methoden jedoch nicht Objective-C ausgesetzt sind. Das ist ziemlich schrecklich wie es aussieht ...

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"
        }
    }
}
Dan Rosenstark
quelle
4

Dies ist mein Anwendungsfall:

  • Ich vermeide fest codierte Zeichenfolgen, wann immer ich kann, damit ich Kompilierungswarnungen erhalte, wenn ich etwas ändere
  • Ich habe eine feste Liste von String-Werten, die von einem Back-End stammen und auch Null sein können

Hier ist meine Lösung, die überhaupt keine fest codierten Zeichenfolgen enthält, fehlende Werte unterstützt und sowohl in Swift als auch in Obj-C elegant verwendet werden kann:

@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
        }
    }
}
Chris Garrett
quelle
1
Ich kann nicht [InventoryItemType fromString:]von Objective-C verwenden: Es gibt den Fehler "Empfängertyp 'InventoryItemType' ist keine Objective-C-Klasse"
Agirault
1
Dies liegt daran, dass in Objective-C InventoryItemType vom Typ Int oder NSInteger ist und keine Klasse.
Chris Garrett
Hallo @ChrisGarrett, ich habe eine Aufzählung wie: public enum TrackingValue { case constant(String) case customVariable(name: String) case defaultVariable(DefaultVariable) public enum DefaultVariable { case advertisingId case advertisingTrackingEnabled case appVersion case connectionType case interfaceOrientation case isFirstEventAfterAppUpdate case requestQueueSize case adClearId } }Was ist der beste Weg, um es als ObjC-Aufzählung zu sehen? Danke im Voraus!
Dragisa Dragisic
@agirault können Sie es in eine andere Klasse einwickeln, zBclass InventoryItemTypeParser: NSObject { @objc static func fromString(_ string: String?) -> InventoryItemType { return InventoryItemType.fromString(string) } }
Krzysztof Skrzynecki
2

Folgendes habe ich mir ausgedacht. In meinem Fall war diese Aufzählung im Zusammenhang mit Informationen für eine bestimmte Klasse 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"
        }
    }
}

Von Swift aus können Sie .stringeine Aufzählung verwenden (ähnlich wie .rawValue). In Objective-C können Sie verwenden[ServiceProvider FieldNameToString:enumValue];

Chris Prince
quelle
1

Sie können eine private InnerAufzählung erstellen . Die Implementierung ist etwas wiederholbar, aber klar und einfach. 1 Zeile rawValue, 2 Zeilen init, die immer gleich aussehen. Das Innerhat eine Methode, die das "äußere" Äquivalent zurückgibt und umgekehrt.

Hat den zusätzlichen Vorteil, dass Sie den Enum-Fall im StringGegensatz zu anderen Antworten hier direkt einem zuordnen können .

Wenn Sie wissen, wie Sie das Wiederholbarkeitsproblem mit Vorlagen lösen können, können Sie gerne auf dieser Antwort aufbauen. Ich habe momentan keine Zeit, mich damit zu beschäftigen.

@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
            }
        }
    }
}
Pawel Jurczyk
quelle
1

Ich denke, @Remis Antwort stürzt in einigen Situationen ab, da ich Folgendes hatte:

Screesshot meines Fehlers . Also poste ich meine Ausgabe für @Remis Antwort:

@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
        }
    }
}
Jafar Khoshtabiat
quelle
Gibt es eine Möglichkeit, diese Aufzählung aus Objc zu erstellen, wenn ich [LogSeverity rawValue:] versuche, wird der Initialisierer nicht gefunden.
Retro
Wie kann ich von Ziel c aus auf den Zeichenfolgenwert zugreifen?
OliverD