10

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

समस्या तब होती है जब डाउनलोड समाप्त होने से पहले उपयोगकर्ता द्वारा मेरा व्यू कंट्रोलर खारिज कर दिया जाता है। मुझे संदेह है कि क्या हो रहा है, एक बार मेरा व्यू कंट्रोलर खारिज कर दिया जाता है, कॉलबैक ब्लॉक एकमात्र चीज है जो नियंत्रक के लिए एक मजबूत संदर्भ बनाए रखता है। कॉलबैक ब्लॉक केवल पृष्ठभूमि थ्रेड में ही बनाए रखा जाता है, इसलिए इसे रिलीज़ होने के बाद, कॉलबैक ब्लॉक के दायरे में कैप्चर की गई सभी ऑब्जेक्ट्स को भी पृष्ठभूमि कतार में छोड़ दिया जाता है।

यह समस्या है: पृष्ठभूमि कतार में रिलीज़ होने से मुख्य कतार में नहीं, उसी कतार में डेलोक को चलाने का कारण बनता है।

2012-01-19 12:47:36.349 500px iOS[4892:12107] bool _WebTryThreadLock(bool), 0x306c10: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now... 
[Switching to process 16643 thread 0x4103] 
[Switching to process 16643 thread 0x4103] 
(gdb) where 
#0 0x307fd3c8 in _WebTryThreadLock() 
#1 0x307ff1b0 in WebThreadLock() 
#2 0x33f7865e in -[UITextView dealloc]() 
#3 0x0005d2ce in -[GCPlaceholderTextView dealloc] (self=0x309010, _cmd=0x340ce6b8) at /Users/ash/Dropbox/500px/500px-ios/500px iOS/500px iOS/GCPlaceholderTextView.m:113 
#4 0x337cac42 in -[NSObject(NSObject) release]() 
#5 0x33dee5f4 in -[UIView(Hierarchy) removeFromSuperview]() 
#6 0x33e836cc in -[UIScrollView removeFromSuperview]() 
#7 0x33f762f0 in -[UITextView removeFromSuperview]() 
#8 0x33e01de2 in -[UIView dealloc]() 
#9 0x337cac42 in -[NSObject(NSObject) release]() 
#10 0x33dee5f4 in -[UIView(Hierarchy) removeFromSuperview]() 
#11 0x33e01de2 in -[UIView dealloc]() 
#12 0x33f437e4 in -[UIScrollView dealloc]() 
#13 0x337cac42 in -[NSObject(NSObject) release]() 
#14 0x33dee5f4 in -[UIView(Hierarchy) removeFromSuperview]() 
#15 0x33e836cc in -[UIScrollView removeFromSuperview]() 
#16 0x33e01de2 in -[UIView dealloc]() 
#17 0x337cac42 in -[NSObject(NSObject) release]() 
#18 0x33dee5f4 in -[UIView(Hierarchy) removeFromSuperview]() 
#19 0x33e01de2 in -[UIView dealloc]() 
#20 0x337cac42 in -[NSObject(NSObject) release]() 
#21 0x33e5a00e in -[UIViewController dealloc]() 
#22 0x00035f16 in -[PXPhotoViewController dealloc] (self=0x5158d0, _cmd=0x340ce6b8) at /Users/ash/Dropbox/500px/500px-ios/500px iOS/500px iOS/PXPhotoViewController.m:118 
#23 0x337cac42 in -[NSObject(NSObject) release]() 
#24 0x337e5046 in sendRelease() 
#25 0x331fc92e in _Block_object_dispose() 
#26 0x0003c33a in __destroy_helper_block_() at /Users/ash/Dropbox/500px/500px-ios/500px iOS/500px iOS/PXPhotoViewController.m:878 
#27 0x331fc88e in _Block_release() 
#28 0x331fc91c in _Block_object_dispose() 
#29 0x000c8d32 in __destroy_helper_block_() at /Users/ash/Dropbox/500px/500px-ios/500px iOS/500px iOS/PXPhotoFetcher.m:557 
#30 0x331fc88e in _Block_release() 
#31 0x35eec8ec in _dispatch_call_block_and_release() 
#32 0x35ee2de2 in _dispatch_queue_drain() 
#33 0x35ee2f32 in _dispatch_queue_invoke() 
#34 0x35ee24f2 in _dispatch_worker_thread2() 
#35 0x34ecb590 in _pthread_wqthread() 
#36 0x34ecbbc4 in start_wqthread() 

कोड मैं मुख्य थ्रेड पर निष्पादित इस तरह दिखता है: यह, बारी में, पृष्ठभूमि में dealloc और ऐप्लिकेशन क्रैश कॉल

[[PXPhotoFetcher sharedPXPhotoFetcher] fetchPhotoDetailsWithPriority:PhotoRequestLowPriority 
    withCallback:^(PXPhotoModel *thePhotoModel) { 
     // a callback function which captures self in its scope 
    } forModel:model]; 

मैं 4.3 के लिए निर्माण कर रहा हूँ, इसलिए यदि मैं कॉलबैक ब्लॉक में स्वयं को __unsafe_unretained संदर्भ का उपयोग करता हूं, यह मेरी वर्तमान समस्या को ठीक करेगा लेकिन एक लटकते पॉइंटर होने की नई समस्या पेश करेगा।

इसके लिए समाधान बनाने के लिए आप क्या सलाह देते हैं? अन्य कामकाजों में overriding release शामिल था ताकि इसे हमेशा मुख्य धागे पर बुलाया जा सके, लेकिन यह स्पष्ट रूप से एआरसी पर्यावरण में काम नहीं करेगा।

ऐसा लगता है कि यह एआरसी और जीसीडी के साथ एक बहुत ही आम समस्या होनी चाहिए, लेकिन मुझे कुछ भी ऑनलाइन नहीं मिल रहा है। क्या यह सिर्फ इसलिए है क्योंकि मैं < आईओएस 5 को लक्षित कर रहा हूं और कमजोर संदर्भों का उपयोग नहीं कर सकता?

उत्तर

5

अपडेट: नीचे दिया गया समाधान वास्तव में आईओएस 4 पर काम नहीं करता था। यह किसी कारण से 5 पर काम करता था, लेकिन 4 नहीं, इसलिए मैं एक बेहतर समाधान के साथ आया।

समस्या पृष्ठभूमि में अवरुद्ध होने के कारण हुई है, इसलिए मैंने इसे स्थानीय चर में डाल दिया है, और पृष्ठभूमि ब्लॉक के भीतर, इसे आमंत्रित किया है और फिर इसे मुख्य थ्रेड पर एक ब्लॉक में असीमित रूप से पास कर दिया है, इसलिए इसे वहां रिलीज़ किया गया है । यह भी गंदा है, लेकिन ऐसा दिखाई देता है:

void(^block)(void) = ^{/*do all the things*/}; 

dispatch_async(queue, ^{ 

    block(); 

    dispatch_async(dispatch_get_main_queue(), ^{ 
     if ([block isKindOfClass:[NSString class]]) 
      NSLog(@"Whoa, bro"); 
    }); 
}); 

मुख्य थ्रेड पर कोड सिर्फ एक यकीन है कि संकलक बस पूरी तरह कोड दूर अनुकूलन नहीं करता बनाने के लिए चाल है, मुझे मुख्य थ्रेड द्वारा आखिरकार जारी किए जाने वाले ब्लॉक ऑब्जेक्ट की आवश्यकता है। यह कोड -Os कंपाइलर अनुकूलन स्तर के साथ काम करता प्रतीत होता है।

तो मैं अपनी समस्या के समाधान के साथ आया हूं, हालांकि यह सुपर हैकी है। मैं इस फिक्स के बाद से समस्या को पुन: उत्पन्न करने में असमर्थ रहा हूं, हालांकि यह एक बहुत ही खराब वास्तुशिल्प डिजाइन है जो मुझे लगता है।

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

dispatch_async(backgroundQueue, ^{ 
    /* do all the things */ 
}); 
इस के लिए

:

dispatch_async(dispatch_get_main_queue(), ^{ 
    dispatch_async(backgroundQueue, ^{ 
     /* do all the things */ 
    }); 
}); 

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

स्पष्ट आर्किटेक्चरल समाधान मेरे कॉलबैक ब्लॉक में __weak संदर्भ का उपयोग करना है, लेकिन मुझे आईओएस 4.3 के लिए समर्थन छोड़ने तक प्रतीक्षा करनी होगी।

+1

यदि आप एक कमजोर संदर्भ पसंद करते हैं तो आप http://mikeash.com/pyblog/introducing-mazeroingweakref.html – tapi

+0

देख सकते हैं यह थोड़ा बदसूरत है, लेकिन यह एक चालाक चाल है। काश मैं एक बेहतर जवाब था। –

+0

हाँ मैंने माइक ऐश के समाधान को देखा, लेकिन चूंकि मैं केवल अल्पकालिक अवधि में आईओएस 4.3 का समर्थन कर रहा हूं, इसलिए मैं थोड़ी देर के लिए इस हैक के साथ रहूंगा। –

0

आपका विश्लेषण ध्वनि लगता है। तो शायद ब्लॉक का अंत नियंत्रक को बनाए रख सकता है, और मुख्य धागे पर एक ऑटोरेलीज कर सकता है (शायद थोड़ी देर के बाद, दौड़ की स्थिति से बचने के लिए)।

+0

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

+0

दोह। यद्यपि शायद यह उस ब्लॉक को प्रेषित करेगा जिसमें संदर्भ है (मुझे लगता है कि यह वही है जो आपने किया था, लेकिन अंदरूनी)। –

0

आप कभी भी गारंटी नहीं दे सकते कि किसी वस्तु को किसी विशेष थ्रेड पर अस्वीकार कर दिया जाएगा, जैसा कि आपने पाया है।

मैंने पाया है कि सबसे अच्छा समाधान आपके डेलोक में है, यह देखने के लिए जांचें कि क्या आप मुख्य धागे पर हैं या नहीं। यदि नहीं, तो बाकी थैलोक के साथ मुख्य धागे पर प्रदर्शन चयनकर्ता और पूरा होने की प्रतीक्षा करें।

वैकल्पिक रूप से, ब्लॉक के साथ उसी तर्क को लागू करें: यदि वह मुख्य धागे पर नहीं है, तो डिस्चैक के साथ मुख्य कतार में dispatch_sync।

+0

लेकिन यह एआरसी में काम नहीं करेगा क्योंकि मैं स्पष्ट रूप से 'dealloc' को कॉल नहीं कर सकता, यहां तक ​​कि '[सुपर डेलोक]', इसलिए मुख्य धागे पर निष्पादन स्विच करने का कोई तरीका नहीं है। –

+0

डेलोक अभी भी मौजूद है। एआरसी सिर्फ इसे आपके लिए स्वचालित रूप से कॉल करता है। – user1139069

2

ऐश,

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

dispatch_async(backgroundQueue, ^{ 

    /* download stuff */ 

    dispatch_sync(dispatch_get_main_queue(), ^{ 

     // Pass the data to the controller. 
    }); 
}); 

यह पैटर्न आप मुख्य थ्रेड पर किसी भी UI घटक के लिए अपने डेटा के समुचित वितरण की गारंटी और फिर सुंदर रिहाई की अनुमति देता है की सुविधा देता है।

एंड्रयू

__block चर के साथ दूसरे जवाब के लिए संपादित करें:

__block UIViewController *vc = danglingVC; 

dispatch_async(backgroundQueue, ^{ 

    /* download stuff */ 

    dispatch_async(dispatch_get_main_queue(), ^{ 

     // Pass the data to the controller. 

     vc = nil; 
    }); 
}); 
एआरसी के साथ

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

एंड्रयू

+1

यह उस क्रम को नहीं बदलेगा जिसमें चीजों को हटाया जाता है, केवल उस क्रम में जिसमें आंतरिक ब्लॉक चलाया जाता है। –

+0

बीजे, जिस क्रम में चीजें चल रही हैं उसे बदलना बिंदु है। सिंक्रोनस कॉल एसिंक कतार को निकालने से रोकता है। इसलिए, नियंत्रक पर बनाए रखा डाउनलोड जारी होने के बाद तक जारी नहीं किया जाता है और डेटा नियंत्रक को दिया जाता है। यह नाटकीय रूप से एक सुरक्षित आदेश पर हटाने के आदेश को बदलता है। एंड्रयू – adonoho

+0

@adonoho यह मेरी किसी भी वस्तु पर समय से पहले रिलीज नहीं है, यह दायरा का मामला है। यहां तक ​​कि यदि मैं आपके 'dispatch_sync' समाधान का उपयोग करता हूं, तो पहली पंक्ति (' backgroundQueue' 'पर पारित ब्लॉक ऑब्जेक्ट अंततः मेरे व्यू कंट्रोलर को बनाए रखता है और उस ब्लॉक ऑब्जेक्ट को पृष्ठभूमि थ्रेड पर हमेशा सिंक्रोनस कॉल के साथ जारी किया जाएगा । –

3

वास्तव में, GCD को बनाए रखने और इस उद्देश्य के लिए प्रेषण कतारों के लिए जारी की अनुमति देता है। यह वास्तव में दस्तावेज है: "Memory Management for Dispatch Queues"

dispatch_retain(first_queue); 
dispatch_async(a_queue, ^{ 
          do_not_wait_for_me(); 
          dispatch_async(first_queue, ^{ i_am_done_now(); }); 
          dispatch_release(first_queue); 
         }); 

आपके परिदृश्य में मैं आपकी मुख्य प्रेषण कतार के साथ first_queue को प्रतिस्थापित कर दूंगा। मुख्य प्रेषण कतार को बनाए रखकर, आप यह सुनिश्चित करेंगे कि कॉलबैक समाप्त होने तक इसे रिलीज़ नहीं किया जाएगा।

एक और उदाहरण पाया जा सकता है पर: "Performing a Completion Block When a Task Is Done."

+0

यह पृष्ठभूमि थ्रेड पर uitextview * deallocated * की समस्या का समाधान नहीं करता है। –

+0

क्या आप मुझे UITextView से संबंधित कुछ और संदर्भ दे सकते हैं। आपके उदाहरण और उत्तर दोनों में मुझे डीबग आउटपुट को छोड़कर UITextView का कोई संदर्भ नहीं दिखता है। साथ ही, आपके उत्तर में आप एक पैटर्न का उपयोग कर रहे हैं जो आपके ब्लॉक को एक स्टैक-स्थानीय डेटा संरचना बनाता है। यह एक पैटर्न है कि ["ब्लॉक प्रोग्रामिंग विषय: पैटर्न से बचें"] (http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Blocks/Articles/bxUsing.html#//apple_ref/डॉक्टर/यूआईडी/टीपी 40007502-सीएच 5-एसडब्ल्यू 6) दायरे की समस्याओं के कारण सलाह देता है। – flukey

0

एक वैकल्पिक प्रेषण async के बाहर एक दायरे में वस्तु का एक संदर्भ स्टोर करने के लिए किया जाएगा।उदाहरण के लिए:

// create a dispatch queue 
dispatch_queue_t sdq = dispatch_queue_create("com.fred.exampleQueue", NULL); 

// my object references container 
__block NSMutableArray *ressources = [[NSMutableArray alloc] init]; 

dispatch_async(sdq, ^{ 
    __block MyLoader *ml = [[MyAsyncLoader alloc] initWithCallback:^(id result) 
    { 
     NSLog(@"loader result: %@", result); 
     // since I ask for ressource in my final CallBack it's still here 
     // and my loader too, I can now dispose of it. 
     [ressources removeObject:ml]; 
    }]; 

    // perform async loading and call my callback when it's done... 
    [ml asyncLoad]; 
    // save my object 
    [ressources addObject:ml]; 
}); 
0

मुझे हाल ही में इसी तरह की समस्या आई। इस विशेष समस्या को हल करने के लिए मुझे एआरसी में कमजोर संदर्भों का शून्य उपयोग करने की लक्जरी थी। हालांकि मैंने एक वैकल्पिक समाधान पर विचार किया जो एमआरसी के साथ काम करना चाहिए।

__block UIViewController *vc = ...; 
[vc retain]; 
dispatch_async(backgroundQueue, ^{ 
    // ... do some things with vc 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     // ... do more things with vc 
     [vc release]; // vc might be dealloc'd here but not before 
    }); 
}); 

vc पर __block भंडारण क्वालीफायर सुनिश्चित करता है कि ब्लॉक वस्तु vc से जाना जाता बनाए रखने नहीं है। व्यू कंट्रोलर तब तक बनाए रखा जाता है जब तक कि मुख्य कतार में कॉल किए जाने वाले ब्लॉक को रिलीज़ नहीं किया जाता है और संभवतः उस बिंदु पर dealloc'd हो जाता है।