एपीआई को लागू करते समय मैं ब्लॉकों में स्वयं को कैद करने से कैसे बचूँ?


222

मेरे पास एक काम करने वाला ऐप है और मैं इसे Xcode 4.2 में ARC में परिवर्तित करने पर काम कर रहा हूं। पूर्व-चेक चेतावनियों selfमें से एक में एक चक्र को बनाए रखने वाले ब्लॉक में दृढ़ता से कब्जा करना शामिल है। मैंने समस्या का वर्णन करने के लिए एक सरल कोड नमूना बनाया है। मेरा मानना ​​है कि मैं इसका मतलब समझता हूं लेकिन मुझे इस प्रकार के परिदृश्य को लागू करने के लिए "सही" या अनुशंसित तरीका सुनिश्चित नहीं है।

  • स्व वर्ग MyAPI का एक उदाहरण है
  • नीचे दिए गए कोड को मेरे प्रश्न से संबंधित वस्तुओं और ब्लॉकों के साथ केवल बातचीत दिखाने के लिए सरल बनाया गया है
  • मान लें कि MyAPI को एक दूरस्थ स्रोत से डेटा मिलता है और MyDataProcessor उस डेटा पर काम करता है और एक आउटपुट तैयार करता है
  • प्रोसेसर प्रगति और राज्य को संवाद करने के लिए ब्लॉक के साथ कॉन्फ़िगर किया गया है

कोड नमूना:

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};

self.dataProcessor.completion = ^{
    [self.delegate myAPIDidFinish:self];
    self.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

प्रश्न: मैं "गलत" क्या कर रहा हूं और / या इसे एआरसी सम्मेलनों के अनुरूप कैसे संशोधित किया जाना चाहिए?

जवाबों:


509

संक्षिप्त जवाब

selfसीधे पहुंचने के बजाय , आपको इसे अप्रत्यक्ष रूप से एक्सेस करना चाहिए, एक संदर्भ से जिसे बरकरार नहीं रखा जाएगा। यदि आप स्वचालित संदर्भ गणना (ARC) का उपयोग नहीं कर रहे हैं , तो आप यह कर सकते हैं:

__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}

__blockकीवर्ड निशान चर कि ब्लॉक के अंदर संशोधित किया जा सकता है (हम कि नहीं कर रहे हैं), लेकिन यह भी तो वे अपने आप जब ब्लॉक बनाए रखा है बनाए रखा नहीं कर रहे हैं (जब तक आप एआरसी का उपयोग कर रहे हैं)। यदि आप ऐसा करते हैं, तो आपको यह सुनिश्चित करना चाहिए कि MyDataProcessor इंस्टेंस जारी होने के बाद ब्लॉक को निष्पादित करने की कोशिश करने के लिए और कुछ नहीं हो रहा है। (आपके कोड की संरचना को देखते हुए, यह समस्या नहीं होनी चाहिए।) के बारे में और पढ़ें__block

यदि आप एआरसी का उपयोग कर रहे हैं , तो __blockपरिवर्तनों के संदर्भ और संदर्भ को बरकरार रखा जाएगा, जिस स्थिति में आपको इसके __weakबजाय इसकी घोषणा करनी चाहिए ।

लंबा जवाब

मान लीजिए कि आपके पास इस तरह का कोड था:

self.progressBlock = ^(CGFloat percentComplete) {
    [self.delegate processingWithProgress:percentComplete];
}

यहाँ समस्या यह है कि स्वयं ब्लॉक के संदर्भ को बरकरार रखे हुए है; इस बीच ब्लॉक को अपनी प्रतिनिधि संपत्ति लाने और प्रतिनिधि को एक विधि भेजने के लिए स्वयं के संदर्भ को बनाए रखना चाहिए। यदि आपके ऐप में मौजूद हर चीज इस ऑब्जेक्ट के लिए अपना संदर्भ जारी करती है, तो इसकी रिटन काउंट शून्य नहीं होगी (क्योंकि ब्लॉक इसे इंगित कर रहा है) और ब्लॉक कुछ भी गलत नहीं कर रहा है (क्योंकि ऑब्जेक्ट इसे इंगित कर रहा है) और इसलिए वस्तुओं की जोड़ी ढेर में लीक हो जाएगी, स्मृति पर कब्जा कर लेगी लेकिन हमेशा डिबगर के बिना पहुंच से बाहर होगी। दुखद, वास्तव में।

इसके बजाय यह मामला आसानी से तय किया जा सकता है:

id progressDelegate = self.delegate;
self.progressBlock = ^(CGFloat percentComplete) {
    [progressDelegate processingWithProgress:percentComplete];
}

इस कोड में, स्वयं ब्लॉक को बनाए रख रहा है, ब्लॉक प्रतिनिधि को बनाए रख रहा है, और कोई चक्र नहीं हैं (यहां से दिखाई दे रहे हैं; प्रतिनिधि हमारी वस्तु को बनाए रख सकता है लेकिन यह अभी हमारे हाथ से बाहर है)। यह कोड उसी तरह से रिसाव का जोखिम नहीं उठाएगा, क्योंकि ब्लॉक करते समय प्रत्यायोजित संपत्ति का मूल्य कैप्चर किया जाता है, बजाय इसके निष्पादित होने पर देखा जाता है। एक दुष्प्रभाव यह है कि, यदि आप इस ब्लॉक के बनने के बाद प्रतिनिधि को बदलते हैं, तो ब्लॉक पुराने प्रतिनिधि को अपडेट संदेश भेजेगा। ऐसा होने की संभावना है या नहीं यह आपके आवेदन पर निर्भर करता है।

यहां तक ​​कि अगर आप उस व्यवहार से शांत थे, तब भी आप अपने मामले में उस चाल का उपयोग नहीं कर सकते हैं:

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};

यहां आप selfमेथड कॉल में सीधे प्रतिनिधि के पास जा रहे हैं , इसलिए आपको इसे वहां पहुंचाना होगा। यदि आपके पास ब्लॉक प्रकार की परिभाषा पर नियंत्रण है, तो सबसे अच्छी बात यह होगी कि प्रतिनिधि को पैरामीटर के रूप में ब्लॉक में पास करें:

self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
};

यह समाधान बरकरार चक्र से बचा जाता है और हमेशा वर्तमान प्रतिनिधि को बुलाता है।

यदि आप ब्लॉक नहीं बदल सकते हैं, तो आप इससे निपट सकते हैं । एक चक्र बनाए रखने का कारण एक चेतावनी है, एक त्रुटि नहीं है, यह है कि वे आपके आवेदन के लिए जरूरी कयामत नहीं उड़ाते हैं। यदि MyDataProcessorऑपरेशन पूरा होने पर ब्लॉक जारी करने में सक्षम है, तो इससे पहले कि उसके माता-पिता इसे जारी करने की कोशिश करेंगे, चक्र टूट जाएगा और सब कुछ ठीक से साफ हो जाएगा। यदि आप इसके बारे में सुनिश्चित हो सकते हैं, तो #pragmaकोड के उस ब्लॉक के लिए चेतावनियों को दबाने के लिए उपयोग करना सही होगा । (या एक प्रति फ़ाइल संकलक ध्वज का उपयोग करें। लेकिन पूरी परियोजना के लिए चेतावनी को अक्षम न करें।)

आप ऊपर एक समान चाल का उपयोग कर सकते हैं, एक संदर्भ को कमजोर या अप्राप्य घोषित कर सकते हैं और ब्लॉक में इसका उपयोग कर सकते हैं। उदाहरण के लिए:

__weak MyDataProcessor *dp = self; // OK for iOS 5 only
__unsafe_unretained MyDataProcessor *dp = self; // OK for iOS 4.x and up
__block MyDataProcessor *dp = self; // OK if you aren't using ARC
self.progressBlock = ^(CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}

उपरोक्त तीनों आपको परिणाम को बनाए रखने के बिना एक संदर्भ देंगे, हालांकि वे सभी थोड़ा अलग व्यवहार करते हैं: __weakऑब्जेक्ट जारी होने पर संदर्भ को शून्य करने की कोशिश करेंगे; __unsafe_unretainedअमान्य सूचक के साथ आपको छोड़ देगा; __blockवास्तव में अप्रत्यक्ष का एक और स्तर जोड़ देगा और आपको ब्लॉक के भीतर से संदर्भ के मूल्य को बदलने की अनुमति देगा (इस मामले में अप्रासंगिक, चूंकि dpकहीं और उपयोग नहीं किया जाता है)।

जो सबसे अच्छा है वह इस बात पर निर्भर करेगा कि आप किस कोड को बदलने में सक्षम हैं और आप क्या नहीं कर सकते हैं। लेकिन उम्मीद है कि इसने आपको आगे बढ़ने के बारे में कुछ विचार दिए हैं।


1
बहुत बढ़िया जवाब! धन्यवाद, मुझे इस बारे में बहुत बेहतर समझ है कि क्या चल रहा है और यह सब कैसे काम करता है। इस मामले में, मेरे पास सब कुछ पर नियंत्रण है इसलिए मैं आवश्यकतानुसार कुछ वस्तुओं को फिर से आर्किटेक्ट करूँगा।
XJones

18
O_O मैं बस थोड़ी अलग समस्या से गुजर रहा था, पढ़ने में अटक गया, और अब इस पृष्ठ को सभी जानकार और शांत महसूस कर रहा हूं। धन्यवाद!
Orc JMR

यह सही है, कि यदि किसी कारणवश ब्लॉक निष्पादन के क्षण को dpजारी किया जाएगा (उदाहरण के लिए यदि यह एक दृश्य नियंत्रक था और इसे [dp.delegate ...पॉप किया गया था), तो लाइन EXC_BADACCESS का कारण बनेगी?
Peetonn

क्या प्रखंड को धारण करने वाली संपत्ति (जैसे dataProcess.progress) होनी चाहिए strongया weak?
djskinner

1
आपके पास libextobjc पर एक नज़र हो सकती है जो कि दो आसान मैक्रो प्रदान करता है @weakify(..)और @strongify(...)जो आपको selfगैर-बनाए रखने वाले फैशन में ब्लॉक में उपयोग करने की अनुमति देता है ।

25

जब आप सकारात्मक होंगे कि भविष्य में चक्र टूट जाएगा, तो चेतावनी को दबाने का विकल्प भी है:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"

self.progressBlock = ^(CGFloat percentComplete) {
    [self.delegate processingWithProgress:percentComplete];
}

#pragma clang diagnostic pop

इस तरह आप के साथ चारों ओर बंदर की जरूरत नहीं है __weak, selfaliasing और स्पष्ट इवर लगाकर।


8
एक बहुत ही बुरे अभ्यास की तरह लगता है जो कोड की 3 से अधिक पंक्तियों को लेता है जिसे __weak id weakSelf = self के साथ बदला जा सकता है;
बेन सिंक्लेयर

3
अक्सर कोड का एक बड़ा ब्लॉक होता है जो दबाए गए चेतावनियों से लाभ उठा सकता है।
जौल

2
सिवाय इसके कि __weak id weakSelf = self;चेतावनी को दबाने की तुलना में मौलिक रूप से अलग व्यवहार है। प्रश्न "... अगर आप सकारात्मक हैं कि रिटेन चक्र टूट जाएगा" के साथ शुरू हुआ
टिम

अक्सर अक्सर लोग नेत्रहीनता को समझने के बिना नेत्रहीन रूप से चर बनाते हैं। उदाहरण के लिए, मैंने देखा है कि लोग किसी ऑब्जेक्ट को कमजोर करते हैं और फिर, ब्लॉक में वे करते हैं: [array addObject:weakObject];यदि कमजोर ऑबजेक्ट जारी किया गया है, तो यह क्रैश का कारण बनता है। स्पष्ट रूप से यह एक बरकरार चक्र पर पसंद नहीं है। आपको यह समझना होगा कि क्या आपका ब्लॉक वास्तव में कमजोर पड़ने के लिए लंबे समय तक जीवित रहता है, और यह भी कि क्या आप चाहते हैं कि ब्लॉक में कार्रवाई इस बात पर निर्भर हो कि कमजोर वस्तु अभी भी वैध है या नहीं।
महबूबज

14

एक सामान्य समाधान के लिए, मैंने इन्हें प्री-कम्पाइल हेडर में परिभाषित किया है। कैप्चरिंग से बचा जाता है और फिर भी उपयोग करने से बचते हुए कंपाइलर की मदद करता हैid

#define BlockWeakObject(o) __typeof(o) __weak
#define BlockWeakSelf BlockWeakObject(self)

फिर कोड में आप कर सकते हैं:

BlockWeakSelf weakSelf = self;
self.dataProcessor.completion = ^{
    [weakSelf.delegate myAPIDidFinish:weakSelf];
    weakSelf.dataProcessor = nil;
};

सहमत हो गए, इससे ब्लॉक के अंदर समस्या हो सकती है। ReactiveCocoa के पास इस समस्या का एक और दिलचस्प समाधान है जो आपको selfअपने ब्लॉक @weakify (self) का उपयोग जारी रखने की अनुमति देता है ; id block = ^ {@strongify (स्व); [self.delegate myAPIDidFinish: self]; };
डेमियन पोंटिफेक्स


11

मेरा मानना ​​है कि एआरसी के बिना समाधान भी __blockकीवर्ड का उपयोग करते हुए एआरसी के साथ काम करता है :

EDIT: ARC रिलीज़ नोट्स के लिए संक्रमण के अनुसार , __blockभंडारण के साथ घोषित एक वस्तु अभी भी बरकरार है। उपयोग __weak(पसंदीदा) या __unsafe_unretained(पीछे की संगतता के लिए)।

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

// Use this inside blocks
__block id myself = self;

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [myself.delegate myAPI:myself isProcessingWithProgress:percentComplete];
};

self.dataProcessor.completion = ^{
    [myself.delegate myAPIDidFinish:myself];
    myself.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

यह महसूस नहीं किया कि __blockकीवर्ड ने इसे बनाए रखने से परहेज किया है। धन्यवाद! मैंने अपना अखंड उत्तर अपडेट किया। :-)
बेंज़ादो

3
Apple डॉक्स के अनुसार "मैनुअल रेफरेंस काउंटिंग मोड में, __block आईडी x; एक्स को रिटेन नहीं करने का प्रभाव है। एआरसी मोड में, __block आईडी एक्स; एक्स को बनाए रखने के लिए डिफॉल्ट करता है (अन्य सभी मूल्यों की तरह)।"
XJones

11

कुछ अन्य उत्तरों को मिलाकर, यह वही है जो मैं अब टाइप्ड कमजोर उपयोग के लिए ब्लॉक में उपयोग करता हूं:

__typeof(self) __weak welf = self;

मैंने एक XCode कोड स्निपेट के रूप में सेट किया "विधियों" / कार्यों में "welf" के पूर्ण उपसर्ग के साथ, जो केवल "हम" टाइप करने के बाद हिट करता है।


क्या आपको यकीन है? इस कड़ी में और बजना डॉक्स दोनों कर सकते हैं सोचने के लिए लग रहे हैं और वस्तु के लिए एक संदर्भ रखने के लिए इस्तेमाल किया जाना चाहिए, लेकिन एक लिंक नहीं होगा जो एक चक्र को बनाए रखें: stackoverflow.com/questions/19227982/using-block-and-weak
केंडल हेल्मिसेटर जेलर

क्लैग डॉक्स से: clang.llvm.org/docs/BlockLanguageSpec.html "ऑब्जेक्टिव-सी और ऑब्जेक्टिव-सी ++ भाषाओं में, हम ऑब्जेक्ट टाइप के __ब्लॉक वेरिएबल्स के लिए __weak स्पेसियर की अनुमति देते हैं। यदि कचरा संग्रह सक्षम नहीं है, तो यह क्वालीफायर का कारण बनता है। भेजे जा रहे संदेशों के बिना रखे जाने वाले इन चरों को "
केंडल हेल्मिस्सेटर गेलनर


6

चेतावनी => "ब्लॉक के अंदर स्वयं को कैप्चर करने से चक्र बनाए रखने की संभावना है"

जब आप किसी ब्लॉक के अंदर स्वयं या उसकी संपत्ति का जिक्र करते हैं जो चेतावनी से ऊपर की तुलना में स्वयं द्वारा दृढ़ता से बनाए रखा जाता है।

इसलिए इसे टालने के लिए हमें इसे एक हफ़्ते का समय देना होगा

__weak typeof(self) weakSelf = self;

इसलिए उपयोग करने के बजाय

blockname=^{
    self.PROPERTY =something;
}

हमें उपयोग करना चाहिए

blockname=^{
    weakSelf.PROPERTY =something;
}

नोट: चक्र बनाए रखना आमतौर पर तब होता है जब कुछ कैसे दो ऑब्जेक्ट एक दूसरे का संदर्भ देते हैं जिसके द्वारा दोनों में संदर्भ संख्या = 1 होती है और उनके डेलोक विधि को कभी नहीं कहा जाता है।



-1

यदि आप सुनिश्चित हैं कि आपका कोड एक चक्र बनाए नहीं रखेगा, या यह चक्र बाद में टूट जाएगा, तो चेतावनी को चुप करने का सबसे सरल तरीका है:

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

[self dataProcessor].progress = ^(CGFloat percentComplete) {
    [self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};

[self dataProcessor].completion = ^{
    [self.delegate myAPIDidFinish:self];
    self.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

कारण यह है कि यह काम करता है, जबकि संपत्तियों के डॉट-एक्सेस को Xcode के विश्लेषण द्वारा ध्यान में रखा जाता है, और इसलिए

x.y.z = ^{ block that retains x}

y के x (असाइनमेंट के बाईं ओर) और x (दाईं ओर) के y द्वारा बनाए रखने के रूप में देखा जाता है, विधि कॉल समान विश्लेषण के अधीन नहीं हैं, भले ही वे संपत्ति-एक्सेस विधि कॉल हों डॉट-ऐक्सेस के समतुल्य होते हुए भी, जब वे प्रॉपर्टी ऐक्सेस विधियाँ संकलित-उत्पन्न होती हैं, तो

[x y].z = ^{ block that retains x}

केवल सही पक्ष को एक बनाए रखने के रूप में देखा जाता है (x के y द्वारा), और कोई अनुरक्षण चक्र चेतावनी उत्पन्न नहीं होती है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.