Исходная ссылка: https://marcosantadev.com/arrayslice-in-swift/
Вступление
Apple описывает ArraySlice
как просмотры массивов. Это означает, что ArraySlice
- это объект, который представляет подпоследовательность своего массива. Это очень мощный инструмент, который позволяет нам выполнять те же операции с массивами на срезе, как append
, map
и так далее.
В этой статье мы увидим, как его создать, и некоторые внутренние поведения, чтобы избежать проблем.
Приятного чтения!
СОДЕРЖАНИЕ
- Как создать
ArraySlice
- Конвертировать
ArraySlice
вArray
- Связь между
ArraySlice
и егоArray
- Индексы срезов
- "Заключение"
Как создать 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
, и любой другой тип пользовательской коллекции.