Безопасный (проверенный границами) поиск в массиве в Swift через необязательные привязки?

271

Если у меня есть массив в Swift, и я пытаюсь получить доступ к индексу, выходящему за пределы, возникает неудивительная ошибка времени выполнения:

var str = ["Apple", "Banana", "Coconut"]

str[0] // "Apple"
str[3] // EXC_BAD_INSTRUCTION

Тем не менее, я бы подумал, что со всеми дополнительными цепочками и безопасностью, которые приносит Swift, было бы тривиально сделать что-то вроде:

let theIndex = 3
if let nonexistent = str[theIndex] { // Bounds check + Lookup
    print(nonexistent)
    ...do other things with nonexistent...
}

Вместо того:

let theIndex = 3
if (theIndex < str.count) {         // Bounds check
    let nonexistent = str[theIndex] // Lookup
    print(nonexistent)   
    ...do other things with nonexistent... 
}

Но это не так - я должен использовать ifвыражение ol ', чтобы проверить и убедиться, что индекс меньше str.count.

Я попытался добавить свою собственную subscript()реализацию, но я не уверен, как передать вызов исходной реализации или получить доступ к элементам (на основе индекса) без использования индексной записи:

extension Array {
    subscript(var index: Int) -> AnyObject? {
        if index >= self.count {
            NSLog("Womp!")
            return nil
        }
        return ... // What?
    }
}
Крейг Отис
источник
2
Я понимаю, что это немного OT, но я также чувствую, что было бы неплохо, если бы у Swift был четкий синтаксис для выполнения каких-либо проверок границ, включая списки. У нас уже есть подходящее ключевое слово для этого, например. Так, например, если X в (1,2,7) ... или если X в myArray
Мори Марковиц

Ответы:

652

У ответа Алекса есть хороший совет и решение для вопроса, однако я случайно наткнулся на более хороший способ реализации этой функциональности:

Swift 3.2 и новее

extension Collection {

    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

Swift 3.0 и 3.1

extension Collection where Indices.Iterator.Element == Index {

    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Generator.Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

Благодарим Хэмиша за то, что он предложил решение для Swift 3 .

Swift 2

extension CollectionType {

    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Generator.Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

пример

let array = [1, 2, 3]

for index in -20...20 {
    if let item = array[safe: index] {
        print(item)
    }
}
Никита Кукушкин
источник
45
Я думаю, что это определенно заслуживает внимания - хорошая работа. Мне нравится имя включенного safe:параметра, чтобы гарантировать разницу.
Крейг Отис
11
Начиная с Swift 2 (Xcode 7) это требует небольшой настройки:return self.indices ~= index ? self[index] : nil;
Тим
7
Относительно версии Swift 3: возможно, только в угловом случае, но подсказка, тем не менее: бывают случаи, когда «безопасная» версия нижнего индекса выше не безопасна (тогда как версия Swift 2 была): для Collectionтипов, где Indicesесть не смежный Например Set, если мы обращаемся к элементу set с помощью index ( SetIndex<Element>), мы можем столкнуться с исключениями времени выполнения для индексов, которые, >= startIndexи < endIndexв этом случае безопасный индекс не работает (см., Например, этот надуманный пример ).
dfri
12
ПРЕДУПРЕЖДЕНИЕ! Такая проверка массивов может быть очень дорогой. containsМетод итерации по всем показателям , таким образом , делая это O (N). Лучше использовать индекс и счетчик для проверки границ.
Стефан Васильевич
6
Чтобы предотвратить генерацию индексов и итерацию по ним (O (n)), лучше использовать сравнения (O (1)): return index >= startIndex && index < endIndex ? self[index] : nil Collectionтипы имеют startIndex, endIndexкоторые есть Comparable. Конечно, это не будет работать для некоторых странных коллекций, которые, например, не имеют индексов посередине, решение с indicesболее общим.
Зубко
57

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

Я думаю, причина, по которой это не работает, заключается в выборе дизайна, сделанном разработчиками Swift. Возьмите свой пример:

var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0] )"

Если вы уже знаете, что индекс существует, как и в большинстве случаев, когда вы используете массив, этот код отлично подходит. Однако, если доступ к подстрочному мог вернуться , nilто вы изменили тип возвращаемого из Array«S subscriptспособа быть необязательным. Это изменит ваш код на:

var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0]! )"
//                                     ^ Added

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

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

Вместо этого вы можете создать подкласс Arrayи переопределить, subscriptчтобы вернуть необязательный. Или, на практике, вы можете расширить Arrayс помощью не подстрочный метод, который делает это.

extension Array {

    // Safely lookup an index that might be out of bounds,
    // returning nil if it does not exist
    func get(index: Int) -> T? {
        if 0 <= index && index < count {
            return self[index]
        } else {
            return nil
        }
    }
}

var fruits: [String] = ["Apple", "Banana", "Coconut"]
if let fruit = fruits.get(1) {
    print("I ate a \( fruit )")
    // I ate a Banana
}

if let fruit = fruits.get(3) {
    print("I ate a \( fruit )")
    // never runs, get returned nil
}

Swift 3 Обновление

func get(index: Int) ->T? должен быть заменен func get(index: Int) ->Element?

Алекс Уэйн
источник
2
+1 (и согласие) за упоминание проблемы с изменением типа возвращаемого значения subscript()на необязательный - это было основным препятствием на пути переопределения поведения по умолчанию. (Я вообще не мог заставить его работать . ) Я избегал написания get()метода расширения, который является очевидным выбором в других сценариях (категории Obj-C, кто-нибудь?), Но get(не намного больше [, и делает его Понятно, что поведение может отличаться от того, что другие разработчики могут ожидать от оператора индекса Swift. Спасибо!
Крейг Отис
3
Чтобы сделать его еще короче, я использую в ();) Спасибо!
Hyouuu
7
По состоянию на Swift 2.0 Tбыл переименован в Element. Просто дружеское напоминание :)
Стас Жуковский
3
Чтобы добавить к этому обсуждению, еще одна причина, по которой проверка границ не включается в Swift для возврата необязательного параметра, заключается в том, что возврат nilвместо вызова исключения из индекса за пределами границ будет неоднозначным. Так как eg Array<String?>может также вернуть nil в качестве действительного члена коллекции, вы не сможете провести различие между этими двумя случаями. Если у вас есть свой собственный тип коллекции, который, как вы знаете, никогда не сможет вернуть nilзначение, то есть это контекстно для приложения, тогда вы можете расширить Swift для безопасной проверки границ, как ответили в этом посте.
Аарон
Работает прекрасно
kamyFC
20

Чтобы опираться на ответ Никиты Кукушкина, иногда нужно безопасно присваивать индексам массивов, а также читать из них, т.е.

myArray[safe: badIndex] = newValue

Итак, вот обновление ответа Никиты (Swift 3.2), которое также позволяет безопасно записывать в индексы изменяемого массива, добавляя параметр safe: name.

extension Collection {
    /// Returns the element at the specified index iff it is within bounds, otherwise nil.
    subscript(safe index: Index) -> Element? {
        return indices.contains(index) ? self[ index] : nil
    }
}

extension MutableCollection {
    subscript(safe index: Index) -> Element? {
        get {
            return indices.contains(index) ? self[ index] : nil
        }

        set(newValue) {
            if let newValue = newValue, indices.contains(index) {
                self[ index] = newValue
            }
        }
    }
}
SafeFastExpressive
источник
2
Чрезвычайно недооцененный ответ! Это правильный способ сделать это!
Рейд
14

Действительно в Swift 2

Несмотря на то, что на это уже много раз отвечали, я хотел бы представить более последовательный ответ о том, куда движется мода программирования Swift, который, по словам Красти, таков: « protocolСначала подумай »

• Что мы хотим сделать?
- Получить элемент Arrayданного индекса только тогда, когда это безопасно, и в nilпротивном случае
• На чем должна основываться эта функциональность, на которой она реализуется?
- Array subscripting
• Откуда он получает эту функцию?
- Его определение struct Arrayв Swiftмодуле имеет это
• Ничего более общего / абстрактного?
- Он принимает, protocol CollectionTypeчто обеспечивает это также
• Ничего более общего / абстрактного?
- Он также принимает protocol Indexable...
• Да, звучит как лучшее, что мы можем сделать. Можем ли мы затем расширить его, чтобы получить эту функцию, которую мы хотим?
- Но у нас очень ограниченные типы (нет Int) и свойства (нетcount) работать с сейчас!
• этого будет достаточно. Stdlib Свифта сделан довольно хорошо;)

extension Indexable {
    public subscript(safe safeIndex: Index) -> _Element? {
        return safeIndex.distanceTo(endIndex) > 0 ? self[safeIndex] : nil
    }
}

¹: не правда, но это дает идею

DeFrenZ
источник
2
Как новичок Свифта, я не понимаю этот ответ. Что представляет собой код в конце? Это решение, и если да, то как мне его использовать?
Томас Темпельманн
3
Извините, этот ответ больше не действителен для Swift 3, но процесс, безусловно, так и есть. Разница лишь в том, что теперь вам стоит остановиться на Collection:)
DeFrenZ
11
extension Array {
    subscript (safe index: Index) -> Element? {
        return 0 <= index && index < count ? self[index] : nil
    }
}
  • O (1) производительность
  • типа сейф
  • правильно работает с опциями для [MyType?] (возвращает MyType ??, который можно развернуть на обоих уровнях)
  • не приводит к проблемам для наборов
  • краткий код

Вот несколько тестов, которые я провел для вас:

let itms: [Int?] = [0, nil]
let a = itms[safe: 0] // 0 : Int??
a ?? 5 // 0 : Int?
let b = itms[safe: 1] // nil : Int??
b ?? 5 // nil : Int?
let c = itms[safe: 2] // nil : Int??
c ?? 5 // 5 : Int?
thetrutz
источник
10
  • Поскольку в массивах могут храниться нулевые значения, возвращать нулевое значение не имеет смысла, если вызов массива [index] выходит за пределы.
  • Поскольку мы не знаем, как пользователь хотел бы справиться с задачами вне границ, нет смысла использовать пользовательские операторы.
  • Напротив, используйте традиционный поток управления для развертывания объектов и обеспечения безопасности типов.

если пусть index = array.checkIndexForSafety (index: Int)

  let item = array[safeIndex: index] 

если пусть index = array.checkIndexForSafety (index: Int)

  array[safeIndex: safeIndex] = myObject
extension Array {

    @warn_unused_result public func checkIndexForSafety(index: Int) -> SafeIndex? {

        if indices.contains(index) {

            // wrap index number in object, so can ensure type safety
            return SafeIndex(indexNumber: index)

        } else {
            return nil
        }
    }

    subscript(index:SafeIndex) -> Element {

        get {
            return self[index.indexNumber]
        }

        set {
            self[index.indexNumber] = newValue
        }
    }

    // second version of same subscript, but with different method signature, allowing user to highlight using safe index
    subscript(safeIndex index:SafeIndex) -> Element {

        get {
            return self[index.indexNumber]
        }

        set {
            self[index.indexNumber] = newValue
        }
    }

}

public class SafeIndex {

    var indexNumber:Int

    init(indexNumber:Int){
        self.indexNumber = indexNumber
    }
}
MS Cline
источник
1
Интересный подход. Любая причина SafeIndex- это класс, а не структура?
stef
8

Swift 4

Расширение для тех, кто предпочитает более традиционный синтаксис:

extension Array {

    func item(at index: Int) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}
Matjan
источник
вам не нужно ограничивать элементы массива равными, чтобы проверить, содержит ли индекс свой индекс.
Лев Дабус
да - хорошая мысль - это понадобится только для дополнительных безопасных методов, таких как deleteObject и т. д.
Matjan
5

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

/**
 Safe array get, set, insert and delete.
 All action that would cause an error are ignored.
 */
extension Array {

    /**
     Removes element at index.
     Action that would cause an error are ignored.
     */
    mutating func remove(safeAt index: Index) {
        guard index >= 0 && index < count else {
            print("Index out of bounds while deleting item at index \(index) in \(self). This action is ignored.")
            return
        }

        remove(at: index)
    }

    /**
     Inserts element at index.
     Action that would cause an error are ignored.
     */
    mutating func insert(_ element: Element, safeAt index: Index) {
        guard index >= 0 && index <= count else {
            print("Index out of bounds while inserting item at index \(index) in \(self). This action is ignored")
            return
        }

        insert(element, at: index)
    }

    /**
     Safe get set subscript.
     Action that would cause an error are ignored.
     */
    subscript (safe index: Index) -> Element? {
        get {
            return indices.contains(index) ? self[index] : nil
        }
        set {
            remove(safeAt: index)

            if let element = newValue {
                insert(element, safeAt: index)
            }
        }
    }
}

тесты

import XCTest

class SafeArrayTest: XCTestCase {
    func testRemove_Successful() {
        var array = [1, 2, 3]

        array.remove(safeAt: 1)

        XCTAssert(array == [1, 3])
    }

    func testRemove_Failure() {
        var array = [1, 2, 3]

        array.remove(safeAt: 3)

        XCTAssert(array == [1, 2, 3])
    }

    func testInsert_Successful() {
        var array = [1, 2, 3]

        array.insert(4, safeAt: 1)

        XCTAssert(array == [1, 4, 2, 3])
    }

    func testInsert_Successful_AtEnd() {
        var array = [1, 2, 3]

        array.insert(4, safeAt: 3)

        XCTAssert(array == [1, 2, 3, 4])
    }

    func testInsert_Failure() {
        var array = [1, 2, 3]

        array.insert(4, safeAt: 5)

        XCTAssert(array == [1, 2, 3])
    }

    func testGet_Successful() {
        var array = [1, 2, 3]

        let element = array[safe: 1]

        XCTAssert(element == 2)
    }

    func testGet_Failure() {
        var array = [1, 2, 3]

        let element = array[safe: 4]

        XCTAssert(element == nil)
    }

    func testSet_Successful() {
        var array = [1, 2, 3]

        array[safe: 1] = 4

        XCTAssert(array == [1, 4, 3])
    }

    func testSet_Successful_AtEnd() {
        var array = [1, 2, 3]

        array[safe: 3] = 4

        XCTAssert(array == [1, 2, 3, 4])
    }

    func testSet_Failure() {
        var array = [1, 2, 3]

        array[safe: 4] = 4

        XCTAssert(array == [1, 2, 3])
    }
}
Иван Реп
источник
3
extension Array {
  subscript (safe index: UInt) -> Element? {
    return Int(index) < count ? self[Int(index)] : nil
  }
}

Используя вышеуказанное расширение, верните nil, если индекс в любое время выходит за пределы.

let fruits = ["apple","banana"]
print("result-\(fruits[safe : 2])")

результат - ноль

Винаяк Пал
источник
3

Я понимаю, что это старый вопрос. Я использую Swift5.1 на данный момент, ОП был для Swift 1 или 2?

Мне нужно было что-то подобное сегодня, но я не хотел добавлять полномасштабное расширение только для одного места и хотел что-то более функциональное (более поточно-ориентированное?). Мне также не нужно было защищать от отрицательных индексов, только те, которые могут быть после конца массива:

let fruit = ["Apple", "Banana", "Coconut"]

let a = fruit.dropFirst(2).first // -> "Coconut"
let b = fruit.dropFirst(0).first // -> "Apple"
let c = fruit.dropFirst(10).first // -> nil

Для тех, кто спорит о последовательностях с nil, что вы делаете со свойствами firstand, lastкоторые возвращают nil для пустых коллекций?

Мне понравилось это, потому что я мог просто взять существующий материал и использовать его, чтобы получить желаемый результат. Я также знаю, что dropFirst (n) - это не целая копия коллекции, а всего лишь фрагмент. И тогда уже существующее поведение сначала вступает во владение для меня.

Трэвис Григгс
источник
1

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

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

Вы можете сделать переопределение таким же образом, как вы использовали, и просто написать подписку по-своему. Единственным недостатком является то, что существующий код не будет совместимым. Я думаю, что найти крючок для переопределения универсального x [i] (также без текстового препроцессора, как в C) будет непросто.

Самое близкое, о чем я могу думать, это

// compile error:
if theIndex < str.count && let existing = str[theIndex]

РЕДАКТИРОВАТЬ : Это на самом деле работает. Один лайнер!!

func ifInBounds(array: [AnyObject], idx: Int) -> AnyObject? {
    return idx < array.count ? array[idx] : nil
}

if let x: AnyObject = ifInBounds(swiftarray, 3) {
    println(x)
}
else {
    println("Out of bounds")
}
Mundi
источник
6
Я бы не согласился - смысл необязательного связывания заключается в том, что оно успешно выполняется только при выполнении условия. (Для необязательного, это означает, что есть значение.) Использование if letв этом случае не делает программу более сложной, и ошибки не становятся более неразрешимыми. Он просто объединяет традиционную ifпроверку границ двух операторов и фактический поиск в сжатый оператор в одну строку. Есть случаи (особенно работающие в пользовательском интерфейсе) , где он является нормальным показателем , чтобы быть вне границ, как спрашивать NSTableViewдля selectedRowбез выбора.
Крейг Отис
3
@ Манди, похоже, это комментарий, а не ответ на вопрос ОП.
jlehr
1
@CraigOtis Не уверен, что я согласен. Вы можете записать эту проверку лаконично в «однострочном сокращенном выражении», например, используя countElementsили как OP count, но не так, как язык определяет запись индексов массива.
Мунди
1
@jlehr Возможно нет. Справедливо ставить под сомнение намерение или мудрость поставленной проблемы.
Мунди
2
@Mundi Хех, особенно если вы позже отредактируете его, чтобы действительно ответить на вопрос. :-)
'17
1

Я добавил массив с nils в моем случае использования:

let components = [1, 2]
var nilComponents = components.map { $0 as Int? }
nilComponents += [nil, nil, nil]

switch (nilComponents[0], nilComponents[1], nilComponents[2]) {
case (_, _, .Some(5)):
    // process last component with 5
default:
    break
}

Также проверьте расширение safe:нижнего индекса с меткой Эрики Садун / Майка Эша: http://ericasadun.com/2015/06/01/swift-safe-array-indexing-my-favorite-thing-of-the-new-week/

Мариан Черны
источник
0

«Обычно отклоненные изменения» для списка Swift содержат упоминание об изменении доступа к индексам массива для возврата необязательного, а не сбойного:

Сделать Array<T>индекс возврат доступа T?или T!вместо T: Текущее поведение массива намеренное , так как он точно отражает тот факт , что вне границ доступа к массиву является логической ошибкой. Изменение текущего поведения замедлит Arrayдоступ к недопустимой степени. Эта тема поднималась несколько раз, но вряд ли будет принята.

https://github.com/apple/swift-evolution/blob/master/commonly_proposed.md#strings-characters-and-collection-types

Таким образом, базовый индексный доступ не изменится и вернет необязательный.

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

Это было предложено и обсуждено на форуме Swift Evolution здесь:

https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871

В частности, Крис Латтнер дал идею «+1»:

Согласен, наиболее часто предлагаемые варианты написания для этого:, yourArray[safe: idx]что мне кажется великолепным. Я очень +1 за добавление этого.

https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871/13

Так что это может быть возможно из коробки в какой-то будущей версии Swift. Я бы посоветовал всем, кто хочет, чтобы они внесли свой вклад в эту тему Swift Evolution

pkamb
источник
0

Чтобы объяснить, почему операции терпят неудачу, ошибки лучше, чем необязательные. Подписчики не могут выдавать ошибки, поэтому это должен быть метод.

public extension Collection {
  /// - Returns: same as subscript, if index is in bounds
  /// - Throws: CollectionIndexingError
  func element(at index: Index) throws -> Element {
    guard indices.contains(index)
    else { throw CollectionIndexingError() }

    return self[index]
  }
}

/// Thrown when `element(at:)` is called with an invalid index.
public struct CollectionIndexingError: Error { }
XCTAssertThrowsError( try ["🐾", "🥝"].element(at: 2) )

let optionals = [1, 2, nil]
XCTAssertEqual(try optionals.element(at: 0), 1)

XCTAssertThrowsError( try optionals.element(at: optionals.endIndex) )
{ XCTAssert($0 is CollectionIndexingError) }
Джесси
источник
0

Не уверен, почему никто не выставил расширение, которое также имеет установщик для автоматического увеличения массива

extension Array where Element: ExpressibleByNilLiteral {
    public subscript(safe index: Int) -> Element? {
        get {
            guard index >= 0, index < endIndex else {
                return nil
            }

            return self[index]
        }

        set(newValue) {
            if index >= endIndex {
                self.append(contentsOf: Array(repeating: nil, count: index - endIndex + 1))
            }

            self[index] = newValue ?? nil
        }
    }
}

Использование простое и работает с Swift 5.1

var arr:[String?] = ["A","B","C"]

print(arr) // Output: [Optional("A"), Optional("B"), Optional("C")]

arr[safe:10] = "Z"

print(arr) // [Optional("A"), Optional("B"), Optional("C"), nil, nil, nil, nil, nil, nil, nil, Optional("Z")]

Примечание: вы должны понимать стоимость производительности (как во времени, так и в пространстве) при выращивании массива в swift - но для небольших проблем иногда вам просто нужно заставить Swift остановить сам Swifting в ноге

Шахин Гиасси
источник
-1

Я сделал простое расширение для массива

extension Array where Iterator.Element : AnyObject {
    func iof (_ i : Int ) -> Iterator.Element? {
        if self.count > i {
            return self[i] as Iterator.Element
        }
        else {
            return nil
        }
    }

}

он отлично работает как задумано

пример

   if let firstElemntToLoad = roots.iof(0)?.children?.iof(0)?.cNode, 
Мохамед Де
источник
-1

Swift 5 Использование

extension WKNavigationType {
    var name : String {
        get {
            let names = ["linkAct","formSubm","backForw","reload","formRelo"]
            return names.indices.contains(self.rawValue) ? names[self.rawValue] : "other"
        }
    }
}

в конечном итоге, но очень хотел сделать, как правило, как

[<collection>][<index>] ?? <default>

но поскольку коллекция контекстуальна, я думаю, что это правильно.

slashlos
источник
Чем этот ответ отличается от принятого? Как по мне, это выглядит точно так же (дубликат).
Легонафтик
-1

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

// Assuming you have a collection named array:
let safeArray = Dictionary(uniqueKeysWithValues: zip(0..., array))
let value = safeArray[index] ?? defaultValue;
Сорин Долха
источник