developer tip

사용자 지정 이니셜 라이저가있는 Swift 열거 형에서 rawValue 이니셜 라이저가 손실 됨

copycodes 2020. 9. 8. 08:09
반응형

사용자 지정 이니셜 라이저가있는 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원시 값 이니셜 라이저 로 초기화하려고 할 때 다음 오류를 표시합니다 .

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

노트

  1. 스위프트 언어 가이드 :

    원시 값 유형으로 열거 형을 정의하면 열거 형은 원시 값 유형의 값 (이라는 매개 변수)을 사용하는 이니셜 라이저를 자동으로 수신 rawValue하고 열거 형 멤버 또는 nil.

  2. 에 대한 사용자 정의 이니셜 MyEnum라이저는 언어 가이드 의 다음 사례로 인해 열거 형의 원시 값 이니셜 라이저가 제거되었는지 여부를 테스트하기 위해 확장에 정의되었습니다 . 그러나 동일한 오류 결과를 얻습니다.

    값 유형에 대해 사용자 정의 이니셜 라이저를 정의하면 해당 유형에 대한 기본 이니셜 라이저 (또는 구조 인 경우 멤버 단위 이니셜 라이저)에 더 이상 액세스 할 수 없습니다. [...]
    기본 이니셜 라이저와 멤버 별 이니셜 라이저 및 사용자 지정 이니셜 라이저를 사용하여 사용자 지정 값 형식을 초기화하려면 값 형식의 원래 구현의 일부가 아닌 확장에 사용자 지정 이니셜 라이저를 작성합니다.

  3. Moving the enum definition to MyClass.swift resolves the error for bar but not for foo.

  4. Removing the custom initializer resolves both errors.

  5. One workaround is to include the following function in the enum definition and use it in place of the provided raw-value initializer. So it seems as if adding a custom initializer has a similar effect to marking the raw-value initializer private.

    init?(raw: Int) {
        self.init(rawValue: raw)
    }
    
  6. Explicitly declaring protocol conformance to RawRepresentable in MyClass.swift resolves the inline error for bar, but results in a linker error about duplicate symbols (because raw-value type enums implicitly conform to RawRepresentable).

    extension MyEnum: RawRepresentable {}
    

Can anyone provide a little more insight into what's going on here? Why isn't the raw-value initializer accessible?


This bug is solved in Xcode 7 and Swift 2


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

In your case this would result in the following extension:

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

You can even make the code simpler and useful without switch cases, this way you don't need to add more cases when you add a new type.

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

Yeah this is an annoying issue. I'm currently working around it using a global-scope function that acts as a factory, i.e.

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

This works for Swift 4 on Xcode 9.2 together with my 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)")
    }
}

Output

A for Apple
C for Cat
F for Fun

Add this to your code:

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

참고URL : https://stackoverflow.com/questions/27390989/swift-enum-with-custom-initializer-loses-rawvalue-initializer

반응형