समाधान
कंपाइलर एक कारण से इस बारे में चेतावनी दे रहा है। यह बहुत दुर्लभ है कि इस चेतावनी को केवल अनदेखा किया जाना चाहिए, और इसके आसपास काम करना आसान है। ऐसे:
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>
)। इन फ़ंक्शन पॉइंटर्स को IMP
s कहा जाता है , और सरल 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
- ध्यान न दें गैर वस्तु प्रकार (
void
, int
, आदि)
- ऑब्जेक्ट का मूल्य फिर से प्राप्त करें, तब जारी करें जब इसका उपयोग नहीं किया जाता है (मानक धारणा)
- अब उपयोग नहीं किए जाने पर नए ऑब्जेक्ट वैल्यू जारी करें ( परिवार
init
/ copy
परिवार के तरीके ns_returns_retained
)
- कुछ भी न करें और लौटाए गए ऑब्जेक्ट मान स्थानीय दायरे में मान्य होंगे (जब तक कि आंतरिक सबसे रिलीज पूल को सूखा नहीं जाता है, इसके साथ जिम्मेदार ठहराया जाता है
ns_returns_autoreleased
)
कॉल यह methodForSelector:
मानती है कि जिस विधि से कॉल किया जा रहा है उसका रिटर्न मान एक ऑब्जेक्ट है, लेकिन इसे बनाए / जारी नहीं करता है। तो आप एक रिसाव पैदा कर सकते हैं यदि आपकी वस्तु को ऊपर # 3 के रूप में जारी किया जाना चाहिए (अर्थात, वह विधि जिसे आप एक नई वस्तु कहते हैं)।
चयनकर्ताओं के लिए आप उस रिटर्न void
या अन्य गैर-ऑब्जेक्ट को कॉल करने का प्रयास कर रहे हैं, आप चेतावनी को अनदेखा करने के लिए संकलक सुविधाओं को सक्षम कर सकते हैं, लेकिन यह खतरनाक हो सकता है। मैंने देखा है कि क्लैंग ने कुछ पुनरावृत्तियों के माध्यम से जाना कि यह कैसे रिटर्न वैल्यूज़ को हैंडल करता है जो कि स्थानीय वेरिएबल्स को नहीं सौंपा गया है। ऐसा कोई कारण नहीं है कि एआरसी ने सक्षम किया है कि वह उस वस्तु के मूल्य को बनाए नहीं रख सकता और जारी कर सकता है जिसे methodForSelector:
आप इसे उपयोग नहीं करना चाहते हैं, भले ही वह वापस आ जाए। संकलक के दृष्टिकोण से, यह एक वस्तु है। इसका मतलब है कि यदि आप जिस विधि को कॉल कर रहे हैं, someMethod
वह एक गैर वस्तु (सहित void
) लौटा रही है , तो आप एक कचरा सूचक मान को बनाए रखा जा सकता है / जारी किया जा सकता है और क्रैश कर सकता है।
अतिरिक्त तर्क
एक विचार यह है कि यह वही चेतावनी है जिसके साथ होगा performSelector:withObject:
और आप इसी तरह की समस्याओं में भाग लेने के साथ यह घोषित नहीं कर सकते हैं कि यह पद्धति कैसे मापदंडों का उपभोग करती है। एआरसी उपभोग किए गए मापदंडों को घोषित करने की अनुमति देता है , और यदि विधि पैरामीटर का उपभोग करती है, तो आप संभवतः एक ज़ोंबी और दुर्घटना के लिए एक संदेश भेजेंगे। ब्रिजिंग कास्टिंग के साथ इसके आस-पास काम करने के तरीके हैं, लेकिन वास्तव में यह बेहतर होगा कि आप केवल IMP
ऊपर और फ़ंक्शन पॉइंटरोलॉजी का उपयोग करें । चूंकि खपत किए गए पैरामीटर शायद ही कभी एक मुद्दा होते हैं, इसलिए यह आने की संभावना नहीं है।
स्थैतिक चयनकर्ता
दिलचस्प बात यह है कि कंपाइलर सेलेक्टर्स के बारे में स्टेटिकली घोषित नहीं करेगा:
[_controller performSelector:@selector(someMethod)];
इसका कारण यह है क्योंकि संकलनकर्ता वास्तव में संकलन के दौरान चयनकर्ता और वस्तु के बारे में सभी जानकारी रिकॉर्ड करने में सक्षम है। इसे किसी भी चीज के बारे में कोई धारणा बनाने की जरूरत नहीं है। (मैंने स्रोत को देखकर एक साल पहले यह जाँच की थी, लेकिन अभी एक संदर्भ नहीं है।)
दमन
ऐसी स्थिति के बारे में सोचने की कोशिश में जहां इस चेतावनी का दमन आवश्यक और अच्छे कोड डिजाइन के रूप में हो रहा है, मैं खाली आ रहा हूं। यदि किसी को यह अनुभव हुआ है तो कृपया साझा करें यदि यह चेतावनी देना आवश्यक था (और उपरोक्त चीजों को ठीक से नहीं संभालता है)।
अधिक
इसे NSMethodInvocation
संभालने के लिए एक निर्माण करना संभव है , लेकिन ऐसा करने के लिए बहुत अधिक टाइपिंग की आवश्यकता होती है और यह धीमा भी होता है, इसलिए ऐसा करने का बहुत कम कारण है।
इतिहास
जब performSelector:
तरीकों के परिवार को पहले उद्देश्य-सी में जोड़ा गया था, तो एआरसी मौजूद नहीं था। ARC का निर्माण करते समय, Apple ने निर्णय लिया कि इन तरीकों के लिए एक गाइड के रूप में डेवलपर्स के लिए एक चेतावनी तैयार की जानी चाहिए ताकि अन्य साधनों का उपयोग करके स्पष्ट रूप से परिभाषित किया जा सके कि नामित चयनकर्ता के माध्यम से मनमाना संदेश भेजते समय मेमोरी को कैसे संभाला जाना चाहिए। ऑब्जेक्टिव-सी में, डेवलपर्स कच्चे फ़ंक्शन पॉइंटर्स पर सी स्टाइल कास्ट्स का उपयोग करके ऐसा करने में सक्षम हैं।
स्विफ्ट की शुरुआत के साथ, ऐप्पल नेperformSelector:
तरीकों के परिवार को "स्वाभाविक रूप से असुरक्षित" के रूप में प्रलेखित किया है और वे स्विफ्ट के लिए उपलब्ध नहीं हैं।
समय के साथ, हमने यह प्रगति देखी है:
- उद्देश्य-सी अनुमति के प्रारंभिक संस्करण
performSelector:
(मैनुअल मेमोरी मैनेजमेंट)
- एआरसी के साथ उद्देश्य-सी के उपयोग के लिए चेतावनी देता है
performSelector:
- स्विफ्ट की
performSelector:
इन विधियों तक पहुँच नहीं है और इन तरीकों को "स्वाभाविक रूप से असुरक्षित" माना जाता है।
एक नामित चयनकर्ता के आधार पर संदेश भेजने का विचार हालांकि, "स्वाभाविक रूप से असुरक्षित" विशेषता नहीं है। इस विचार का उपयोग लंबे समय तक उद्देश्य-सी के साथ-साथ कई अन्य प्रोग्रामिंग भाषाओं में सफलतापूर्वक किया गया है।
1 सभी उद्देश्य-सी तरीकों में दो छिपे हुए तर्क होते हैं, self
और _cmd
जब आप किसी विधि को कॉल करते हैं, तो इसे जोड़ दिया जाता है।
2 एक NULL
फ़ंक्शन को कॉल करना सी में सुरक्षित नहीं है। नियंत्रक की उपस्थिति के लिए जांच करने के लिए उपयोग किया जाने वाला गार्ड यह सुनिश्चित करता है कि हमारे पास एक वस्तु है। इसलिए हम जानते हैं कि हमें एक IMP
से मिलेगा methodForSelector:
(हालांकि यह हो सकता है _objc_msgForward
, संदेश अग्रेषण प्रणाली में प्रवेश)। मूल रूप से, गार्ड के साथ, हमें पता है कि हमारे पास कॉल करने के लिए एक फ़ंक्शन है।
3 वास्तव में, यह गलत जानकारी प्राप्त करने के लिए संभव है यदि आप वस्तुओं को घोषित करते हैं id
और आप सभी हेडर आयात नहीं कर रहे हैं। आप कोड में क्रैश के साथ समाप्त हो सकते हैं जो संकलक को लगता है कि ठीक है। यह बहुत दुर्लभ है, लेकिन ऐसा हो सकता है। आमतौर पर आपको बस एक चेतावनी मिलेगी कि यह नहीं पता है कि चुनने के लिए कौन से दो विधि हस्ताक्षर हैं।
4 पर एआरसी संदर्भ देखें बनाए रखा वापसी मूल्यों और unretained वापसी मान अधिक जानकारी के लिए।