Sobes.tech
Middle

Циклҳои нигоҳдорӣ (retain cycles) дар iOS чист?

sobes.tech AI

Ҷавоб аз AI

دورهای نگهداری زمانی رخ می‌دهند که دو یا چند شیء دارای ارجاعات قوی به یکدیگر باشند، و یک "حلقه بسته" ایجاد کنند. در نتیجه، شمارنده ارجاع هر شیء هرگز به صفر نمی‌رسد و نمی‌تواند توسط ARC (شماره‌گذاری مرجع خودکار) آزاد شود. این منجر به نشت حافظه می‌شود، زیرا اشیاء حتی پس از پایان استفاده در حافظه باقی می‌مانند.

مثال:

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

    init(name: String) {
        self.name = name
        print("\(name) در حال مقداردهی اولیه است")
    }

    deinit {
        print("\(name) در حال آزادسازی است")
    }
}

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

    init(unit: String) {
        self.unit = unit
        print("Apartment \(unit) در حال مقداردهی اولیه است")
    }

    deinit {
        print("Apartment \(unit) در حال آزادسازی است")
    }
}

در این مثال، اگر Person ارجاع قوی به Apartment داشته باشد و Apartment ارجاع قوی به Person، در تلاش برای آزادسازی این اشیاء، آنها حذف نخواهند شد، زیرا شمارنده‌های ارجاع آنها به صفر نمی‌رسد.

چگونه مشکل حلقه‌های نگهداری را حل کنیم:

از ارجاعات ضعیف (weak) یا بدون مالک (unowned) استفاده کنید.

  • weak: زمانی استفاده می‌شود که ممکن است شیء مرتبط در طول چرخه حیات شیء دیگر nil شود. ارجاع به صورت خودکار nil می‌شود زمانی که شیء مورد اشاره آزاد می‌شود. این اختیاری است.
  • unowned: زمانی استفاده می‌شود که می‌دانید شیء مرتبط همان یا طولانی‌تر از چرخه حیات شیء است که به آن ارجاع دارد. نمی‌تواند nil باشد.

مثال اصلاح‌شده با استفاده از weak:

ارجاع tenant در کلاس Apartment را به صورت ضعیف تغییر می‌دهیم:

class Apartment {
    let unit: String
    weak var tenant: Person? // استفاده از weak

    init(unit: String) {
        self.unit = unit
        print("Apartment \(unit) در حال مقداردهی اولیه است")
    }

    deinit {
        print("Apartment \(unit) در حال آزادسازی است")
    }
}

حالا، زمانی که شیء Person آزاد می‌شود، ارجاع tenant در Apartment به صورت خودکار nil می‌شود، حلقه نگهداری قطع می‌شود و اجازه می‌دهد Apartment نیز آزاد شود.

مقایسه weak و unowned:

ویژگی weak unowned
نوع ارجاع اختیاری غیر اختیاری
می‌تواند nil باشد؟ بله، زمانی که شیء آزاد می‌شود خیر
چه زمانی استفاده شود؟ زمانی که شیء ممکن است nil شود زمانی که شیء نباید nil باشد
عملکرد کمی کندتر به دلیل بررسی nil کمی سریع‌تر

همچنین، حلقه‌های نگهداری ممکن است در بسته‌ها (closures) نیز رخ دهند.

وقتی یک بسته، ویژگی‌های نمونه یا self را می‌گیرد، می‌تواند ارجاع قوی به شیء ایجاد کند. اگر این شیء نیز ارجاع قوی به بسته داشته باشد (مثلاً از طریق ویژگی-بسته یا delegate)، حلقه نگهداری ایجاد می‌شود.

چگونه مشکل حلقه‌های نگهداری در بسته‌ها را حل کنیم:

از لیست ضبط (capture list) استفاده کنید.

class SomeClass {
    var value = 0

    func doSomethingDelayed() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in // لیست ضبط با weak self
            guard let self = self else { return } // بررسی `nil` برای weak self
            self.value += 1
            print("مقدار اکنون \(self.value)")
        }
    }
}

در این مثال، [weak self] یک ارجاع ضعیف به self در داخل بسته ایجاد می‌کند، که از حلقه نگهداری جلوگیری می‌کند.