iOS. 99.99% Crash-free users: Опционалы в Swift

Краши одна из главных причин удаления вашего приложения

Сегодня поговорим о том как уменьшить количество крашей в приложении. Но для начала давайте разберемся действительно ли это заслуживающая внимания проблема? Некоторые разработчики считают что краши это нормально и они помогают быстрее отловить проблему на этапе разработки да и код с форскастами и неявным извлечение опционалов получается кратче и читабельнее. Итак, согласно исследованию компании uSamp для 71% пользователей краши могу стать причиной удаления приложения. А согласно опросу CleverTap более 2000 пользователей, краши так же являются одной из причин по которой удаляют приложение и одной из главных причин в зоне ответственности разработчика на ряду с багами.

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

Используем опционалы без форскастов

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

Для начала рассмотрим явное принудительное извлечение.

let a: Int? a = 42 let b: Int? let result = a! + b! // краш, b == nil

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

Теперь рассмотрим неявное принудительное извлечение.

let a: Int! a = 42 let b: Int! let result = a + b // краш, b == nil

Если мы уверены что опционалу будет присвоено значение перед его использованием мы может поставить восклицательный знак (!) в конце типа при его объявлении. Это позволит автоматически принудительно извлекать его при каждом обращении. Но это, опять же, как раз то от чего нам стоит отказаться если мы хотим избавиться от крашей. Причина точно такая же как с явным извлечением, вы или другой разработчик можете внести изменения в код после которых появится ситуация когда значение опционалу так и не будет установлено до обращения к нему.

Теперь давайте рассмотрим альтернативы принудительному извлечению которые помогут нам исключить возможность краша.

У нас есть 4 варианта:

  • привязку опционалов (if let, if var)
  • ранний выход (guard let, guard var)
  • значение по умолчанию (optional ? defaultValue)
  • опциональная последовательность (optional?. member)

Рассмотрим все варианты подробнее.

Начнем с привязки опционалов (if let, if var). При помощи if мы можем попытаться извлечь значение опционала и сделать его доступным в ограниченной области видимости. Таким же образом можно извлекать значение с while.

let a: Int? a = 42 let b: Int? if let a = a { print("a has a value. a ==", a) if let b = b { print("b has a value. b ==", b) let result = a + b print("result ==", result) } } // выведет "a has a value. a == 42". краша не будет

Второй вариант — ранний выход (guard let, guard var). Похож на вариант с if только извлеченное значение становится доступно после блока guard, а внутри него мы должны вызвать return, continue или break чтобы прервать выполнение блока.

let a: Int? a = 42 let b: Int? guard let valueOfA = a else { print("a == nil") return } guard let valueOfB = b else { print("b == nil") return } let result = valueOfA + valueOfB print("result ==", result) // выведет "b == nil". краша не будет

Третий вариант безопасного извлечение — использовать значение по умолчанию (optional ? defaultValue). При обращении к опционалу мы указываем после двух знаков вопроса (?) значение которое будет использовано если опционал равен nil.

let a: Int? a = 42 let b: Int? let result = (a ?? 0) + (b ?? 0) // краш, b == nil print(result) // выведет "42". // так как b == nil будет использовано значение по умолчанию "0". // краша не будет

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

struct Container { let value: Int } let c: Container? c = Container(value: 42) guard let value = c?.value else { print("c == nil or c.value == nil") return } print("c != nil and c.value != nil") if let value = c?.value { print("c != nil and c.value != nil") } print("c == nil or c.value == nil") print("c.value ==", c?.value ?? 0) // выведет // "c != nil and c.value != nil" // "c != nil and c.value != nil" // "c.value == 42"

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

Так же предлагаю пройти опрос.

Пользуетесь ли вы форскастами на коммерческих проектах?
Никогда - безопасность превыше всего
Иногда - когда лень писать громоздкие извлечения
Постоянно - никогда не забываю устанавливать значения! (или почти никогда)
3
Начать дискуссию