Можно ли хранить замыкания (closures) в массиве в Swift?

Ответ

Да, можно. Поскольку замыкания в Swift являются ссылочными типами, их можно хранить в коллекциях, таких как массивы. Для этого необходимо явно указать тип элементов массива как тип замыкания.

Пример:

// Массив замыканий, которые не принимают и не возвращают значения
var completionHandlers: [() -> Void] = []

func addClosure(_ closure: @escaping () -> Void) {
    completionHandlers.append(closure)
}

addClosure { print("Выполнено задание 1") }
addClosure { print("Выполнено задание 2") }

// Последовательный вызов всех замыканий
for handler in completionHandlers {
    handler()
}
// Вывод:
// Выполнено задание 1
// Выполнено задание 2

Важные аспекты:

  • Типизация. Все замыкания в массиве должны иметь идентичную сигнатуру (тип принимаемых и возвращаемых значений).
  • Захват переменных (capture). Замыкания захватывают переменные из окружающего контекста по сильной ссылке (strong reference) по умолчанию.
  • Циклы сильных ссылок. Если замыкание захватывает self, а self хранит этот массив, может возникнуть цикл удержания. Используйте списки захвата [weak self] или [unowned self].
    class Controller {
    var handlers: [() -> Void] = []
    func setupHandler() {
        // БЕЗОПАСНО: предотвращаем цикл сильных ссылок
        handlers.append { [weak self] in
            self?.doWork()
        }
    }
    func doWork() {}
    }

Ответ 18+ 🔞

О, смотри-ка, какой интересный вопрос подвалил! Можно ли засунуть замыкания в массив? Да хули нет-то, конечно можно, они же в Swift — ссылочные типы, как какой-нибудь класс! Только вот надо чётко понимать, что ты делаешь, а то потом будешь ловить циклы удержания, как дурак.

Смотри, вот тебе живой пример, как это выглядит:

// Массив замыканий, которые не принимают и не возвращают значения
var completionHandlers: [() -> Void] = []

func addClosure(_ closure: @escaping () -> Void) {
    completionHandlers.append(closure)
}

addClosure { print("Выполнено задание 1") }
addClosure { print("Выполнено задание 2") }

// Последовательный вызов всех замыканий
for handler in completionHandlers {
    handler()
}
// Вывод:
// Выполнено задание 1
// Выполнено задание 2

Видишь? Всё просто, как три копейки. Собрал их в кучу и потом вызывай, когда вздумается. Но тут, блядь, есть свои подводные грабли, о которые можно ебалом приложиться.

Первое — типизация. Все эти замыкания в массиве должны быть как близнецы-братья, с одинаковой сигнатурой. Нельзя в один массив пихнуть () -> Void и (Int) -> String. Компилятор тебе такого не простит, сразу начнёт орать, как сука.

Второе — захват переменных. Замыкания по умолчанию хватают всё, что вокруг них болтается, за горло сильной ссылкой. Это как тот пьяный друг на вечеринке, который вцепился в тебя и не отпускает.

И третье, самое пиздатое — циклы сильных ссылок. Вот представь: у тебя есть класс, который хранит массив замыканий. А внутри замыкания ты обращаешься к self. И получается, что self держит массив, а замыкание в массиве держит self. И они обнимаются, и никто не может умереть. Классический мексиканский сериал, ёпта! Чтобы этого не было, надо использовать списки захвата — [weak self] или [unowned self].

class Controller {
    var handlers: [() -> Void] = []
    func setupHandler() {
        // БЕЗОПАСНО: предотвращаем цикл сильных ссылок
        handlers.append { [weak self] in
            self?.doWork()
        }
    }
    func doWork() {}
}

Вот так, сука, и живём. Слабость — наше всё. Используй [weak self], и спи спокойно, не боясь утечек памяти. А то потом будешь как Герасим — Муму топить, только уже не собачку, а свою программу.