Что такое 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 деаллоцировать его, если нет других сильных ссылок, и тем самым избежать утечек памяти.