Avatar ·

Как отсортировать массив пользовательских объектов по значению свойства в Swift?

Всем привет! Я новичок в Swift и пишу небольшое приложение для управления задачами. У меня есть структура `Task` с полями `title`, `priority` (Int) и `dueDate` (Date). Я создал массив таких задач: ```swift struct Task { let title: String var priority: Int var dueDate: Date } var tasks = [ Task(title: "Купить продукты", priority: 3, dueDate: Date()), Task(title: "Сделать отчет", priority: 1, dueDate: Date().addingTimeInterval(86400)), Task(title: "Позвонить клиенту", priority: 2, dueDate: Date().addingTimeInterval(172800)) ] ``` Теперь я хочу отсортировать этот массив по приоритету (от самого высокого к низкому), а затем по дате (от самой ранней к поздней). Я пробовал использовать `sorted(by:)` с замыканием, но получаю ошибки типов или неправильный порядок. Например: ```swift let sortedTasks = tasks.sorted(by: { $0.priority < $1.priority }) ``` Это сортирует по возрастанию, а мне нужно по убыванию. И как добавить второе условие сортировки? Пробовал писать сложные условия в замыкании, но код становится нечитаемым. Помогите разобраться, как правильно сортировать кастомные объекты в Swift, желательно с примерами для нескольких свойств. Заранее спасибо! <<>>

Основные способы сортировки массивов пользовательских объектов в Swift

В Swift есть два основных подхода для сортировки массивов: мутирующий метод sort() (изменяет исходный массив) и немутирующий метод sorted() (возвращает новый отсортированный массив). Оба принимают замыкание, в котором вы определяете порядок сортировки.

1. Сортировка по одному свойству (убывание)

Чтобы отсортировать массив по убыванию приоритета, используйте оператор > вместо <:

let sortedByPriorityDesc = tasks.sorted(by: { $0.priority > $1.priority })
// Результат: сначала priority = 3, затем 2, затем 1

2. Множественная сортировка (по приоритету, затем по дате)

Для сортировки по нескольким критериям используйте последовательные проверки в замыкании. Сначала сравниваем приоритеты (по убыванию), если они равны — сравниваем даты (по возрастанию):

let sortedTasks = tasks.sorted { first, second in
    if first.priority != second.priority {
        return first.priority > second.priority // по убыванию приоритета
    } else {
        return first.dueDate < second.dueDate // по возрастанию даты
    }
}

3. Использование кортежей для краткой записи

Swift позволяет сравнивать кортежи поэлементно. Это делает код очень компактным:

let sortedTasks = tasks.sorted {
    ($0.priority, $0.dueDate) > ($1.priority, $1.dueDate)
}

Но внимание: такое сравнение работает только если все элементы кортежа соответствуют протоколу Comparable. В данном случае priority (Int) и dueDate (Date) — Comparable, поэтому код корректен. Если нужно смешивать направления сортировки (одно по возрастанию, другое по убыванию), используйте явные замыкания из пункта 2.

4. Сортировка с помощью ключевых путей (Key Paths) — Swift 5.6+

Начиная с Swift 5.6, вы можете использовать ключевые пути для ещё более лаконичной записи. Создайте массив сортировок:

import Foundation

// Определяем порядок сортировки
let sortDescriptors: [SortDescriptor] = [
    SortDescriptor(\Task.priority, order: .reverse), // по убыванию
    SortDescriptor(\Task.dueDate, order: .forward)   // по возрастанию
]

// Применяем
let sortedTasks = tasks.sorted(using: sortDescriptors)

5. Альтернатива: реализация протокола Comparable

Если вы часто сортируете объекты одного типа, можно сделать структуру соответствующей протоколу Comparable:

struct Task: Comparable {
    let title: String
    var priority: Int
    var dueDate: Date

    static func < (lhs: Task, rhs: Task) -> Bool {
        if lhs.priority != rhs.priority {
            return lhs.priority > rhs.priority // убывание
        } else {
            return lhs.dueDate < rhs.dueDate // возрастание
        }
    }
}

// Теперь можно сортировать просто так:
let sortedTasks = tasks.sorted() // по умолчанию использует оператор <

Важные замечания:

  • Метод sort() изменяет исходный массив, sorted() — нет. Выбирайте по ситуации.
  • Замыкание должно возвращать true, если первый элемент должен идти перед вторым.
  • Для сортировки по возрастанию используйте <, по убыванию — >.
  • Если свойство опционально (например, dueDate: Date?), нужно обрабатывать nil. Обычно nil помещают либо в начало, либо в конец.

Выберите подход, который лучше всего подходит под ваш стиль кода. Для простых случаев достаточно замыкания с кортежами, для сложных — реализация Comparable или SortDescriptor.

<<>>

Сортировка с использованием NSSortDescriptor (для совместимости с Objective-C)

Если вы работаете с классами, унаследованными от NSObject, или вам нужно передавать сортировку как данные, можно использовать NSSortDescriptor из Foundation:

import Foundation

class TaskObj: NSObject {
    @objc let title: String
    @objc var priority: Int
    @objc var dueDate: Date

    init(title: String, priority: Int, dueDate: Date) {
        self.title = title
        self.priority = priority
        self.dueDate = dueDate
    }
}

let tasksObj = [
    TaskObj(title: "Купить продукты", priority: 3, dueDate: Date()),
    TaskObj(title: "Сделать отчет", priority: 1, dueDate: Date().addingTimeInterval(86400))
]

let sortDesc1 = NSSortDescriptor(key: #keyPath(TaskObj.priority), ascending: false)
let sortDesc2 = NSSortDescriptor(key: #keyPath(TaskObj.dueDate), ascending: true)

let sortedArray = (tasksObj as NSArray).sortedArray(using: [sortDesc1, sortDesc2]) as! [TaskObj]

Этот способ удобен, когда сортировка задаётся динамически (например, из пользовательского интерфейса), но требует наследования от NSObject и аннотаций @objc. Для чистых Swift-структур лучше использовать методы из первого ответа.