0

Why Swift Enums with Associated Values Cannot Have a Raw Value

Một enumeration — short,một enum - là một tập hợp các giá trị riêng biệt mà bằng cách nào đó thuộc về nhau, ví dụ một danh sách các sân bay:

enum Airport {
    case munich
    case sanFrancisco
    case singapore
}

Theo truyền thống, mỗi trường hợp enum chỉ là nhãn cho giá trị Integer. Các nhãn này chỉ là cần thiết để làm cho mã có thể đọc được cho con người trong khi máy tính nội bộ làm việc với những Số nguyên.

enum Airport: Int {
    case munich = 0
    case sanFrancisco = 1
    case singapore = 2
}

Swift enums vẫn có chức năng cốt lõi nhưng bạn có thể làm được nhiều hơn với nó.

Raw Values

Trước hết, bạn không bị ràng buộc phải sử dụng Số nguyên cho giá trị được đại diện bởi một trường hợp cụ thể. Bạn có thể sử dụng Strings, Charecters hoặc thậm chí Floats thay thế. Nếu bạn muốn sử dụng mã IATA ba chữ như là giá trị hỗ trợ của các trường hợp enum bạn có thể làm điều đó:

enum Airport: String {
    case munich = "MUC"
    case sanFrancisco = "SFO"
    case singapore = "SIN"
}

Bất kể loại bạn chọn, giá trị bạn chỉ định cho một trường hợp được gọi là rawValue.

Associated Values

Bây giờ bạn có một danh sách các sân bay được dán nhãn liên tục với tên của thành phố nơi chúng được đặt. Nhưng sau đó bạn nhận ra rằng có một số thành phố có nhiều sân bay: ví dụ như London. Trong thời gian trước đây, bạn phải đổi tên tất cả các trường hợp enum để đạt được tên phù hợp, ví dụ:


enum Airport: String {
    case munichFranzJosephStrauss = "MUC"
    case sanFranciscoInternational = "SFO"
    case singaporeChangi = "SIN"
    case londonStansted = "STN"
    case londonHeathrow = "LHR"
    case londonGatwick = "LGW"
}

Nhưng có lẽ chúng ta đồng ý rằng điều này là xấu xí và dễ đọc hơn so với enum được đặt tên thành phố đơn giản mà chúng ta có trước đây. Lý do là chúng tôi đang thực sự trộn lẫn hai loại thông tin khác nhau: Thành phố nơi có sân bay và sân bay riêng của mình. Nói theo phương pháp toán học, đây là hai chiều mà chúng ta đang cố gắng chỉ chích vào một enum duy nhất chỉ có một chiều. (Rốt cuộc, đó chỉ là một danh sách.)

May mắn thay, Swift đưa ra cách giải quyết vấn đề này: Nó cho phép bạn ràng buộc một (hoặc nhiều) các giá trị bổ sung vào một trường hợp enum. Các giá trị này được gọi là các giá trị liên kết. Ví dụ: chúng tôi có thể sử dụng String để xác định sân bay cụ thể và vì nó chỉ cần cho thành phố London trong danh sách hiện tại của chúng tôi, chúng tôi chỉ thêm giá trị liên quan này vào trường hợp London:

enum Airport {
    case munich
    case sanFrancisco
    case singapore
    case london(airportName: String)

Bây giờ chúng tôi trở lại với enum nhãn sạch của chúng tôi và chỉ sử dụng một giá trị bổ sung để xác định các sân bay cụ thể khi cần thiết.

Như một String không phải là rất an toàn cho loại và dễ bị lỗi, chúng ta có thể đi thêm một bước nữa và thay thế bằng một enum khác:

enum LondonAirportName {
    case stansted
    case heathrow
    case gatwick
}

Bây giờ enum Airport của chúng tôi sẽ như sau:


enum Airport {
    case munich
    case sanFrancisco
    case singapore
    case london(airportName: LondonAirportName)
}

Khi chúng ta xác định một biến kiểu enum này, bây giờ chúng ta có thể chỉ định một trường hợp enum "thường xuyên" cho biến này, cũng như một trường hợp enum có giá trị liên quan.

var airport: Airport

airport = .munich
airport = .london(airportName: .heathrow)

Lưu ý: Cũng có thể bỏ qua nhãn cho giá trị được liên kết. Ví dụ, chúng ta có thể đã xác định trường hợp london trong enum như trường hợp london (LondonAirportName) và sau đó sử dụng nó như sau:

var airport: Airport

airport = .london(.heathrow)

The Problem with Associated Values

Bây giờ bạn có thể đã nhận ra rằng chúng tôi âm thầm bỏ qua các giá trị nguyên và chú thích loại cho enum ngay khi chúng tôi giới thiệu một giá trị liên quan cho một trường hợp enum. Chúng ta phải làm điều này vì Swift không cho phép chúng ta có cả hai: các giá trị thô và các giá trị liên quan trong cùng một enum. Một enum Swift có thể có giá trị thô hoặc các giá trị liên quan. Tại sao vậy? Đó là vì định nghĩa của giá trị nguyên: Giá trị nguyên là một cái gì đó nhận dạng duy nhất một giá trị của một loại cụ thể. "Duy nhất" có nghĩa là bạn không mất bất kỳ thông tin nào bằng cách sử dụng giá trị nguyên thay vì giá trị ban đầu. Nó được chính thức hoá trong Swift với giao thức RawRepresentable mà tài liệu của nó nêu rõ:

Với kiểu RawRepresentable, bạn có thể chuyển qua lại giữa một kiểu tùy chỉnh và một kiểu RawValue có liên quan mà không làm mất giá trị của kiểu RawRepresentable ban đầu.

Về mặt toán học, lập bản đồ từ một loại đến giá trị nguyên của nó phải là đường injective. Phải luôn luôn có thể tái tạo lại giá trị ban đầu chỉ từ một giá trị nguyên nhất định.

Thật không may, ngay khi chúng ta thêm các giá trị liên quan đến các trường hợp enum của chúng tôi, chúng tôi làm cho nó không thể cho enum được injective vào đối với giá trị nguyên của nó.

Nó có được cách quá lý thuyết, phải không? Chúng ta hãy nhìn vào một ví dụ:

Enums Without Associated Values

Enum ban đầu của chúng tôi không có bất kỳ giá trị liên quan, chỉ có giá trị nguyên:

enum Airport: String {
    case munich = "MUC"
    case sanFrancisco = "SFO"
    case singapore = "SIN"
}

Vì vậy, khi chúng ta đặt một biến cho một trong những trường hợp này và có được giá trị nguyên của nó:

let airport = .sanFrancisco

let airportRawValue = airport.rawValue // "SFO"

Chúng ta có thể dễ dàng tái thiết lại sân bay ban đầu với giá trị nguyên:

let reconstructedAirport = Airport(rawValue: airportRawValue)! // .sanFrancisco

Điều này sẽ làm việc với bất kỳ của enum trường hợp, không có hạn chế.

Enums With Associated Values

Bây giờ chúng ta cùng cố gắng làm điều tương tự với enum của chúng ta với giá trị liên quan và giả vờ rằng nó cũng có một giá trị thô. Thay vì mã IATA, chúng tôi chỉ sử dụng một "city identifier" gồm ba chữ cái (giống như mã IATA trong ba trường hợp đầu, nhưng khác với trường hợp của London):


enum Airport: String {
    case munich = "MUC"
    case sanFrancisco = "SFO"
    case singapore = "SIN"
    case london(airportName: LondonAirportName) = "LON"
}

Chúng ta vẫn có thể đặt một biến cho một trong những trường hợp này và thậm chí có được giá trị nguyên:

let airport = .london(airportName: .stansted)

let airportRawValue = airport.rawValue // “LON”

Tuy nhiên, chúng tôi không thể xây dựng lại sân bay ban đầu từ giá trị thô đó bởi vì không có cách nào để cho biết giá trị liên quan nào được lựa chọn:

let reconstructedAirport = Airport(rawValue: airportRawValue)! // .london(💥❓)

Thông tin đó bị mất. Và đó chính là lý do tại sao nó là một trong hai.

How to Get Your Raw Values Back

Khi bạn đang suy nghĩ về việc lấy các giá trị thô để làm việc với các giá trị kết hợp có giá trị liên kết, điều bạn thực sự muốn thực sự thường là một cái gì đó khác: Giá trị nhận dạng duy nhất một nhãn trường hợp giá trị enum mà không làm phiền về giá trị liên quan của nó. Nó tương tự như giá trị nguyên, nhưng nó không giống nhau.

Thực hiện giá trị này thực sự đơn giản: Bạn chỉ cần thêm một tài sản tính cho enum của bạn và trả lại một giá trị khác nhau cho từng trường hợp.

enum Airport {
    case munich
    case sanFrancisco
    case singapore
    case london(airportName: LondonAirportName)
    
    var cityIdentifier: String {
        switch self {
        case .munich:
            return "MUC"
        case .sanFrancisco:
            return "SFO"
        case .singapore:
            return "SIN"
        case .london:
            return "LON"
        }
    }
}

Tất nhiên, bạn cũng có thể di chuyển khai báo biến này thành một phần mở rộng trên sân bay để tách nó ra khỏi các định nghĩa của trường hợp. Bây giờ nếu bạn xác định một biến như thế này:

var airport: Airport = .london(airportName: .heathrow)

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí