관련 값으로 Swift 열거 형의 동등성을 테스트하는 방법


193

두 개의 Swift 열거 형 값의 동등성을 테스트하고 싶습니다. 예를 들면 다음과 같습니다.

enum SimpleToken {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
XCTAssert(t1 == t2)

그러나 컴파일러는 등식을 컴파일하지 않습니다.

error: could not find an overload for '==' that accepts the supplied arguments
    XCTAssert(t1 == t2)
    ^~~~~~~~~~~~~~~~~~~

동등 연산자의 자체 과부하를 정의해야합니까? Swift 컴파일러가 Scala 및 Ocaml처럼 자동으로 처리하기를 바랐습니다.



1
SE-0185 로 인한 Swift 4.1부터 Swift 는 관련 값이있는 열거 EquatableHashable열거를 지원 합니다.
jedwidz

답변:


245

스위프트 4.1 이상

마찬가지로 @jedwidz가 유용하게 스위프트 4.1 (인해, 지적 SE-0185 , 스위프트 또한 합성 지원 EquatableHashable연관된 값에 대한 열거.

따라서 Swift 4.1 이상을 사용하는 경우 다음은 XCTAssert(t1 == t2)작동 하는 필요한 방법을 자동으로 합성 합니다. 열쇠는 Equatable열거 형에 프로토콜 을 추가하는 것 입니다.

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)

스위프트 4.1 이전

다른 사람들이 지적했듯이 Swift는 필요한 항등 연산자를 자동으로 합성하지 않습니다. 그러나 더 깨끗한 (IMHO) 구현을 제안하겠습니다.

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}

public func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
    switch (lhs, rhs) {
    case let (.Name(a),   .Name(b)),
         let (.Number(a), .Number(b)):
      return a == b
    default:
      return false
    }
}

이상적인 것은 아니지만 반복이 많이 있지만 적어도 if 문으로 중첩 스위치를 수행 할 필요는 없습니다.


39
이것에 대해 짜증나는 것은 스위치에서 기본 명령문을 사용해야한다는 것입니다. 따라서 새로운 열거 형 케이스를 추가하는 경우 컴파일러는 새로운 케이스를 동등하게 비교하기 위해 절을 추가하지 않아도됩니다. 나중에 줄을 바꿀 때 기억하고주의해야합니다!
Michael Waterfall

20
당신은 @MichaelWaterfall 교체에 의해 언급 된 문제를 제거 할 수 default와 함께 case (.Name, _): return false; case(.Number, _): return false.
Kazmasaurus

25
더 나은 : case (.Name(let a), .Name(let b)) : return a == b
마틴 R

1
where 절을 사용하면 모든 경우에 기본값이 될 때까지 각 사례를 계속 테스트하지 false않습니까? 사소한 일이지만 특정 시스템에서는 이런 종류의 일이 더해질 수 있습니다.
Christopher Swasey 2016 년

1
이것이 작동 enum하고 ==기능은 전역 범위 (보기 컨트롤러의 범위 밖)에서 구현되어야합니다.
Andrej

77

구현 Equatable은 과도한 IMHO입니다. 많은 경우와 많은 다른 매개 변수가있는 복잡하고 큰 열거 형이 있다고 상상해보십시오. 이 매개 변수들도 모두 Equatable구현 해야합니다 . 또한 누가 열거 형 사례를 전혀 또는 전혀 비교하지 않았다고 누가 말했습니까? 값을 테스트하고 특정 열거 형 매개 변수를 하나만 스텁 한 경우는 어떻습니까? 나는 다음과 같은 간단한 접근법을 강력히 제안한다.

if case .NotRecognized = error {
    // Success
} else {
    XCTFail("wrong error")
}

... 또는 파라미터 평가의 경우 :

if case .Unauthorized401(_, let response, _) = networkError {
    XCTAssertEqual(response.statusCode, 401)
} else {
    XCTFail("Unauthorized401 was expected")
}

자세한 설명은 https://mdcdeveloper.wordpress.com/2016/12/16/unit-testing-swift-enums/에서 확인 하십시오.


테스트가 아닌 이것을 사용하려고 할 때 더 완벽한 예를 들어 주시겠습니까?
teradyl

질문이 무엇인지 잘 모르겠습니다. if caseguard case뿐만 아니라 단위 테스트에서,이 경우 열거의 평등을 테스트 할 때 어디서나 사용할 수 있습니다, 단순히 언어 구조이다.
mbpro

3
기술적 으로이 답변은 질문에 대한 답변이 아니지만 실제로 많은 사람들이 검색을 통해 여기에 도착한다고 생각합니다. 감사!
Nikolay Suvandzhiev

15

열거 형이나 구조체에 대해 컴파일러에서 항등 연산자를 생성하지 않은 것 같습니다.

"예를 들어 복잡한 데이터 모델을 표현하기 위해 고유 한 클래스 또는 구조를 생성하는 경우 해당 클래스 또는 구조에 대한"동일 "의 의미는 Swift가 사용자에게 추측 할 수있는 것이 아닙니다." [1]

평등 비교를 구현하려면 다음과 같이 작성하십시오.

@infix func ==(a:SimpleToken, b:SimpleToken) -> Bool {
    switch(a) {

    case let .Name(sa):
        switch(b) {
        case let .Name(sb): return sa == sb
        default: return false
        }

    case let .Number(na):
        switch(b) {
        case let .Number(nb): return na == nb
        default: return false
        }
    }
}

[1] https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AdvancedOperators.html#//apple_ref/doc/uid/TP40014097-CH27-XID_43의 "등가 연산자"를 참조하십시오.


14

다른 옵션이 있습니다. 구문을 사용하여 중첩 된 switch 문을 피한다는 점을 제외하면 주로 다른 것과 동일 if case합니다. 나는 이것이 약간 더 읽기 쉽고 (/ 견딜 수 있음) 기본 사례를 피하는 이점을 가지고 있다고 생각합니다.

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}
extension SimpleToken {
    func isEqual(st: SimpleToken)->Bool {
        switch self {
        case .Name(let v1): 
            if case .Name(let v2) = st where v1 == v2 { return true }
        case .Number(let i1): 
            if case .Number(let i2) = st where i1 == i2 { return true }
        }
        return false
    }
}

func ==(lhs: SimpleToken, rhs: SimpleToken)->Bool {
    return lhs.isEqual(rhs)
}

let t1 = SimpleToken.Number(1)
let t2 = SimpleToken.Number(2)
let t3 = SimpleToken.Name("a")
let t4 = SimpleToken.Name("b")

t1 == t1  // true
t1 == t2  // false
t3 == t3  // true
t3 == t4  // false
t1 == t3  // false

14
enum MyEnum {
    case None
    case Simple(text: String)
    case Advanced(x: Int, y: Int)
}

func ==(lhs: MyEnum, rhs: MyEnum) -> Bool {
    switch (lhs, rhs) {
    case (.None, .None):
        return true
    case let (.Simple(v0), .Simple(v1)):
        return v0 == v1
    case let (.Advanced(x0, y0), .Advanced(x1, y1)):
        return x0 == x1 && y0 == y1
    default:
        return false
    }
}

이것은 case (.Simple(let v0), .Simple(let v1)) 또한 연산자가 static열거 형 안에 있을 수있는 것과 같이 쓸 수 있습니다 . 여기 내 대답을 참조하십시오.
LShi

11

단위 테스트 코드 에서이 간단한 해결 방법을 사용하고 있습니다.

extension SimpleToken: Equatable {}
func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
    return String(stringInterpolationSegment: lhs) == String(stringInterpolationSegment: rhs)
}

문자열 보간을 사용하여 비교를 수행합니다. 프로덕션 코드에는 권장하지 않지만 간결하고 단위 테스트 작업을 수행합니다.


2
나는 단위 테스트를 위해 이것이 괜찮은 해결책이라는 데 동의합니다.
Daniel Wood

init (stringInterpolationSegment :)의 Apple 문서는 "이 이니셜 라이저를 직접 호출하지 마십시오. 문자열 보간을 해석 할 때 컴파일러에서 사용합니다"라고 말합니다. 그냥 사용하십시오 "\(lhs)" == "\(rhs)".
skagedal

String(describing:...)또는 이와 동등한 것을 사용할 수도 있습니다 "\(...)". 그러나 관련 값이 다른 경우에는 작동하지 않습니다. (
Martin

10

다른 옵션은 케이스의 문자열 표현을 비교하는 것입니다.

XCTAssert(String(t1) == String(t2))

예를 들면 다음과 같습니다.

let t1 = SimpleToken.Number(123) // the string representation is "Number(123)"
let t2 = SimpleToken.Number(123)
let t3 = SimpleToken.Name("bob") // the string representation is "Name(\"bob\")"

String(t1) == String(t2) //true
String(t1) == String(t3) //false

3

if caseSwift 3에서 작동하는 쉼표와 함께 사용 하는 또 다른 접근법 :

enum {
  case kindOne(String)
  case kindTwo(NSManagedObjectID)
  case kindThree(Int)

  static func ==(lhs: MyEnumType, rhs: MyEnumType) -> Bool {
    if case .kindOne(let l) = lhs,
        case .kindOne(let r) = rhs {
        return l == r
    }
    if case .kindTwo(let l) = lhs,
        case .kindTwo(let r) = rhs {
        return l == r
    }
    if case .kindThree(let l) = lhs,
        case .kindThree(let r) = rhs {
        return l == r
    }
    return false
  }
}

이것이 내가 프로젝트에 쓴 방법입니다. 하지만 어디서 아이디어를 얻었는지 기억이 나지 않습니다. (지금 막 봤지만 그러한 사용법을 보지 못했습니다.) 의견이 있으면 감사하겠습니다.


2

t1과 t2는 숫자가 아니며 값이 연결된 SimpleToken의 인스턴스입니다.

당신은 말할 수 있습니다

var t1 = SimpleToken.Number(123)

그런 다음 말할 수 있습니다

t1 = SimpleToken.Name(Smith) 

컴파일러 오류없이.

t1에서 값을 검색하려면 switch 문을 사용하십시오.

switch t1 {
    case let .Number(numValue):
        println("Number: \(numValue)")
    case let .Name(strValue):
        println("Name: \(strValue)")
}

2

허용 된 답변과 비교할 때 '장점'은 'main'switch 문에 'default'사례가 없으므로 다른 경우와 함께 열거 형을 확장하면 컴파일러가 나머지 코드를 업데이트하도록 강제합니다.

enum SimpleToken: Equatable {
    case Name(String)
    case Number(Int)
}
extension SimpleToken {
    func isEqual(st: SimpleToken)->Bool {
        switch self {
        case .Name(let v1):
            switch st {
            case .Name(let v2): return v1 == v2
            default: return false
            }
        case .Number(let i1):
            switch st {
            case .Number(let i2): return i1 == i2
            default: return false
            }
        }
    }
}


func ==(lhs: SimpleToken, rhs: SimpleToken)->Bool {
    return lhs.isEqual(rhs)
}

let t1 = SimpleToken.Number(1)
let t2 = SimpleToken.Number(2)
let t3 = SimpleToken.Name("a")
let t4 = SimpleToken.Name("b")

t1 == t1  // true
t1 == t2  // false
t3 == t3  // true
t3 == t4  // false
t1 == t3  // false

2

mbpro의 답변을 확장하여 다음과 같은 접근 방식을 사용하여 일부 경우와 관련된 값으로 신속한 열거 형이 동일한 지 확인했습니다.

물론 switch 문을 수행 할 수도 있지만 한 줄에서 하나의 값만 확인하는 것이 좋습니다. 당신은 그렇게 할 수 있습니다 :

// NOTE: there's only 1 equal (`=`) sign! Not the 2 (`==`) that you're used to for the equality operator
// 2nd NOTE: Your variable must come 2nd in the clause

if case .yourEnumCase(associatedValueIfNeeded) = yourEnumVariable {
  // success
}

동일한 if 절에서 두 조건을 비교하려면 &&연산자 대신 쉼표를 사용해야합니다 .

if someOtherCondition, case .yourEnumCase = yourEnumVariable {
  // success
}

2

Swift 4.1에서 Equatable프로토콜을 열거 형에 추가 하고 XCTAssert또는 XCTAssertEqual:

enum SimpleToken : Equatable {
    case Name(String)
    case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
XCTAssertEqual(t1, t2) // OK

-1

스위치를 사용하여 비교할 수 있습니다

enum SimpleToken {
    case Name(String)
    case Number(Int)
}

let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)

switch(t1) {

case let .Number(a):
    switch(t2) {
        case let . Number(b):
            if a == b
            {
                println("Equal")
        }
        default:
            println("Not equal")
    }
default:
    println("No Match")
}

두 개의 인수를 가진 스위치를위한 완벽한 장소. 위의 경우 한 줄에 한 줄의 코드 만 사용하는 방법을 참조하십시오. 그리고 코드가 동일하지 않은 두 숫자로 실패합니다.
gnasher729
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.