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

«Можно ли сохранить замыкание (closure) в массиве в Swift?» — вопрос из категории Swift Core, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, замыкания в 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] = []