فهم العد المرجعي باستخدام Cocoa وObjective-C
-
08-06-2019 - |
سؤال
لقد بدأت للتو في إلقاء نظرة على Objective-C وCocoa بهدف اللعب باستخدام iPhone SDK.أنا مرتاح بشكل معقول مع C malloc
و free
المفهوم، لكن مخطط حساب مراجع الكاكاو جعلني في حيرة من أمري.قيل لي إنها أنيقة للغاية بمجرد أن تفهمها، لكنني لم أتجاوز الحدبة بعد.
كيف release
, retain
و autorelease
العمل وما هي الاتفاقيات حول استخدامها؟
(أو إذا فشل ذلك، ما الذي قرأته وساعدك في الحصول عليه؟)
المحلول
دعنا نبدء ب retain
و release
; autorelease
هي في الحقيقة مجرد حالة خاصة بمجرد فهم المفاهيم الأساسية.
في Cocoa، يتتبع كل كائن عدد المرات التي تمت الإشارة إليه فيها (على وجه التحديد، ملف NSObject
الطبقة الأساسية تنفذ هذا).بالاتصال retain
على كائن ما، فإنك تخبره أنك تريد زيادة عدد مراجعه بمقدار واحد.بالاتصال release
, ، فأنت تخبر الكائن أنك تتخلى عنه، ويتناقص عدد مراجعه.إذا، بعد الاتصال release
, ، أصبح عدد المرجع الآن صفرًا، ثم يتم تحرير ذاكرة هذا الكائن بواسطة النظام.
الطريقة الأساسية التي تختلف عن malloc
و free
هو أن أي كائن معين لا يحتاج إلى القلق بشأن تعطل أجزاء أخرى من النظام لأنك قمت بتحرير الذاكرة التي كانوا يستخدمونها.بافتراض أن الجميع يلعبون معًا ويحتفظون/يحررون وفقًا للقواعد، عندما يحتفظ جزء واحد من التعليمات البرمجية بالكائن ثم يطلقه، فإن أي جزء آخر من التعليمات البرمجية يشير أيضًا إلى الكائن لن يتأثر.
ما يمكن أن يكون مربكًا في بعض الأحيان هو معرفة الظروف التي يجب عليك الاتصال فيها retain
و release
.قاعدتي العامة هي أنه إذا كنت أرغب في التمسك بكائن ما لبعض الوقت (إذا كان متغير عضو في الفصل الدراسي، على سبيل المثال)، فأنا بحاجة للتأكد من أن العدد المرجعي للكائن يعرف عني.كما هو موضح أعلاه، يتم زيادة العدد المرجعي للكائن عن طريق الاتصال retain
.وفقًا للاتفاقية، تتم زيادتها أيضًا (تم ضبطها على 1، حقًا) عندما يتم إنشاء الكائن باستخدام أسلوب "init".في كلتا الحالتين، تقع على عاتقي مسؤولية الاتصال release
على الكائن عندما انتهيت منه.إذا لم أفعل ذلك، سيكون هناك تسرب للذاكرة.
مثال على إنشاء الكائن:
NSString* s = [[NSString alloc] init]; // Ref count is 1
[s retain]; // Ref count is 2 - silly
// to do this after init
[s release]; // Ref count is back to 1
[s release]; // Ref count is 0, object is freed
الآن ل autorelease
.يتم استخدام الإصدار التلقائي كوسيلة ملائمة (وضرورية في بعض الأحيان) لإخبار النظام بتحرير هذا الكائن بعد فترة قصيرة.من منظور السباكة، متى autorelease
يسمى الخيط الحالي NSAutoreleasePool
يتم تنبيهه للمكالمة.ال NSAutoreleasePool
يعرف الآن أنه بمجرد حصوله على فرصة (بعد التكرار الحالي لحلقة الحدث)، يمكنه الاتصال release
على الكائن.من وجهة نظرنا كمبرمجين، فهو يعتني بالاتصال release
بالنسبة لنا، لذلك لا يتعين علينا ذلك (وفي الواقع، لا ينبغي لنا ذلك).
ما هو مهم أن نلاحظ أنه (مرة أخرى، حسب الاتفاقية) يتم إنشاء جميع الكائنات فصل تقوم الأساليب بإرجاع كائن تم إصداره تلقائيًا.على سبيل المثال، في المثال التالي، يحتوي المتغير "s" على عدد مرجعي قدره 1، ولكن بعد اكتمال حلقة الحدث، سيتم تدميره.
NSString* s = [NSString stringWithString:@"Hello World"];
إذا كنت تريد التمسك بتلك السلسلة، فستحتاج إلى الاتصال retain
صراحة ثم صراحة release
عند الانتهاء.
فكر في الجزء التالي (المفتعل جدًا) من التعليمات البرمجية، وسترى موقفًا حيث autorelease
مطلوب:
- (NSString*)createHelloWorldString
{
NSString* s = [[NSString alloc] initWithString:@"Hello World"];
// Now what? We want to return s, but we've upped its reference count.
// The caller shouldn't be responsible for releasing it, since we're the
// ones that created it. If we call release, however, the reference
// count will hit zero and bad memory will be returned to the caller.
// The answer is to call autorelease before returning the string. By
// explicitly calling autorelease, we pass the responsibility for
// releasing the string on to the thread's NSAutoreleasePool, which will
// happen at some later time. The consequence is that the returned string
// will still be valid for the caller of this function.
return [s autorelease];
}
أدرك أن كل هذا مربك بعض الشيء - لكن في مرحلة ما، سينجح الأمر.فيما يلي بعض المراجع التي ستساعدك على المضي قدمًا:
- مقدمة أبل لإدارة الذاكرة.
- برمجة الكاكاو لنظام التشغيل Mac OS X (الإصدار الرابع), بقلم آرون هيليجاس - كتاب مكتوب بشكل جيد للغاية ويحتوي على الكثير من الأمثلة الرائعة.يقرأ مثل البرنامج التعليمي.
- إذا كنت تغوص حقًا، فيمكنك التوجه إلى مزرعة الطالب الذي يذاكر كثيرا.هذه منشأة تدريب يديرها آرون هيليجاس - مؤلف الكتاب المذكور أعلاه.لقد حضرت دورة "مقدمة للكاكاو" هناك منذ عدة سنوات، وكانت طريقة رائعة للتعلم.
نصائح أخرى
إذا فهمت عملية الاحتفاظ/التحرير، فهناك قاعدتان ذهبيتان واضحتان لمبرمجي Cocoa، ولكن لسوء الحظ نادرًا ما يتم توضيح ذلك بوضوح للوافدين الجدد.
إذا كانت الوظيفة التي ترجع كائنًا لها
alloc
,create
أوcopy
باسمه فإن الكائن لك.يجب عليك الاتصال[object release]
عندما تنتهي من ذلك.أوCFRelease(object)
, ، إذا كان كائنًا أساسيًا.إذا لم يكن باسمه إحدى هذه الكلمات، فهذا يعني أن الكائن ينتمي إلى شخص آخر.يجب عليك الاتصال
[object retain]
إذا كنت ترغب في الاحتفاظ بالكائن بعد انتهاء وظيفتك.
سيكون من الأفضل لك أيضًا اتباع هذه الاتفاقية في الوظائف التي تنشئها بنفسك.
(المتصيدون:نعم، هناك للأسف عدد قليل من استدعاءات واجهة برمجة التطبيقات (API) التي تعتبر استثناءات لهذه القواعد ولكنها نادرة).
إذا كنت تكتب تعليمات برمجية لسطح المكتب ويمكنك استهداف نظام التشغيل Mac OS X 10.5، فيجب عليك على الأقل التفكير في استخدام مجموعة البيانات المهملة Objective-C.إنه سيعمل بالفعل على تبسيط معظم عمليات التطوير الخاصة بك - ولهذا السبب بذلت Apple كل جهد في إنشائها في المقام الأول، وجعلها تعمل بشكل جيد.
أما بالنسبة لقواعد إدارة الذاكرة عند عدم استخدام GC:
- إذا قمت بإنشاء كائن جديد باستخدام
+alloc/+allocWithZone:
,+new
,-copy
أو-mutableCopy
أو إذا كنت-retain
كائن ما، فأنت تمتلك ملكيته ويجب عليك التأكد من إرساله-release
. - إذا تلقيت شيئًا بأي طريقة أخرى، فأنت كذلك لا صاحبه وينبغي لا تأكد من إرساله
-release
. - إذا كنت تريد التأكد من إرسال كائن
-release
يمكنك إما إرسال ذلك بنفسك، أو يمكنك إرسال الكائن-autorelease
والتيار تجمع الإصدار التلقائي سوف نرسله-release
(مرة واحدة لكل استلام-autorelease
) عندما يتم تصريف حوض السباحة.
عادة -autorelease
يتم استخدامه كوسيلة لضمان بقاء الكائنات على قيد الحياة طوال مدة الحدث الحالي، ولكن يتم تنظيفها بعد ذلك، حيث يوجد تجمع للإصدار التلقائي يحيط بمعالجة حدث Cocoa.في الكاكاو، هو عليه بعيد من الشائع إرجاع الكائنات إلى المتصل التي تم تحريرها تلقائيًا بدلاً من إرجاع الكائنات التي يحتاج المتصل نفسه إلى تحريرها.
استخدامات الهدف-C العد المرجعي, مما يعني أن كل كائن له عدد مرجعي.عندما يتم إنشاء كائن، فإنه يحتوي على عدد مرجعي "1".ببساطة، عندما تتم الإشارة إلى كائن ما (أي تخزينه في مكان ما)، يتم "الاحتفاظ به" مما يعني زيادة عدد مراجعه بمقدار واحد.عندما لا تكون هناك حاجة لكائن ما، يتم "تحريره" مما يعني انخفاض عدد مراجعه بمقدار واحد.
عندما يكون العدد المرجعي لكائن ما هو 0، يتم تحرير الكائن.هذا هو العد المرجعي الأساسي.
بالنسبة لبعض اللغات، يتم زيادة المراجع وتقليلها تلقائيًا، لكن Object-c ليست إحدى تلك اللغات.وبالتالي فإن المبرمج مسؤول عن الاحتفاظ والإفراج.
الطريقة النموذجية لكتابة الطريقة هي:
id myVar = [someObject someMessage];
.... do something ....;
[myVar release];
return someValue;
إن مشكلة الحاجة إلى تذكر تحرير أي موارد مكتسبة داخل التعليمات البرمجية هي مشكلة مملة وعرضة للخطأ.يقدم Objective-C مفهومًا آخر يهدف إلى تسهيل ذلك كثيرًا:تجمعات الإصدار التلقائي.تجمعات الإصدار التلقائي هي كائنات خاصة يتم تثبيتها على كل مؤشر ترابط.إنها فئة بسيطة إلى حد ما، إذا بحثت عن NSAutoreleasePool.
عندما يتلقى كائن رسالة "الإصدار التلقائي" مرسلة إليه، سيبحث الكائن عن أي تجمعات تحرير تلقائي موجودة على المكدس لمؤشر الترابط الحالي هذا.سيتم إضافة الكائن إلى القائمة ككائن لإرسال رسالة "إصدار" إليه في وقت ما في المستقبل، وهو عادةً عندما يتم تحرير التجمع نفسه.
بأخذ الكود أعلاه، يمكنك إعادة كتابته ليكون أقصر وأسهل في القراءة بالقول:
id myVar = [[someObject someMessage] autorelease];
... do something ...;
return someValue;
نظرًا لأنه تم تحرير الكائن تلقائيًا، لم نعد بحاجة إلى استدعاء "الإصدار" بشكل صريح عليه.هذا لأننا نعلم أن بعض مجموعات الإصدار التلقائي ستفعل ذلك لنا لاحقًا.
نأمل أن يساعد هذا.مقالة ويكيبيديا جيدة جدًا فيما يتعلق بإحصاء المراجع.مزيد من المعلومات حول يمكن العثور على مجمعات الإصدار التلقائي هنا.لاحظ أيضًا أنه إذا كنت تقوم بالإنشاء لنظام التشغيل Mac OS X 10.5 والإصدارات الأحدث، فيمكنك إخبار Xcode بالإنشاء مع تمكين جمع البيانات المهملة، مما يسمح لك بتجاهل الاحتفاظ/الإصدار/الإصدار التلقائي تمامًا.
Joshua (#6591) - تبدو عناصر مجموعة البيانات المهملة في نظام التشغيل Mac OS X 10.5 رائعة جدًا، ولكنها غير متوفرة لجهاز iPhone (أو إذا كنت تريد تشغيل تطبيقك على إصدارات ما قبل 10.5 من نظام التشغيل Mac OS X).
أيضًا، إذا كنت تكتب مكتبة أو شيئًا يمكن إعادة استخدامه، فإن استخدام وضع GC يمنع أي شخص يستخدم الكود من استخدام وضع GC أيضًا، لذا، كما أفهمه، فإن أي شخص يحاول كتابة تعليمات برمجية قابلة لإعادة الاستخدام على نطاق واسع يميل إلى الإدارة الذاكرة يدويا.
كما هو الحال دائمًا، عندما يبدأ الأشخاص بمحاولة إعادة صياغة المادة المرجعية، فإنهم يخطئون دائمًا تقريبًا أو يقدمون وصفًا غير كامل.
توفر Apple وصفًا كاملاً لنظام إدارة ذاكرة Cocoa في دليل برمجة إدارة الذاكرة للكاكاو, ، وفي نهايته يوجد ملخص موجز ولكن دقيق لل قواعد إدارة الذاكرة.
لن أضيف إلى تفاصيل الاحتفاظ/الإصدار بخلاف أنك قد ترغب في التفكير في إسقاط 50 دولارًا والحصول على كتاب Hillegass، ولكني أقترح بشدة الدخول في استخدام أدوات الأدوات في وقت مبكر جدًا من تطوير تطبيقك (حتى تطبيقك أول واحد!).للقيام بذلك، تشغيل->ابدأ بأدوات الأداء.سأبدأ مع التسريبات التي تعد مجرد واحدة من العديد من الأدوات المتاحة ولكنها ستساعد في إظهار متى نسيت إصدارها.إنه أمر شاق للغاية مقدار المعلومات التي سيتم تقديمها لك.لكن تحقق من هذا البرنامج التعليمي للنهوض والتحرك بسرعة:
دروس الكاكاو:إصلاح تسرب الذاكرة باستخدام الأدوات
في الواقع تحاول قوة قد تكون التسريبات طريقة أفضل لتعلم كيفية منعها!حظ سعيد ؛)
إرجاع إصدار [[الإصدار التلقائي]]؛
الإصدار التلقائي يفعل لا الاحتفاظ بالكائن.يقوم الإصدار التلقائي ببساطة بوضعه في قائمة الانتظار ليتم إصداره لاحقًا.أنت لا تريد أن يكون لديك بيان الإفراج هناك.
مجموعتي المعتادة من مقالات إدارة ذاكرة الكاكاو:
يتوفر تسجيل رقمي للشاشة مجانًا من شبكة iDeveloperTV
تعتبر إجابة NilObject بداية جيدة.إليك بعض المعلومات التكميلية المتعلقة بإدارة الذاكرة اليدوية (مطلوب على اي فون).
إذا كنت شخصيا alloc/init
كائن، ويأتي مع عدد مرجعي من 1.أنت مسؤول عن التنظيف بعد ذلك عندما لا تكون هناك حاجة إليه، إما عن طريق الاتصال [foo release]
أو [foo autorelease]
.يقوم الإصدار بتنظيفه على الفور، بينما يقوم الإصدار التلقائي بإضافة الكائن إلى تجمع الإصدار التلقائي، والذي سيحرره تلقائيًا في وقت لاحق.
يكون الإصدار التلقائي في المقام الأول عندما يكون لديك طريقة تحتاج إلى إرجاع الكائن المعني (لذلك لا يمكنك تحريره يدويًا، وإلا فسوف تقوم بإرجاع كائن صفري) ولكنك لا تريد الاحتفاظ بها أيضًا.
إذا حصلت على كائن لم تقم باستدعاء تخصيص/init للحصول عليه - على سبيل المثال:
foo = [NSString stringWithString:@"hello"];
لكنك تريد التمسك بهذا الكائن، فأنت بحاجة إلى الاتصال بـ [foo Retain].خلاف ذلك، فمن الممكن أن يحصل autoreleased
وسوف تتمسك بمرجع صفري (كما هو الحال في ما سبق stringWithString
مثال).عندما لم تعد بحاجة إليها، اتصل [foo release]
.
الإجابات أعلاه تعطي إعادة صياغة واضحة لما تقوله الوثائق؛المشكلة التي يواجهها معظم الأشخاص الجدد هي الحالات غير الموثقة.على سبيل المثال:
الإصدار التلقائي:تقول المستندات إنها ستؤدي إلى إصدار "في مرحلة ما في المستقبل". متى؟!في الأساس، يمكنك الاعتماد على الكائن الموجود حولك حتى تخرج من التعليمات البرمجية الخاصة بك مرة أخرى إلى حلقة أحداث النظام.قد يقوم النظام بتحرير الكائن في أي وقت بعد دورة الحدث الحالية.(أعتقد أن مات قال ذلك سابقًا).
سلاسل ثابتة:
NSString *foo = @"bar";
- هل يجب عليك الاحتفاظ بذلك أو إطلاقه؟لا.ماذا عن-(void)getBar { return @"bar"; }
...
NSString *foo = [self getBar]; // still no need to retain or release
قاعدة الخلق:إذا قمت بإنشائه، فأنت تملكه، ومن المتوقع أن تقوم بإصداره.
بشكل عام، الطريقة التي يخطئ بها مبرمجو Cocoa الجدد هي عدم فهم الإجراءات الروتينية التي تُرجع كائنًا ذو قيمة retainCount > 0
.
هنا مقتطف من قواعد بسيطة جدًا لإدارة الذاكرة في الكاكاو:
قواعد عدد الاستبقاء
- داخل كتلة معينة، يجب أن يكون استخدام -copy و-alloc و-retain مساويًا لاستخدام -release و-autorelease.
- الكائنات التي تم إنشاؤها باستخدام منشئات الراحة (على سبيل المثال.تعتبر سلسلة NSString's stringWithString) تم إصدارها تلقائيًا.
- قم بتنفيذ طريقة -dealloc لتحرير متغيرات المثيل التي تمتلكها
الرصاصة الأولى تقول:إذا اتصلت alloc
(أو new fooCopy
)، تحتاج إلى استدعاء الإصدار على هذا الكائن.
الرصاصة الثانية تقول:إذا كنت تستخدم منشئ الراحة وتحتاج إلى الكائن للتسكع (كما هو الحال مع الصورة التي سيتم رسمها لاحقًا)، تحتاج إلى الاحتفاظ بها (ثم تحريرها لاحقًا).
والثالث ينبغي أن يكون واضحا.
الكثير من المعلومات الجيدة عن Cocodev أيضًا:
كما ذكر العديد من الأشخاص بالفعل، شركة أبل مقدمة لإدارة الذاكرة هو إلى حد بعيد أفضل مكان للبدء.
أحد الروابط المفيدة التي لم أرها مذكورة بعد هو إدارة الذاكرة العملية.ستجده في منتصف مستندات Apple إذا قرأتها، ولكنه يستحق الارتباط المباشر.إنه ملخص تنفيذي رائع لقواعد إدارة الذاكرة مع أمثلة وأخطاء شائعة (في الأساس ما تحاول الإجابات الأخرى هنا شرحه، ولكن ليس كذلك).