Массивы, словари и циклы в Swift

В этом уроке мы возьмем список задач, отфильтруем выполненные, посчитаем минуты, сгруппируем количество по статусам через словарь и разберем, когда писать for-in, а когда удобнее map, filter, reduce

Так коллекции Swift будут не справочником, а рабочим инструментом для моделей, экранов и будущих сетевых данных

Что получится в конце

Код:

struct Task {
    let title: String
    let minutes: Int
    let isDone: Bool
}

let tasks = [
    Task(title: "Learn optionals", minutes: 30, isDone: true),
    Task(title: "Practice structs", minutes: 40, isDone: true),
    Task(title: "Build SwiftUI screen", minutes: 50, isDone: false)
]

let doneTasks = tasks.filter { $0.isDone }
let totalMinutes = doneTasks.reduce(0) { result, task in
    result + task.minutes
}

print("Done tasks: \(doneTasks.count)")
print("Minutes: \(totalMinutes)")

Ожидаемый смысл:

Done tasks: 2
Minutes: 70

Array: список одного типа

Массив Swift хранит значения одного типа:

let numbers = [1, 2, 3]
let names = ["Swift", "Xcode", "SwiftUI"]

Для своих моделей:

let tasks: [Task] = [...]

Swift часто выводит тип сам, но явная запись [Task] полезна в учебных примерах

Если массив должен изменяться:

var names = ["Swift"]
names.append("SwiftUI")

Если список не меняется, используйте let. Это та же привычка, что с обычными переменными

Индексы и безопасный доступ

К элементу можно обратиться по индексу:

let first = tasks[0]

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

let index = 10

if tasks.indices.contains(index) {
    print(tasks[index].title)
} else {
    print("No task with this index")
}

Не полагайтесь на то, что индекс «обычно правильный». Данные от пользователя, сети или UI могут быть неожиданными

for-in: простой перебор

Цикл:

for task in tasks {
    print(task.title)
}

хорош для понятного действия с каждым элементом. Если нужно вместе с индексом:

for (index, task) in tasks.enumerated() {
    print("\(index + 1). \(task.title)")
}

enumerated() возвращает пары индекс-значение. Это удобно для нумерации списка в консоли или SwiftUI

filter, map, reduce

filter оставляет элементы:

let doneTasks = tasks.filter { $0.isDone }

map превращает элементы:

let titles = tasks.map { $0.title }

reduce сворачивает список в одно значение:

let minutes = tasks.reduce(0) { result, task in
    result + task.minutes
}

Не нужно насильно заменять каждый цикл на map или reduce. Если цикл читается яснее, оставьте цикл. Но стандартные операции помогают писать код ближе к смыслу задачи

Dictionary: ключ и значение

Словарь хранит значения по ключам:

var counters: [String: Int] = [:]

for task in tasks {
    let key = task.isDone ? "done" : "todo"
    counters[key, default: 0] += 1
}

print(counters)

default: 0 означает: если ключа еще нет, взять ноль и увеличить его. Это удобный способ считать категории

Если обратиться к словарю:

let doneCount = counters["done"]

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

Домашка: отчет по задачам

Сделайте функцию:

func report(tasks: [Task]) -> String

Она должна вернуть строку:

Done: 2, todo: 1, minutes: 70

Используйте filter, reduce или обычный for-in. Дополнительная задача: отсортируйте задачи по minutes через sorted

Мини-практика: поиск первой невыполненной

Swift дает метод first(where:):

let nextTask = tasks.first { !$0.isDone }

if let nextTask {
    print("Next: \(nextTask.title)")
} else {
    print("All tasks are done")
}

Результат optional, потому что подходящей задачи может не быть. Это хороший пример связки коллекций и безопасного извлечения

Частые ошибки и порядок проверки

Индекс вышел за границы Проверяйте tasks.indices.contains(index), если индекс приходит извне

Думаете, что filter меняет массив filter возвращает новый массив. Исходный массив остается прежним

Путаете map и compactMap map сохраняет optional-результаты, compactMap убирает nil. Не используйте compactMap, если nil несет важный смысл

Словарь возвращает optional Ключа может не быть, поэтому dictionary[key] возвращает optional-значение

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

В Swift обращение по индексу строгое:

let names = ["Swift", "SwiftUI"]
print(names[2])

Такой код упадет, потому что элемента с индексом 2 нет. Это не optional-операция. Поэтому перед доступом по индексу проверяйте границы:

let index = 1

if names.indices.contains(index) {
    print(names[index])
} else {
    print("Index is out of range")
}

В реальном UI это важно: список может быть пустым, фильтр может вернуть меньше элементов, а пользователь может быстро менять состояние экрана

Практика: отчет по задачам

Создайте массив задач:

struct TaskItem {
    let title: String
    let minutes: Int
    let isDone: Bool
}

let tasks = [
    TaskItem(title: "Read optionals", minutes: 30, isDone: true),
    TaskItem(title: "Build SwiftUI screen", minutes: 50, isDone: false),
    TaskItem(title: "Make network request", minutes: 45, isDone: false)
]

Посчитайте готовые задачи:

let doneCount = tasks.filter { $0.isDone }.count
print("Done: \(doneCount) / \(tasks.count)")

Посчитайте общее время:

let totalMinutes = tasks.reduce(0) { result, task in
    result + task.minutes
}

print("Total minutes: \(totalMinutes)")

Так коллекции перестают быть абстрактными. Массив хранит модели, filter выбирает нужные, reduce собирает итог

Dictionary для группировки

Словарь удобен, когда нужно быстро получить значение по ключу:

var minutesByTopic: [String: Int] = [:]

for task in tasks {
    minutesByTopic[task.title] = task.minutes
}

print(minutesByTopic["Read optionals"] ?? 0)

Доступ к словарю возвращает optional, потому что ключа может не быть. Именно здесь прошлый урок про optionals начинает работать в связке с коллекциями

Если нужно увеличить счетчик, используйте default:

var countByStatus: [String: Int] = [:]

for task in tasks {
    let key = task.isDone ? "done" : "open"
    countByStatus[key, default: 0] += 1
}

print(countByStatus)

default: 0 говорит словарю: если ключа еще нет, начни с нуля

Когда for-in лучше map

map, filter и reduce удобны, но обычный for-in не хуже. Если внутри много шагов, логирования или условий, цикл часто читается проще:

for task in tasks {
    if task.isDone {
        print("Done: \(task.title)")
    } else {
        print("Open: \(task.title)")
    }
}

Не превращайте каждую задачу в цепочку методов. Хороший Swift-код не обязан быть максимально коротким. Он должен быть понятным для следующего человека, который откроет файл

Сортировка и вывод top-листа

Еще один частый сценарий — отсортировать массив и показать несколько первых элементов:

let sortedTasks = tasks.sorted { first, second in
    first.minutes > second.minutes
}

for task in sortedTasks.prefix(2) {
    print("\(task.title): \(task.minutes) min")
}

sorted не меняет исходный массив, а возвращает новый. Это спокойнее для учебного кода: вы можете сравнить tasks и sortedTasks и увидеть, что исходные данные остались прежними

Если нужно изменить массив на месте, есть sort, но начинающему проще сначала пользоваться sorted. Меньше побочных эффектов, легче отлаживать результат

Домашка: мини-аналитика по урокам

Добавьте в TaskItem поле topic, например Swift, SwiftUI, Networking. Затем посчитайте, сколько минут уходит на каждую тему через словарь с default: 0

Проверка результата простая: сумма значений в словаре должна совпасть с totalMinutes. Если не совпала, значит вы потеряли одну из задач в цикле или неверно выбрали ключ

Что может быть еще интересно по этой теме

Что лучше: цикл или map/filter? Для обучения полезны оба. Цикл проще для пошаговой логики, map/filter/reduce лучше выражают типичные операции над коллекциями

Почему Swift не дает безопасный tasks[10]? Индексный доступ быстрый и строгий. Для потенциально неверного индекса проверяйте границы вручную

Когда использовать Set? Когда важна уникальность элементов, а порядок не является главным смыслом

Что дальше после коллекций? SwiftUI. Экран почти всегда показывает массивы моделей: список задач, товаров, пользователей или результатов запроса

Что почитать дальше по Swift

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x