2012-05-03 10 views
13

में ब्लॉक पास करना एक विधि लिखते समय जो एक ब्लॉक को तर्क के रूप में स्वीकार करता है, क्या मुझे कुछ विशेष करने की आवश्यकता है जैसे कि इसे निष्पादित करने से पहले ब्लॉक को ढेर में कॉपी करना? उदाहरण के लिए, मैं था निम्न विधि:उद्देश्य-सी

- (void)testWithBlock:(void (^)(NSString *))block { 
    NSString *testString = @"Test"; 
    block(testString); 
} 

मैं इसे बुला, या जब विधि प्रवेश करने से पहले block साथ कुछ भी करना चाहिए? या पारित ब्लॉक का उपयोग करने का सही तरीका ऊपर है? साथ ही, विधि को सही तरीके से कॉल करने का निम्न तरीका है, या मुझे इसे पार करने से पहले ब्लॉक के साथ कुछ करना चाहिए?

[object testWithBlock:^(NSString *test){ 
    NSLog(@"[%@]", test); 
}]; 

कहाँ करना मैं ब्लॉक कॉपी करने की जरूरत है? और यदि मैं एआरसी का उपयोग नहीं कर रहा था तो यह अलग कैसे होगा?

+0

आप एआरसी का उपयोग कर रहे हैं? –

+0

@pcperini, हाँ। – rid

उत्तर

18

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

typedef void (^IDBlock) (id); 
@implementation MyClass{ 
    IDBlock _completionBlock; 
} 

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

जब आप कोई ब्लॉक निष्पादित करते हैं, तो ब्लॉक को nil पर पहले परीक्षण करना महत्वपूर्ण है। उद्देश्य-सी आपको ब्लॉक विधि पैरामीटर में nil पास करने की अनुमति देगा। यदि वह ब्लॉक शून्य है, तो आप इसे निष्पादित करने का प्रयास करते समय EXC_BAD_ACCESS प्राप्त करेंगे। सौभाग्य से यह करना आसान है। अपने उदाहरण में, आप लिखना चाहते हैं:

- (void)testWithBlock:(void (^)(NSString *))block { 
    NSString *testString = @"Test"; 
    if (block) block(testString); 
} 

नकल ब्लॉकों में प्रदर्शन विचार कर रहे हैं। ढेर पर एक ब्लॉक बनाने की तुलना में, एक ब्लॉक को ढेर में कॉपी करना तुच्छ नहीं है। यह सामान्य रूप से एक बड़ा सौदा नहीं है, लेकिन यदि आप इसे अवरुद्ध रूप से ब्लॉक का उपयोग कर रहे हैं या ब्लॉक के समूह का उपयोग कर रहे हैं और प्रत्येक निष्पादन पर उन्हें कॉपी कर रहे हैं, तो यह एक प्रदर्शन हिट बनाएगा। इसलिए यदि आपकी विधि - (void)testWithBlock:(void (^)(NSString *))block; किसी प्रकार की लूप में थी, तो उस ब्लॉक को कॉपी करने से आपको अपने प्रदर्शन को नुकसान पहुंचा सकता है यदि आपको इसकी प्रतिलिपि बनाने की आवश्यकता नहीं है।

एक ब्लॉक को कॉपी करने के लिए आपको एक और जगह है यदि आप उस ब्लॉक को स्वयं कॉल करना चाहते हैं (ब्लॉक रिकर्सन)। यह सब सामान्य नहीं है, लेकिन यदि आप ऐसा करना चाहते हैं तो आपको ब्लॉक की प्रतिलिपि बनाना होगा। SO पर मेरा प्रश्न/उत्तर यहां देखें: Recursive Blocks In Objective-C

अंत में, यदि आप एक ब्लॉक स्टोर करने जा रहे हैं तो आपको बनाए रखने वाले चक्र बनाने के बारे में वास्तव में सावधान रहना होगा। ब्लॉक किसी भी ऑब्जेक्ट को इसमें बनाए रखेंगे, और यदि वह ऑब्जेक्ट एक आवृत्ति चर है, तो यह आवृत्ति चर के वर्ग (स्वयं) को बनाए रखेगा। मैं व्यक्तिगत रूप से ब्लॉक प्यार करता हूं और हर समय उनका उपयोग करता हूं। लेकिन एक कारण है कि ऐप्पल अपने UIKit वर्गों के लिए ब्लॉक का उपयोग/स्टोर नहीं करता है और इसके बजाय लक्ष्य/क्रिया या प्रतिनिधि पैटर्न के साथ चिपक जाता है। यदि आप (ब्लॉक बनाने वाली कक्षा) उस वर्ग को बनाए रखते हैं जो ब्लॉक प्राप्त/प्रतिलिपि/संग्रहित कर रही है, और उस ब्लॉक में आप या तो स्वयं या किसी भी वर्ग आवृत्ति चर का संदर्भ लेते हैं, तो आपने एक बनाए रखा चक्र बनाया है (कक्षा ए -> कक्षाबी - > ब्लॉक -> कक्षा ए)। यह करना उल्लेखनीय रूप से आसान है, और यह कुछ ऐसा है जो मैंने कई बार किया है। इसके अलावा, इंस्ट्रूमेंट्स में "लीक्स" इसे पकड़ नहीं लेता है। इसके चारों ओर जाने का तरीका आसान है: बस एक अस्थायी __weak चर (एआरसी के लिए) या __block चर (गैर-एआरसी) बनाएं और ब्लॉक उस चर को बनाए रखेगा।

[object testWithBlock:^(NSString *test){ 
    _iVar = test; 
    NSLog(@"[%@]", test); 
}]; 

बहरहाल, यह (एआरसी का प्रयोग करके) ठीक करने के लिए:: तो, उदाहरण के लिए, निम्नलिखित एक चक्र को बनाए रखने अगर 'वस्तु' प्रतियां/दुकानों ब्लॉक होगा

__weak IVarClass *iVar = _iVar; 
[object testWithBlock:^(NSString *test){ 
    iVar = test; 
    NSLog(@"[%@]", test); 
}]; 

आप भी कर सकते हैं कुछ इस तरह करते हैं:

__weak ClassOfSelf _self = self; 
[object testWithBlock:^(NSString *test){ 
    _self->_iVar = test; 
    NSLog(@"[%@]", test); 
}]; 

नोट है कि कई लोगों के ऊपर पसंद नहीं है क्योंकि वे इसे कमजोर करने पर विचार, लेकिन यह चर तक पहुँचने का एक मान्य तरीका है। अद्यतन - वर्तमान कंपाइलर अब चेतावनी देता है कि यदि आप सीधे '->' का उपयोग कर चर का उपयोग करने का प्रयास करते हैं। इस कारण से (सुरक्षा के कारण भी) उन चरों के लिए एक संपत्ति बनाना सर्वोत्तम है, जिन्हें आप एक्सेस करना चाहते हैं। तो _self->_iVar = test; के बजाय आप इसका उपयोग करेंगे: _self.iVar = test;

अद्यतन (अधिक जानकारी)

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

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

+0

कैसे एक विधि है कि एक पूरा होने हैंडलर है कि यह बाद में कार्यान्वित स्वीकार करता है के बारे में? क्या कॉलर या विधि ब्लॉक की प्रतिलिपि बनाना चाहिए? – rid

+0

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

+0

@Radu मैंने अधिक जानकारी जोड़ने के लिए अपना उत्तर अपडेट कर दिया है। मैंने लेखों के कुछ लिंक भी शामिल किए हैं जो यहां संभवतः मुकाबले में गहराई से ब्लॉक पर चर्चा करते हैं। –

7

यह सब अच्छा लग रहा है। हालांकि, आप दोबारा जांच करने के ब्लॉक पैरामीटर चाहते हो सकता है:

@property id myObject; 
@property (copy) void (^myBlock)(NSString *); 

....

- (void)testWithBlock: (void (^)(NSString *))block 
{ 
    NSString *testString = @"Test"; 
    if (block) 
    { 
     block(test); 
     myObject = Block_copy(block); 
     myBlock = block; 
    } 
} 

...

[object testWithBlock: ^(NSString *test) 
{ 
    NSLog(@"[%@]", test); 
}]; 

ठीक होना चाहिए। और मेरा मानना ​​है कि वे Block_copy() को चरणबद्ध करने की भी कोशिश कर रहे हैं, लेकिन वे अभी तक नहीं हैं।

+0

तो अगर मैं एआरसी का उपयोग नहीं कर रहा था, तो क्या मुझे विधि दर्ज करते समय ब्लॉक की प्रतिलिपि बनाना होगा, लेकिन एआरसी स्वचालित रूप से ऐसा करता है? या अगर यह एआरसी का उपयोग नहीं करता है तो भी यह समान होगा? – rid

+2

यदि आप इसे वर्तमान विधि के निष्पादन से परे रखते हैं तो आपको केवल ब्लॉक की प्रतिलिपि बनाना होगा। यदि आप इसे गैर-स्थानीय मजबूत ब्लॉक चर में संग्रहीत करते हैं तो एआरसी इसे आपके लिए कॉपी करेगा। यदि आप इसे 'आईडी' के रूप में संग्रहीत करते हैं, तो एआरसी केवल इसे बनाए रखेगा, जो पर्याप्त नहीं है। –

+0

@ केनहोम्स, मुझे यकीन नहीं है कि मैं पूरी तरह से समझता हूं। क्या आप कृपया एक उदाहरण पोस्ट कर सकते हैं जहां मुझे इसे कॉपी करने की आवश्यकता होगी? – rid

4

प्रोग्रामिंग विषयों गाइड ब्लॉक के रूप में 'Copying Blocks' के तहत कहते हैं:

आमतौर पर, आप कॉपी करने के लिए (या बनाए रखने के) एक ब्लॉक की जरूरत नहीं होनी चाहिए। आपको केवल एक प्रतिलिपि बनाने की आवश्यकता होती है जब आप उस दायरे के विनाश के बाद ब्लॉक का उपयोग करने की अपेक्षा करते हैं जिसमें इसे घोषित किया गया था।

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

जैसा कि उद्धृत पाठ से पता चलता है, आपको केवल एक ब्लॉक को स्पष्ट रूप से कॉपी करने की आवश्यकता होती है, जब आप इसे उस दायरे से बाहर पहुंचने के लिए चाहते हैं जहां यह बनाया गया था (आपके मामले में, स्टैक फ्रेम के जीवनकाल से परे आपकी विधि)। उदाहरण के तौर पर, मान लीजिए कि आप एक विधि चाहते हैं जो वेब से कुछ डेटा प्राप्त करे, और प्राप्त होने पर कोड के ब्लॉक को चलाए। आपका विधि हस्ताक्षर लग सकता है जैसे:

- (void)getDataFromURL:(NSURL *)url completionHandler:(void(^)(void))completionHandler;

के बाद से डेटा लाने एसिंक्रोनस रूप से होता है, आप (अपने वर्ग के एक संपत्ति में होने की संभावना) के आसपास ब्लॉक रखने और फिर ब्लॉक चलाना चाहूंगा एक बार डेटा कर दिया गया है पूरी तरह से लाया।इस मामले में, अपने कार्यान्वयन दिखाई देंगे:

@interface MyClass 

@property (nonatomic, copy) void(^dataCompletion)(NSData *); 

@end 



@implementation MyClass 
@synthesize dataCompletion = _dataCompletion; 

- (void)getDataFromURL:(NSURL *)url completionHandler:(void(^)(NSData *fetchedData))completionHandler { 
    self.dataCompletion = completionHandler; 
    [self fetchDataFromURL:url]; 
} 

- (void)fetchDataFromURL:(NSURL *)url { 
    // Data fetch starts here 
} 

- (void)finishedFetchingData:(NSData *)fetchedData { 
    // Called when the data is done being fetched 
    self.dataCompletion(fetchedData) 
    self.dataCompletion = nil; 
} 

इस उदाहरण में, एक copy अर्थ के साथ एक संपत्ति का उपयोग कर खंड पर एक Block_copy() करते हैं और यह ढेर को कॉपी कर देंगे। यह self.dataCompletion = completionHandler लाइन में होता है। इस प्रकार, ब्लॉक को -getDataFromURL:completionHandler: विधि के ढेर फ्रेम से ढेर तक ले जाया जाता है जो इसे finishedFetchingData: विधि में बाद में कॉल करने की अनुमति देता है। बाद की विधि में, लाइन self.dataCompletion = nil संपत्ति को रद्द कर देती है और संग्रहीत ब्लॉक में Block_release() भेजती है, इस प्रकार इसे हटा दिया जाता है।

इस तरह से एक संपत्ति का उपयोग करना अच्छा है, क्योंकि यह अनिवार्य रूप से आप के लिए ब्लॉक स्मृति प्रबंधन के सभी संभाल लेंगे (सिर्फ यकीन है कि यह एक copy (या strong) संपत्ति है बनाने के लिए और नहीं बस एक retain) और दोनों गैर में काम करेंगे एआरसी और एआरसी मामले। यदि इसके बजाय आप अपने ब्लॉक को स्टोर करने के लिए कच्चे इंस्टेंस वैरिएबल का उपयोग करना चाहते थे और गैर-एआरसी वातावरण में काम कर रहे थे, तो आपको Block_copy(), Block_retain(), और Block_release() को सभी उचित स्थानों पर कॉल करना होगा यदि आप किसी के आसपास ब्लॉक रखना चाहते हैं विधि के जीवनकाल से अधिक जिसमें यह पैरामीटर के रूप में पारित किया गया है। एक ही कोड लिखा ऊपर एक संपत्ति के बजाय एक इवर का उपयोग कर इस प्रकार दिखाई देगा:

@interface MyClass { 
    void(^dataCompletion)(NSData *); 
} 

@end 



@implementation MyClass 

- (void)getDataFromURL:(NSURL *)url completionHandler:(void(^)(NSData *fetchedData))completionHandler { 
    dataCompletion = Block_copy(completionHandler); 
    [self fetchDataFromURL:url]; 
} 

- (void)fetchDataFromURL:(NSURL *)url { 
    // Data fetch starts here 
} 

- (void)finishedFetchingData:(NSData *)fetchedData { 
    // Called when the data is done being fetched 
    dataCompletion(fetchedData) 
    Block_release(dataCompletion); 
    dataCompletion = nil; 
} 
+0

तुम सिर्फ उपयोग कर सकते हैं '[completionHandler प्रतिलिपि]' 'Block_copy (completionHandler)' और '[dataCompletion रिहाई]' 'Block_release (dataCompletion) के बजाय के बजाय' – user102008

0

तुम्हें पता ब्लॉक के दो प्रकार होते हैं:

  1. ब्लॉक ढेर में जमा हो जाती, जो कि आप स्पष्ट रूप से^{...} के रूप में लिखें, और जैसे ही वे रिटर्न में बनाए गए फ़ंक्शन के रूप में गायब हो जाते हैं, जैसे नियमित स्टैक वैरिएबल। जब आप फ़ंक्शन के बाद स्टैक ब्लॉक को कॉल करते हैं तो यह वापस आ गया है, बुरी चीजें होती हैं।

  2. ढेर में ब्लॉक, जो आप किसी अन्य ब्लॉक की प्रतिलिपि करते समय प्राप्त करते हैं, वे जो किसी अन्य वस्तु के रूप में लंबे समय तक रहते हैं, नियमित वस्तुओं की तरह, उनके संदर्भ में रहते हैं।

आप एक ब्लॉक को कॉपी करने के लिए एकमात्र कारण है जब आप एक ब्लॉक है कि दिया जाता है, या एक ढेर ब्लॉक (स्पष्ट स्थानीय ब्लॉक^{...}, या विधि तर्क जिसका मूल आपके पास हो सकता है पता नहीं है), और आप अपने जीवन का विस्तार स्टैक ब्लॉक के सीमित एक से बाहर करना चाहते हैं, और यह कि संकलक पहले से ही आपके लिए यह काम नहीं करता है।

सोचें: एक उदाहरण चर में एक ब्लॉक रखना।

एनएसएआरएआरए जैसे संग्रह में एक ब्लॉक जोड़ना।

उन मामलों के सामान्य उदाहरण हैं जहां आपको ब्लॉक की प्रतिलिपि बनाना चाहिए, जब आप सुनिश्चित नहीं हैं कि यह पहले से ही एक ढेर ब्लॉक है।

ध्यान दें कि जब किसी अन्य ब्लॉक में ब्लॉक को कॉल किया जाता है तो संकलक आपके लिए करता है।