Друзья, сегодня поговорим о массивах в
Swift. Казалось бы, самой базовой структуре данных, но с интересными особенностями под капотом.
Value type но со своими хитростями:
Массивы - это структуры (value types), но данные хранятся в куче. При присваивании в переменную создается новая структура, но буфер с данными остается общим до момента модификации (Copy-on-Write):
var array1 = [1, 2, 3]
var array2 = array1 // Пока нет копирования данных
array1.append(4) // Только теперь данные копируются
Работа с ссылочными типами:
С классами нужно быть аккуратнее:
class User {
var name: String
init(name: String) {
self.name = name
}
}
let users1 = [User(name: "Artem"), User(name: "Dima")]
let users2 = users1
users1[0].name = "Egor" // Изменение затронет оба массива
print(users2[0].name) // Egor
Для глубокого копирования нужно реализовать протокол
NSCopying. Без него при копировании массива с классами создаются только новые ссылки на те же объекты в памяти.
NSCopying позволяет создать настоящие копии каждого объекта.
Производительность операций с массивами:
На практике важно понимать сложность основных операций с массивами, чтобы выбирать оптимальные подходы:
- append() - в среднем O(1), но при расширении буфера происходит копирование всех элементов.
- removeLast() - O(1), так как не требует сдвига элементов.
- remove(at:) - O(n) из-за необходимости сдвигать все последующие элементы.
- insert(_:at:) - O(n) по той же причине, что и remove(at:).
Эффективная работа с частями массива (ArraySlice):
ArraySlice работает как указатель на определенный диапазон элементов в оригинальном массиве. Это особенно полезно при работе с большими массивами:
let largeArray = Array(0..<1000000) // Большой массив
let slice = largeArray[1000..<2000] // Не копирует миллион элементов!
// Используем срез как обычный массив
for element in slice {
print(element) // Работает с элементами 1000-1999
}
Но важно помнить: ArraySlice сохраняет сильную ссылку на весь исходный массив. Это может привести к неожиданному потреблению памяти. Вот решение:
let largeArray = Array(0..<1000000) // Большой массив
let slice = largeArray[1000..<2000] // Не копирует миллион элементов!
// Создаем новый массив если нужна независимая копия
let independentCopy = Array(slice) // Теперь largeArray можно освободить
Массивы под капотом:
Swift использует разные буферы в зависимости от контекста:
- _ContiguousArrayBuffer - для Swift-типов.
- _ArrayBuffer - при работе с Objective-C.
Вывод:
Массивы в
Swift - это не просто списки значений. Понимание их устройства помогает писать более эффективный код и избегать неочевидных проблем.