Как добавить extension для безопасного доступа к элементам массива в Swift
Что проверяют
Умеете ли вы писать расширения с учётом протоколов стандартной библиотеки и понимаете ли, почему выход за границы массива в Swift приводит к падению, а не к nil. Следом часто просят обобщить решение на любую коллекцию или спрашивают, почему такого сабскрипта нет в языке.
Как отвечать
Идиоматичный вариант это не метод, а сабскрипт с внешней меткой, объявленный сразу на Collection:
extension Collection {
subscript(safe index: Index) -> Element? {
indices.contains(index) ? self[index] : nil
}
}Вызов выглядит как items[safe: 5] и возвращает опционал, дальше обычные if let или guard let.
- почему
Collection, а неArray: так расширение работает и дляArraySlice,String,Data. У среза индексы наследуются от исходного массива: уarray[2...]первый индекс равен 2, а не 0; - сложность: у
Arrayсвойствоindicesимеет типRange<Int>, иcontainsработает за O(1). У произвольной коллекцииindices.containsможет пройти её целиком, тогда дешевле сравнить индекс соstartIndexиendIndex; - почему этого нет в stdlib: выход за границы считается ошибкой программиста, и Swift падает сознательно, чтобы баг не прятался. Идею безопасного сабскрипта не раз предлагали на форумах Swift, в язык она не вошла;
- где уместно: индекс пришёл извне, например из ответа сервера или из
IndexPath, пока таблица обновляется асинхронно.
Где спотыкаются
Проверяют index >= 0 && index < count. На массиве это работает, а на срезе индекс 0 проходит проверку и роняет приложение, а последний валидный индекс отбрасывается.
Объявляют subscript(index: Int) -> Element? без внешней метки. У сабскриптов имя параметра по умолчанию не становится меткой, поэтому вызов array[0] конфликтует со стандартным сабскриптом: компилятор ругается на неоднозначность или выбирает не ту перегрузку.
Используют safe везде вместо нормальной логики. Если индекс вне границ означает баг, лучше упасть в отладке, чем молча показать пустую ячейку и потом неделю искать причину.