Site icon AppTractor

ARC в Swift: лучшие практики для предотвращения утечек памяти

Что такое ARC?

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

Как работает ARC

Когда вы создаете ссылку на объект в Swift, автоматический подсчет ссылок (ARC) инициализирует счетчик с начальным значением 1. Каждый раз, когда ссылка копируется, ARC увеличивает счетчик на 1, чтобы отследить дополнительную ссылку. И наоборот, когда ссылка используется в последний раз, ARC уменьшает счетчик на 1. Этот процесс увеличения и уменьшения количества ссылок обрабатывается операторами retain и release. Как только счетчик достигает 0, ARC автоматически деаллокирует объект из памяти, обеспечивая эффективное управление памятью и предотвращая возможные утечки памяти.

Давайте разберемся в этом на примере:

class Weight {
    var weight = 0
}

func test() {
    // once you created the reference variable retain operator comes in action and create reference counter for object for weight and set it to 1
    let reference1 = Weight()    // reference count = 1
    // let's create one more reference for Weight object by coping reference1
    let reference2 = reference1  // reference count = 2
    // This is the last time when we used reference1 so now release operator will decrease reference count by 1
    // reference count = 1
    reference2.weight = 56
    print("Weight = ",reference2.weight)
    // This is the last time when we used reference2 so now release operator will decrease reference count by 1
    // reference count = 0
    // Once reference count become 0 ARC will remove Weight object from memory
    print("end----------")
}

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

При использовании ссылочных типов в вашем проекте, особенно когда вы полагаетесь на наблюдаемое свойство объекта, вы можете столкнуться с тихими ошибками или неожиданным поведением из-за автоматического подсчета ссылок (ARC). Чтобы минимизировать эти риски, рекомендуется придерживаться типов значений, таких как структуры и перечисления, которые помогут предотвратить проблемы во время выполнения. Кроме того, будущие оптимизации ARC могут потенциально изменить поток вашего проекта, а это значит, что, хотя ваш код может работать нормально сегодня, его поведение может измениться по мере развития ARC. Более того, если ваш код создает сильный цикл ссылок, это может привести к утечкам памяти, что еще больше ухудшит производительность и стабильность приложения.

Что такое сильный цикл ссылок

Сильный цикл ссылок в Swift возникает, когда два или более объекта держат сильные ссылки друг на друга, не позволяя ARC уменьшить количество ссылок до нуля. В результате эти объекты остаются в памяти, что приводит к утечке памяти, поскольку ARC не может их деаллоцировать.

class Person {
    var name: String
    var vehicle: Vehicle?
    
    init(_ name: String) {
        self.name = name
        print("Person object is created")
    }
    
    deinit {
        print("Name of the person is \(name)")
    }
}

class Vehicle {
    var owner: Person?
    var modal: String
    
    init(_ modal: String) {
        self.modal = modal
        print("Vehicle object is created")
    }
    
    deinit {
        print("Model of vehicle is \(modal)")
    }
}

func test() {
    let person = Person("John") // Person reference count = 1
    let vehicle = Vehicle("ModelX") // Vehicle reference count = 1
    person.vehicle = vehicle // Vehicle reference count = 2
    vehicle.owner = person // Person reference count = 2
    // we are using person and vehicle for last time hear so 
    // Person reference count = 1
    // Vehicle reference count = 1
    // This will create strong reference cycle
}

Выход тестовой функции:

Способы борьбы с сильным циклом ссылок

Вы можете разорвать сильный цикл ссылок в Swift, используя weak и unowned ссылки, поскольку они не участвуют в подсчете. Это означает, что они не увеличивают количество ссылок на объекты, на которые они указывают. В результате, когда на объект есть только слабые или бесхозные ссылки, ARC может деаллоцировать его память, эффективно разрывая цикл.

class Person {
    var name: String
    weak var vehicle: Vehicle?
    
    init(_ name: String) {
        self.name = name
        print("Person object is created")
    }
    
    deinit {
        print("Name of the person is \(name)")
    }
}

class Vehicle {
    weak var owner: Person?
    var modal: String
    
    init(_ modal: String) {
        self.modal = modal
        print("Vehicle object is created")
    }
    
    deinit {
        print("Model of \(owner!.name) vehicle is \(modal)") // this can give you an crash
    }
}

func test() {
    let person = Person("John")
    let vehicle = Vehicle("ModelX")
    person.vehicle = vehicle
    vehicle.owner = person
}

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

Чтобы предотвратить проблемы, возникающие из-за неожиданного превращения weak ссылки в nil, в Swift можно использовать функцию withExtendedLifetime. Эта функция продлевает время жизни объекта, на который ссылаются, на время действия области видимости, в которой он используется, гарантируя, что слабый объект останется в памяти на протяжении всего выполнения функции.

func test() {
    let person = Person("John")
    let vehicle = Vehicle("ModelX")
    person.vehicle = vehicle
    vehicle.owner = person 
    withExtendedLifetime(person) {}  // This will hold the wear reference 
}

Хотя withExtendedLifetime может помочь решить проблемы, связанные со слабыми ссылками, продлевая время жизни объекта в функции, он имеет свои собственные сложности. Использование этого подхода может увеличить сложность вашей кодовой базы и требует тщательного рассмотрения того, как будет вести себя ваш код. Неправильное использование может привести к непредвиденным побочным эффектам, что усложнит сопровождение и отладку проекта.

Лучшая практика

Чтобы избежать утечек памяти, проблем во время выполнения и сложностей со слабыми ссылками в Swift, важно предотвратить создание циклов сильных ссылок. Один из эффективных подходов — уделить время продуманному структурированию кодовой базы. Разделив обязанности классов и убедившись, что каждый класс имеет четкую и изолированную цель, вы сможете свести к минимуму вероятность непреднамеренного создания сильных циклов ссылок. Такая практика не только помогает поддерживать чистоту кодовой базы, но и снижает риск столкнуться со сложными проблемами управления памятью в будущем.

class Person {
    var name: String
    
    init(_ name: String) {
        self.name = name
    }
    
}

class User {
    var id: Int
    var personalInfo: Person?
    
    init(id: Int, personalInfo: Person?) {
        self.id = id
        self.personalInfo = personalInfo
    }
    
    func printInfo() {
        print("ID of \(personalInfo!.name) is \(id)")
    }
}

class Vehicle {
    var personalInfo: Person?
    var modal: String
    
    init(personalInfo: Person?, modal: String) {
        self.personalInfo = personalInfo
        self.modal = modal
    }
    
    func printInfo() {
        print("Model of \(personalInfo!.name) vehicle is \(modal)")
    }
}

func test() {
    let person = Person("John")
    let vehicle = Vehicle(personalInfo: person, modal: "ModelX")
    let user = User(id: 007, personalInfo: person)
    vehicle.printInfo()
    user.printInfo()
}

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

Используйте [weak self]

При использовании замыканий в Swift важно помнить о потенциальных циклах сильных ссылок, особенно если замыкание захватывает self. Чтобы предотвратить такие циклы, следует использовать [weak self] в списке захвата замыкания. Это гарантирует, что ссылка на self будет слабой, что позволит ARC деаллоцировать его, если нет других сильных ссылок, и тем самым избежать утечек памяти.

Exit mobile version