관련 유형의 매거진을 가진 원시 값 초기화 구조기

6438 단어
저자: Benedikt Terhechte, 원문 링크, 원문 날짜: 2016/04/23 번역자: Lanford33;교정:saitjr;마무리: CMB
스위프트에서 매거(Enums)는 우아한 구조화된 정보의 방식이다.때때로 당신은 원시 값(raw values)을 통해 매거진을 구성해야 한다는 것을 발견할 수 있다. 왜냐하면 이 값들은 일부분에 저장될 수 있기 때문이다. 예를 들어 NSUserDefaults:
enum Device: String {
  case Phone, Tablet, Watch
}
let aDevice = Device(rawValue: "Phone")
print(aDevice)

//      : Optional(main.Device.Phone)

문제.
그러나 매거진에서 관련 값 (associated values) 을 사용하면 이 방식은 효력을 상실합니다.예를 들어 이런 매거:
enum Example {
   case Factory(workers: Int)
   case House(street: String)
}

이 매거진 두 멤버case인 팩토리와 하우스는 서로 다른 관련 유형(workers이 정형street이 문자열)이기 때문에 스와프는 Example의 실례를 구성할 수 없다.Example 각 구성원의 호출은 서로 다른 유형의 매개 변수를 필요로 하기 때문에 이런 방법은 통용할 수 없다.
그러나 모든 구성원의 연관 유형이 같아도 이런 방법은 소용없다.
enum Device {
    case Phone(name: String, screenSize: CGSize)
    case Watch(name: String, screenSize: CGSize)
    case Tablet(name: String, screenSize: CGSize)
}

이 예에서 모든 관련 유형(associated types)은 똑같다. 사실상 여러 가지 방식으로 문제를 설명할 수 있지만 Device 매거진으로 간단명료하게 설명할 수 있다는 것을 발견했다. 각 Device 구성원의 관련 유형이 똑같아도 원시 값 같은 것을 사용하여 그것을 만들고 정확한 유형을 얻을 수 없다는 것이다.정확한 실례를 만들기 위해 일치해야 합니다.
import Foundation

enum Device {
    case Phone(name: String, screenSize: CGSize)
    case Watch(name: String, screenSize: CGSize)
    case Tablet(name: String, screenSize: CGSize)

    static func fromDefaults(rawValue: String, name: String, screenSize: CGSize) -> Device? {
        switch rawValue {
        case "Phone": return Device.Phone(name: name, screenSize: screenSize)
        case "Watch": return Device.Watch(name: name, screenSize: screenSize)
        case "Tablet": return Device.Tablet(name: name, screenSize: screenSize)
        default: return nil
        }
    }
}
let b = Device.fromDefaults("Phone", name: "iPhone SE", screenSize: CGSize(width: 640, height: 1136))
print(b)

//      : Optional(main.Device.phone("iPhone SE", (640.0, 1136.0)))

이것은 보기에는 괜찮은 것 같지만, 이 코드들은 확실히 좀 불필요하다.일단 당신이 만들어야 할 매거 중 세 개 이상의 매거 구성원이나 두 가지 이상의 관련 유형이 있다면 일은 곧 통제력을 잃게 될 것이다.
enum Vehicle {
  case Car(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)
  case Ship(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)
  case Yacht(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)
  case Truck(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)
  case Motorbike(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)
  case Helicopter(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)
  case Train(wheels: Int, capacity: Int, weight: Int, length: Int, height: Int, width: Int, color: Int, name: Int, producer: Int, creation: NSDate, amountOfProducedUnits: Int)
  // ...
}

나는 네가 내가 표현하고 싶은 뜻을 이해하고 싶다.
해결 방법
그래서...우리는 이런 상황을 어떻게 처리해야 합니까?흥미로운 것은 관련 유형의 초기화 구조기(initializer)와 클로즈업 사이에 미스터리한 유사성이 있다는 점이다.다음 코드를 보십시오.
enum Example {
    case Test(x: Int)
}
let exampleClosure = Example.Test

코드 중 exampleClosure의 유형은 무엇입니까?답은: (Int) -> Example.네, 파라미터를 추가하지 않고 관련 형식을 가진 매거 구성원을 호출하면 패킷을 생성합니다. 정확한 형식의 파라미터를 사용하여 이 패킷을 호출하면 매거 구성원의 실례를 되돌려줍니다.
이것은 다음 코드가 올바르고 정상적으로 작동할 수 있음을 나타낸다.
enum Fruit {
    case Apple(amount: Int)
    case Orange(amount: Int)
}

let appleMaker = Fruit.Apple
let firstApple = appleMaker(amount: 10)
let secondApple = appleMaker(amount: 12)
print(firstApple, secondApple)

//      : Apple(10) Apple(12)

그래서 위의 엉망진창인 코드 중복 문제를 어떻게 간소화할 수 있을까?어디 보자.
import Foundation

enum Device {
    case Phone(name: String, screenSize: CGSize)
    case Watch(name: String, screenSize: CGSize)
    case Tablet(name: String, screenSize: CGSize)

    private static var initializers: [String: (name: String, screenSize: CGSize) -> Device] = {
        return ["Phone": Device.Phone, "Watch": Device.Watch, "Tablet": Device.Tablet]
    }()

    static func fromDefaults(rawValue: String, name: String, screenSize: CGSize) -> Device? {
        return Device.initializers[rawValue]?(name: name, screenSize: screenSize)
    }
}

let iPhone = Device.fromDefaults("Phone", name: "iPhone SE", screenSize: CGSize(width: 640, height: 1134))
print(iPhone)

//      :Optional(main.Device.Phone("iPhone SE", (640.0, 1134.0)))

이 코드가 무엇을 했는지 지적해 봅시다.우리는 Device에 새로운 속성initializers을 추가했다.이것은 유형이 [String: (name: String, screenSize: CGSize) -> Device]인 사전Dictionary이다.즉 String 유형의 키(key)에서 우리Device의 구성원 유형과 같은 클립을 비추는 것이다.간단하게 이전의 작은 기교를 이용하여 Phone: Device.Phone라는 방식으로 클립을 되돌려주면 이 사전은 우리 모든 매거 구성원의 초기화 구조기를 포함한다.
다음 fromDefaults 함수는 우리가 만들고 싶은 장치의 키 값만 알면 적합한 클립을 호출할 수 있습니다.이로 인해 코드가 많이 간소화되었다. 특히 더 큰 매거진 (위의 Vehicle의 예와 같다).보시다시피 현재 Device 실례를 만드는 것은 매우 간단합니다.
Device.initializers["Phone"]?(name: "iPhone 5", screenSize: CGSize(width: 640, height: 1134))

원래 값을 그대로 사용하는 것처럼 열거에 Phone이라는 구성원이 없으면 빈 값을 되돌려줍니다.
물론 이 해결 방안은 완벽하지 않다. 우리는 여전히 initializers라는 사전을 사용해야 한다. 그러나 모든 매거 구성원을 수동으로 일치시켜야 하는 것보다 이렇게 하면 대량의 중복 작업을 줄일 수 있다.
마지막으로, 내가 더 이상 말하지 않아도 위의 코드가 조금 소홀히 되어 있다. 합격의 가장 좋은 실천은 간결하고 사람들로 하여금 손에 쥐고 있는 일에 전념하게 하는 것이다.그러나 Device.initializers["phone"]처럼 문자열화된 방식은 가장 좋은 문법이 아니기 때문에 이 키들은 다른 곳에 합리적으로 정의되어야 한다.
만약 네가 여기까지 읽었다면, 나는 네가 트위터에 나를 팬으로 찍어야 한다고 생각한다. (@terhechte)
이 문서는 SwiftGG 번역팀에서 번역하였으며, 작성자 번역 승인을 받았습니다. 최신 기사는http://swift.gg.

좋은 웹페이지 즐겨찾기