ब्लॉक के साथ `स्व` पर चक्र को बनाए रखें


167

मुझे डर है कि यह प्रश्न बहुत बुनियादी है, लेकिन मुझे लगता है कि यह बहुत से उद्देश्य-सी प्रोग्रामर के लिए प्रासंगिक है जो ब्लॉक में हो रहे हैं।

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

__block typeof(self) bself = self;
[someObject messageWithBlock:^{ [bself doSomething]; }];

इसके बजाय बस

[someObject messageWithBlock:^{ [self doSomething]; }];

जो मैं जानना चाहता हूं वह निम्नलिखित है: अगर यह सच है, तो क्या कोई तरीका है जिससे मैं कुरूपता (जीसी का उपयोग करने से अलग) से बच सकता हूं?


3
मैं अपने कॉल करना चाहते selfप्रॉक्सी thisसिर्फ चारों ओर फ्लिप बातें करने के लिए। जावास्क्रिप्ट में मैं अपनी thisक्लोजर कॉल करता हूं self, इसलिए यह अच्छा और संतुलित लगता है। :)
devios1

मुझे आश्चर्य है कि अगर स्विफ्ट ब्लॉकों का उपयोग किया जा रहा है तो कोई भी समकक्ष कार्रवाई करने की आवश्यकता है
बेन लू

@ बनलू बिल्कुल! स्विफ्ट क्लोजर में (और फ़ंक्शंस जो उस चारों ओर से गुजरते हैं जो स्पष्ट रूप से या स्पष्ट रूप से उल्लेख करते हैं) स्वयं को बनाए रखेगा। कभी-कभी यह वांछित होता है, और अन्य बार यह एक चक्र बनाता है (क्योंकि बंद होने पर स्वयं का स्वामित्व हो जाता है (या स्वयं के स्वामित्व का स्वामित्व होता है)। इसका मुख्य कारण एआरसी के कारण होता है।
एथान

1
समस्याओं से बचने के लिए, किसी ब्लॉक में उपयोग किए जाने वाले 'स्व' को परिभाषित करने का उपयुक्त तरीका '__typeof (self) __weak कमजोरसेल्फ = स्वयं;' कमजोर संदर्भ के लिए।
XLE_22

जवाबों:


169

कड़े शब्दों में, यह तथ्य कि यह एक कास्ट कॉपी है, इस समस्या से कोई लेना-देना नहीं है। ब्लॉक किसी भी obj-c मान को बनाए रखेंगे, जो उनके बनाए जाने पर कैप्चर किए जाते हैं। यह सिर्फ इतना होता है कि कॉन्स्ट-कॉपी मुद्दे के लिए समाधान, बनाए रखने के मुद्दे के लिए समाधान के समान है; अर्थात्, __blockचर के लिए भंडारण वर्ग का उपयोग करना ।

किसी भी स्थिति में, आपके प्रश्न का उत्तर देने के लिए, यहाँ कोई वास्तविक विकल्प नहीं है। यदि आप अपने स्वयं के ब्लॉक-आधारित एपीआई को डिजाइन कर रहे हैं, और यह ऐसा करने के लिए समझ में आता है, तो आप ब्लॉक selfको एक तर्क के रूप में प्राप्त कर सकते हैं । दुर्भाग्य से, यह सबसे एपीआई के लिए कोई मतलब नहीं है।

कृपया ध्यान दें कि एक आइवर को संदर्भित करना एक ही मुद्दा है। यदि आपको अपने ब्लॉक में एक आइवर को संदर्भित करने की आवश्यकता है, या तो इसके बजाय एक संपत्ति का उपयोग करें या उपयोग करें bself->ivar


परिशिष्ट: जब एआरसी के रूप में संकलित किया जाता है, __blockतो अब चक्र को बनाए रखने के लिए ब्रेक नहीं होता है। यदि आप ARC के लिए संकलन कर रहे हैं, तो आपको इसका उपयोग करने __weakया __unsafe_unretainedइसके बजाय करने की आवश्यकता है ।


कोई दिक्कत नहीं है! यदि इसने आपकी संतुष्टि के लिए प्रश्न का उत्तर दिया है, तो यदि आप इसे अपने प्रश्न के सही उत्तर के रूप में चुन सकते हैं तो मैं इसकी सराहना करूंगा। यदि नहीं, तो कृपया मुझे बताएं कि मैं आपके प्रश्न का बेहतर उत्तर कैसे दे सकता हूं।
लिली बॉलर

4
कोई बात नहीं, केविन। SO आपको तुरंत एक प्रश्न के उत्तर का चयन करने से रोकता है, इसलिए मुझे थोड़ी देर बाद वापस आना पड़ा। चीयर्स।
जोनाथन स्टर्लिंग

__unsafe_unretain id bself = self;
caleb

4
@JKLaiho: ज़रूर, __weakठीक भी है। यदि आप इस तथ्य के लिए जानते हैं कि ऑब्जेक्ट को दायरे से बाहर नहीं किया जा सकता है, जब ब्लॉक को आमंत्रित किया जाता है, तो __unsafe_unretainedकभी थोड़ा अधिक तेज़ होता है, लेकिन सामान्य तौर पर इससे कोई फर्क नहीं पड़ेगा। यदि आप उपयोग करते हैं __weak, तो इसे एक __strongस्थानीय चर में फेंकना सुनिश्चित करें और परीक्षण करें कि nilइसके साथ कुछ भी करने से पहले गैर के लिए ।
लिली बॉलर

2
@ रप्राणता: हाँ। __blockबनाए रखने और जारी नहीं करने का साइड-इफ़ेक्ट विशुद्ध रूप से उस बारे में ठीक से असमर्थता के कारण था। एआरसी के साथ, संकलक ने उस क्षमता को प्राप्त किया, और इसलिए __blockअब बनाए रखता है और रिलीज करता है। यदि आपको __unsafe_unretainedउससे बचने की आवश्यकता है, तो आपको उपयोग करने की आवश्यकता है , जो संकलक को चर में मूल्य पर कोई अनुरक्षण या रिलीज नहीं करने का निर्देश देता है।
लिली बॉलर

66

महज प्रयोग करें:

__weak id weakSelf = self;

[someObject someMethodWithBlock:^{
    [weakSelf someOtherMethod];
}];

अधिक जानकारी के लिए: WWDC 2011 - प्रैक्टिस में ब्लॉक और ग्रैंड सेंट्रल डिस्पैच

https://developer.apple.com/videos/wwdc/2011/?id=308

नोट: यदि वह काम नहीं करता है तो आप कोशिश कर सकते हैं

__weak typeof(self)weakSelf = self;

2
और क्या आपने किसी भी तरह से इसे पाया :)?
तीये की

2
आप वीडियो यहाँ देख सकते हैं - developer.apple.com/videos/wwdc/2011/…
nanjunda

क्या आप "someOtherMethod" के अंदर स्वयं को संदर्भित करने में सक्षम हैं? क्या उस बिंदु पर स्वयं संदर्भों को संदर्भित करेगा या यह भी एक चक्र बनाए रखेगा?
ओरेन

हाय @ ऑरेन, यदि आप "someOtherMethod" के अंदर स्वयं को संदर्भित करने का प्रयास करते हैं, तो आपको एक Xcode चेतावनी मिलेगी। मेरा दृष्टिकोण सिर्फ स्वयं का कमजोर संदर्भ बनाता है।
एल्विस

1
मुझे केवल एक चेतावनी मिली जब सीधे ब्लॉक के अंदर स्वयं को संदर्भित किया गया था। SomeOtherMethod के अंदर सेल्फ डालने से कोई चेतावनी नहीं हुई। क्या ऐसा इसलिए है क्योंकि xcode पर्याप्त स्मार्ट नहीं है या यह कोई समस्या नहीं है? कुछ के भीतर स्वयं को संदर्भित करना कुछ अन्य मैथोड को कमजोर कर सकता है क्योंकि यह पहले से ही है कि आपका क्या तरीका है?
ओरेन

22

यह स्पष्ट हो सकता है, लेकिन आपको केवल बदसूरत selfउर्फ करना होगा जब आप जानते हैं कि आपको एक चक्र बनाए रखा जाएगा। अगर ब्लॉक सिर्फ एक-शॉट वाली चीज है तो मुझे लगता है कि आप सुरक्षित रूप से रिटेन को अनदेखा कर सकते हैं self। खराब मामला तब होता है जब आपके पास कॉलबैक इंटरफ़ेस के रूप में ब्लॉक होता है, उदाहरण के लिए। जैसे यहाँ:

typedef void (^BufferCallback)(FullBuffer* buffer);

@interface AudioProcessor : NSObject {…}
@property(copy) BufferCallback bufferHandler;
@end

@implementation AudioProcessor

- (id) init {
    
    [self setBufferCallback:^(FullBuffer* buffer) {
        [self whatever];
    }];
    
}

यहाँ एपीआई का बहुत अर्थ नहीं है, लेकिन यह सुपरक्लॉस के साथ संचार करते समय समझ में आता है, उदाहरण के लिए। हम बफ़र हैंडलर को बनाए रखते हैं, बफ़र हैंडलर हमें बनाए रखता है। कुछ इस तरह से तुलना करें:

typedef void (^Callback)(void);

@interface VideoEncoder : NSObject {…}
- (void) encodeVideoAndCall: (Callback) block;
@end

@interface Foo : NSObject {…}
@property(retain) VideoEncoder *encoder;
@end

@implementation Foo
- (void) somewhere {
    [encoder encodeVideoAndCall:^{
        [self doSomething];
    }];
}

इन स्थितियों में मैं selfअलियासिंग नहीं करता । आपको एक चक्र बनाए रखना पड़ता है, लेकिन ऑपरेशन अल्पकालिक होता है और ब्लॉक अंततः चक्र को तोड़कर स्मृति से बाहर निकल जाएगा। लेकिन ब्लॉक के साथ मेरा अनुभव बहुत छोटा है और यह हो सकता है कि selfलंबे समय में अलियासिंग एक सर्वोत्तम अभ्यास के रूप में सामने आए।


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

6
सिद्धांत रूप में, आप सही हैं। हालाँकि, यदि आप उदाहरण में कोड निष्पादित करते हैं, तो आप दुर्घटनाग्रस्त हो जाएंगे। ब्लॉक की संपत्तियों को हमेशा घोषित किया जाना चाहिए copy, नहीं retain। यदि वे बस रहे हैं retain, तो कोई गारंटी नहीं है कि वे स्टैक से चले जाएंगे, जिसका अर्थ है कि जब आप इसे निष्पादित करने के लिए जाते हैं, तो यह अब नहीं होगा। (और नकल और पहले से ही कॉपी किए गए ब्लॉक को एक
रिटेन के

आह, यकीन है, एक टाइपो। मैं retainथोड़ी देर पहले चरण के माध्यम से चला गया और जल्दी से एहसास हुआ कि आप जो बात कह रहे हैं :) धन्यवाद!
जूल डे

मुझे पूरा यकीन retainहै कि ब्लॉकों के लिए पूरी तरह से नजरअंदाज कर दिया गया है (जब तक कि वे पहले से ही ढेर से दूर नहीं चले गए हैं copy)।
स्टीवन फिशर

@ डेव्लॉन्ग, नहीं, क्योंकि यह क्रैश नहीं होगा क्योंकि @property (रिटेन) का उपयोग केवल ऑब्जेक्ट रेफरेंस के लिए किया जाता है, न कि ब्लॉक के लिए .. यहाँ पर कॉपी की जरूरत नहीं है।
DeniziOS

20

एक और जवाब पोस्ट करना क्योंकि यह मेरे लिए भी एक समस्या थी। मैंने मूल रूप से सोचा था कि मुझे ब्लॉकसेल्फ का उपयोग करना था कहीं भी एक ब्लॉक के अंदर एक आत्म संदर्भ था। यह मामला नहीं है, यह केवल तब होता है जब ऑब्जेक्ट खुद में एक ब्लॉक होता है। और वास्तव में, यदि आप इन मामलों में ब्लॉकसेल्फ का उपयोग करते हैं, तो ऑब्जेक्ट ब्लॉक से वापस परिणाम प्राप्त करने से पहले ऑब्जेक्ट को प्राप्त कर सकता है और फिर इसे कॉल करने की कोशिश करने पर दुर्घटनाग्रस्त हो जाएगा, इसलिए स्पष्ट रूप से आप चाहते हैं कि प्रतिक्रिया तक स्वयं को बनाए रखा जाए। वापस आता है।

पहला मामला यह दर्शाता है कि जब एक चक्र बरकरार रहेगा, क्योंकि इसमें एक ब्लॉक होता है जिसे ब्लॉक में संदर्भित किया जाता है:

#import <Foundation/Foundation.h>

typedef void (^MyBlock)(void);

@interface ContainsBlock : NSObject 

@property (nonatomic, copy) MyBlock block;

- (void)callblock;

@end 

@implementation ContainsBlock
@synthesize block = _block;

- (id)init {
    if ((self = [super init])) {

        //__block ContainsBlock *blockSelf = self; // to fix use this.
        self.block = ^{
                NSLog(@"object is %@", self); // self retain cycle
            };
    }
    return self;
}

- (void)dealloc {
    self.block = nil;
    NSLog (@"ContainsBlock"); // never called.
    [super dealloc];
} 

- (void)callblock {
    self.block();
} 

@end 

 int main() {
    ContainsBlock *leaks = [[ContainsBlock alloc] init];
    [leaks callblock];
    [leaks release];
}

आपको दूसरे मामले में ब्लॉकसेल्फ की आवश्यकता नहीं है क्योंकि कॉलिंग ऑब्जेक्ट में एक ब्लॉक नहीं होता है जो आपके द्वारा संदर्भ दिए जाने पर एक बनाए रखने के चक्र का कारण होगा:

#import <Foundation/Foundation.h>

typedef void (^MyBlock)(void);

@interface BlockCallingObject : NSObject 
@property (copy, nonatomic) MyBlock block;
@end

@implementation BlockCallingObject 
@synthesize block = _block;

- (void)dealloc {
    self.block = nil;
    NSLog(@"BlockCallingObject dealloc");
    [super dealloc];
} 

- (void)callblock {
    self.block();
} 
@end

@interface ObjectCallingBlockCallingObject : NSObject 
@end

@implementation ObjectCallingBlockCallingObject 

- (void)doneblock {
    NSLog(@"block call complete");
}

- (void)dealloc {
    NSLog(@"ObjectCallingBlockCallingObject dealloc");
    [super dealloc];
} 

- (id)init {
    if ((self = [super init])) {

        BlockCallingObject *myobj = [[BlockCallingObject alloc] init];
        myobj.block = ^() {
            [self doneblock]; // block in different object than this object, no retain cycle
        };
        [myobj callblock];
        [myobj release];
    }
    return self;
}
@end

int main() {

    ObjectCallingBlockCallingObject *myObj = [[ObjectCallingBlockCallingObject alloc] init];
    [myObj release];

    return 0;
} 

यह एक सामान्य गलत धारणा है और खतरनाक हो सकती है, क्योंकि जिन ब्लॉकों को बनाए रखना चाहिए, वे selfइस फिक्स को लागू करने वाले लोगों के कारण नहीं हो सकते हैं। गैर-एआरसी कोड में रिटेन साइकिल से बचने का यह एक अच्छा उदाहरण है, पोस्ट करने के लिए धन्यवाद।
कार्ल वीज़े

9

यह भी याद रखें कि यदि आपका ब्लॉक किसी अन्य ऑब्जेक्ट को संदर्भित करता है, जो तब बरकरार रहता है तो चक्र को बनाए रखा जा सकता है self

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

चूंकि कोई साफ समाधान नहीं हैं, इसलिए मैं निम्नलिखित वर्कअराउंड की सिफारिश करूंगा। अपनी समस्या को ठीक करने के लिए उनमें से एक या अधिक का चयन करने के लिए स्वतंत्र महसूस करें।

  • केवल पूर्ण होने के लिए ब्लॉक का उपयोग करें , और ओपन-एंडेड घटनाओं के लिए नहीं। उदाहरण के लिए, जैसे कि तरीकों के लिए ब्लॉक का उपयोग करें doSomethingAndWhenDoneExecuteThisBlock:, न कि तरीकों की तरह setNotificationHandlerBlock:। पूर्णता के लिए उपयोग किए जाने वाले ब्लॉक में जीवन के निश्चित छोर हैं, और मूल्यांकन के बाद सर्वर ऑब्जेक्ट द्वारा जारी किया जाना चाहिए। यह बनाए रखने के चक्र को लंबे समय तक रहने से रोकता है, भले ही ऐसा होता हो।
  • उस कमजोर-संदर्भ नृत्य का वर्णन करें।
  • रिलीज़ होने से पहले अपनी ऑब्जेक्ट को साफ़ करने के लिए एक विधि प्रदान करें, जो सर्वर ऑब्जेक्ट से ऑब्जेक्ट को "डिस्कनेक्ट" करता है जो इसके संदर्भ को पकड़ सकता है; और ऑब्जेक्ट पर कॉल करने से पहले इस विधि को कॉल करें। हालांकि यह विधि पूरी तरह से ठीक है यदि आपकी वस्तु में केवल एक ग्राहक है (या कुछ संदर्भ में एक सिंगलटन है), लेकिन यदि यह कई क्लाइंट हैं तो टूट जाएगा। आप मूल रूप से यहां बनाए रखने-गिनती तंत्र को हरा रहे हैं; deallocइसके बजाय कॉल करने के लिए समान है release

यदि आप सर्वर ऑब्जेक्ट लिख रहे हैं, तो केवल पूर्ण होने के लिए ब्लॉक तर्क लें। कॉलबैक के लिए ब्लॉक तर्क स्वीकार न करें, जैसे कि setEventHandlerBlock:। इसके बजाय, क्लासिक प्रतिनिधि पैटर्न पर वापस जाएं: एक औपचारिक प्रोटोकॉल बनाएं, और एक setEventDelegate:विधि का विज्ञापन करें । प्रतिनिधि को न रखें। यदि आप एक औपचारिक प्रोटोकॉल भी नहीं बनाना चाहते हैं, तो एक चयनकर्ता को प्रतिनिधि कॉलबैक के रूप में स्वीकार करें।

और अंत में, इस पैटर्न को अलार्म बजना चाहिए:

- (शून्य) डीललोक {
    [myServerObject रिलीज़CallbackBlocksForObject: स्व];
    ...
}

यदि आप उन ब्लॉक को हटाने का प्रयास कर रहे हैं जो selfअंदर से संदर्भित कर सकते हैं dealloc, तो आप पहले से ही परेशानी में हैं। deallocब्लॉक में संदर्भों के कारण बनाए रखने वाले चक्र के कारण कभी भी कॉल नहीं किया जा सकता है, जिसका अर्थ है कि आपकी वस्तु बस तब तक लीक होने वाली है जब तक कि सर्वर ऑब्जेक्ट को हटा नहीं दिया जाता है।


यदि आप __weakउचित रूप से उपयोग करते हैं तो GC मदद करता है।
टीसी

ट्रेसिंग कचरा संग्रह निश्चित रूप से बनाए रखने वाले चक्र से निपट सकता है रिटेन साइकल केवल संदर्भ गिनती के वातावरण के लिए एक समस्या है
newacct

बस हर कोई जानता है, कचरा संग्रह को स्वचालित संदर्भ गणना (एआरसी) के पक्ष में ओएस एक्स v10.8 में हटा दिया गया था और इसे ओएस एक्स ( डेवलपर. apple.com/library/mac/#aceasenotes) के भविष्य के संस्करण में हटा दिया जाएगा। / उद्देश्य /… )।
रिकार्डो सांचेज-साज़

1

__block __unsafe_unretainedकेविन के पद में सुझाए गए संशोधक एक अलग धागे में निष्पादित ब्लॉक के मामले में खराब पहुंच अपवाद का कारण बन सकते हैं। यह अस्थायी चर के लिए केवल __block संशोधक का उपयोग करना बेहतर है और उपयोग के बाद इसे शून्य करना है।

__block SomeType* this = self;
[someObject messageWithBlock:^{
  [this doSomething]; // here would be BAD_ACCESS in case of __unsafe_unretained with
                      //  multithreading and self was already released
  this = nil;
}];

क्या वास्तव में इसे उपयोग करने के बाद चर को शून्य करने की आवश्यकता से बचने के लिए __ब्लॉक के बजाय केवल __weak का उपयोग करना सुरक्षित नहीं होगा? मेरा मतलब है, यह समाधान बहुत अच्छा है यदि आप अन्य प्रकार के चक्रों को तोड़ना चाहते हैं, लेकिन निश्चित रूप से मुझे इस पर "स्वयं" बनाए रखने वाले चक्रों के लिए कोई विशेष लाभ नहीं दिखता है।
ale0xB

यदि आपका प्लेटफ़ॉर्म लक्ष्य iOS 4.x है तो आप __weak का उपयोग नहीं कर सकते। इसके अलावा कभी-कभी आपको इसकी आवश्यकता होती है कि ब्लॉक में कोड को मान्य ऑब्जेक्ट के लिए निष्पादित किया गया है, शून्य के लिए नहीं।
b1gbr0

1

आप libextobjc लाइब्रेरी का उपयोग कर सकते हैं। यह काफी लोकप्रिय है, इसका उपयोग उदाहरण के लिए ReactiveCocoa में किया जाता है। https://github.com/jspahrsummers/libextobjc

यह 2 मैक्रोज़ @weakify और @strongify प्रदान करता है, इसलिए आपके पास हो सकता है:

@weakify(self)
[someObject messageWithBlock:^{
   @strongify(self)
   [self doSomething]; 
}];

यह एक प्रत्यक्ष मजबूत संदर्भ को रोकता है ताकि हम स्वयं को एक चक्र में न रखें। और यह भी, यह स्वयं को आधे रास्ते में शून्य होने से रोकता है, लेकिन फिर भी ठीक से गणना को बढ़ाता है। इस लिंक में और अधिक: http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html


1
सरलीकृत कोड दिखाने से पहले यह जानना बेहतर होगा कि इसके पीछे क्या है, हर किसी को कोड की वास्तविक दो पंक्तियों को जानना चाहिए।
एलेक्स Cio

0

इस बारे में कैसा है?

- (void) foo {
     __weak __block me = self;

     myBlock = ^ {
        [[me someProp] someMessage];
     }
     ...
 }

मुझे अब कंपाइलर की चेतावनी नहीं मिलती है।


-1

ब्लॉक: एक बनाए रखने का चक्र होगा क्योंकि इसमें एक ब्लॉक होता है जिसे ब्लॉक में संदर्भित किया जाता है; यदि आप ब्लॉक कॉपी बनाते हैं और एक सदस्य चर का उपयोग करते हैं, तो स्व बरकरार रहेगा।

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