Swift
November 6

Как управлять захватом переменных в Swift с помощью Capture Lists?

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

В этой статье сосредоточимся на концепции списков захвата и посмотрим, как они позволяют разработчикам контролировать поведение замыканий при захвате переменных.

Структура без списка захвата

Для начала рассмотрим, как работает замыкание без использования списка захвата для структуры. Создадим две структуры и замыкание, которое захватывает их переменные.

struct SimpleStruct {
    var value: Int = 0
}

var a = SimpleStruct()
var b = SimpleStruct()

let closure = {
    print(a.value, b.value)
}

a.value = 20
b.value = 20

closure() // 20 20

Как видно из примера, замыкание захватывает переменные из области видимости, и изменения, внесённые в них, отражаются при вызове замыкания.

Структура со списком захвата

Теперь посмотрим, как работает замыкание со списком захвата для структуры.

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

В случае со структурами замыкание создает копию переменной a.

struct SimpleStruct {
    var value: Int = 0
}

var a = SimpleStruct()
var b = SimpleStruct()

let closure = { [a] in
    print(a.value, b.value)
}

a.value = 20
b.value = 20

closure() // 0 20

Таким образом, при вызове замыкания выводится 0, поскольку a захвачено до изменения его значения, и замыкание на самом деле работает с копией переменной, а не с ссылкой на неё.

Теперь аналогичные эксперименты проведем с классами.

Класс без списка захвата

Теперь перейдём к аналогичному примеру, но с использованием классов. Важно помнить, что классы — это ссылочные типы.

class SimpleClass {
    var value: Int = 0
}

var a = SimpleClass()
var b = SimpleClass()

let closure = {
    print(a.value, b.value)
}

a.value = 20
b.value = 20

closure() // 20 20

Здесь замыкание также захватывает переменные a и b, и результаты отражают изменения в значениях, так как в данном случае замыкание работает с ссылками на объекты.

Класс со списком захвата

Давайте рассмотрим, что произойдёт, если передадим экземпляр класса в список захвата:

class SimpleClass {
    var value: Int = 0
}

var a = SimpleClass()
var b = SimpleClass()

let closure = { [a] in
    print(a.value, b.value)
}

a.value = 20
b.value = 20

closure() // 20 20

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

Однако, поскольку классы в Swift являются ссылочными типами, замыкание сохраняет ссылку на объект a, а не его копию. Это может создать потенциальную проблему: если объект a будет освобождён из памяти, а замыкание продолжит ссылаться на несуществующий объект, это может привести к ошибкам выполнения или циклам сильных ссылок, что, в свою очередь, может привести к утечкам памяти.

Надеюсь, теперь стало понятно, как работает механизм захвата переменных и какое влияние он оказывает на поведение замыканий в Swift.

👍 Если вам понравилась статья, поставьте лайк и подпишитесь на обновления, чтобы не пропустить новые материалы.