Automatic Reference Counting (ARC) هو نظام إدارة ذاكرة في Swift و Objective-C يقوم تلقائياً بعد عدد المراجع لكل كائن وتحريره عندما يصل العداد إلى الصفر. وفقاً توثيق Apple Swift، 2026، يتم دمج ARC في المترجم ويعمل في مرحلة الترجمة، مدخلاً استدعاءات retain/release في الأماكن المناسبة. على عكس Garbage Collection، لا يتطلب ARC خيطاً منفصلاً للمجمع ولا يُنشئ توقفاً أثناء تنفيذ التطبيق.
النقاط الرئيسية
ARC (Automatic Reference Counting) هو آلية إدارة ذاكرة قائمة على المترجم قدمتها Apple في Xcode 4.2 (2011) لـ Objective-C وورثتها Swift. على عكس إدارة الذاكرة اليدوية (Manual Retain-Release، MRR)، يقوم ARC بأتمتة استدعاءات retain و release و autorelease بالكامل، مدخلاً إياها في مرحلة الترجمة دون تدخل المطور.
ARC ليس مجمع قمامة. إنه تحليل ثابت مع إدخال ديناميكي للكود: يحلل المترجم أعمار الكائنات ويضع retain/release في النقاط التي يتم فيها إنشاء الكائنات أو نسخها أو خروجها من النطاق. النتيجة هي تحرير ذاكرة حتمي: يتم حذف الكائن بالضبط عندما لا تشير إليه أي مراجع أخرى، دون تأخير أو توقف.
وفقاً WWDC 2011 Session 323، أدى الانتقال من MRR إلى ARC إلى تقليل أخطاء الأعطال المتعلقة بالذاكرة بنسبة 70% في تطبيقات Apple. توقف المطورون عن الموازنة اليدوية لـ retain/release، مما قضى على فئة كاملة من تسربات الذاكرة وأخطاء double-free.
كل كائن في الذاكرة له عداد مراجع (retain count). عند إنشاء كائن، يتم تعيين العداد إلى 1. عندما يشير مرجع strong جديد إلى الكائن — يزداد العداد (retain). عندما يختفي مرجع strong — ينخفض العداد (release). عند الوصول إلى الصفر، يتم تحرير الكائن فوراً.
يقوم مترجم Swift بإدراج retain/release ليس عند كل تعيين — بل يستخدم التحليل الثابت للتحسين. على سبيل المثال، إذا تم ضمان عدم استخدام كائن بعد تمريره، قد يتخطى المترجم release/retain غير ضروري. يُسمى هذا التحسين ARC Optimization.
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) initialized (retain count: 1)")
}
deinit {
print("\(name) deallocated")
}
}
func testARC() {
let p = Person(name: "Alice") // retain count = 1
let q = p // retain count = 2
// q يخرج من النطاق
// retain count = 1
// p يخرج من النطاق
// retain count = 0 → deinit
}
يوضح هذا المثال كيف يدير ARC العداد: عند تعيين q = p، يزداد العداد؛ عندما يخرج q من النطاق، ينخفض. عندما يختفي آخر مرجع strong، يتم استدعاء المدمر فوراً. لا ينتظر أي مجمع قمامة — يتم تحرير الذاكرة فوراً.
ARC و Garbage Collection يحلان نفس المشكلة — إدارة الذاكرة التلقائية — ولكن بمناهج مختلفة جوهرياً. يحدد الاختيار بينهما بنية اللغة: Swift (ARC) مقابل Java/Go (GC). دعونا نلقي نظرة على الفروق الرئيسية.
| الخاصية | ARC (Swift/ObjC) | GC (Java/Go) |
|---|---|---|
| وقت التحرير | حتمي: فوراً عند وصول العداد إلى الصفر | غير حتمي: في دورة التجميع التالية |
| توقف التنفيذ | لا يوجد (retain/release مدخلة في الترجمة) | توقف Stop-The-World (2–200 ملي ثانية) |
| الكلفة الإضافية | زيادة/نقصان العداد عند كل مرجع | اجتياز رسم بياني للكائنات، وضع علامات، تحرير |
| المشاكل | Retain Cycle (حل يدوي) | تجزئة الكومة، تسربات من مراجع منسية |
| خيط إضافي | غير مطلوب | يتطلب خيط مجمع القمامة |
المقايضة الرئيسية: ARC يوفر أعمار كائنات قابلة للتنبؤ وبدون توقف، لكنه يتطلب من المطور فهم retain cycles واختيار weak/unowned بشكل صحيح. GC يحرر المطور من هذه الاهتمامات، ولكن على حساب توقف غير حتمي وخيط إضافي.
يحدد ARC ثلاثة أنواع من معدّلات المرجع، كل منها يؤثر على العداد ودورة حياة الكائن بشكل مختلف. اختيار المُعدّل الصحيح هو أساس إدارة الذاكرة الآمنة في Swift.
Strong هو المُعدّل الافتراضي. كل مرجع strong يزيد retain count للكائن بمقدار 1. طالما يوجد مرجع strong واحد على الأقل، يبقى الكائن حياً. جميع خصائص الفئة والمتغيرات المحلية في Swift هي strong بشكل افتراضي. تُنشئ المراجع strong علاقة ملكية: الكائن A يملك الكائن B.
Weak هو مرجع لا يزيد retain count. يمكن تحرير كائن حتى لو كان هناك مرجع weak يشير إليه. بعد التحرير، يتم تعيين المرجع weak تلقائياً إلى nil. تُعلن المراجع weak دائماً كـ var بنوع اختياري (?). تُستخدم لكسر retain cycles، خاصة في نمط delegate.
Unowned هو مرجع غير مالك، مثل weak، لا يزيد retain count. ومع ذلك، لا يتم تعيين المرجع unowned إلى nil بعد التحرير — الوصول إلى كائن محرر يسبب تعطل التطبيق. يُستخدم unowned عندما يكون مضموناً أن الكائن يعيش على الأقل بقدر الكائن المُشار إليه. حالات الاستخدام النموذجية هي الإغلاقات (closures) وعلاقات الأب-الابن مع عمر مضمون.
class Customer {
let name: String
var card: CreditCard? // strong
init(name: String) { self.name = name }
deinit { print("\(name) deallocated") }
}
class CreditCard {
let number: String
unowned let customer: Customer // unowned — لا يملك
init(number: String, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card \(number) deallocated") }
}
var customer: Customer? = Customer(name: "Bob")
customer?.card = CreditCard(number: "1234", customer: customer!)
customer = nil
// Customer و CreditCard كلاهما محرر — لا يوجد retain cycle
هنا تستخدم CreditCard مرجعاً unowned إلى Customer. Customer يملك البطاقة (strong)، والبطاقة لا تملك العميل (unowned). عندما يتم تحرير Customer، يتم تحرير كلا الكائنين — لا يحدث retain cycle. لو كان card.customer strong، لكانت الدورة قد منعت التحرير.
على الرغم من الأتمتة، ARC ليس حلاً سحرياً. يواجه المطورون عدة مشاكل نموذجية تتطلب فهماً للآلية الداخلية لإدارة الذاكرة.
الإغلاقات (Closures) في Swift تلتقط المتغيرات الخارجية بمرجع strong. إذا تم تعيين إغلاق لخاصية فئة والتقط self — يحدث retain cycle: الفئة تحتفظ بالإغلاق، والإغلاق يحتفظ self. الحل هو قائمة التقاط مع weak أو unowned.
class NetworkManager {
var completionHandler: ((Data?) -> Void)?
var data: Data?
func fetchData() {
completionHandler = { [weak self] result in
guard let self else { return }
self.data = result
self.processResult()
}
}
func processResult() { }
}
قائمة الالتقاط [weak self] تُنشئ مرجعاً weak إلى self داخل الإغلاق. هذا يكسر retain cycle المحتمل. Guard let self يضمن أن الكائن حي قبل تنفيذ الكود. weak self هي الممارسة القياسية للإغلاقات غير المتزامنة في Swift.
على الرغم من أن retain/release عمليات خفيفة، إلا أن الزيادات/النقصانات المتكررة للعداد في الحلقات المكثفة تُضيف كلفة إضافية. في Swift 5.9+، يستخدم المترجم تحسيناً يزيل retain/release الزائدة إذا أثبت المحلل أنها آمنة. ومع ذلك، في Objective-C، لا يزال retain/release يمكن أن يكون عنق زجاجة في سيناريوهات الحمل العالي بملايين الاستدعاءات في الثانية.
Autorelease Pool هو آلية تحرير مؤجلة تُستخدم في Objective-C وبعض سيناريوهات Swift. تُوضع الكائنات في المجمع وتتلقى release عند تفريغ المجمع. في الحلقات التي تحتوي على العديد من الكائنات المؤقتة (مثل تحليل JSON)، يُقلل إنشاء autoreleasepool مخصص من الاستهلاك الأقصى للذاكرة.
الأسئلة الشائعة
في الإدارة اليدوية (MRR)، كان المطور يستدعي صراحة retain و release و autorelease. ARC يُدخل هذه الاستدعاءات تلقائياً في مرحلة الترجمة، مما يلغي خطر double-free والتسربات بسبب نسيان release وأخطاء موازنة retain/release.
ARC يدير فقط كائنات Objective-C وفئات Swift. بالنسبة لهياكل ومؤشرات C/C++، لا يُطبق ARC — تُدار هذه الكائنات يدوياً أو عبر المؤشرات الذكية لـ C++ (shared_ptr, unique_ptr). كائنات Core Foundation (CFString, CGColor) أيضاً لا تغطيها ARC.
weak — عندما يمكن تحرير الكائن قبل الكائن المُشار إليه (المفوّضون، الإغلاقات غير المتزامنة). unowned — عندما يكون مضموناً أن الكائن يعيش على الأقل بقدر المُشار إليه (أب-ابن حيث لا يمكن للابن الوجود بدون الأب). إذا لم تكن متأكداً، اختر weak.
الأنواع الوجودية (protocol كـنوع) في Swift تُغلّف القيمة في حاوية خاصة (حاوية وجودية). هذا يزيد عدد retain/release على حدود البروتوكولات. في Swift 5.7+، تُقلل أنواع النتائج غير الشفافة ومعاملات some الكلفة الإضافية بإزالة الحاوية.
لا توجد واجهة برمجة تطبيقات مباشرة لقراءة retain count في Swift — فهي تعتبر تفصيل تنفيذي. للتشخيص، استخدم Instruments (Allocations, Leaks) أو مصحح الذاكرة في Xcode. تُظهر هذه الأدوات عدد نسخ الفئة الحية وسلاسل الاحتفاظ.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا