Назад к вопросам
Junior
77
questionbank

Что такое утечки памяти?

Sobes Copilot

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

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

sobes.tech AI

Утечки памяти — это ситуация, когда выделенная в памяти область более не используется программой, но при этом ссылки на нее сохраняются, препятствуя ее освобождению сборщиком мусора или системой управления памятью. В Swift, управляемом ARC (Automatic Reference Counting), утечки часто происходят из-за циклических сильных ссылок (strong reference cycles).

ARC учитывает количество сильных ссылок на объект и освобождает его, когда счетчик достигает нуля. Если два объекта сильно ссылаются друг на друга, их счетчики никогда не станут нулем, даже если на них больше нет внешних ссылок.

Примеры причин утечек:

  • Циклические сильные ссылки между экземплярами классов.
  • Циклические сильные ссылки в замыканиях. Замыкание по умолчанию захватывает переменные по сильной ссылке. Если экземпляр класса содержит сильную ссылку на замыкание, а замыкание сильно ссылается обратно на этот экземпляр, возникает цикл.

Для предотвращения циклических ссылок используются слабые (weak) и бесхозные (unowned) ссылки:

  • weak: Не увеличивает счетчик ссылок. Ссылка становится nil, когда объект, на который она ссылается, освобождается. Используется, когда объекты имеют связанный, но не эксклюзивный жизненный цикл (например, делегат). Объявляется как опционал.
  • unowned: Не увеличивает счетчик ссылок. Используется, когда ссылка на объект всегда будет существовать, пока существует объект, который на нее ссылается. Является неоционалом; при попытке обращения к освобожденному объекту приводит к сбою программы (crash). Предпочтительно для ситуаций, где один объект является "родителем" другого, и "дочерний" объект не может существовать без "родителя".

В замыканиях для разрыва циклов используются списки захвата (capture lists):

// До утечки
class A {
    var b: B?
    deinit { print("A deallocated") }
}

class B {
    var a: A?
    deinit { print("B deallocated") }
}

var instanceA: A? = A()
var instanceB: B? = B()

instanceA?.b = instanceB
instanceB?.a = instanceA

instanceA = nil
instanceB = nil
// Оба объекта не будут освобождены из-за сильных циклических ссылок


// После устранения утечки с использованием weak
class A {
    weak var b: B? // Использование weak
    deinit { print("A deallocated") }
}

class B {
    weak var a: A? // Использование weak
    deinit { print("B deallocated") }
}

var instanceA: A? = A()
var instanceB: B? = B()

instanceA?.b = instanceB
instanceB?.a = instanceA

instanceA = nil
instanceB = nil
// Оба объекта будут освобождены

// Утечка памяти с замыканием
class ViewController {
    var dataFetcher: DataFetcher?

    func setupDataFetcher() {
        dataFetcher = DataFetcher()
        dataFetcher?.completionHandler = {
            // Замыкание сильно ссылается на self (ViewController)
            self.updateUI()
        }
    }

    func updateUI() {
        print("UI updated")
    }

    deinit {
        print("ViewController deallocated")
    }
}

class DataFetcher {
    var completionHandler: (() -> Void)?
    deinit { print("DataFetcher deallocated") }
}

var vc: ViewController? = ViewController()
vc?.setupDataFetcher()
vc = nil // ViewController не будет освобожден

// Устранение утечки памяти с замыканием (использование weak self)
class ViewController {
    var dataFetcher: DataFetcher?

    func setupDataFetcher() {
        dataFetcher = DataFetcher()
        dataFetcher?.completionHandler = { [weak self] in // Использование [weak self]
            // Теперь замыкание слабо ссылается на self
            self?.updateUI() // self теперь опционал
        }
    }

    func updateUI() {
        print("UI updated")
    }

    deinit {
        print("ViewController deallocated")
    }
}

class DataFetcher {
    var completionHandler: (() -> Void)?
    deinit { print("DataFetcher deallocated") }
}

var vc: ViewController? = ViewController()
vc?.setupDataFetcher()
vc = nil // ViewController будет освобожден

Использование unowned self вместо weak self в замыканиях возможно, если вы уверены, что экземпляр self будет существовать на момент выполнения замыкания. В противном случае unowned self приведет к краху.

Инструменты для обнаружения утечек:

  • Instruments (Allocations, Leaks): Позволяют профилировать потребление памяти и определять циклические ссылки.
  • Memory Graph Debugger в Xcode: Визуализирует граф объектов и ссылок в памяти, что помогает быстро найти циклические ссылки.
  • MLeaksFinder (сторонняя библиотека для UIKit-приложений): Автоматически обнаруживает утечки View Controller'ов и View'ов.

Эффективное управление памятью и предотвращение утечек критически важно для стабильности и производительности iOS-приложений.