web-dev-qa-db-ja.com

Swiftカスタム初期化子を持つ列挙型は、rawValue初期化子を失います

私はこの問題を次のように最も単純な形に要約しようとしました。

セットアップ

Xcodeバージョン6.1.1(6A2008a)

MyEnum.Swiftで定義されている列挙型:

internal enum MyEnum: Int {
    case Zero = 0, One, Two
}

extension MyEnum {
    init?(string: String) {
        switch string.lowercaseString {
        case "zero": self = .Zero
        case "one": self = .One
        case "two": self = .Two
        default: return nil
        }
    }
}

そして、別のファイルの列挙型を初期化するコード、MyClass.Swift

internal class MyClass {
    let foo = MyEnum(rawValue: 0)  // Error
    let fooStr = MyEnum(string: "zero")

    func testFunc() {
        let bar = MyEnum(rawValue: 1)  // Error
        let barStr = MyEnum(string: "one")
    }
}

エラー

Xcodeでは、MyEnumをraw値初期化子で初期化しようとすると、次のエラーが表示されます。

Cannot convert the expression's type '(rawValue: IntegerLiteralConvertible)' to type 'MyEnum?'

ノート

  1. Swift言語ガイド

    Raw-value型で列挙を定義すると、列挙は自動的にraw_valueの型の値(rawValueというパラメーターとして)を受け取り、列挙メンバーまたはnilのいずれかを返す初期化子を受け取ります。

  2. MyEnumのカスタム初期化子は、 言語ガイド からの次のケースのために、列挙型の未加工値初期化子が削除されたかどうかをテストする拡張機能で定義されました。ただし、同じエラー結果が得られます。

    値型のカスタム初期化子を定義すると、その型のデフォルトの初期化子(または構造体の場合はメンバーごとの初期化子)にアクセスできなくなることに注意してください。 [...]
    デフォルトの初期化子とメンバーごとの初期化子、および独自のカスタム初期化子でカスタム値型を初期化できるようにする場合は、値型の元の実装の一部としてではなく、拡張でカスタム初期化子を記述します。

  3. Enum定義をMyClass.Swiftに移動すると、barのエラーは解決されますが、fooのエラーは解決されません。

  4. カスタム初期化子を削除すると、両方のエラーが解決します。

  5. 回避策の1つは、列挙型定義に次の関数を含め、提供されたraw値初期化子の代わりにそれを使用することです。そのため、カスタム初期化子を追加すると、生の値の初期化子privateをマークするのと同様の効果があるようです。

    init?(raw: Int) {
        self.init(rawValue: raw)
    }
    
  6. MyClass.SwiftRawRepresentableへのプロトコル準拠を明示的に宣言すると、barのインラインエラーが解決されますが、重複シンボルに関するリンカーエラーが発生します(raw値型の列挙型はRawRepresentableに暗黙的に準拠するため)。

    extension MyEnum: RawRepresentable {}
    

誰もがここで何が起こっているかについてもう少し洞察を提供できますか?生の値の初期化子にアクセスできないのはなぜですか?

88
nickgraef

このバグはXcode 7およびSwift 2で解決されました

24
alcamla
extension TemplateSlotType {
    init?(rawString: String) {
        // Check if string contains 'carrousel'
        if rawString.rangeOfString("carrousel") != nil {
            self.init(rawValue:"carrousel")
        } else {
            self.init(rawValue:rawString)
        }
    }
}

あなたの場合、これは次の拡張子になります:

extension MyEnum {
    init?(string: String) {
        switch string.lowercaseString {
        case "zero": 
            self.init(rawValue:0)
        case "one": 
            self.init(rawValue:1)
        case "two":
            self.init(rawValue:2)
        default: 
            return nil
        }
    }
}
13
Antoine

switchケースを使用せずにコードをよりシンプルで便利なものにすることもできます。これにより、新しいタイプを追加するときにケースを追加する必要がなくなります。

enum VehicleType: Int, CustomStringConvertible {
    case car = 4
    case moped = 2
    case truck = 16
    case unknown = -1

    // MARK: - Helpers

    public var description: String {
        switch self {
        case .car: return "Car"
        case .truck: return "Truck"
        case .moped: return "Moped"
        case .unknown: return "unknown"
        }
    }

    static let all: [VehicleType] = [car, moped, truck]

    init?(rawDescription: String) {
        guard let type = VehicleType.all.first(where: { description == rawDescription })
            else { return nil }
        self = type
    }
}
6
carbonr

ええ、これは迷惑な問題です。私は現在、ファクトリとして機能するグローバルスコープ関数を使用してそれを回避しています、つまり.

func enumFromString(string:String) -> MyEnum? {
    switch string {
    case "One" : MyEnum(rawValue:1)
    case "Two" : MyEnum(rawValue:2)
    case "Three" : MyEnum(rawValue:3)
    default : return nil
    }
}
1
Ash

これは、Xcode 9.2のSwift 4と EnumSequence で機能します。

enum Word: Int, EnumSequenceElement, CustomStringConvertible {
    case Apple, cat, fun

    var description: String {
        switch self {
        case .Apple:
            return "Apple"
        case .cat:
            return "Cat"
        case .fun:
            return "Fun"
        }
    }
}

let Words: [String: Word] = [
    "A": .Apple,
    "C": .cat,
    "F": .fun
]

extension Word {
    var letter: String? {
        return Words.first(where: { (_, Word) -> Bool in
            Word == self
        })?.key
    }

    init?(_ letter: String) {
        if let Word = Words[letter] {
            self = Word
        } else {
            return nil
        }
    }
}

for Word in EnumSequence<Word>() {
    if let letter = Word.letter, let lhs = Word(letter), let rhs = Word(letter), lhs == rhs {
        print("\(letter) for \(Word)")
    }
}

出力

A for Apple
C for Cat
F for Fun
0
mclam