Программирование SwiftSwift CoreМладший iOS-разработчик

Наблюдается следующий код. Какой результат напечатает программа и почему второй вызов не создаёт частично и...

Наблюдается следующий код. Какой результат напечатает программа и почему второй вызов не создаёт частично инициализированный экземпляр?

struct Port {
    let number: Int

    init?(number: Int) {
        guard (1...65535).contains(number) else { return nil }
        self.number = number
    }
}

let valid = Port(number: 443)
let invalid = Port(number: 0)
print(valid != nil, invalid == nil)
Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Программа напечатает true true. Инициализатор init? является условно успешным: при допустимом значении он возвращает экземпляр Port, а при недопустимом — nil вместо экземпляра.

При неудаче объект не считается созданным, поэтому Swift не оставляет частично инициализированное значение в переменной invalid.

Исторический контекст

Фейлящиеся инициализаторы появились как безопасный способ представить ситуацию, когда входные данные не позволяют создать корректный экземпляр. Такой подход особенно важен для типов, которые должны поддерживать свои инварианты с момента создания.

Вместо создания потенциально некорректного объекта и последующей проверки Swift связывает результат инициализации с Optional: отсутствие экземпляра выражается значением nil.

Постановка проблемы

Порт с номером 0 не должен существовать как корректный экземпляр Port. Если бы структура создавалась всегда, пришлось бы разрешить некорректное состояние или выбрасывать ошибку отдельным этапом после инициализации.

valid и invalid имеют тип Port?, потому что вызов init? может вернуть либо Port, либо nil. Обращаться к свойству number напрямую без распаковки optional нельзя.

Подробное решение

Вызов Port(number: 443) проходит проверку guard, присваивает значение хранимому свойству и возвращает экземпляр, упакованный в Optional.some. Вызов Port(number: 0) выполняет return nil, поэтому получает значение Optional.none.

Код демонстрирует механизм:

struct UserID { let value: Int init?(value: Int) { guard value > 0 else { return nil } self.value = value } } if let id = UserID(value: 42) { print(id.value) }

После успешной инициализации все хранимые свойства должны быть инициализированы. При фейле экземпляр не возвращается, поэтому частично инициализированное значение недоступно вызывающему коду.

Основное ограничение — init? требует обработки optional: через if let, guard let, оператор ?? или optional chaining. Если некорректный ввод является ожидаемой ситуацией, это обычно лучше, чем аварийное завершение; если же нужно передать подробную причину отказа, уместнее throwing-инициализатор.

Ситуация из практики

При разборе ответа сервера приложение создаёт модель по внешнему идентификатору. Вариант с обычным инициализатором потребовал бы либо допуска недействительного идентификатора, либо отдельной валидации после создания. Принудительный вызов через ! короче, но превращает ошибку данных в аварийное завершение.

Throwing-инициализатор лучше, если вызывающему коду нужна причина отказа: например, отдельные ошибки для пустого и слишком длинного идентификатора. Для простого правила «значение допустимо или нет» выбран init?: он сохраняет инвариант модели, не усложняет API и позволяет безопасно пропустить некорректную запись.

Что кандидаты часто упускают

  1. Можно ли выполнить return nil до инициализации всех свойств?

Да, в фейлящемся инициализаторе можно завершиться с nil до полной инициализации экземпляра. Экземпляр в этом случае не возвращается, поэтому требования к состоянию успешно созданного значения не нарушаются. При успешном завершении все хранимые свойства по-прежнему должны иметь значения.

  1. Может ли нефейлящийся инициализатор переопределить фейлящийся?

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

  1. Чем init! отличается от init??

init! также может завершиться неудачей, но представляет результат как implicitly unwrapped optional. В контексте, где ожидается optional, можно получить nil; при использовании результата как обычного значения неудача приводит к аварийному завершению. Поэтому init? явно показывает необходимость обработки ошибки, а init! следует применять только при обоснованной гарантии успеха.