PerformSelector एक रिसाव का कारण हो सकता है क्योंकि इसका चयनकर्ता अज्ञात है


1258

मुझे ARC संकलक द्वारा निम्नलिखित चेतावनी मिल रही है:

"performSelector may cause a leak because its selector is unknown".

यहाँ मैं क्या कर रहा हूँ:

[_controller performSelector:NSSelectorFromString(@"someMethod")];

मुझे यह चेतावनी क्यों मिलती है? मैं समझता हूं कि संकलक यह जांच नहीं कर सकता है कि चयनकर्ता मौजूद है या नहीं, लेकिन यह रिसाव का कारण क्यों होगा? और मैं अपना कोड कैसे बदल सकता हूं ताकि मुझे यह चेतावनी न मिले?


3
चर का नाम गतिशील है, यह कई अन्य चीजों पर निर्भर करता है। वहाँ जोखिम है कि मैं ऐसा कुछ कहता हूं जो मौजूद नहीं है, लेकिन यह समस्या नहीं है।
एडुआर्डो स्कोज

6
@matt किसी वस्तु पर गतिशील रूप से कॉल करने का तरीका खराब अभ्यास क्यों होगा? इस अभ्यास का समर्थन करने के लिए NSSelectorFromString () का पूरा उद्देश्य नहीं है?
एडुआर्डो स्कोज


50
@ महामहिम काश मैं वोट कर सकता था: "वह ... बुरा व्यवहार है।"
ctpenrose

6
यदि आप जानते हैं कि स्ट्रिंग एक शाब्दिक है, तो बस @selector () का उपयोग करें ताकि संकलक बता सके कि चयनकर्ता का नाम क्या है। यदि आपका वास्तविक कोड NSSelectorFromString () का निर्माण एक स्ट्रिंग के साथ कर रहा है या रनटाइम पर प्रदान किया गया है, तो आपको NSSelectorFromString () का उपयोग करना होगा।
क्रिस पेज

जवाबों:


1211

समाधान

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

if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);

या अधिक प्रतिकूल (हालांकि पढ़ने के लिए और गार्ड के बिना कठिन):

SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);

व्याख्या

यहाँ क्या चल रहा है आप नियंत्रक के लिए विधि के लिए सी फ़ंक्शन पॉइंटर के लिए नियंत्रक से पूछ रहे हैं। सभी NSObjectएस प्रतिक्रिया करते हैं methodForSelector:, लेकिन आप class_getMethodImplementationऑब्जेक्टिव-सी रनटाइम में भी उपयोग कर सकते हैं (उपयोगी है यदि आपके पास केवल एक प्रोटोकॉल संदर्भ है, जैसे id<SomeProto>)। इन फ़ंक्शन पॉइंटर्स को IMPs कहा जाता है , और सरल typedefएड फंक्शन पॉइंटर्स ( id (*IMP)(id, SEL, ...)) 1 हैं । यह विधि के वास्तविक विधि हस्ताक्षर के करीब हो सकता है, लेकिन हमेशा बिल्कुल मेल नहीं खाएगा।

आपके पास एक बार IMP, आपको इसे एक फ़ंक्शन पॉइंटर में डालना होगा जिसमें सभी विवरण शामिल हों जो एआरसी की आवश्यकता है (दो निहित छिपे हुए तर्क selfऔर _cmdप्रत्येक उद्देश्य-सी विधि कॉल सहित)। यह तीसरी पंक्ति में संभाला जाता है ( (void *)दाएं हाथ की तरफ बस संकलक को बताता है कि आप जानते हैं कि आप क्या कर रहे हैं और सूचक प्रकार से मेल नहीं खाने के बाद चेतावनी उत्पन्न करने के लिए नहीं)।

अंत में, आप फ़ंक्शन पॉइंटर 2 कहते हैं

जटिल उदाहरण

जब चयनकर्ता तर्क देता है या मूल्य लौटाता है, तो आपको चीजों को थोड़ा बदलना होगा:

SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = _controller ?
  func(_controller, selector, someRect, someView) : CGRectZero;

चेतावनी के लिए तर्क

इस चेतावनी का कारण यह है कि एआरसी के साथ, रनटाइम को यह जानना होगा कि आपके द्वारा कॉल की जा रही विधि के परिणाम के साथ क्या करना है। परिणाम कुछ भी हो सकता है: void, int, char, NSString *, id, आदि एआरसी सामान्य रूप से ऑब्जेक्ट प्रकार आप के साथ काम कर रहे हैं के शीर्षक से यह जानकारी मिलती है। 3

वास्तव में केवल 4 चीजें हैं जो एआरसी रिटर्न मूल्य के लिए विचार करेंगे: 4

  1. ध्यान न दें गैर वस्तु प्रकार ( void, int, आदि)
  2. ऑब्जेक्ट का मूल्य फिर से प्राप्त करें, तब जारी करें जब इसका उपयोग नहीं किया जाता है (मानक धारणा)
  3. अब उपयोग नहीं किए जाने पर नए ऑब्जेक्ट वैल्यू जारी करें ( परिवार init/ copyपरिवार के तरीके ns_returns_retained)
  4. कुछ भी न करें और लौटाए गए ऑब्जेक्ट मान स्थानीय दायरे में मान्य होंगे (जब तक कि आंतरिक सबसे रिलीज पूल को सूखा नहीं जाता है, इसके साथ जिम्मेदार ठहराया जाता है ns_returns_autoreleased)

कॉल यह methodForSelector:मानती है कि जिस विधि से कॉल किया जा रहा है उसका रिटर्न मान एक ऑब्जेक्ट है, लेकिन इसे बनाए / जारी नहीं करता है। तो आप एक रिसाव पैदा कर सकते हैं यदि आपकी वस्तु को ऊपर # 3 के रूप में जारी किया जाना चाहिए (अर्थात, वह विधि जिसे आप एक नई वस्तु कहते हैं)।

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

अतिरिक्त तर्क

एक विचार यह है कि यह वही चेतावनी है जिसके साथ होगा performSelector:withObject:और आप इसी तरह की समस्याओं में भाग लेने के साथ यह घोषित नहीं कर सकते हैं कि यह पद्धति कैसे मापदंडों का उपभोग करती है। एआरसी उपभोग किए गए मापदंडों को घोषित करने की अनुमति देता है , और यदि विधि पैरामीटर का उपभोग करती है, तो आप संभवतः एक ज़ोंबी और दुर्घटना के लिए एक संदेश भेजेंगे। ब्रिजिंग कास्टिंग के साथ इसके आस-पास काम करने के तरीके हैं, लेकिन वास्तव में यह बेहतर होगा कि आप केवल IMPऊपर और फ़ंक्शन पॉइंटरोलॉजी का उपयोग करें । चूंकि खपत किए गए पैरामीटर शायद ही कभी एक मुद्दा होते हैं, इसलिए यह आने की संभावना नहीं है।

स्थैतिक चयनकर्ता

दिलचस्प बात यह है कि कंपाइलर सेलेक्टर्स के बारे में स्टेटिकली घोषित नहीं करेगा:

[_controller performSelector:@selector(someMethod)];

इसका कारण यह है क्योंकि संकलनकर्ता वास्तव में संकलन के दौरान चयनकर्ता और वस्तु के बारे में सभी जानकारी रिकॉर्ड करने में सक्षम है। इसे किसी भी चीज के बारे में कोई धारणा बनाने की जरूरत नहीं है। (मैंने स्रोत को देखकर एक साल पहले यह जाँच की थी, लेकिन अभी एक संदर्भ नहीं है।)

दमन

ऐसी स्थिति के बारे में सोचने की कोशिश में जहां इस चेतावनी का दमन आवश्यक और अच्छे कोड डिजाइन के रूप में हो रहा है, मैं खाली आ रहा हूं। यदि किसी को यह अनुभव हुआ है तो कृपया साझा करें यदि यह चेतावनी देना आवश्यक था (और उपरोक्त चीजों को ठीक से नहीं संभालता है)।

अधिक

इसे NSMethodInvocationसंभालने के लिए एक निर्माण करना संभव है , लेकिन ऐसा करने के लिए बहुत अधिक टाइपिंग की आवश्यकता होती है और यह धीमा भी होता है, इसलिए ऐसा करने का बहुत कम कारण है।

इतिहास

जब performSelector:तरीकों के परिवार को पहले उद्देश्य-सी में जोड़ा गया था, तो एआरसी मौजूद नहीं था। ARC का निर्माण करते समय, Apple ने निर्णय लिया कि इन तरीकों के लिए एक गाइड के रूप में डेवलपर्स के लिए एक चेतावनी तैयार की जानी चाहिए ताकि अन्य साधनों का उपयोग करके स्पष्ट रूप से परिभाषित किया जा सके कि नामित चयनकर्ता के माध्यम से मनमाना संदेश भेजते समय मेमोरी को कैसे संभाला जाना चाहिए। ऑब्जेक्टिव-सी में, डेवलपर्स कच्चे फ़ंक्शन पॉइंटर्स पर सी स्टाइल कास्ट्स का उपयोग करके ऐसा करने में सक्षम हैं।

स्विफ्ट की शुरुआत के साथ, ऐप्पल नेperformSelector: तरीकों के परिवार को "स्वाभाविक रूप से असुरक्षित" के रूप में प्रलेखित किया है और वे स्विफ्ट के लिए उपलब्ध नहीं हैं।

समय के साथ, हमने यह प्रगति देखी है:

  1. उद्देश्य-सी अनुमति के प्रारंभिक संस्करण performSelector:(मैनुअल मेमोरी मैनेजमेंट)
  2. एआरसी के साथ उद्देश्य-सी के उपयोग के लिए चेतावनी देता है performSelector:
  3. स्विफ्ट की performSelector:इन विधियों तक पहुँच नहीं है और इन तरीकों को "स्वाभाविक रूप से असुरक्षित" माना जाता है।

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


1 सभी उद्देश्य-सी तरीकों में दो छिपे हुए तर्क होते हैं, selfऔर _cmdजब आप किसी विधि को कॉल करते हैं, तो इसे जोड़ दिया जाता है।

2 एक NULLफ़ंक्शन को कॉल करना सी में सुरक्षित नहीं है। नियंत्रक की उपस्थिति के लिए जांच करने के लिए उपयोग किया जाने वाला गार्ड यह सुनिश्चित करता है कि हमारे पास एक वस्तु है। इसलिए हम जानते हैं कि हमें एक IMPसे मिलेगा methodForSelector:(हालांकि यह हो सकता है _objc_msgForward, संदेश अग्रेषण प्रणाली में प्रवेश)। मूल रूप से, गार्ड के साथ, हमें पता है कि हमारे पास कॉल करने के लिए एक फ़ंक्शन है।

3 वास्तव में, यह गलत जानकारी प्राप्त करने के लिए संभव है यदि आप वस्तुओं को घोषित करते हैं idऔर आप सभी हेडर आयात नहीं कर रहे हैं। आप कोड में क्रैश के साथ समाप्त हो सकते हैं जो संकलक को लगता है कि ठीक है। यह बहुत दुर्लभ है, लेकिन ऐसा हो सकता है। आमतौर पर आपको बस एक चेतावनी मिलेगी कि यह नहीं पता है कि चुनने के लिए कौन से दो विधि हस्ताक्षर हैं।

4 पर एआरसी संदर्भ देखें बनाए रखा वापसी मूल्यों और unretained वापसी मान अधिक जानकारी के लिए।


@wbyoung अगर आपका कोड रिटेनिंग समस्या को हल करता है, तो मुझे आश्चर्य है कि performSelector:इस तरीके को लागू क्यों नहीं किया जाता है। उनके पास सख्त विधि हस्ताक्षर हैं (लौटने id, एक या दो idएस लेने ), इसलिए किसी भी आदिम प्रकार को संभालने की आवश्यकता नहीं है।
Tricertops 10

1
@ और तर्क को विधि के प्रोटोटाइप की परिभाषा के आधार पर नियंत्रित किया जाता है (इसे बरकरार / जारी नहीं किया जाएगा)। चिंता ज्यादातर रिटर्न प्रकार पर आधारित है।
wbyoung

2
"कॉम्प्लेक्स उदाहरण" Cannot initialize a variable of type 'CGRect (*)(__strong id, SEL, CGRect, UIView *__strong)' with an rvalue of type 'void *'नवीनतम Xcode का उपयोग करते समय एक त्रुटि देता है । (५.१.१) फिर भी, मैंने बहुत कुछ सीखा!
स्टेन जेम्स

2
void (*func)(id, SEL) = (void *)imp;संकलन नहीं करता है, मैंने इसे बदल दिया हैvoid (*func)(id, SEL) = (void (*)(id, SEL))imp;
डेविड गेल

1
परिवर्तन void (*func)(id, SEL) = (void *)imp;करने के लिए <…> = (void (*))imp;या<…> = (void (*) (id, SEL))imp;
Isaak Osipovich Dunayevsky

1182

XV 4.2 में LLVM 3.0 कंपाइलर में आप चेतावनी को इस प्रकार दबा सकते हैं:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self.ticketTarget performSelector: self.ticketAction withObject: self];
#pragma clang diagnostic pop

यदि आप कई स्थानों पर त्रुटि प्राप्त कर रहे हैं, और व्यावहारिकताओं को छिपाने के लिए सी मैक्रो प्रणाली का उपयोग करना चाहते हैं, तो आप चेतावनी को दबाने के लिए मैक्रो को आसान बनाने के लिए परिभाषित कर सकते हैं:

#define SuppressPerformSelectorLeakWarning(Stuff) \
    do { \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
        Stuff; \
        _Pragma("clang diagnostic pop") \
    } while (0)

आप इस तरह मैक्रो का उपयोग कर सकते हैं:

SuppressPerformSelectorLeakWarning(
    [_target performSelector:_action withObject:self]
);

यदि आपको प्रदर्शन किए गए संदेश के परिणाम की आवश्यकता है, तो आप यह कर सकते हैं:

id result;
SuppressPerformSelectorLeakWarning(
    result = [_target performSelector:_action withObject:self]
);

यह विधि मेमोरी लीक का कारण बन सकती है जब अनुकूलन किसी और के अलावा किसी अन्य चीज़ पर सेट हो।
एरिक

4
जब तक आप "initSomething" या "newSomething" या "somethingCopy" जैसे मजाकिया तरीकों का आह्वान नहीं कर रहे, तब तक यह नहीं हो सकता।
एंड्री टारनटोसोव

3
@ जूलियन यह काम करता है, लेकिन यह पूरी फाइल के लिए चेतावनी को बंद कर देता है - आपको इसकी आवश्यकता नहीं है या नहीं चाहिए। साथ यह Wrappping popऔर push-pragmas अधिक स्वच्छ और अधिक सुरक्षित हैं।
एमिल

2
यह सब करता है क्या यह संकलक चुप है। इससे समस्या का समाधान नहीं होता है। यदि चयनकर्ता मौजूद नहीं है तो आप बहुत अधिक खराब हैं।
आंद्रा टोडोरेसु

2
इसका उपयोग केवल तभी किया जाना चाहिए जब एक if ([_target respondsToSelector:_selector]) {या समान तर्क द्वारा लपेटा जाए ।

208

इसके बारे में मेरा अनुमान यह है: चूँकि चयनकर्ता कंपाइलर से अनजान है, इसलिए ARC उचित मेमोरी मैनेजमेंट लागू नहीं कर सकता है।

वास्तव में, ऐसे समय होते हैं जब मेमोरी प्रबंधन एक विशिष्ट सम्मेलन द्वारा विधि के नाम से बंधा होता है। विशेष रूप से, मैं के बारे में सोच रहा हूँ सुविधा कंस्ट्रक्टर्स बनाम मेकअप तरीकों; एक ऑटोरेल्ड ऑब्जेक्ट कन्वेंशन द्वारा पूर्व वापसी; उत्तरार्द्ध एक बरकरार वस्तु है। सम्मेलन चयनकर्ता के नामों पर आधारित है, इसलिए यदि कंपाइलर चयनकर्ता को नहीं जानता है, तो वह उचित मेमोरी प्रबंधन नियम को लागू नहीं कर सकता है।

यदि यह सही है, तो मुझे लगता है कि आप सुरक्षित रूप से अपने कोड का उपयोग कर सकते हैं, बशर्ते आप यह सुनिश्चित करें कि सब कुछ मेमोरी मैनेजमेंट की तरह ठीक है (जैसे, आपके तरीके उन वस्तुओं को वापस नहीं करते हैं जो वे आवंटित करते हैं)।


5
उत्तर के लिए धन्यवाद, मैं इस पर अधिक गौर करूंगा कि क्या चल रहा है। हालांकि मैं चेतावनी को कैसे दरकिनार कर सकता हूं और इसे गायब कर दूंगा इस पर कोई विचार? मुझे चेतावनी है कि मेरे कोड में हमेशा के लिए बैठे रहने से क्या सुरक्षित कॉल होगा।
एडुआर्डो स्कोज़

84
इसलिए मुझे उनके मंचों पर Apple से किसी की पुष्टि मिली कि यह वास्तव में मामला है। वे भविष्य में रिलीज़ में लोगों को इस चेतावनी को अक्षम करने की अनुमति देने के लिए एक भूल ओवरराइड जोड़ रहे होंगे। धन्यवाद।
एडुआर्डो स्कोज़

5
यह उत्तर कुछ प्रश्न उठाता है, जैसे यदि एआरसी, सम्मेलन और विधि के नामों के आधार पर कुछ जारी करने के लिए निर्धारण करने की कोशिश करता है, तो यह "संदर्भ गणना" कैसे है? आप जिस व्यवहार का वर्णन करते हैं, वह केवल पूरी तरह से मनमाने ढंग से बेहतर लगता है, अगर एआरसी कोड मान रहा है तो वास्तव में संदर्भों का ट्रैक रखने के लिए एक निश्चित सम्मेलन का विरोध करता है, चाहे कोई भी सम्मेलन हो।
अरथ

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

8
इसलिए हमारे पास अब प्रकार का आइवर नहीं हो सकता है SELऔर स्थिति के आधार पर विभिन्न चयनकर्ताओं को असाइन कर सकता है? जाने का रास्ता, गतिशील भाषा ...
निकोलस मारी

121

अपनी परियोजना में अन्य चेतावनी झंडे ( ) के तहत सेटिंग बनाएँ , जोड़ेंWARNING_CFLAGS
-Wno-arc-performSelector-leaks

अब बस यह सुनिश्चित करें कि आप जिस चयनकर्ता को बुला रहे हैं वह आपकी वस्तु को बनाए रखने या कॉपी करने का कारण नहीं है।


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

111

जब तक संकलक चेतावनी को ओवरराइड करने की अनुमति देता है तब तक वर्कअराउंड के रूप में, आप रनटाइम का उपयोग कर सकते हैं

objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));

के बजाय

[_controller performSelector:NSSelectorFromString(@"someMethod")];

तुमको करना होगा

#import <objc/message.h>


8
एआरसी कोको सम्मेलनों को मान्यता देता है और फिर उन सम्मेलनों के आधार पर रिटेन और रिलीज जोड़ता है। क्योंकि C उन सम्मेलनों का पालन नहीं करता है, ARC आपको मैन्युअल मेमोरी प्रबंधन तकनीकों का उपयोग करने के लिए मजबूर करता है। यदि आप एक CF ऑब्जेक्ट बनाते हैं, तो आपको CFRelease () होना चाहिए। यदि आप भेजते हैं, तो आपको डिस्पैच_रेल () करना होगा। निचला रेखा, यदि आप एआरसी चेतावनियों से बचना चाहते हैं, तो आप सी ऑब्जेक्ट्स और मैनुअल मेमोरी मैनेजमेंट का उपयोग करके उनसे बच सकते हैं। साथ ही, आप उस फ़ाइल पर -fno-objc-arc संकलक ध्वज का उपयोग करके ARC को प्रति-फ़ाइल के आधार पर अक्षम कर सकते हैं।
jluckyiv

8
कास्टिंग के बिना, आप नहीं कर सकते। Varargs स्पष्ट रूप से टाइप की गई तर्क सूची के समान नहीं है। यह आम तौर पर संयोग से काम करेगा, लेकिन मैं "संयोग से" सही नहीं मानता।
बीबम

21
ऐसा मत करो, [_controller performSelector:NSSelectorFromString(@"someMethod")];और objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));समकक्ष नहीं हैं! मेथड सिग्नेचर मिसमैच पर एक नजर डालें और ऑब्जेक्टिव-सी के कमजोर टाइपिंग में एक बड़ी कमजोरी वे समस्या को गहराई से समझा रहे हैं।
0x

5
@ 0xced इस मामले में, यह ठीक है। objc_msgSend किसी भी चयनकर्ता के लिए एक विधि हस्ताक्षर बेमेल नहीं बनाएगा, जिसने PerformSelector में सही तरीके से काम किया होता: या इसके वेरिएंट के बाद से वे कभी भी ऑब्जेक्ट को पैरामीटर के रूप में लेते हैं। जब तक आपके सभी पैरामीटर पॉइंटर्स (incl। ऑब्जेक्ट्स), डबल्स और NSInteger / लॉन्ग हैं, और आपका रिटर्न टाइप शून्य, पॉइंटर या लॉन्ग है, तब तक objc_msgSend सही तरीके से काम करेगा।
मैट गलाघेर

88

प्रदर्शन चयनकर्ता के साथ केवल फ़ाइल में त्रुटि को अनदेखा करने के लिए, निम्नानुसार एक #pragma जोड़ें:

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

यह इस लाइन पर चेतावनी को नजरअंदाज कर देगा, लेकिन फिर भी इसे आपकी बाकी परियोजना के दौरान अनुमति देगा।


6
मैं इकट्ठा करता हूं कि आप विचाराधीन पद्धति के तुरंत बाद चेतावनी को वापस चालू कर सकते हैं #pragma clang diagnostic warning "-Warc-performSelector-leaks"। मुझे पता है कि अगर मैं चेतावनी को बंद कर देता हूं, तो मैं इसे जल्द से जल्द वापस चालू करना पसंद करता हूं, इसलिए मैं गलती से एक और अप्रत्याशित चेतावनी पर्ची नहीं देता हूं। यह संभावना नहीं है कि यह एक समस्या है, लेकिन जब भी मैं एक चेतावनी को बंद करता हूं तो यह सिर्फ मेरा अभ्यास है।
रोब

2
आप #pragma clang diagnostic warning pushकोई भी परिवर्तन #pragma clang diagnostic warning popकरने और पिछली स्थिति को पुनर्स्थापित करने से पहले अपने पिछले संकलक कॉन्फ़िगरेशन स्थिति को पुनर्स्थापित कर सकते हैं। उपयोगी है अगर आप लोड को बंद कर रहे हैं और अपने कोड में बहुत सारी पुन: सक्षम लाइनों को सक्षम नहीं करना चाहते हैं।
deanWombourne

यह केवल निम्नलिखित पंक्ति की उपेक्षा करेगा?
hfossli

70

अजीब लेकिन सच है: अगर स्वीकार्य (यानी परिणाम शून्य है और आप रनओलोप चक्र को एक बार देने का बुरा नहीं मानते हैं), तो विलंब जोड़ें, भले ही यह शून्य हो:

[_controller performSelector:NSSelectorFromString(@"someMethod")
    withObject:nil
    afterDelay:0];

यह चेतावनी को हटा देता है, संभवतः क्योंकि यह संकलक को आश्वस्त करता है कि कोई भी वस्तु वापस नहीं की जा सकती है और किसी भी तरह से कुप्रबंधन किया जा सकता है।


2
क्या आप जानते हैं कि क्या यह वास्तव में संबंधित मेमोरी प्रबंधन मुद्दों को हल करता है, या क्या इसमें समान मुद्दे हैं लेकिन Xcode इस कोड के साथ आपको चेतावनी देने के लिए पर्याप्त स्मार्ट नहीं है?
एरोन ब्रेजर

यह शब्दार्थ ही नहीं है! PerformSelector का उपयोग करना: withObject: AfterDelay: रनलूप के अगले भाग में चयनकर्ता का प्रदर्शन करेगा। इसलिए, यह विधि तुरंत वापस आती है।
फ्लोरियन

10
@Florian बेशक यह समान नहीं है! मेरा उत्तर पढ़ें: मैं कहता हूं कि यदि स्वीकार्य है, क्योंकि परिणाम शून्य है और रनलूप चक्र। यह मेरे उत्तर का पहला वाक्य है।
मैट

34

यहाँ ऊपर दिए गए उत्तर के आधार पर एक अद्यतन मैक्रो है। यह आपको अपने कोड को रिटर्न स्टेटमेंट के साथ भी लपेटने की अनुमति दे सकता है।

#define SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(code)                        \
    _Pragma("clang diagnostic push")                                        \
    _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")     \
    code;                                                                   \
    _Pragma("clang diagnostic pop")                                         \


SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(
    return [_target performSelector:_action withObject:self]
);

6
returnमैक्रो के अंदर होने की जरूरत नहीं है; return SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_target performSelector:_action withObject:self]);यह भी काम करता है और साफ दिखता है।
उशी १५'१३ को

31

इस कोड में कंपाइलर फ़्लैग या डायरेक्ट रनटाइम कॉल शामिल नहीं है:

SEL selector = @selector(zeroArgumentMethod);
NSMethodSignature *methodSig = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setSelector:selector];
[invocation setTarget:self];
[invocation invoke];

NSInvocationइसके विपरीत कई तर्क देने की अनुमति देता है, इसलिए performSelectorयह किसी भी विधि पर काम करेगा।


3
क्या आप जानते हैं कि क्या यह वास्तव में संबंधित मेमोरी प्रबंधन मुद्दों को हल करता है, या क्या इसमें समान मुद्दे हैं लेकिन Xcode इस कोड के साथ आपको चेतावनी देने के लिए पर्याप्त स्मार्ट नहीं है?
हारून ब्रेजर

1
आप कह सकते हैं कि यह स्मृति प्रबंधन के मुद्दों को हल करता है; लेकिन यह इसलिए है क्योंकि यह मूल रूप से आपको व्यवहार को निर्दिष्ट करने देता है। उदाहरण के लिए, आप इनवोकेशन को तर्कों को बनाए रखने के लिए चुन सकते हैं या नहीं। मेरे वर्तमान ज्ञान के लिए, यह हस्ताक्षर की बेमेल समस्याओं को ठीक करने का प्रयास करता है, जो यह विश्वास करके प्रकट हो सकता है कि आप जानते हैं कि आप क्या कर रहे हैं और इसे गलत डेटा प्रदान नहीं करते हैं। मुझे यकीन नहीं है कि सभी चेक रनटाइम पर किए जा सकते हैं। जैसा कि एक अन्य टिप्पणी में उल्लेख किया गया है, mikeash.com/pyblog/… अच्छी तरह से बताता है कि बेमेल क्या कर सकता है।
मिहाई तिमार

20

वैसे, यहाँ बहुत सारे उत्तर हैं, लेकिन चूंकि यह थोड़ा अलग है, इसलिए कुछ उत्तरों के संयोजन से मुझे लगा कि मैं इसे लगाऊंगा। मैं एक NSObject श्रेणी का उपयोग कर रहा हूं जो यह सुनिश्चित करने के लिए जांच करता है कि चयनकर्ता रिटर्न शून्य है, और संकलक को भी दबा देता है। चेतावनी।

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "Debug.h" // not given; just an assert

@interface NSObject (Extras)

// Enforce the rule that the selector used must return void.
- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object;
- (void) performVoidReturnSelector:(SEL)aSelector;

@end

@implementation NSObject (Extras)

// Apparently the reason the regular performSelect gives a compile time warning is that the system doesn't know the return type. I'm going to (a) make sure that the return type is void, and (b) disable this warning
// See http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown

- (void) checkSelector:(SEL)aSelector {
    // See http://stackoverflow.com/questions/14602854/objective-c-is-there-a-way-to-check-a-selector-return-value
    Method m = class_getInstanceMethod([self class], aSelector);
    char type[128];
    method_getReturnType(m, type, sizeof(type));

    NSString *message = [[NSString alloc] initWithFormat:@"NSObject+Extras.performVoidReturnSelector: %@.%@ selector (type: %s)", [self class], NSStringFromSelector(aSelector), type];
    NSLog(@"%@", message);

    if (type[0] != 'v') {
        message = [[NSString alloc] initWithFormat:@"%@ was not void", message];
        [Debug assertTrue:FALSE withMessage:message];
    }
}

- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    // Since the selector (aSelector) is returning void, it doesn't make sense to try to obtain the return result of performSelector. In fact, if we do, it crashes the app.
    [self performSelector: aSelector withObject: object];
#pragma clang diagnostic pop    
}

- (void) performVoidReturnSelector:(SEL)aSelector {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self performSelector: aSelector];
#pragma clang diagnostic pop
}

@end

क्या 'v' को _C_VOID द्वारा प्रतिस्थापित किया जाना चाहिए? _C_VOID <objc / runtime.h> में घोषित किया गया है।
रिक रेनिच

16

पोस्टर के लिए, मैंने अपनी टोपी को रिंग में फेंकने का फैसला किया है :)

हाल ही में मैं target/ अधिक से अधिक पुनर्गठन को देख रहा हूं selector, जैसे कि प्रोटोकॉल, ब्लॉक आदि जैसी चीजों के पक्ष में / प्रतिमान, हालांकि, एक ड्रॉप-इन प्रतिस्थापन है जिसके लिए performSelectorमैंने कुछ समय का उपयोग किया है:

[NSApp sendAction: NSSelectorFromString(@"someMethod") to: _controller from: nil];

ये साफ-सुथरे, ARC-safe और लगभग समान प्रतिस्थापन performSelectorवाले लगते हैं, जिनके बारे में बहुत कुछ किए बिना objc_msgSend()

हालांकि, मुझे इस बात का कोई अंदाजा नहीं है कि iOS पर कोई एनालॉग उपलब्ध है।


6
इसे शामिल करने के लिए धन्यवाद .. यह iOS में उपलब्ध है [[UIApplication sharedApplication] sendAction: to: from: forEvent:]:। मैंने इसे एक बार देखा था, लेकिन यह एक गतिशील कॉल करने के लिए आपके डोमेन या सेवा के बीच में UI से संबंधित वर्ग का उपयोग करने के लिए अजीब लगता है .. हालांकि इसको शामिल करने के लिए धन्यवाद!
एडुआर्डो स्कोज

2
Ew! यह अधिक उपरि होगा (चूँकि यह जाँचने की आवश्यकता है कि क्या विधि उपलब्ध है और यदि यह नहीं है तो उत्तरदाता श्रृंखला को चलाएं) और अलग-अलग त्रुटि व्यवहार करें (उत्तरदाता श्रृंखला पर चलना और सं। नहीं तो कुछ भी नहीं मिल सकता है। जो विधि का जवाब देता है, बस दुर्घटनाग्रस्त होने के बजाय)। यह भी काम नहीं करता है जब आप चाहते हैं idसे-performSelector:...
टीसी।

2
@tc। यह "रिस्पॉन्डर श्रृंखला को नहीं चलता" है जब तक to:कि शून्य नहीं है, जो कि यह नहीं है। यह पहले से ही बिना किसी जाँच के सीधे लक्षित वस्तु पर पहुँच जाता है। तो वहाँ "अधिक उपरि" नहीं है। यह एक महान समाधान नहीं है, लेकिन आप जो कारण देते हैं वह कारण नहीं है। :)
मैट

15

इस धागे पर मैट गैलोवे का जवाब क्यों समझाता है:

निम्नलिखित को धयान मे रखते हुए:

id anotherObject1 = [someObject performSelector:@selector(copy)];
id anotherObject2 = [someObject performSelector:@selector(giveMeAnotherNonRetainedObject)];

अब, एआरसी को कैसे पता चल सकता है कि पहला एक ऑब्जेक्ट को 1 की बरकरार गणना के साथ लौटाता है लेकिन दूसरा एक वस्तु देता है जो ऑटोरेलिज्ड है?

ऐसा लगता है कि यह आम तौर पर चेतावनी को दबाने के लिए सुरक्षित है यदि आप रिटर्न वैल्यू की अनदेखी कर रहे हैं। मुझे यकीन नहीं है कि सबसे अच्छा अभ्यास क्या है यदि आपको वास्तव में PerformSelector से एक बनाए रखने की वस्तु प्राप्त करने की आवश्यकता है - "ऐसा न करें" के अलावा।


14

@ सी-रोड यहां समस्या वर्णन के साथ सही लिंक प्रदान करता है । नीचे आप मेरे उदाहरण को देख सकते हैं, जब PerformSelector एक मेमोरी लीक का कारण बनता है।

@interface Dummy : NSObject <NSCopying>
@end

@implementation Dummy

- (id)copyWithZone:(NSZone *)zone {
  return [[Dummy alloc] init];
}

- (id)clone {
  return [[Dummy alloc] init];
}

@end

void CopyDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy copy];
}

void CloneDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy clone];
}

void CopyDummyWithLeak(Dummy *dummy, SEL copySelector) {
  __unused Dummy *dummyClone = [dummy performSelector:copySelector];
}

void CloneDummyWithoutLeak(Dummy *dummy, SEL cloneSelector) {
  __unused Dummy *dummyClone = [dummy performSelector:cloneSelector];
}

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    Dummy *dummy = [[Dummy alloc] init];
    for (;;) { @autoreleasepool {
      //CopyDummy(dummy);
      //CloneDummy(dummy);
      //CloneDummyWithoutLeak(dummy, @selector(clone));
      CopyDummyWithLeak(dummy, @selector(copy));
      [NSThread sleepForTimeInterval:1];
    }} 
  }
  return 0;
}

एकमात्र तरीका, जो मेरे उदाहरण में मेमोरी लीक का कारण है, CopyDummyWithLeak है। कारण यह है कि एआरसी को पता नहीं है, कि copySelector रिटर्न ऑब्जेक्ट को बनाए रखा है।

यदि आप मेमोरी लीक टूल चलाएंगे तो आप निम्न चित्र देख सकते हैं: यहां छवि विवरण दर्ज करें ... और किसी अन्य मामले में मेमोरी लीक नहीं हैं: यहां छवि विवरण दर्ज करें


6

स्कॉट थॉम्पसन के मैक्रो को अधिक सामान्य बनाने के लिए:

// String expander
#define MY_STRX(X) #X
#define MY_STR(X) MY_STRX(X)

#define MYSilenceWarning(FLAG, MACRO) \
_Pragma("clang diagnostic push") \
_Pragma(MY_STR(clang diagnostic ignored MY_STR(FLAG))) \
MACRO \
_Pragma("clang diagnostic pop")

फिर इसे इस तरह उपयोग करें:

MYSilenceWarning(-Warc-performSelector-leaks,
[_target performSelector:_action withObject:self];
                )

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

@ScottThompson यह उचित है। मेरे लिए इस मैक्रो को अपने कोड बेस पर खोजना आसान है और मैं आमतौर पर अंतर्निहित मुद्दे से निपटने के लिए एक अन-सिल्टेड चेतावनी भी जोड़ता हूं।
बेन फ्लिन

6

चेतावनियों को दबाओ मत!

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

सुरक्षित मार्ग:

ये सभी समाधान आपके मूल इरादे से कुछ हद तक भिन्नता के साथ काम करेंगे। मान लो कि अगर तुम चाहो तो paramहो सकता है nil:

सुरक्षित मार्ग, समान वैचारिक व्यवहार:

// GREAT
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

सुरक्षित मार्ग, थोड़ा अलग व्यवहार:

( यह प्रतिक्रिया देखें )
बदले में किसी भी धागे का उपयोग करें [NSThread mainThread]

// GOOD
[_controller performSelector:selector withObject:anArgument afterDelay:0];
[_controller performSelector:selector withObject:anArgument afterDelay:0 inModes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorInBackground:selector withObject:anArgument];

[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

खतरनाक रूट

कुछ प्रकार के संकलक की आवश्यकता होती है, जो टूटने के लिए बाध्य है। ध्यान दें कि वर्तमान समय में, यह स्विफ्ट में टूट गया था

// AT YOUR OWN RISK
[_controller performSelector:selector];
[_controller performSelector:selector withObject:anArgument];
[_controller performSelector:selector withObject:anArgument withObject:nil];

3
शब्दांकन बहुत गलत है। सुरक्षित मार्ग खतरनाक से अधिक सुरक्षित नहीं हैं। यह यकीनन ज्यादा खतरनाक है क्योंकि यह चेतावनी को छुपाता है।
ब्रायन चेन

मैं अपमान न करने के लिए शब्द को ठीक कर दूंगा, लेकिन मैं अपने शब्द के साथ खड़ा हूं। जब मुझे कोडिंग स्वीकार्य नहीं होती है, तब ही मुझे मौन चेतावनी स्वीकार्य लगती है। कोई भी इंजीनियर सभी परिणामों को समझने के बिना सुरक्षित रूप से खामोश कोड को बनाए रख सकता है, जिसका अर्थ होगा कि इस तर्क को पढ़ें, और यह अभ्यास सादा जोखिम भरा है; खासकर यदि आप 12, सादे अंग्रेजी, मजबूत विकल्प पर विचार करते हैं।
स्विफ्टअर्चेक्ट

1
नहीं। आपको मेरी बात समझ में नहीं आई। का उपयोग करना performSelectorOnMainThreadहै नहीं एक अच्छा तरीका चेतावनी मौन करने और यह दुष्प्रभाव है। (यह मेमोरी लीक को हल नहीं करता है) अतिरिक्त #clang diagnostic ignored स्पष्ट रूप से चेतावनी को बहुत स्पष्ट तरीके से दबा देता है।
ब्रायन चेन

यह सच है कि एक गैर - (void)विधि पर चयनकर्ता का प्रदर्शन वास्तविक मुद्दा है।
स्विफ्टआर्किटेक्ट

और आप इसके माध्यम से कई तर्कों के साथ एक चयनकर्ता को कैसे बुलाते हैं और एक ही समय में सुरक्षित रहते हैं? @SwiftArchitect
Catalin

4

क्योंकि आप ARC का उपयोग कर रहे हैं इसलिए आपको iOS 4.0 या बाद का उपयोग करना चाहिए। इसका मतलब है कि आप ब्लॉक का उपयोग कर सकते हैं। यदि आप एक ब्लॉक लेने के बजाय चयनकर्ता को याद करने के लिए याद करते हैं, तो ARC बेहतर ट्रैक कर पाएगा कि वास्तव में क्या हो रहा है और आपको गलती से मेमोरी रिसाव शुरू करने का जोखिम नहीं उठाना पड़ेगा।


वास्तव में, ब्लॉक इसे बहुत आसानी से गलती से एक बनाए रखने के चक्र को बनाते हैं जो एआरसी हल नहीं करता है। मैं अभी भी चाहता हूं कि जब आप selfएक आइवर (जैसे ivarइसके बजाय self->ivar) के माध्यम से इस्तेमाल करते हैं तो एक संकलक चेतावनी थी ।
टीसी

आपका मतलब है -विप्लस-रिटेन-सेल्फ?
ऑरेंजडॉग

2

ब्लॉक एप्रोच का उपयोग करने के बजाय, जिसने मुझे कुछ समस्याएं दीं:

    IMP imp = [_controller methodForSelector:selector];
    void (*func)(id, SEL) = (void *)imp;

मैं NSInvocation का उपयोग इस तरह से करूंगा:

    -(void) sendSelectorToDelegate:(SEL) selector withSender:(UIButton *)button 

    if ([delegate respondsToSelector:selector])
    {
    NSMethodSignature * methodSignature = [[delegate class]
                                    instanceMethodSignatureForSelector:selector];
    NSInvocation * delegateInvocation = [NSInvocation
                                   invocationWithMethodSignature:methodSignature];


    [delegateInvocation setSelector:selector];
    [delegateInvocation setTarget:delegate];

    // remember the first two parameter are cmd and self
    [delegateInvocation setArgument:&button atIndex:2];
    [delegateInvocation invoke];
    }

1

यदि आपको किसी भी तर्क को पारित करने की आवश्यकता नहीं है तो उपयोग करने के लिए एक आसान समाधान है valueForKeyPath। यह किसी Classवस्तु पर भी संभव है ।

NSString *colorName = @"brightPinkColor";
id uicolor = [UIColor class];
if ([uicolor respondsToSelector:NSSelectorFromString(colorName)]){
    UIColor *brightPink = [uicolor valueForKeyPath:colorName];
    ...
}

-2

आप यहां एक प्रोटोकॉल का उपयोग भी कर सकते हैं। तो, ऐसा एक प्रोटोकॉल बनाएं:

@protocol MyProtocol
-(void)doSomethingWithObject:(id)object;
@end

आपकी कक्षा में जिसे आपके चयनकर्ता को कॉल करने की आवश्यकता है, आपके पास तब @property है।

@interface MyObject
    @property (strong) id<MyProtocol> source;
@end

जब आपको @selector(doSomethingWithObject:)MyObject के उदाहरण में कॉल करने की आवश्यकता हो , तो यह करें:

[self.source doSomethingWithObject:object];

2
हे वू, धन्यवाद, लेकिन NSSelectorFromString का उपयोग करने की बात यह है कि आपको पता नहीं है कि आप किस चयनकर्ता को रनटाइम के दौरान कॉल करना चाहते हैं।
एडुआर्डो स्कोज़
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.