Исходная ссылка: https://marcosantadev.com/arrayslice-in-swift/

Вступление

Apple описывает ArraySlice как просмотры массивов. Это означает, что ArraySlice - это объект, который представляет подпоследовательность своего массива. Это очень мощный инструмент, который позволяет нам выполнять те же операции с массивами на срезе, как append, map и так далее.

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

Приятного чтения!

СОДЕРЖАНИЕ

Как создать ArraySlice

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

drop

Array предоставляет несколько методов для создания подпоследовательности, удаляющей некоторые элементы. Это методы dropFirst, dropLast и drop:

let array = [5, 2, 10, 1, 0, 100, 46, 99]
// Drops first
array.dropFirst() // ArraySlice<Int>[2, 10, 1, 0, 100, 46, 99]
// Drops first three elements
array.dropFirst(3) // ArraySlice<Int>[1, 0, 100, 46, 99]
/ Drops last
array.dropLast() // ArraySlice<Int>[5, 2, 10, 1, 0, 100, 46]
// Drops last three elements
array.dropLast(3) // ArraySlice<Int>[5, 2, 10, 1, 0]
// Drops all the elements less than 15
array.drop { $0 < 15 } // ArraySlice<Int>[100, 46, 99]

prefix

Если мы хотим создать подпоследовательность с первыми элементами данного массива, мы можем использовать метод prefix:

let array = [5, 2, 10, 1, 0, 100, 46, 99]
// First 4 elements
array.prefix(4) // ArraySlice<Int>[5, 2, 10, 1]
// First elements until the index 4, excluding the element at index 4
array.prefix(upTo: 4) // ArraySlice<Int>[5, 2, 10, 1]
// First elements until the index 4, including the element at index 4
array.prefix(through: 4) // ArraySlice<Int>[5, 2, 10, 1, 0]
// First elements until the condition fails (in this case, the elements must be less than 10)
array.prefix { $0 < 10 } // ArraySlice<Int>[5, 2]

suffix

suffix имеет противоположное поведение prefix. Он возвращает последние элементы массива:

let array = [5, 2, 10, 1, 0, 100, 46, 99]
// Last 3 elements
array.suffix(3) // ArraySlice<Int>[100, 46, 99]
// Last elements from the index 5, including the element at index 5
array.suffix(from: 5) // ArraySlice<Int>[100, 46, 99]

Range

Мы можем создать ArraySlice, используя также ряд индексов:

let array = [5, 2, 10, 1, 0, 100, 46, 99]
// From index 3 to index 5
array[3...5] // ArraySlice<Int>[1, 0, 100]
// From index 3 to index less than 5
array[3..<5] // ArraySlice<Int>[1, 0]

В Swift 4 мы можем опустить startIndex, если хотим использовать индекс 0, и endIndex, если мы хотим использовать последний индекс массива:

let array = [5, 2, 10, 1, 0, 100, 46, 99]
// From index 0 to index 2
array[...2] // ArraySlice<Int> [5, 2, 10]
// From index 0 to index less than 2
array[..<2] // ArraySlice<Int> [5, 2]
// From index 6 to index 7
array[6...] // ArraySlice<Int> [46, 99]
// From index 0 to index 7
array[...] // ArraySlice<Int> [5, 2, 10, 1, 0, 100, 46, 99]

Преобразовать ArraySlice в Array

Предположим, мы хотим удалить последние 5 элементов из массива. Мы можем использовать метод dropLast(5) и присвоить результат массиву:

var array = [5, 2, 10, 1, 0, 100, 46, 99]
let slice = array.dropLast(5)
array = slice // Cannot assign value of type 'ArraySlice<Int>' to type '[Int]'

К сожалению, Swift не позволяет назначать ArraySlice объект Array. По этой причине в примере выше у нас есть ошибка компиляции Cannot assign value of type 'ArraySlice<Int>' to type '[Int]'. Нам нужен способ преобразовать объект ArraySlice в Array. К счастью, Swift позволяет нам преобразовывать переменную среза, используя синтаксис Array(<slice_variable>). Мы можем использовать это приведение, чтобы исправить приведенный выше пример следующим образом:

var array = [5, 2, 10, 1, 0, 100, 46, 99]
let slice = array.dropLast(5)
array = Array(slice) // [5, 2, 10]

Связь между ArraySlice и Array

Сильная ссылка

ArraySlice содержит сильную ссылку на свой массив. Значит, нужно обращать внимание на то, как мы используем срез, чтобы не было проблем с памятью. Apple также добавила предупреждение об этом в документации:

Не рекомендуется долгое хранение экземпляров ArraySlice. Срез содержит ссылку на все хранилище большего массива, а не только на ту часть, которую он представляет, даже после окончания срока службы исходного массива. Таким образом, долгосрочное хранение фрагмента может продлить срок службы элементов, которые более недоступны иным образом, что может выглядеть как утечка памяти и объектов.

Гражданин

Мы можем проверить это поведение на следующем примере:

final class Element {
    deinit {
        print("Element deinit is called")
    }
}
final class ArrayHandler {
    let array = [Element(), Element()]
}
final class Main {
    var slice: ArraySlice<Element>
    
    init() {
        let arrayHandler = ArrayHandler()
        slice = arrayHandler.array.dropLast() // `slice` holds a strong reference of `arrayHandler.array`
    }
}
let main = Main()

Если мы позволим запустить этот пример, мы можем заметить, что свойство slice содержит сильную ссылку на элементы массива. Несмотря на то, что arrayHandler уничтожается в конце init области видимости, deinit метод Element не вызывается.

Последствия ArraySlice и Array изменений

Предположим, у нас есть Array и его ArraySlice. Если мы изменим элементы Array, ArraySlice не пострадают и продолжат сохранять исходные элементы:

var array = [10, 46, 99]
let slice = array.dropLast() // ArraySlice<Int> [10, 46]
array.append(333) // [10, 46, 99, 333]
// `slice` is not affected and continues storing the original elements
print(slice) // ArraySlice<Int> [10, 46]

И наоборот, если мы изменим элементы ArraySlice, это не повлияет на Array:

let array = [10, 46, 99]
var slice = array.dropLast() // ArraySlice<Int> [10, 46]
slice.append(333) // ArraySlice<Int> [10, 46, 333]
// `array` is not affected and continues storing the original elements
print(array) // [10, 46, 99]

Индексы срезов

ArraySlice поддерживает те же индексы своего массива. Это означает, что элемент с индексом 3 в ArraySlice является тем же элементом с индексом 3 исходного массива.

Давайте рассмотрим ArraySlice с диапазоном 2...4. Если мы попытаемся прочитать элемент с индексом 0 среза, мы получим ошибку времени выполнения:

let array = [10, 46, 99, 00, 6]
var slice = array[2...4] // ArraySlice<Int> [99, 0, 6]
slice[0] // fatal error: Index out of bounds

Это происходит потому, что срез не содержит элемент с индексом 0 массива, а содержит только элементы с индексами 2, 3 и 4.

Если мы хотим получить первый и последний индекс среза безопасным способом, мы должны использовать startIndex и endIndex:

let array = [10, 46, 99, 00, 6]
var slice = array[2...4] // ArraySlice<Int> [99, 0, 6]
slice[slice.startIndex] // 99
slice[slice.endIndex - 1] // 6

endIndex возвращает позицию, на единицу превышающую последний действительный индекс. По этой причине мы должны использовать endIndex - 1 в приведенном выше примере, чтобы избежать Index out of bounds ошибки.

Заключение

Помимо ArraySlice, Swift предоставляет также базовый объект Slice. Это позволяет нам получить подпоследовательность других объектов коллекции, таких как Dictionary, Set, и любой другой тип пользовательской коллекции.