Menu
Coddy logo textTech

Инициализаторы с возможностью сбоя

Часть раздела Объектно-ориентированное программирование путешествия по Swift на Coddy. Урок 20 из 57.

Некоторые значения нельзя создать из любых входных данных. Инициализатор, допускающий сбой, записываемый как init?, возвращает nil вместо экземпляра, если входные данные недействительны, поэтому недействительный экземпляр никогда не может существовать:

struct Percentage {
    let value: Int

    init?(_ value: Int) {
        guard value >= 0, value <= 100 else { return nil }
        self.value = value
    }
}

print(Percentage(42)?.value ?? -1)
print(Percentage(150)?.value ?? -1)

Вывод:

42
-1

Результат инициализатора, который может завершиться неудачей, является опциональным значением, поэтому вызывающий код должен распаковать его, обычно с помощью if let или guard let:

struct Email {
    let address: String

    init?(_ text: String) {
        let parts = text.split(separator: "@")
        guard parts.count == 2, parts[1].contains(".") else { return nil }
        address = text.lowercased()
    }
}

for text in ["Ada@Mail.com", "not-an-email"] {
    if let email = Email(text) {
        print("valid: \(email.address)")
    } else {
        print("invalid: \(text)")
    }
}

Результат:

valid: ada@mail.com
invalid: not-an-email

Собственные типы Swift используют ту же идею: Int("42") и Double("x") — это инициализаторы, которые могут завершиться неудачей, как и init(rawValue:) перечисления. Инициализатор, который может завершиться неудачей, может вызвать их и передать ошибку дальше:

struct Coordinate {
    let lat: Double
    let lon: Double

    init?(text: String) {
        let parts = text.split(separator: ",")
        guard parts.count == 2,
              let lat = Double(parts[0]), let lon = Double(parts[1]),
              abs(lat) <= 90, abs(lon) <= 180 else { return nil }
        self.lat = lat
        self.lon = lon
    }
}

print(Coordinate(text: "51.5,-0.1") != nil)
print(Coordinate(text: "95,10") != nil)

Вывод:

true
false

Используйте init?, когда отсутствующее значение — это достаточная информация. Используйте инициализатор, выдающий ошибку, init(...) throws, когда вызывающему коду нужно узнать, какое правило не выполнено:

struct Username {
    let text: String
    init(_ text: String) throws {
        guard text.count >= 3 else { throw NameError.tooShort }
        guard text.allSatisfy({ $0.isLetter }) else { throw NameError.badCharacters }
        self.text = text
    }
}
challenge icon

Задание

Легко

Дополните структуру Rgb, которая хранит цвет как значения red, green и blue от 0 до 255. Её инициализатор, допускающий сбой, init?(hex:) считывает текст вроде #FF8000: он возвращает nil, если текст не состоит из #, за которым следуют ровно шесть шестнадцатеричных цифр. Используйте Int(text, radix: 16) для каждой пары. Добавьте вычисляемое свойство label, которое возвращает rgb(255, 128, 0).

Предоставленный код считывает по одному цвету в каждой строке и выводит метку или invalid: text.

Ваш код должен находиться в Rgb.swift. В main.swift содержится предоставленный код ввода/вывода, и его нельзя редактировать.

Попробуйте сами

// Предоставленный код ввода/вывода: оставьте его как есть
var input: [String] = []
while let line = readLine() { input.append(line) }
for line in input {
    if let color = Rgb(hex: line) {
        print(color.label)
    } else {
        print("invalid: \(line)")
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Песочница Swift