ios - प्रदर्शन चयनकर्ता रिसाव का कारण बन सकता है क्योंकि इसका चयनकर्ता अज्ञात है




objective-c memory-leaks (13)

चेतावनी दबाने मत करो!

कंपाइलर के साथ टंकण करने के लिए 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]];

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

( this प्रतिक्रिया देखें)
[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]];

खतरनाक मार्ग

कुछ प्रकार के कंपाइलर सिलेंसिंग की आवश्यकता होती है, जो तोड़ने के लिए बाध्य है। Note that at present time, it did break in Swift .

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

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

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

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

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

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


उपाय

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

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

या अधिक tersely (हालांकि गार्ड पढ़ने के लिए और बिना मुश्किल):

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

व्याख्या

यहां क्या हो रहा है कि आप नियंत्रक से संबंधित विधि के लिए सी फ़ंक्शन पॉइंटर के लिए नियंत्रक से पूछ रहे हैं। सभी NSObject s methodForSelector: जवाब methodForSelector: लेकिन आप उद्देश्य-सी रनटाइम में class_getMethodImplementation भी उपयोग कर सकते हैं (उपयोगी यदि आपके पास केवल प्रोटोकॉल संदर्भ है, जैसे id<SomeProto> )। इन फ़ंक्शन पॉइंटर्स को IMP एस कहा जाता है, और सरल 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 family में विधियां या ns_returns_retained साथ ns_returns_retained )
  4. कुछ भी न करें और मान लें कि लौटाया गया ऑब्जेक्ट वैल्यू स्थानीय दायरे में मान्य होगा (जब तक कि आंतरिक रिहाई पूल निकाला न जाए, ns_returns_autoreleased साथ ns_returns_autoreleased )

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

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

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

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

स्टेटिक चयनकर्ता

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

[_controller performSelector:@selector(someMethod)];

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

दमन

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

अधिक

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

इतिहास

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

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

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

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

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

1 सभी उद्देश्य-सी विधियों में दो छिपे हुए तर्क होते हैं, self और _cmd जिन्हें आप विधि कहते समय स्पष्ट रूप से जोड़ते हैं।

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

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

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


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

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

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


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

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


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

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

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


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

#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]
);

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

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

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


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

#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://.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown

- (void) checkSelector:(SEL)aSelector {
    // See http://.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

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

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

के बजाय

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

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

#import <objc/message.h>


वंशावली के लिए, मैंने अंगूठी में अपनी टोपी फेंकने का फैसला किया है :)

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

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

यह objc_msgSend() साथ बहुत कुछ किए बिना objc_msgSend() लिए एक स्वच्छ, एआरसी-सुरक्षित, और लगभग समान प्रतिस्थापन objc_msgSend()

हालांकि, मुझे कोई जानकारी नहीं है कि आईओएस पर कोई एनालॉग उपलब्ध है या नहीं।


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

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

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

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

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


If you don't need to pass any arguments an easy workaround is to use valueForKeyPath . This is even possible on a Class object.

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

You could also use a protocol here. So, create a protocol like so:

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

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

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

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

[self.source doSomethingWithObject:object];






automatic-ref-counting