Назад к вопросам
Middle
107
questionbank

Как работает автоматическое управление памятью (ARC) в Swift?

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

ARC (Automatic Reference Counting) в Swift отслеживает и управляет использованием памяти вашими приложениями. Он автоматически освобождает память, используемую экземплярами классов, когда они больше не нужны, что предотвращает утечки памяти.

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

ARC работает, подсчитывая количество сильных ссылок (strong references) на каждый экземпляр класса. Сильная ссылка — это ссылка, которая удерживает экземпляр в памяти. Когда количество сильных ссылок на экземпляр становится равным нулю, ARC высвобождает связанную с ним память.

Существует три типа ссылок в Swift, влияющих на ARC:

  1. Сильные ссылки (Strong References): Увеличивают счетчик ссылок на экземпляр.
  2. Слабые ссылки (Weak References): Не увеличивают счетчик ссылок. Используются, чтобы избежать циклов сильных ссылок, когда два экземпляра класса держат сильные ссылки друг на друга. Слабая ссылка автоматически становится nil при деинициализации объекта, на который она ссылается.
  3. Бесхозные ссылки (Unowned References): Также не увеличивают счетчик ссылок. Используются, когда вы уверены, что экземпляр, на который ссылается бесхозная ссылка, всегда будет существовать, пока существует текущий экземпляр. Не могут быть опциональными (nil). Попытка доступа к деинициализированному объекту через бесхозную ссылку приведет к ошибке выполнения.

Пример цикла сильных ссылок:

class Person {
    let name: String
    var apartment: Apartment?

    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }

    deinit {
        print("\(name) is being deinitialized")
    }
}

class Apartment {
    let unit: String
    var tenant: Person?

    init(unit: String) {
        self.unit = unit
        print("Apartment \(unit) is being initialized")
    }

    deinit {
        print("Apartment \(unit) is being deinitialized")
    }
}

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john!.apartment = unit4A
unit4A!.tenant = john

john = nil // Person не деинициализируется
unit4A = nil // Apartment не деинициализируется

Чтобы решить проблему цикла, используйте слабые или бесхозные ссылки. В данном случае, tenant в Apartment должен быть слабой ссылкой:

class Person {
    let name: String
    var apartment: Apartment?

    init(name: String) {
        self.name = name
        print("\(name) is being initialized")
    }

    deinit {
        print("\(name) is being deinitialized")
    }
}

class Apartment {
    let unit: String
    weak var tenant: Person? // Использование weak reference

    init(unit: String) {
        self.unit = unit
        print("Apartment \(unit) is being initialized")
    }

    deinit {
        print("Apartment \(unit) is being deinitialized")
    }
}

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john?.apartment = unit4A
unit4A?.tenant = john

john = nil // Person деинициализируется
unit4A = nil // Apartment деинициализируется

Выбор между слабой и бесхозной ссылкой зависит от отношения между объектами:

  • Weak: Если один из объектов может стать nil до того, как другой деинициализируется (опциональное значение).
  • Unowned: Если один из объектов всегда существует, пока существует другой (не опциональное значение).

ARC также управляет памятью, используемой замыканиями (closures), захватывающими экземпляры классов. Циклы сильных ссылок могут возникать, когда замыкание захватывает экземпляр класса, а этот экземпляр класса также имеет сильную ссылку на замыкание. Для предотвращения этого используются списки захвата (capture lists) в замыканиях.

class HTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -> String = {
        // Списка захвата нет, возможен сильный цикл захвата
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name)>"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) is being deinitialized")
    }
}

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())

paragraph = nil // HTMLElement не деинициализируется

Решение с использованием списка захвата [weak self]:

class AnHTMLElement {
    let name: String
    let text: String?

    lazy var asHTML: () -> String = { [weak self] in // Использование weak self
        guard let self = self else { return "" } // Проверка на существование self
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name)>"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) is being deinitialized")
    }
}

var paragraph: AnHTMLElement? = AnHTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())

paragraph = nil // AnHTMLElement деинициализируется

Использование [unowned self] возможно, если вы уверены, что экземпляр класса (self) не станет nil до выполнения замыкания.

ARC значительно упрощает управление памятью в Swift по сравнению с ручным управлением, но требует понимания типов ссылок для предотвращения утечек памяти.