ios - هل من الآمن فرض المتغيرات غير الملزمة التي تم الوصول إليها اختياريًا في نفس سطر التعليمات البرمجية؟




swift memory-management (4)

هل هذا آمن دائمًا؟

لا أنت لا تفعل "رقصة ضعيفة ضعيفة". افعلها! كلما استخدمت weak self ، يجب عليك لف الخيار الاختياري بأمان ، ثم الرجوع فقط إلى نتيجة إلغاء التغليف - مثل هذا:

someFunction(completion: { [weak self] in
    if let sself = self { // safe unwrap
        // now refer only to `sself` here
        sself.variable = sself.otherVariable
        // ... and so on ...
    }
})
someFunction(completion: { [weak self] in
    self?.variable = self!.otherVariable
})

هل هذا آمن دائمًا ؟ يمكنني الوصول إلى self الاختيارية في بداية البيان ، وأنا شخصياً أفترض أن الجزء الثاني من هذا البيان لن يتم تنفيذه أبدًا إذا كانت self غير موجودة. هل هذا صحيح؟ إذا كانت self nil بالفعل ، فإن الجزء الثاني لن يحدث أبداً؟ ولن يحدث أبدًا أن self يمكن أن "تُحذف" خلال هذا السطر الوحيد من الشفرة؟


لا ، هذا ليس آمنا

كما أشارHamish في تعليق أدناه ، يصف مهندس المترجم السريع Swift Joe Groff أنه لا يوجد أي ضمان بأن يتم الاحتفاظ بمرجع قوي طوال فترة تقييم RHS [ منجم التركيز ]

تأكيد ترتيب العمليات

Rod_Brown:

مرحبا،

أنا أتساءل عن سلامة نوع الوصول على متغير ضعيف:

class MyClass {

    weak var weakProperty: MyWeakObject?

    func perform() {
        // Case 1
        weakProperty?.variable = weakProperty!.otherVariable

        // Case 2
        weakProperty?.performMethod(weakProperty!)
    }
}

مع الحالتين المذكورتين أعلاه ، هل تضمن سويفت إمكانية weakProperty قوة weakProperty في هذه المواقف؟

أنا weakProperty! بالضمانات التي تجعلها سويفت حول الوصول خلال weakProperty! الاختيارية على سبيل المثال هي weakProperty! الملحقات المضمونة لإطلاق النار فقط إذا كان التسلسل الاختياري يحدد أولاً أن القيمة غير موجودة بالفعل؟

إضافةً إلى ذلك ، هل يُمكن الاحتفاظ بالكائن الضعيف طوال فترة هذا التقييم ، أم هل من المحتمل أن يكون المتغير الضعيف قادرًا على التنقل بين الوصول الاختياري والطريقة التي يتم استدعاؤها؟

Joe_Groff:

هذا غير مضمون. قد يتم تحسين الإصدارات في وقت مبكر من هذا ، إلى أي نقطة بعد الاستخدام الرسمي الأخير للإشارة القوية. نظرًا لأن المرجع القوي الذي تم تحميله من أجل تقييم الجانب weakProperty?.variable لا يتم استخدام weakProperty?.variable بعد ذلك ، فلا يوجد شيء يبقيه على قيد الحياة ، لذلك يمكن إصداره على الفور. إذا كان هناك أي آثار جانبية في getter للمتغير والتي تتسبب في إلغاء تخصيص الكائن المشار إليه weakProperty ، مع nil وجود مرجع ضعيف ، فإن ذلك قد يتسبب في فشل weakProperty القوة على الجانب الأيمن . يجب أن تستخدم إذا سمحت لاختبار المرجع الضعيف ، وأن تشير إلى المرجع القوي المرتبط بـ if let:

if let property = weakProperty {
  property.variable = property.otherVariable
  property.performMethod(property)
}

يجب أن يكون هذا أكثر أمانًا وأكثر كفاءة ، حيث يتم تحميل المرجع الضعيف واختباره مرة واحدة بدلاً من أربع مرات.

بالنظر إلى الإجابة المقتبسة من إجابة Joe Groff أعلاه ، فإن إجابتي السابقة هي moot ، لكنني سأتركها هنا في رحلة مثيرة ربما (وإن كانت فاشلة) إلى أعماق برنامج التحويل البرمجي لـ Swift.

إجابة تاريخية تصل إلى حجة نهائية صحيحة ، ولكن من خلال رحلة مثيرة للاهتمام ، مع ذلك

سأبني هذه الإجابة على تعليقي علىappzYourLife: الإجابة المحذوفة:

هذه مجرد تكهنات محضة ، ولكن بالنظر إلى الارتباط الوثيق إلى حد ما بين العديد من المطورين ذوي الخبرة من Swift و L + C ++: s Boost ، أفترض أن الإشارة weak مؤمنة في إشارة قوية طوال فترة التعبير ، إذا كان هذا يعين / يتحول شيء ما في self ، يشبه إلى حد كبير std::weak_ptr::lock() المستخدم بشكل صريح std::weak_ptr::lock() C ++.

دعونا نلقي نظرة على مثالك ، حيث تم التقاط self من خلال مرجع weak وليس nil عند الوصول إلى الجانب الأيسر من تعبير المهمة

self?.variable = self!.otherVariable
/* ^             ^^^^^-- what about this then?
   |
    \-- we'll assume this is a success */

قد ننظر إلى المعالجة الأساسية لمراجع (Swift) weak في وقت تشغيل Swift ، swift/include/swift/Runtime/HeapObject.h وجه التحديد :

/// Load a value from a weak reference.  If the current value is a
/// non-null object that has begun deallocation, returns null;
/// otherwise, retains the object before returning.
///
/// \param ref - never null
/// \return can be null
SWIFT_RUNTIME_EXPORT
HeapObject *swift_weakLoadStrong(WeakReference *ref);

المفتاح هنا هو التعليق

إذا كانت القيمة الحالية هي كائن غير صفري بدأ deallocation ، بإرجاع فارغة؛ خلاف ذلك ، يحتفظ الكائن قبل أن يعود .

نظرًا لأن هذا يعتمد على تعليق كود وقت التشغيل الخلفي ، فإنه لا يزال مضارباً إلى حد ما ، لكنني أقول أن ما ورد أعلاه يشير إلى أنه عند محاولة الوصول إلى القيمة المشار إليها بمرجع weak ، سيتم بالفعل الاحتفاظ بمرجع كإشارة قوية مدى الحياة الدعوة ( "... حتى العودة" ).

لمحاولة استرداد الجزء "المضارب إلى حد ما" من الأعلى ، قد نواصل البحث في كيفية معالجة Swift للوصول إلى قيمة عبر مرجع weak . من idmean: s تعليق أدناه (دراسة رمز SIL الذي تم إنشاؤه للحصول على مثال مثل OP: s) نعلم أن الدالة swift_weakLoadStrong(...) تسمى.

لذلك سنبدأ من خلال النظر في تنفيذ swift_weakLoadStrong(...) في swift/stdlib/public/runtime/HeapObject.cpp ونرى من أين سنصل من هناك:

HeapObject *swift::swift_weakLoadStrong(WeakReference *ref) {
  return ref->nativeLoadStrong();
}

نجد تنفيذ أسلوب nativeLoadStrong() من WeakReference من swift/include/swift/Runtime/HeapObject.h

HeapObject *nativeLoadStrong() {
  auto bits = nativeValue.load(std::memory_order_relaxed);
  return nativeLoadStrongFromBits(bits);
}

من نفس الملف ، فإن تطبيق nativeLoadStrongFromBits(...) :

HeapObject *nativeLoadStrongFromBits(WeakReferenceBits bits) {
  auto side = bits.getNativeOrNull();
  return side ? side->tryRetain() : nullptr;
}

المتابعة على طول سلسلة الاتصال ، tryRetain() هي طريقة من HeapObjectSideTableEntry (وهي ضرورية لجهاز حالة دورة حياة الكائن ) ، ونجد تنفيذها في swift/stdlib/public/SwiftShims/RefCount.h

HeapObject* tryRetain() {
  if (refCounts.tryIncrement())
    return object.load(std::memory_order_relaxed);
  else
    return nullptr;
}

يمكن العثور على تطبيق tryIncrement() استدعاؤه هنا من خلال نسخة من typedef : تخصص ed ) في نفس الملف على النحو الوارد أعلاه :

// Increment the reference count, unless the object is deiniting.
bool tryIncrement() {
  ...
}

أعتقد أن التعليق هنا يكفي بالنسبة لنا لاستخدام هذه الطريقة كنقطة نهاية: إذا لم يكن الكائن محددًا (الذي افترضنا أعلاه أنه لا يفعل ذلك ، حيث lhs أن lhs التعيين في OP: s مثال بنجاح) ، سيتم زيادة عدد المرجع (قوي) على الكائن ، وسيتم HeapObject مؤشر HeapObject (مدعومًا بزيادة عدد مرجع قوي) إلى مشغل المهمة. لا نحتاج إلى دراسة كيفية إجراء تناقص عدد المرجع المرجعي في نهاية المهمة في نهاية المهمة ، لكننا نعرف الآن بما يتجاوز التوقعات أن الكائن المرتبط بالمرجع weak سيتم الاحتفاظ به ككيان قوي طوال مدة المهمة ، بالنظر إلى أن لم يتم تحريره / إلغاء تخصيصه في وقت وصول الجانب الأيسر منه (في هذه الحالة لن يتم معالجة الجانب الأيمن منه أبدًا ، كما تم شرحه في إجابةMartinR: s ).


تشير الوثائق بوضوح إلى أنه إذا تم تحديد الجانب الأيسر من المهمة على أنه لا شيء ، فلن يتم تقييم الجانب الأيمن. ومع ذلك ، في المثال المعطى ، تعتبر الإشارة ضعيفة وقد يتم إطلاقها (وإلغاءها) مباشرة بعد مرور الاختيار الاختياري ، ولكن قبل حدوث عملية إلغاء القوة مباشرةً ، مما يجعل التعبير بأكمله غير آمن.


قبل التصحيح:

أعتقد أن الآخرين قد أجابوا على تفاصيل سؤالك بشكل أفضل بكثير.

لكن بصرف النظر عن التعلم. إذا كنت تريد فعلًا أن تعمل التعليمات البرمجية الخاصة بك بشكل موثوق ، فمن الأفضل القيام بذلك على النحو التالي:

someFunction(completion: { [weak self] in
    guard let _ = self else{
        print("self was nil. End of discussion")
        return
    }
    print("we now have safely 'captured' a self, no need to worry about this issue")
    self?.variable = self!.otherVariable
    self!.someOthervariable = self!.otherVariable
}

بعد التصحيح.

بفضل شرح MartinR أدناه ، تعلمت الكثير.

قراءة من هذا المنصب العظيم على إغلاق إغلاق . لقد فكرت بفظاعة عندما ترى شيئًا بين قوسين [] فهذا يعني أنه تم الاستيلاء عليه ولا تتغير قيمته. لكن الشيء الوحيد الذي نفعله بين قوسين هو أننا نضعفه ونعلم أنفسنا أن القيمة قد تصبح nil . لو فعلنا شيئًا مثل [x = self] لكنا نجحنا في الاستيلاء عليها ولكننا سنواجه مشكلة الاحتفاظ بمؤشر قوي self نفسه وإنشاء دورة ذاكرة. (من المثير للاهتمام بمعنى أنه خط رفيع للغاية من الانتقال إلى إنشاء دورة ذاكرة إلى حدوث تعطل نظرًا لأن القيمة يتم تخصيصها لأنك ضعفتها).

حتى الخاتمة:

  1. [capturedSelf = self]

    يخلق دورة الذاكرة. ليست جيدة!

  2. [weak self] 
    in guard let _ = self else{
    return
    } 

    (يمكن أن يؤدي إلى تعطل إذا قمت بفك self بعد ذلك) فالحارس يتركه بلا فائدة. لأن السطر التالي ، لا يزال يمكن أن تصبح self لا nil . ليست جيدة!

  3. [weak self] 
    self?.method1()

    (يمكن أن يؤدي إلى الانهيار إذا أجبرت self الالتفاف بعد ذلك. سوف تمر إذا لم تعد self . ستفشل بسلام إذا كانت self nil ). هذا هو ما تريده على الأرجح. هذا جيد !

  4. [weak self] in 
    guard let strongSelf = self else{ 
    return
    } 

    سوف تفشل بأمان إذا تم تخصيص self أو المضي قدما إن لم يكن لا nil . ولكنه يهزم الغرض ، لأنه يجب ألا تحتاج إلى التواصل مع self عندما تزيل الإشارة. لا أستطيع التفكير في حالة الاستخدام الجيد لهذا الغرض. هذا هو على الأرجح عديمة الفائدة!





optional