swift

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

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

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

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

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

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

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

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

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

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

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

🔢 Важно: Отсчёт всегда начинается с нуля, а не с единицы!

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

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

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

Правильно:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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️⃣ Безопасный доступ

// ✅ Способ 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. Удаление элементов 🗑️

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)") // []

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)") // 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 остались только чётные элементы

5️⃣ Проверка условий для всех элементов

// allSatisfy — все ли элементы удовлетворяют условию?
let allPositive = [1, 2, 3, 4, 5].allSatisfy { $0 > 0 } // true
let allEven = [2, 4, 6, 7, 8].allSatisfy { $0 % 2 == 0 } // false

print("Все числа положительные? \(allPositive)")
print("Все числа четные? \(allEven)")

$0 это более короткая запись ` number in`

Как вы заметили ` number in мы вовсе опустили и оставили только $0`

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

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

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

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

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

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

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("📊 Имена по алфавиту: \(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

Поиск и изменение

// Найдем директора с опытом 15 лет
if let directorIndex = schoolMembers.firstIndex(where: { person in
    // Проверяем, является ли человек директором И имеет ли опыт 15 лет
    if let director = person as? Director, director.experience == 15 {
        return true
    }
    return false
}) {
    print("✅ Нашли директора по индексу \(directorIndex)")
    
    // Меняем данные (нужен var, т.к. собираемся изменять)
    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]

Добавление ➕

Действие Код Пояснение
В конец (1 элемент) array.append(4) [1, 2, 3, 4]
В конец (несколько) array += [5, 6] [1, 2, 3, 4, 5, 6]
В начало array.insert(0, at: 0) [0, 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(0) // Добавит в КОНЕЦ: [1, 2, 3, 0]

// ✅ Если хотели в начало:
array.insert(0, at: 0) // [0, 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. Радуйся

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

Что дальше? 🚀

Поздравляю! Вы освоили массивы — фундаментальную структуру данных. Теперь вы готовы к изучению:

Тема Описание
Множества Как массивы, но без порядка и без повторов
Словари Хранят пары “ключ-значение”