وقتی میخواهید دادهای را مدلسازی کنید که فقط میتواند مجموعهی مشخصی از حالتهای ممکن را بپذیرد، مثل روزهای هفته، جهتهای قطبنما یا دکمههای یک دستهبازی، انواع پایهای مثل Int یا String اشکالات بزرگی دارند.
با عددهای صحیح یا رشتهها، کد شما باید مدام مقادیر نامعتبر را بررسی کند (مثلاً کد وضعیتی برابر با 999 یا غلط تایپی مثل "leftt")، و خوانندگان انسانی هم بهسختی میتوانند بفهمند این اعداد چه چیزی را نشان میدهند.
Swift این مشکل را با شمارشی حل میکند (که اغلب به enum کوتاه میشود).
شمارشی یک نوع مشترک برای گروهی از مقادیر نامدار و مرتبط تعریف میکند. چون کامپایلر از پیش همهی حالتهای ممکن را میداند، میتواند کد شما را در زمان کامپایل از نظر درستی و کامل بودن بررسی کند.
شمارشیها میتوانند متد، سازنده و ویژگیهای محاسبهشده هم داشته باشند.
برای تعریف یک شمارشی از کلیدواژهی enum و بعد از آن اسم نوع استفاده کنید و حالتهایش را داخل آکولاد فهرست کنید.
طبق قرارداد Swift، اسم نوع با UpperCamelCase نوشته میشود و هر حالت با lowerCamelCase:
enum NESButton {
case up
case down
case left
case right
case a
case b
case select
case start
}
میتوانید چندین حالت را هم در یک خط و جداشده با کاما تعریف کنید:
enum NESButton {
case up, down, left, right, a, b, select, start
}
برای اشاره به یک حالت شمارشی، اسم نوع شمارشی را بنویسید و بعد از آن یک نقطه (.) و سپس اسم حالت را بیاورید. وقتی نوع معلوم باشد یا کامپایلر بتواند آن را استنتاج کند، میتوانید اسم نوع را حذف کنید و از روش نوشتن کوتاهتر با نقطه استفاده کنید:
var lastPressed = NESButton.up
// Swift infers the type, so you can omit the type name:
lastPressed = .down
let konamiCode: [NESButton] = [.up, .up, .down, .down, .left, .right, .left, .right, .b, .a]
شمارشیها میتوانند درست مثل کلاسها و ساختارها متد و سازندهی سفارشی تعریف کنند. داخل یک متد شمارشی، self به مقدار حالت جاری اشاره میکند:
enum Coin {
case heads
case tails
init(_ value: Int) {
if value.isMultiple(of: 2) {
self = .heads
} else {
self = .tails
}
}
func flip() -> Coin {
switch self {
case .heads: return .tails
case .tails: return .heads
}
}
}
let tails = Coin(13) // .tails
let heads = Coin(0) // .heads
let flipped = heads.flip() // .tails
حالتهای یک شمارشی میتوانند با مقادیر پیشفرضی به نام مقادیر خام از پیش پر شوند.
همهی مقادیر خام در یک شمارشی باید از یک نوع باشند (مثل Int، String یا Character):
enum NESButton: Character {
case up = "⬆️"
case down = "⬇️"
case left = "⬅️"
case right = "➡️"
case a = "🅰️"
case b = "🅱️"
case select = "✅"
case start = "🚦"
}
print(NESButton.left.rawValue) // Prints "⬅️"
میتوانید یک شمارشی را با init(rawValue:) از یک مقدار خام بسازید. چون ممکن است مقدار خامی نامعتبر داده شود، این سازنده یک مقدار اختیاری برمیگرداند:
let upButton = NESButton(rawValue: "⬆️") // Optional(NESButton.up)
let invalid = NESButton(rawValue: "🙄") // nil
Swift میتواند برای شمارشیهای String و Int بهطور خودکار مقادیر خام تعیین کند:
String، مقدار خام هر حالت بهطور پیشفرض اسم همان حالت است.Int، مقادیر خام از 0 شروع میشوند و تا وقتی خلاف آن مشخص نشده باشد، هر بار یک واحد زیاد میشوند.enum Planet: Int {
case mercury = 1, venus, earth, mars
}
print(Planet.earth.rawValue) // Prints 3
در این تمرین، خطهای لاگ را پردازش میکنید.
هر خط لاگ رشتهای است با قالب زیر: "[<LVL>]: <MESSAGE>".
اینها سطوح مختلف لاگ هستند:
TRC (trace)DBG (debug)INF (info)WRN (warning)ERR (error)FTL (fatal)سه وظیفه دارید و باید هر سه را انجام دهید تا قابلیت لازم فراهم شود.
enum به اسم LogLevel
یک enum به اسم LogLevel تعریف کنید که شش حالت متناظر با سطوح لاگ بالا دارد، به همراه یک حالت unknown برای سطحهایی که ناموجود یا غیراستاندارد هستند.
tracedebuginfowarningerrorfatalunknownسپس، سازندهی LogLevel را پیادهسازی کنید تا سطح لاگ یک خط لاگ را تجزیه کند:
LogLevel("[INF]: File deleted")
// Returns LogLevel.info
LogLevel("Something went wrong!")
// Returns LogLevel.unknown
سطح لاگ یک خط لاگ بسیار طولانی است.
برای کاهش فضای دیسک لازم برای ذخیرهی خطهای لاگ، یک قالب کوتاه طراحی شده است: "<ENCODED_LEVEL>:<MESSAGE>".
سطح لاگ رمزگذاریشده نگاشتی ساده از سطح لاگ به یک عدد است:
trace - 0
debug - 1
info - 4
warning - 5
error - 6
fatal - 7
unknown - 42
متد shortFormat(message: String) -> String را پیادهسازی کنید که میتواند قالب کوتاهشدهی خط لاگ را بهصورت یک String خروجی بدهد:
let overflow = LogLevel.error
overflow.shortFormat(message: "Stack overflow")
// Returns "6:Stack overflow"