swift

Массивы (Array) в Swift 📚

Массив — это упорядоченная коллекция элементов одного типа.

Проще говоря, это пронумерованный список, где у каждого элемента есть свой порядковый номер, который называется индексом:

// Индексы:                0           1         2         3
let shoppingList = ["🥛 Молоко", "🍞 Хлеб", "🧀 Сыр", "🍎 Яблоки"]

Представьте алфавит – он состоит из множества упорядоченных букв, а всё вместе это и есть массив.

// Индексы:      0   1    2    3
let alphabet = ["А, "Б", "В", "Ж"]

При этом, крайне важно сохранить порядок – будет странно, если вдруг “А” окажется не первой буквой, а “Ж” не последней. В случае, когда нам важно сохранить порядок элементов – массив самый оптимальный выбор.

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

// Индексы:                 0         1           2              3
let gobletOfFireList = ["Поттер, "Гермиона", "Гермиона", "Спайдер мен"]

Представьте: у вас проходят соревнования среди магических школ – “Кубок огня” и каждый из учеников бросил в огонь своё имя, очевидно, что среди учеников могут быть и такие, имена у которых совпадают – тёзки. Размеется, мы должны хранить всех учеников, а не только тех, у которых имя уникально.

Таким образом, главное, что нужно понимать о массиве:

  1. Ключевую роль играют индексы (порядковые номера элементов)
  2. Порядок элементов в массиве – постоянен
  3. Могут храниться повторяющиеся элементы

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

Что вы изучите 🎯

Тема Описание
📦 Основы Что такое массив и зачем он нужен
🏗️ Создание Как создавать массивы и получать из них данных
Добавление Как вставлять элементы в массив
🔍 Поиск Как находить элементы и их индексы
🗑️ Удаление Как удалять и фильровать элементы
✏️ Изменение Как менять существующие элементы
🎭 Разные типы Как хранить разные типы в одном массиве

0. Что такое массив? 🧐

Представьте, что вы ведёте список покупок в заметках:

📝 Список покупок:
─────────────────
0. 🥛 Безлактозное молоко
1. 🍞 Хлеб
2. 🧀 Сыр
3. 🍎 Яблоки

В Swift такой список называется массивом (Array)

Это пронумерованный список, где у каждого элемента есть свой порядковый номер (индекс).

🔢 Важно: Отсчёт всегда начинается с нуля, а не с единицы! Готов поспорить – вы не раз это забудите

1. Ключевые особенности массива 🎯

0️⃣ Все элементы должны быть одного типа

Массив может хранить элементы только ОДНОГО ТИПА Нельзя смешивать строки и числа.

Правильно:

let shoppingList = ["Молоко", "Хлеб", "Сыр"]  // 👌 Только строки
let prices = [143, 11, 27]                    // 👌 Только числа

Неправильно:

let mixed = ["Молоко", 100, "Хлеб"] // ❌ Ошибка! Разные типы данных в одном массиве

1️⃣ Порядок сохраняется

Элементы хранятся в том порядке, в котором вы их добавили.

let fruits = ["Яблоко", "Банан", "Апельсин"]
print(fruits[0]) // "Яблоко" всегда первое
print(fruits[1]) // "Банан" всегда второй
print(fruits[2]) // "Апельсин" всегда третий
print(fruits[3]) // "💥 Краш приложения" всегда (Нет такого индекса!)

2️⃣ Элементы могут повторяться

В отличие от множества (Set), массивы разрешают дубликатыSet мы поговорим позднее)

let votes = [5, 5, 3, 5, 4, 5]        // Число 5 встречается 4 раза
print("Всего оценок: \(votes.count)") // 6

3️⃣ Индекс начинаются с 0

Это самая частая ошибка у новичков!

// Индексы:     0    1    2    3
let letters = ["A", "B", "C", "D"]

letters[0] // "A" (первый элемент) ✅
letters[1] // "B" (второй элемент) ✅
letters[4] // 💥 ОШИБКА! Индекс за пределами массива!

⚠️ Запомните: Последний элемент всегда имеет индекс array.count - 1, а первый – 0

4️⃣ Безопасный доступ к элементам

Всегда проверяйте, что индекс существует!

let colors = ["белый", "синий", "красный"]
let index = 5 // Хотим получить элемент с индексом 5

// ✅ Безопасная проверка:
if index < colors.count { // Убедимся, что массив боьше, чем запрашиваемый нами индекс 
    print(colors[index])
} else {
    print("❌ Индекс \(index) вне диапазона. Допустимые индексы: 0...\(colors.count - 1)")
}

// Выведет: ❌ Индекс 5 вне диапазона. Допустимые индексы: 0...2

💀 Запомните: Если обратиться по несуществующему индексу — приложение упадёт! Это очень плохо

2. Создание массивов 🏗️

1️⃣ Пустой массив

// Способ 1: с указанием типа
var emptyStrings: [String] = [] // Пустой массив строк
var emptyNumbers: [Int] = []    // Пустой массив чисел

2️⃣ Массив с элементами

// Swift сам понимает тип (type inference) исходя из данных
let numbers = [1, 2, 3, 4, 5]      // Swift понял: это [Int]
let names = ["Виктория", "Сергей"] // Swift понял: это [String]

// Можно указать тип явно (но это моветон, лучше не указывать)
let explicitNumbers: [Int] = [1, 2, 3]

// Массив с повторяющимися значениями
let zeros = Array(repeating: 0, count: 5)    // [0, 0, 0, 0, 0]
let stars = Array(repeating: "⭐", count: 3) // ["⭐", "⭐", "⭐"]

3️⃣ Важное различие: let и var

// 🟢 var — массив можно изменять
var mutableArray = [1, 2, 3]
mutableArray.append(4) // ✅ Работает! [1, 2, 3, 4]
mutableArray[0] = 10   // ✅ Работает! [10, 2, 3, 4]

// 🔴 let — массив НЕЛЬЗЯ менять
let immutableArray = [1, 2, 3]
immutableArray.append(4) // ❌ Ошибка компиляции!
immutableArray[0] = 10   // ❌ Ошибка компиляции!

🔑 Запомните:

2. Добавление элементов ➕

Рассмотрим пример:

var students = ["Павел", "Еблантий"]

// 📌 append() — добавляет элемент в КОНЕЦ массива
students.append("Мария") // ["Павел", "Еблантий", "Мария"]

// 📌 insert(at:) — вставляет элемент на конкретную позицию
students.insert("Денис", at: 1) // ["Павел", "Денис", "Еблантий", "Мария"]

// 📌 append(contentsOf:) — добавляет несколько элементов в конец массива
let newStudents = ["Галина", "Фёдор"]
students.append(contentsOf: newStudents) // ["Павел", "Денис", "Еблантий", "Мария", "Галина", "Фёдор"]

📝 Шпаргалка по добавлению:

3. Доступ к элементам 🔍

1️⃣ Прямой доступ по индексу (рискованный)

let colors = ["🔴 Красный", "🟢 Зеленый", "🔵 Синий"]

// ✅ Существующие индексы
let firstColor = colors[0]  // "🔴 Красный"
let secondColor = colors[1] // "🟢 Зеленый"
let thirdColor = colors[2]  // "🔵 Синий"

// 💥 НЕСУЩЕСТВУЮЩИЙ ИНДЕКС — КРАШ!
let fourthColor = colors[3] // Fatal error: Index out of range

2️⃣ Безопасный доступ

// Индексы:          0              1            2    
let colors = ["🔴 Красный", "🟢 Зеленый", "🔵 Синий"]

// ✅ Способ 1: проверка границ
let index = 2
if index < colors.count {
    print(colors[index]) // "🔵 Синий"
} else {
    print("Индекс вне диапазона")
}

// ✅ Способ 2: first и last (возвращают Optional)
if let first = colors.first {
    print("Первый цвет: \(first)") // "🔴 Красный"
}

if let last = colors.last {
    print("Последний цвет: \(last)") // "🔵 Синий"
}

// ✅ Способ 3: Модный – indices, безопасный диапазон 
// indices это все доступные индексы в данном массиве
if colors.indices.contains(2) {
    print(colors[2]) // "🔵 Синий"
}

🛡️ Правило безопасности: Всегда проверяйте существование индекса перед попыткой получить элемент из массива!

4. Изменение элементов ✏️

var grades = [4, 5, 3, 5]

if grades.indices.contains(2) {  // Безопасность, превыше всего
    grades[2] = 4                // Заменяем тройку на четверку – [4, 5, 4, 5]
}

⚠️ Важно: При изменении всегда убеждайтесь, что индекс существует!

5. Удаление элементов 🗑️

// Индексы:          0          1         2          3
var playlist = ["Песня 1", "Песня 2", "Песня 3", "Песня 4"]

// 📌 remove(at:) — удаляет по индексу (возвращает удалённый элемент)
let removedSong = playlist.remove(at: 1)
print("🗑️ Удалили: \(removedSong)") // 🗑️ Удалили: "Песня 2"
print("🎵 Осталось: \(playlist)")   // [🎵 Осталось: "Песня 1", "Песня 3", "Песня 4"]

// 📌 removeFirst() — удаляет первый элемент
let firstRemoved = playlist.removeFirst()
print("🗑️ Удалили первый: \(firstRemoved)"). // 🗑️ Удалили первый: "Песня 1"
print("🎵 Осталось: \(playlist)")            // 🎵 Осталось: ["Песня 3", "Песня 4"]

// 📌 removeLast() — удаляет последний элемент
let lastRemoved = playlist.removeLast() 
print("🗑️ Удалили последний: \(lastRemoved)") // 🗑️ Удалили последний: "Песня 4"  
print("🎵 Осталось: \(playlist)")             // 🎵 Осталось: ["Песня 3"]

// 📌 removeAll() — удаляет ВСЁ!
playlist.removeAll()
print("🎵 После removeAll: \(playlist)") // 🎵 После removeAll: []

6. Поиск элементов 🔎

let fruits = ["🍎 яблоко", "🍌 банан", "🍊 апельсин", "🍎 яблоко"]
let numbers = [10, 23, 45, 67, 89, 12, 34, 56]

1️⃣ Проверка наличия

// contains() — содержится ли элемент в массиве ?
let hasApple = fruits.contains("🍎 яблоко")   // true
let hasGrape = fruits.contains("🍇 виноград") // false

2️⃣ Поиск индекса для элемента из массива

// firstIndex(of:) — ищем индекс конкретного элемента (Первый, который нам встетится в массиве)
if let index = fruits.firstIndex(of: "🍌 банан") {
    print("🍌 банан найден на позиции \(index)") // 🍌 банан найден на позиции 1
}

3️⃣ Поиск по условию

// first(where:) — первый элемент, удовлетворяющий условию
if let firstEven = numbers.first(where: { number in
    number % 2 == 0 // Остаток от деления на два т.е. любое чётное число
}) {
    print("Первое чётное число: \(firstEven)") // Первое чётное число: 10
}

// firstIndex(where:) — индекс первого элемента по условию
if let index = numbers.firstIndex(where: { number in
    number > 50
}) {
    print("Первое число >50 – \(index)") // Первое число >50 – 3 (число 67)
}

4️⃣ Фильтрация

// filter — останутся только те элементы, которые удовлетворяют условию
let evenNumbers = numbers.filter { number in
    number % 2 == 0 // % – Операция "Остаток от деления" (4 % 2 = 0 | 5 % 2 = 1 ... )
}
print("Все чётные числа: \(evenNumbers)") // [10, 12, 34, 56]

В массиве evenNumbers остались только чётные элементы Массива numbers при этом НИКАК НЕ ИЗМЕНИЛСЯ

7. Сортировка 🔤

Давайте рассмотрим следущий пример:

var unsorted = [3, 1, 4, 2, 5]
let unsortedCopy = unsorted

1️⃣ Сортировка с изменением оригинала

// sort() — сортирует массив на месте (меняет оригинал)
unsorted.sort(by: <)  // [1, 2, 3, 4, 5]

// Сортировка по убыванию
unsorted.sort(by: >)  // [5, 4, 3, 2, 1]

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

2️⃣ Сортировка с созданием копии

// sorted() — возвращает отсортированную копию
let sorted = unsortedCopy.sorted()
print("📊 Оригинал: \(unsortedCopy)") //📊 Оригинал:  [3, 1, 4, 2, 5] (не изменился)
print("📊 Копия: \(sorted)")          //📊 Копия: [1, 2, 3, 4, 5]

// Сортировка строк
let names = ["Иван", "Анна", "Дмитрий"]
let sortedNames = names.sorted()
print("📊 Оригинал: \(names)") //📊 Оригинал:  ["Иван", "Анна", "Дмитрий"] (не изменился)
print("📊 Имена по алфавиту: \(sortedNames)") // ["Анна", "Дмитрий", "Иван"]

🔑 Запомните:

8. Полезные свойства и методы 📊

Массив для экспериментов:

let array = [1, 2, 3, 4, 5, 3, 2, 1]

1️⃣ Основные свойства

array.isEmpty  // false (пустой ли массив)
array.count    // 8 (количество элементов в массиве)
array.first    // Optional(1) (первый элемент)
array.last     // Optional(1) (последний элемент)

2️⃣ Дополнительные методы

// reversed() — разворачивает массив (В обратном порядке, наизнанку)
let array = [1, 2, 3, 4, 5]
let reversed = array.reversed() // [5, 4, 3, 2, 1]

// shuffled() — перемешивает элементы
let shuffled = array.shuffled()  // [3, 4, 5, 1, 2]

9. Продвинутая тема: Массивы с разными типами 🎭

Проблема

Представьте, что мы пишем приложение для школы.

У нас есть разные типы персон:

struct Student {
    let name: String
    let scores: [Int] // Оценки ученика
}

struct Teacher {
    let name: String
    let subject: String // Предмет
    let salary: Int     // Зарплата
}

struct Director {
    let name: String
    let experience: Int // Опыт работы
}

Как хранить их ВСЕХ в одном массиве? 🤔

Решение: Протокол (protocol)

Протокол — это как контракт.

Если структура подписывает протокол, она ОБЯЗАНА выполнить его требования.

/// 📋 Протокол, объединяющий всех людей в школе
protocol Person {
    var name: String { get }        // Данное поле – можно только читать
    var surname: String { get set } // Данное поле – можно читать и изменять
}

📖 Расшифровка:

Подписываем структуры под протокол

struct Student: Person {
    let name: String
    var surname: String
    let scores: [Int]
}

struct Teacher: Person {
    let name: String
    var surname: String
    let subject: String
    let salary: Int
}

struct Director: Person {
    let name: String
    var surname: String
    let experience: Int
}

Создаём общий массив

Важно понимать, что теперь это не массив конкретных Student, Teacher и Director это массив кого угодно, кто подписался под протокол Person, т.е. все элементы массива это прежде всего Person

// Тип массива — [Person], но внутри могут быть любые типы, подписанные под Person
let schoolMembers: [Person] = [
    Student(name: "Павел", surname: "Петров", scores: [5, 4, 5]),
    Teacher(name: "Василиса", surname: "Иванова", subject: "Математика", salary: 50_000),
    Student(name: "Милена", surname: "Сидорова", scores: [4, 4, 5]),
    Director(name: "Ангелина", surname: "Михайловна", experience: 15)
]

Как работать с таким массивом

// Проходим по всем членам школы
for person in schoolMembers {
    // У всех есть имя и фамилия (из протокола)
    print("👤 \(person.name) \(person.surname)")
    
    // Чтобы получить доступ к УНИКАЛЬНЫМ свойствам,
    // нужно "привести" (скастить) к конкретному типу
    if let student = person as? Student {
        print("📚 Ученик, оценки: \(student.scores)")
    } else if let teacher = person as? Teacher {
        print("🍎 Учитель, предмет: \(teacher.subject)")
    } else if let director = person as? Director {
        print("👔 Директор, опыт: \(director.experience) лет")
    }
}

⚠️ Важно: Постарайтесь осмыслить – данный код выполнится 4ре раза! и в каждом из прогонов будет разный person

Вначале это Student (Павел), затем Teacher (Василиса), вновь Studen (Милена) и на последок Director (Ангелина)

В итоге мы попадём во все if let НО НЕ СРАЗУ, а по одному разу за каждый прогон и только в конкретный if let, в зависимости от текущего типа данных person

Запись for person in schoolMembers встречается ОЧЕНЬ ЧАСТО важно понять следующее:

Если у нас есть массив (schoolMembers – все люди школы), то person это каждый человек из данного массива. Вначале “Петя”, потом “Вася” и так далее…

// Проходим по всем членам школы, person – каждый элемент из массива schoolMembers
for person in schoolMembers {,
  // Дон
  // Ягон
  // Ясус
  // Биб
}

10. Поиск индекса для конкретного элемента

Как вы знаете – индекс в массиве играет решающую роль, без индекса мы просто как без аппендикса.

Давайте в массиве schoolMembers, который я вам напомню – хранит массив, структур, подписанных под протокол Person, найдём индекс Директора, с опытом работы 15 лет, что бы отправить его на пенсию. (Удалить из массива schoolMembers)

// Найдем директора с опытом работы 15 лет
// Искренне надеюсь – запись if let.... хорошо вам знакома из предыдущей тымы об Опционалах
if let directorIndex = schoolMembers.firstIndex(where: { person in
    // Проверяем, является ли текущий элемент из массива директором И имеет ли опыт в 15 лет
    if let director = person as? Director, director.experience == 15 {
        return true
    }
    return false
}) {
    print("✅ Нашли директора по индексу \(directorIndex)")
 
    // Давайте, не будем увольнять, а добавить опыта
    
    // Получаем элемент из массива под найденным индексом 
    // (Мы точно знаем, что там хранится нужный нам директор)
    if var director = schoolMembers[directorIndex] as? Director {
        director.experience = 16
        
        // Важно: director — это КОПИЯ! Нужно обновить в массиве устаревший оригинал
        schoolMembers[directorIndex] = director
        
        print("📈 Опыт увеличен до \(director.experience)")
    }
} else {
    print("❌ Директор с опытом 15 лет не найден")
}

⚠️ Важно: Внутри firstIndex мы обязательно должны вернуть либо true либо false

return true значит, что это именно тот элемент, который нам и нужен

Найденный нами индекс directorIndex может и не существовать (Представьте, что директора с опытом работы 15 лет мы не нашли) в таком случае в массиве нет нужного элемента, а соответственно и индекса, по которому он мог бы находиться.

В таком случае directorIndex будет равен nil

Поле directorIndex: Int? опционально (либо Int, либо nil) и нам необходимо убедиться, что оно всё же существует (Int) и в этом нам помогает запись if let directorIndex = schoolMembers...

Более подробно про опционалы мы уже говорили здесь

Проверь себя! ✍️

Вопрос 1

Какой индекс у первого элемента массива?

let array = [10, 20, 30, 40, 50]

Вопрос 2

Что произойдет при выполнении кода?

let numbers = [1, 2, 3, 4, 5]
print(numbers[5])

Вопрос 3

Что выведет этот код?

var fruits = ["🍎", "🍌", "🍊"]
fruits.append("🍇")
fruits.insert("🍓", at: 1)
print(fruits.count)

Вопрос 4

Какая разница между sort() и sorted()?

Вопрос 5

Что вернет [1, 2, 3, 2, 1].firstIndex(of: 2)?

Вопрос 6

Как безопасно получить первый элемент массива?

Вопрос 7

Что выведет этот код?

var items = [1, 2, 3, 4, 5]
items.remove(at: 2)
items.removeFirst()
print(items)

Вопрос 8

Можно ли хранить в одном массиве Int и String?

На самом деле мы можем хранить разные типы, но Swift – строготипизированный язык, поэтому стоит избегать подобных практик

Вопрос 9

Что выведет этот код?

let numbers = [1, 2, 3, 4, 5, 6]
let result = numbers.filter { number in number % 3 == 0 }
print(result)

Вопрос 10

Как добавить элемент в начало массива?

Шпаргалка 📝

Создание массива 🏗️

Действие Код Результат
Пустой массив var array: [Int] = [] []
С данными let array = [1, 2, 3] [1, 2, 3]
С повторением Array(repeating: 0, count: 3) [0, 0, 0]

Получение элемента из массива 💌

Действие Код Результат
Элемент по индексу (Опасно) let five = numbers[4] 5
Первый элемент (Опционально) let first = numbers.first 0
Последний элемент (Опционально) let last = numbers.first nil

Добавление элемента в массив ➕

Действие Код Пояснение
В конец (1 элемент) array.append(4) [1, 2, 3, 4]
В начало array.insert(0, at: 0) [0, 1, 2, 3, 4, 5, 6]
В конец (несколько) array += [5, 6] [1, 2, 3, 4, 5, 6]

Удаление элемента из массива 🗑️

Действие Код Пояснение
По индексу array.remove(at: 2) Удаляет элемент с индексом 2
Первый array.removeFirst() Удаляет первый элемент
Последний array.removeLast() Удаляет последний
Все array.removeAll() Очищает массив

Поиск элемента из массива 🔍

Действие Код Возвращает
Проверить наличие array.contains(5) true/false
Найти индекс array.firstIndex(of: 3) Optional(Int)
Найти по условию array.first(where: { $0 > 5 }) Optional(Int)

Свойства массива 📊

Свойство Код Значение
Количество array.count Int
Пустой? array.isEmpty true/false
Первый array.first Optional
Последний array.last Optional

Частые ошибки новичков ⚠️

Ошибка 1: Забывают про индекс 0

let array = [10, 20, 30]

// ❌ Неправильно
let first = array[1] // Думают, что это первый, но это ВТОРОЙ!

// ✅ Правильно
let correct = array[0] // Первый элемент

Ошибка 2: Не проверяют границы

let array = [1, 2, 3]

// ❌ Опасно!
let element = array[5] // 💥 Crash!

// ✅ Безопасно
if array.count > 5 {
    let element = array[5]
} else {
    print("Индекс вне диапазона")
}

Ошибка 3: Пытаются изменить let-массив

// ❌ Ошибка!
let array = [1, 2, 3]
array.append(4) // Компилятор не пропустит

// ✅ Правильно
var mutableArray = [1, 2, 3]
mutableArray.append(4) // Работает!

Ошибка 4: Путают append и insert

var array = [1, 2, 3]

// ❌ Непонятно, что хотели
array.append(10) // Добавит в КОНЕЦ: [1, 2, 3, 10]

// ✅ Если хотели в начало: (А вообще можно указать любой индекс)
array.insert(10, at: 0) // [10, 1, 2, 3]

Ошибка 5: Забывают про Optional при first/last

let emptyArray: [Int] = []

// ❌ Опасно! first может быть nil
let first = emptyArray.first! // 💥 Crash на пустом массиве!

// ✅ Безопасно
if let first = emptyArray.first {
    print(first)
} else {
    print("Массив пуст")
}

Задание:

  1. Создайте тип данных Phone
  2. Создайте массив, который хранит в себе два объекта с типом данных Phone
  3. Создайте тип данных Notebook
  4. Попробуйте добавить в массив с телефонами объект с типом данных Notebook
  5. Создайте протокол, который позволит хранить в массиве и Phone и Notebook
  6. Добавьте в конец массива Notebook
  7. Найдите индекс Phone, который тебе нужен (например модель iPhone 13)
  8. Удалите из массива элемент под индексом, который нашли на предыдущем шаге
  9. Поменяйте поле у любого Notebook (Которое есть в протоколе)
  10. Поменяйте специфичное только для Phone поле (Которого нет в протоколе)
  11. Радуйся

Решение можете присылать сюда

Далее

Множества