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

Ответ

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

Базовый пример:

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

tasks.append { print("Task 1") }
tasks.append { print("Task 2") }

// Выполнение всех замыканий в массиве
for task in tasks {
    task() // Выведет "Task 1", затем "Task 2"
}

Пример с параметрами и возвращаемым значением:

// Массив замыканий, преобразующих Int в String
let transformers: [(Int) -> String] = [
    { "Number: ($0)" },
    { String(repeating: "*($0)", count: 3) },
    { _ in "Constant" }
]

let results = transformers.map { $0(5) }
// results = ["Number: 5", "*5*5*5", "Constant"]

Критически важные аспекты:

  1. Единая сигнатура: Все замыкания в массиве должны иметь идентичную сигнатуру (типы параметров и возвращаемого значения).
  2. Циклы сильных ссылок: Если замыкание захватывает self или другие объекты, это может создать retain cycle.

    class Controller {
        var closures: [() -> Void] = []
        var value = 10
    
        func addClosure() {
            // ВНИМАНИЕ: Создается сильная ссылка на `self`
            closures.append { print(self.value) }
            // Решение: использовать capture list [weak self]
            closures.append { [weak self] in print(self?.value ?? 0) }
        }
    }
  3. Типизация: Для сложных сигнатур используйте typealias для читаемости.
    typealias CompletionHandler = (Result<Data, Error>) -> Void
    var completions: [CompletionHandler] = []

Ответ 18+ 🔞

Слушай, а ведь замыкания в Swift — это как те самые универсальные солдатики, их можно в кучки собирать, в массивы пихать, в словари запихивать, да куда угодно, блядь! Типы первого класса, ёпта, вот это мощь!

Смотри, как просто, на примере:

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

tasks.append { print("Task 1") }
tasks.append { print("Task 2") }

// Выполнение всех замыканий в массиве
for task in tasks {
    task() // Выведет "Task 1", затем "Task 2"
}

Вот, собрал задачники в кучку и по очереди их всех запустил — красота, блядь!

А вот если с параметрами поиграться:

// Массив замыканий, преобразующих Int в String
let transformers: [(Int) -> String] = [
    { "Number: ($0)" },
    { String(repeating: "*($0)", count: 3) },
    { _ in "Constant" }
]

let results = transformers.map { $0(5) }
// results = ["Number: 5", "*5*5*5", "Constant"]

Три разных замыкания, но сигнатура одна — (Int) -> String. Как три клоуна в одном цирке, но делают разное, сука!

А теперь, внимание, ебаный рот, важные моменты, где можно на граблях встать:

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

  2. Циклы сильных ссылок — вот это пиздец! Самый частый подводный камень. Если замыкание хватает self и ты его в массив суёшь, а массив живет в этом же self — получается хоровод, где все держатся за руки и никто не умирает. Память течёт, как суп через дырявое сито.

    class Controller {
        var closures: [() -> Void] = []
        var value = 10
    
        func addClosure() {
            // ВНИМАНИЕ: Создается сильная ссылка на `self` — ПИЗДЕЦ, УТЕЧКА!
            closures.append { print(self.value) }
            // Решение, блядь: использовать capture list [weak self]
            closures.append { [weak self] in print(self?.value ?? 0) }
        }
    }

    Видишь? В первом случае self и массив closures будут вечно любить друг друга. Во втором — слабая ссылка, [weak self], это как сказать: «Дружим, но если ты умрёшь — я не повешусь».

  3. Типизация для читаемости. Когда сигнатура замыкания длиннее, чем очередь за колбасой в девяностые, используй typealias. Глаза не сломаешь.

    typealias CompletionHandler = (Result<Data, Error>) -> Void
    var completions: [CompletionHandler] = []

    Согласись, [CompletionHandler] выглядит куда приятнее, чем [(Result<Data, Error>) -> Void]. Ёперный театр!

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