Утверждение: заявляйте факт или убеждение уверенно и убедительно.

Вы когда-нибудь писали код и наполняли его print("Something happened here.") ерундой? Вы знаете, что теоретически он никогда не должен называться, но если это произойдет, вы бы хотели знать? Теперь в вашем коде разбросаны случайные строки, которые не приносят реальной пользы вашему приложению. А что насчет того, когда - шокирующе - это действительно происходит, но ваш отладчик настолько забит спамом, что вы его пропустите.

Моя первая попытка использовать утверждения в моем коде была в начале этого года, когда я проверял несколько тестовых примеров на работе. В то время я был новичком в тестировании и начал находить все эти assert(value != nil, "Error: ...") лакомые кусочки повсюду. Я быстро обнаружил, что утверждения - чрезвычайно полезный инструмент для тестирования и отладки ваших проектов.

Но что они делают? Утверждения требуют, чтобы данное условие было истинным, иначе оно немедленно приведет к сбою приложения. Но разве это не плохо? В производстве да! Обычно вы хотите предотвратить сбои ради пользователя, но иногда приложение находится в серьезной ситуации, когда было бы слишком опасно продолжать. Если вы отлаживаете приложение, сбой может быть очень полезным, так как он может привести вас непосредственно к проблеме (-ам).

Когда я беру данные из файла JSON в Интернете, я не всегда получаю то, что ожидаю. Это могло произойти из-за такой простой опечатки, как попытка доступа к data["results"] вместо data["result"]. Например, если я запрашиваю API социальных сетей для щенков, я могу ожидать, что совпадение будет соответствовать конкретному щенку.

Но что, если учетная запись этого щенка будет удалена из-за слишком милой внешности? Это реальная ситуация, и люди, похоже, не воспринимают ее всерьез. В любом случае, если мой код рассчитывает, что некий щенок по имени «Белль» существует в базе данных, а она этого не делает, то мое приложение может реагировать странным образом.

let myPuppyInfo: [String: Any] = ...
guard let puppyData = myPuppyInfo["puppies"] as? [String: Any],
    let Alice = puppyData["Alice"] as? [String: Any],
    let Belle = puppyData["Belle"] as? [String: Any] else {
        assertionFailure("There was an issue parsing the JSON")
        return Result.failure(.unableToParse)
}

В приведенном выше примере я ожидаю получить данные для двух собак: Алисы и Белль. Только аккаунт Белль по какой-то причине не отображается в JSON? С быстрым утверждением в логике JSON я могу убедиться, что это привлечет мое внимание до публичного выпуска, что дает мне время убедиться, что мой код правильно учитывает проблему.

Но почему я включил возврат после утверждения, если программа все равно выйдет из строя? Хотя при отладке функция return никогда не будет вызываться, утверждения не включаются в производственный код!

Если я отлаживаю это в Xcode, тогда будет вызвано assert и программа завершится, но если этот код находится в App Store, assert будет пропущен, и приложение сможет ответить соответствующим образом.

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

Утверждения бывают трех основных видов:

  1. Утверждения
  2. Предварительные условия
  3. Фатальные ошибки

Каждый из них используется для остановки выполнения вашей программы в отлаживаемом состоянии, как если бы вы установили точку останова в начале строки 256. Если сообщение было включено в утверждение, оно будет передано отладчику. прямо перед остановкой.

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

Примечание перед тем, как мы продолжим: Xcode скомпилирует вашу сборку с одним из трех флагов оптимизации:

  1. - Onone: компилировать без оптимизаций.
  2. - O: компилировать с оптимизацией.
  3. - Не проверено: компилировать с оптимизацией и удалять проверки безопасности во время выполнения, используемые для непроверенной версии.

-Onone используется, когда вы пишете и тестируете свой код. -O, или релиз, используется именно для этого, релизная версия для App Store. -Ounchecked - это версия выпуска, в которой отсутствуют проверки безопасности во время выполнения, которые используются в Swift. Я не рекомендую использовать непроверенную сборку, если вы действительно не знаете, что делаете, поскольку это может привести к непредвиденным и опасным результатам, когда ваше приложение обычно просто аварийно завершает работу.

Вернемся к утверждениям!

Утверждение будет скомпилировано только в конфигурации отладки Xcode (-Onone), в то время как предварительное условие будет скомпилировано как в условиях отладки (-Onone), так и в условиях Release (-O). Так что это значит для вас?

Утверждения будут запускаться только во время написания, тестирования и отладки кода! Предварительные условия не так разборчивы и будут работать там, как и в финальной версии, достойной App Store.

Итак, когда и как мы их используем?

Утверждает

Утверждения следует использовать, когда вы хотите проверить свой код, но проблема в нем не обязательно нарушит работу приложения. Например, если вы используете необязательный инициализатор и ставите задачу просто вернуть nil. Как правило, возврат nil является подходящим способом реагирования на ошибку, но технически это все же временное средство для решения реальной проблемы. По словам Apple:

Используйте эту функцию для внутренних проверок работоспособности, которые активны во время тестирования, но не влияют на производительность кода доставки. - Документация Apple

Отличное применение утверждений, которые я нашел, - это вставлять их, когда я обычно печатаю ошибку, которой не ожидаю. Сторожевые операторы - одни из моих любимых мест в коде, где можно воспользоваться утверждениями.

Swift имеет две основные функции утверждения: assert(_ condition: Bool, _ message: String) и assertionFailure(_ message: String). Это упрощенная версия реальных функций, но для нашего использования просто знайте, что message - это необязательный параметр, который по умолчанию равен пустой строке.

Чтобы использовать assert(...), вам нужно будет включить условие для проверки, например true, 5 < 10 или var == "String".

Их фактическое использование может выглядеть так:

let pupName: String? = "Alice"
// Continue only if pupName != nil
assert(pupName != nil, "pupName variable is nil")
assert(!pupName.isEmpty)
// ** Success: Continuing execution...
// Continue only if pupName == "Belle"
if (pupName != "Belle") {
    assertionFailure("I thought this was Belle?")
}
// ** Failure: End Execution (But only if we are compiled for Debugging)

Интересный факт: компилятор Swift полностью игнорирует утверждения, когда вы компилируете для релизной версии, точно так же, как он игнорирует комментарии.

Предварительные условия

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

Используйте эту функцию для обнаружения условий, которые должны препятствовать продолжению выполнения программы, даже в коде доставки. - Документация Apple

Предусловия могут использоваться одним из двух способов, и их использование почти идентично утверждениям.

let pupName: String? = "Belle"
// Continue only if pupName != nil
precondition(pupName != nil, "pupName variable is nil")
precondition(!pupName.isEmpty)
// ** Success: Continuing execution...
// Continue only if pupName == "Belle"
if (pupName != "Belle") {
    preconditionFailure("I thought this was Belle?")
}
// ** Failure: End Execution (If we are compiled for Debugging or Release)

Фатальные ошибки

Последний вариант утверждений, о которых мы упоминали выше, - это fatalError. Неустранимые ошибки - это, по сути, preconditionFailures, за исключением одного отличия. Когда проект компилируется для выпуска без проверки (-Ounchecked), как мы обсуждали ранее, предполагается, что ни assertionFailure, ни preconditionFailure никогда не будут вызваны.

Использовать фатальную ошибку довольно просто. Как и preconditionFailure, единственный полезный параметр для добавления - это сообщение, но это не обязательно. Значение по умолчанию для сообщения, отправляемого на консоль, будет пустой строкой.

Некоторые разработчики могут запретить пользователям с взломанным телефоном использовать их приложения, чтобы предотвратить читерство в играх или предотвратить другие проблемы с безопасностью. Хотя в наши дни джейлбрейк встречается гораздо реже, есть еще более старые версии iOS, которые могут быть уязвимыми. Если вы можете обнаружить, что устройство пользователя было взломано, то возможное решение для вашего приложения может быть следующим:

let isJailbroken: Bool = ...
if (isJailbroken) {
    fatalError()
}

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

Программа также может отправить фатальную ошибку, когда ее просят разделить на ноль.

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

Утверждения могут быть чрезвычайно полезны в Swift при правильном использовании. Какими способами вы нашли использование утверждений в своих проектах? Вы можете связаться со мной в комментариях ниже или в Twitter по адресу @ _ alecoconnor.