[swift] 문자열 유형으로 열거 형을 열거하는 방법은 무엇입니까?

enum Suit: String {
    case spades = "♠"
    case hearts = "♥"
    case diamonds = "♦"
    case clubs = "♣"
}

예를 들어, 다음과 같은 방법으로 어떻게 할 수 있습니까?

for suit in Suit {
    // do something with suit
    print(suit.rawValue)
}

결과 예 :




답변

스위프트 4.2 이상

시작 스위프트 4.2 (엑스 코드 10), 단지에 프로토콜 적합성을 추가 CaseIterable혜택을 allCases. 이 프로토콜 적합성을 추가하려면 다음과 같이 작성하면됩니다.

extension Suit: CaseIterable {}

열거 형이 자신의 것이라면 선언에 직접 적합성을 지정할 수 있습니다.

enum Suit: String, CaseIterable { case spades = "♠"; case hearts = "♥"; case diamonds = "♦"; case clubs = "♣" }

그런 다음 다음 코드는 가능한 모든 값을 인쇄합니다.

Suit.allCases.forEach {
    print($0.rawValue)
}

이전 Swift 버전 (3.x 및 4.x)과의 호환성

Swift 3.x 또는 4.0을 지원해야하는 경우 다음 코드를 추가하여 Swift 4.2 구현을 모방 할 수 있습니다.

#if !swift(>=4.2)
public protocol CaseIterable {
    associatedtype AllCases: Collection where AllCases.Element == Self
    static var allCases: AllCases { get }
}
extension CaseIterable where Self: Hashable {
    static var allCases: [Self] {
        return [Self](AnySequence { () -> AnyIterator<Self> in
            var raw = 0
            var first: Self?
            return AnyIterator {
                let current = withUnsafeBytes(of: &raw) { $0.load(as: Self.self) }
                if raw == 0 {
                    first = current
                } else if current == first {
                    return nil
                }
                raw += 1
                return current
            }
        })
    }
}
#endif


답변

이 게시물은 여기 관련이 있습니다 https://www.swift-studies.com/blog/2014/6/10/enumerating-enums-in-swift

본질적으로 제안 된 솔루션은

enum ProductCategory : String {
     case Washers = "washers", Dryers = "dryers", Toasters = "toasters"

     static let allValues = [Washers, Dryers, Toasters]
}

for category in ProductCategory.allValues{
     //Do something
}


답변

iterateEnum()임의 enum유형의 경우를 반복 하는 유틸리티 기능 을 만들었습니다 .

사용법 예는 다음과 같습니다.

enum Suit: String {
    case Spades = "♠"
    case Hearts = "♥"
    case Diamonds = "♦"
    case Clubs = "♣"
}

for f in iterateEnum(Suit) {
    println(f.rawValue)
}

어떤 출력 :


그러나 이것은 디버그 또는 테스트 목적으로 사용됩니다. 이것은 문서화되지 않은 여러 Swift1.1 컴파일러 동작에 의존하므로 사용자가 위험을 감수해야합니다.

코드는 다음과 같습니다.

func iterateEnum<T: Hashable>(_: T.Type) -> GeneratorOf<T> {
    var cast: (Int -> T)!
    switch sizeof(T) {
        case 0: return GeneratorOf(GeneratorOfOne(unsafeBitCast((), T.self)))
        case 1: cast = { unsafeBitCast(UInt8(truncatingBitPattern: $0), T.self) }
        case 2: cast = { unsafeBitCast(UInt16(truncatingBitPattern: $0), T.self) }
        case 4: cast = { unsafeBitCast(UInt32(truncatingBitPattern: $0), T.self) }
        case 8: cast = { unsafeBitCast(UInt64($0), T.self) }
        default: fatalError("cannot be here")
    }

    var i = 0
    return GeneratorOf {
        let next = cast(i)
        return next.hashValue == i++ ? next : nil
    }
}

기본 아이디어는 다음과 같습니다.

  • 의 메모리 표현을 enum제외하고, enum관련 유형들, 사건의 개수가 일 때의 경우 단지 인덱스 2...256, 그것은 동일의 UInt8경우, 257...65536그것의, UInt16등등. 따라서 unsafeBitcast해당 부호없는 정수 유형이 될 수 있습니다 .
  • .hashValue enum 값은 케이스의 인덱스와 동일합니다.
  • .hashValue유효하지 않은 인덱스 에서 비트 캐스트 된 열거 형 값 은 0입니다.

Swift2 용으로 수정 되었고 @Kametrixom의 답변 에서 캐스팅 아이디어를 구현했습니다 .

func iterateEnum<T: Hashable>(_: T.Type) -> AnyGenerator<T> {
    var i = 0
    return anyGenerator {
        let next = withUnsafePointer(&i) { UnsafePointer<T>($0).memory }
        return next.hashValue == i++ ? next : nil
    }
}

Swift3 용으로 수정 :

func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
    var i = 0
    return AnyIterator {
        let next = withUnsafePointer(to: &i) {
            $0.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee }
        }
        if next.hashValue != i { return nil }
        i += 1
        return next
    }
}

Swift3.0.1 용으로 수정 :

func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
    var i = 0
    return AnyIterator {
        let next = withUnsafeBytes(of: &i) { $0.load(as: T.self) }
        if next.hashValue != i { return nil }
        i += 1
        return next
    }
}


답변

다른 솔루션은 작동 하지만 모두 가능한 순위 및 수 또는 첫 번째 및 마지막 순위가 무엇인지를 가정합니다. 사실, 카드 데크의 레이아웃은 아마도 가까운 미래에 크게 변하지 않을 것입니다. 그러나 일반적으로 가능한 적은 가정을하는 코드를 작성하는 것이 더 좋습니다. 내 해결책 :

Suit열거 형에 원시 유형을 추가 했으므로 사례 Suit(rawValue:)에 액세스하는 데 사용할 수 있습니다 Suit.

enum Suit: Int {
    case Spades = 1
    case Hearts, Diamonds, Clubs
    func simpleDescription() -> String {
        switch self {
            case .Spades:
                return "spades"
            case .Hearts:
                return "hearts"
            case .Diamonds:
                return "diamonds"
            case .Clubs:
                return "clubs"
        }
    }
    func color() -> String {
        switch self {
        case .Spades:
            return "black"
        case .Clubs:
            return "black"
        case .Diamonds:
            return "red"
        case .Hearts:
            return "red"
        }
    }
}

enum Rank: Int {
    case Ace = 1
    case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
    case Jack, Queen, King
    func simpleDescription() -> String {
        switch self {
            case .Ace:
                return "ace"
            case .Jack:
                return "jack"
            case .Queen:
                return "queen"
            case .King:
                return "king"
            default:
                return String(self.rawValue)
        }
    }
}

카드 createDeck()방법 의 구현 아래 . init(rawValue:)실패한 이니셜 라이저이며 선택적을 반환합니다. 두 while 문에서 값을 풀고 값을 확인하면 다음과 같은 수 Rank또는 Suit경우 를 가정 할 필요가 없습니다 .

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
    func createDeck() -> [Card] {
        var n = 1
        var deck = [Card]()
        while let rank = Rank(rawValue: n) {
            var m = 1
            while let suit = Suit(rawValue: m) {
                deck.append(Card(rank: rank, suit: suit))
                m += 1
            }
            n += 1
        }
        return deck
    }
}

createDeck메소드 를 호출하는 방법 은 다음과 같습니다 .

let card = Card(rank: Rank.Ace, suit: Suit.Clubs)
let deck = card.createDeck()


답변

나는 비트와 바이트를 우연히 발견하고 나중에 @rintaro 의 답변 과 매우 유사한 작동을 발견 한 확장을 만들었습니다 . 다음과 같이 사용됩니다.

enum E : EnumCollection {
    case A, B, C
}

Array(E.cases())    // [A, B, C]

주목할만한 것은 연관된 값이없는 모든 열거 형에서 사용할 수 있다는 것입니다. 케이스가없는 열거 형에는 작동하지 않습니다.

@rintaro 의 답변 과 마찬가지로이 코드는 열거 형의 기본 표현을 사용합니다. 이 표현은 문서화되지 않았으며 나중에 변경 될 수 있으며 이로 인해 문제가 발생할 수 있습니다. 프로덕션에서 이것을 사용하지 않는 것이 좋습니다.

코드 (Swift 2.2, Xcode 7.3.1, Xcode 10에서는 작동하지 않음) :

protocol EnumCollection : Hashable {}
extension EnumCollection {
    static func cases() -> AnySequence<Self> {
        typealias S = Self
        return AnySequence { () -> AnyGenerator<S> in
            var raw = 0
            return AnyGenerator {
                let current : Self = withUnsafePointer(&raw) { UnsafePointer($0).memory }
                guard current.hashValue == raw else { return nil }
                raw += 1
                return current
            }
        }
    }
}

코드 (Swift 3, Xcode 8.1, Xcode 10에서 작동하지 않음) :

protocol EnumCollection : Hashable {}
extension EnumCollection {
    static func cases() -> AnySequence<Self> {
        typealias S = Self
        return AnySequence { () -> AnyIterator<S> in
            var raw = 0
            return AnyIterator {
                let current : Self = withUnsafePointer(to: &raw) { $0.withMemoryRebound(to: S.self, capacity: 1) { $0.pointee } }
                guard current.hashValue == raw else { return nil }
                raw += 1
                return current
            }
        }
    }
}

왜 필요한지 typealias모르겠지만 컴파일러는 컴파일러없이 불평합니다.


답변

ForwardIndexType프로토콜 을 구현하여 열거 형을 반복 할 수 있습니다 .

ForwardIndexType프로토콜은 정의 할 필요 successor()요소를 단계별로 기능을.

enum Rank: Int, ForwardIndexType {
    case Ace = 1
    case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
    case Jack, Queen, King

    // ... other functions

    // Option 1 - Figure it out by hand
    func successor() -> Rank {
        switch self {
            case .Ace:
              return .Two
            case .Two:
              return .Three

            // ... etc.

            default:
              return .King
        }
    }

    // Option 2 - Define an operator!
    func successor() -> Rank {
        return self + 1
    }
}

// NOTE: The operator is defined OUTSIDE the class
func + (left: Rank, right: Int) -> Rank {
    // I'm using to/from raw here, but again, you can use a case statement
    // or whatever else you can think of

    return left == .King ? .King : Rank(rawValue: left.rawValue + right)!
}

개방 또는 폐쇄 범위 ( ..<또는 ...)를 반복 하면 내부적으로 successor()함수를 호출하여 다음과 같이 작성할 수 있습니다.

// Under the covers, successor(Rank.King) and successor(Rank.Ace) are called to establish limits
for r in Rank.Ace...Rank.King {
    // Do something useful
}


답변

이 문제는 훨씬 쉬워졌습니다. 다음은 Swift 4.2 솔루션입니다.

enum Suit: Int, CaseIterable {
  case None
  case Spade, Heart, Diamond, Club

  static let allNonNullCases = Suit.allCases[Spade.rawValue...]
}

enum Rank: Int, CaseIterable {
  case Joker
  case Two, Three, Four, Five, Six, Seven, Eight
  case Nine, Ten, Jack, Queen, King, Ace

  static let allNonNullCases = Rank.allCases[Two.rawValue...]
}

func makeDeck(withJoker: Bool = false) -> [Card] {
  var deck = [Card]()
  for suit in Suit.allNonNullCases {
    for rank in Rank.allNonNullCases {
      deck.append(Card(suit: suit, rank: rank))
    }
  }
  if withJoker {
    deck.append(Card(suit: .None, rank: .Joker))
  }
  return deck
}

4.2 이전

나는 ” Swift의 List comprehension “을 찾은 후이 솔루션을 좋아한다 .

문자열 대신 Int raws를 사용하지만 두 번 입력하지 않고 범위를 사용자 정의 할 수 있으며 원시 값을 하드 코딩하지 않습니다.

이것은 내 원래 솔루션의 Swift 4 버전이지만 위의 4.2 개선 사항을 참조하십시오.

enum Suit: Int {
  case None
  case Spade, Heart, Diamond, Club

  static let allRawValues = Suit.Spade.rawValue...Suit.Club.rawValue
  static let allCases = Array(allRawValues.map{ Suit(rawValue: $0)! })
}
enum Rank: Int {
  case Joker
  case Two, Three, Four, Five, Six
  case Seven, Eight, Nine, Ten
  case Jack, Queen, King, Ace

  static let allRawValues = Rank.Two.rawValue...Rank.Ace.rawValue
  static let allCases = Array(allRawValues.map{ Rank(rawValue: $0)! })
}
func makeDeck(withJoker: Bool = false) -> [Card] {
  var deck = [Card]()
  for suit in Suit.allCases {
    for rank in Rank.allCases {
      deck.append(Card(suit: suit, rank: rank))
    }
  }
  if withJoker {
    deck.append(Card(suit: .None, rank: .Joker))
  }
  return deck
}