カスタム初期化子を持つSwift列挙型がrawValue初期化子を失う


95

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

セットアップ

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

エラー

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

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

ノート

  1. パースウィフト言語ガイド

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

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

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

  3. enum定義を移動してMyClass.swift、のエラーを解決しますがbar、解決しませんfoo

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

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

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

    extension MyEnum: RawRepresentable {}

誰かがここで何が起こっているのかについてもう少し洞察を提供できますか?raw値のイニシャライザにアクセスできないのはなぜですか?


これについてはバグを報告する必要があります。デフォルトの初期化子には、internalスコープではなく(または少なくとも型と一致する)スコープが必要ですprivate
ネイトクック

私はまったく同じ問題を抱えています。カスタムイニシャライザを作成すると、デフォルトのイニシャライザはなくなります
Yariv Nissim

私にとってバグのようなにおいがします。
akashivskyy 14

2
私の疑惑を検証してくれてありがとう。これはバグとして報告されています。
Nickgraef、2015年

5番は私のためにそれをしました。
Andrew Duncan

回答:


25

このバグはXcode 7とSwift 2で解決されています


24
この種の回答は、関連するチケットへのリンクから利益を得るので、将来の訪問者は問題の状態を確認できます。
Raphael

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

7

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

1

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

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

0

これは私のEnumSequenceとともにXcode 9.2のSwift 4で動作します

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

-1

これをコードに追加します。

extension MyEnum {
    init?(rawValue: Int) {
        switch rawValue {
        case 0: self = .Zero
        case 1: self = .One
        case 2: self = .Two
        default: return nil
        }
    }
}

代わりにIntを拡張できますか?簡単そうです。
ericgu 2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.