क्या उद्देश्य-सी में -init पद्धति को निजी बनाना संभव है?


147

मुझे -initऑब्जेक्टिव-सी में अपनी कक्षा की विधि को छिपाने (निजी बनाने) की आवश्यकता है।

मैं उसे कैसे कर सकता हूँ?


3
इसे प्राप्त करने के लिए अब एक विशिष्ट, स्वच्छ और वर्णनात्मक सुविधा है, जैसा कि नीचे इस उत्तर में दिखाया गया है । विशेष रूप से: NS_UNAVAILABLE। मैं आमतौर पर आपसे इस दृष्टिकोण का उपयोग करने का आग्रह करूंगा। क्या ओपी उनके स्वीकृत जवाब को संशोधित करने पर विचार करेंगे? यहां अन्य उत्तर बहुत उपयोगी विवरण प्रदान करते हैं, लेकिन इसे प्राप्त करने का पसंदीदा तरीका नहीं है।
बेंजोएन

जैसा कि दूसरों ने नीचे उल्लेख किया है, फिर NS_UNAVAILABLEभी एक कॉलर को initअप्रत्यक्ष रूप से आह्वान करने की अनुमति देता है new। बस initलौटने के लिए ओवरराइडिंग nilदोनों मामलों को संभाल लेगा।
ग्रेग ब्राउन

जवाबों:


88

ऑब्जेक्टिव-सी, स्मॉलटॉक की तरह, "निजी" बनाम "सार्वजनिक" तरीकों की कोई अवधारणा नहीं है। किसी भी समय किसी भी वस्तु को कोई भी संदेश भेजा जा सकता है।

NSInternalInconsistencyExceptionयदि आपकी -initविधि लागू की जाती है , तो आप क्या कर सकते हैं :

- (id)init {
    [self release];
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:@"-init is not a valid initializer for the class Foo"
                                 userInfo:nil];
    return nil;
}

दूसरा विकल्प - जो शायद व्यवहार में कहीं बेहतर है - -initयदि संभव हो तो अपनी कक्षा के लिए कुछ समझदार बनाना है।

यदि आप ऐसा करने की कोशिश कर रहे हैं क्योंकि आप एक सिंगलटन ऑब्जेक्ट का उपयोग "सुनिश्चित" करने की कोशिश कर रहे हैं, तो परेशान न हों। विशेष रूप से, के साथ परेशान नहीं है "ओवरराइड +allocWithZone:, -init, -retain, -release" एकमात्र बनाने की विधि। यह वास्तव में हमेशा अनावश्यक है और बिना किसी वास्तविक महत्वपूर्ण लाभ के केवल जटिलता को जोड़ रहा है।

इसके बजाय, अपना कोड इस तरह लिखें कि आपका +sharedWhateverतरीका यह है कि आप एक सिंगलटन का उपयोग कैसे करें, और दस्तावेज करें कि आपके हेडर में सिंगलटन इंस्टेंस प्राप्त करने का तरीका क्या है। आपको उन सभी मामलों की आवश्यकता होनी चाहिए, जिनमें से अधिकांश मामलों में आपकी आवश्यकता है।


2
क्या वास्तव में यहां वापसी जरूरी है?
१३:०१ पर १३'०

5
हां, कंपाइलर को खुश रखने के लिए। अन्यथा कंपाइलर शिकायत कर सकता है कि गैर-शून्य रिटर्न वाली विधि से कोई रिटर्न नहीं है।
क्रिस हैनसन

अजीब बात है, यह मेरे लिए नहीं है। शायद एक अलग संकलक संस्करण या स्विच? (मैं तो बस डिफ़ॉल्ट जीसीसी का उपयोग कर रहा XCode 3.1 के साथ स्विच)
philsquared

3
पैटर्न का पालन करने के लिए डेवलपर पर भरोसा करना एक अच्छा विचार नहीं है। अपवाद को फेंकना बेहतर है, इसलिए एक अलग टीम के डेवलपर्स को पता नहीं है। मुझे लगता है कि निजी अवधारणा बेहतर होगी।
निक टर्नर

4
"कोई वास्तविक महत्वपूर्ण लाभ के लिए नहीं" । पूरी तरह से असत्य। महत्वपूर्ण लाभ यह है कि आप सिंगलटन पैटर्न को लागू करना चाहते हैं । यदि आप नए उदाहरण बनाने की अनुमति देते हैं, तो एक डेवलपर जो एपीआई से परिचित नहीं है, वे अपने कोड फ़ंक्शन का गलत तरीके से उपयोग allocऔर उपयोग कर सकते हैं initक्योंकि उनके पास सही वर्ग है, लेकिन गलत उदाहरण। इस के सिद्धांत का सार है कैप्सूलीकरण OO में। आप अपने एपीआई में उन चीजों को छिपाते हैं जिनकी अन्य वर्गों को आवश्यकता नहीं है, या उन्हें प्राप्त करना है। आप केवल सब कुछ सार्वजनिक नहीं रखते हैं और मनुष्यों से अपेक्षा करते हैं कि वे सभी पर नज़र रखें।
नैट

345

NS_UNAVAILABLE

- (instancetype)init NS_UNAVAILABLE;

यह अनुपलब्ध विशेषता का एक छोटा संस्करण है। यह पहली बार macOS 10.7 और iOS 5 में दिखाई दिया । इसे NSObjCRuntime.h में परिभाषित किया गया है #define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE

एक संस्करण है जो केवल स्विफ्ट ग्राहकों के लिए विधि को निष्क्रिय करता है , ओब्जेक्ट कोड के लिए नहीं:

- (instancetype)init NS_SWIFT_UNAVAILABLE;

unavailable

किसी भी कॉल इनिट पर कंपाइलर एररunavailable जेनरेट करने के लिए हेडर में विशेषता जोड़ें ।

-(instancetype) init __attribute__((unavailable("init not available")));  

संकलन समय त्रुटि

यदि आपके पास कोई कारण नहीं है, तो बस टाइप करें __attribute__((unavailable)), या यहां तक ​​कि __unavailable:

-(instancetype) __unavailable init;  

doesNotRecognizeSelector:

doesNotRecognizeSelector:NSInvalidArgumentException बढ़ाने के लिए उपयोग करें । "रनटाइम सिस्टम इस विधि को तब भी आमंत्रित करता है जब कोई ऑब्जेक्ट एक एसेलेक्टर संदेश प्राप्त करता है जो इसका जवाब या आगे नहीं दे सकता है।"

- (instancetype) init {
    [self release];
    [super doesNotRecognizeSelector:_cmd];
    return nil;
}

NSAssert

NSAssertNSInternalInconsistencyException को फेंकने और एक संदेश दिखाने के लिए उपयोग करें :

- (instancetype) init {
    [self release];
    NSAssert(false,@"unavailable, use initWithBlah: instead");
    return nil;
}

raise:format:

raise:format:अपने स्वयं के अपवाद को फेंकने के लिए उपयोग करें :

- (instancetype) init {
    [self release];
    [NSException raise:NSGenericException 
                format:@"Disabled. Use +[[%@ alloc] %@] instead",
                       NSStringFromClass([self class]),
                       NSStringFromSelector(@selector(initWithStateDictionary:))];
    return nil;
}

[self release]जरूरत है क्योंकि ऑब्जेक्ट पहले से ही allocated था । एआरसी का उपयोग करते समय संकलक आपके लिए इसे कॉल करेगा। किसी भी मामले में, चिंता करने के लिए कुछ नहीं जब आप जानबूझकर निष्पादन को रोकते हैं।

objc_designated_initializer

यदि आप initनिर्दिष्ट इनिशियलाइज़र के उपयोग को बाध्य करने के लिए अक्षम करना चाहते हैं , तो इसके लिए एक विशेषता है:

-(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER;

यह एक चेतावनी उत्पन्न करता है जब तक कि कोई अन्य इनिशियलाइज़र विधि myOwnInitआंतरिक रूप से कॉल न करे । अगले एक्सकोड रिलीज (मुझे लगता है) के बाद विवरण एडॉप्टिंग मॉडर्न ऑब्जेक्टिव-सी में प्रकाशित किया जाएगा ।


यह के अलावा अन्य तरीकों के लिए अच्छा हो सकता है init। चूंकि, यदि यह विधि अमान्य है, तो आप किसी ऑब्जेक्ट को इनिशियलाइज़ क्यों करते हैं? इसके अलावा, जब आप एक अपवाद फेंकते हैं, तो आप init*डेवलपर को सही विधि का संचार करने वाले कुछ कस्टम संदेश निर्दिष्ट करने में सक्षम होंगे , जबकि आपके पास ऐसा कोई विकल्प नहीं है doesNotRecognizeSelector
एलेक्स एन।

कोई विचार नहीं एलेक्स, कि वहाँ नहीं होना चाहिए :) मैंने जवाब संपादित किया।
Jano

यह अजीब है क्योंकि इससे आपका सिस्टम क्रैश हो गया। मुझे लगता है कि कुछ नहीं होने देना बेहतर है लेकिन मैं सोच रहा हूं कि क्या कोई बेहतर तरीका है। मैं चाहता हूं कि अन्य डेवलपर्स इसे कॉल करने में सक्षम न हों और इसे संकलक द्वारा पकड़ा जाए जब 'चल रहा है' या 'बिल्डिंग'
okysabeni

1
मैंने यह कोशिश की और यह काम नहीं करता है: - (आईडी) init __attribute __ ((अनुपलब्ध ("init उपलब्ध नहीं है"))) {NSAssert (गलत, @ "initWithType का उपयोग करें"); वापसी; }
ओकिसाबेनी

1
@ मिराज लगता है कि यह आपके कंपाइलर पर समर्थित नहीं है। यह Xcode 6 में समर्थित है। यदि किसी आरंभिक व्यक्ति ने निर्दिष्ट फोन नहीं किया है, तो आपको "सुविधा प्रारंभकर्ता को किसी अन्य प्रारंभकर्ता को 'स्वयं' कॉल गुम होना चाहिए।"
Jano

101

Apple ने init कंस्ट्रक्टर को निष्क्रिय करने के लिए अपनी हेडर फ़ाइलों में निम्नलिखित का उपयोग करना शुरू कर दिया है:

- (instancetype)init NS_UNAVAILABLE;

यह सही ढंग से Xcode में संकलक त्रुटि के रूप में प्रदर्शित करता है। विशेष रूप से, यह उनके HealthKit हेडर फ़ाइलों में से कई में सेट है (HKUnit उनमें से एक है)।


3
ध्यान दें कि आप अभी भी ऑब्जेक्ट को [MyObject नए] के साथ इंस्टेंट कर सकते हैं;
जोस

11
आप NS_UNAVAILABLE नया (इंस्टेंटाइप) भी कर सकते हैं;
सोनिकफ्लाई

@sonicfly कोशिश कर रहा है लेकिन परियोजना अभी भी संकलित है
साइबरएम

3

अगर आप डिफॉल्ट -इनिट विधि के बारे में बात कर रहे हैं तो आप नहीं कर सकते। यह NSObject से विरासत में मिला है और हर वर्ग बिना किसी चेतावनी के इसका जवाब देगा।

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

एक मुख्य कारण यह है कि लोग इनलाइट को छिपाना चाहते हैं, यह एकल वस्तुओं के लिए है । अगर ऐसा है, तो आपको छिपाने की जरूरत नहीं है, बस सिंगलटन ऑब्जेक्ट को वापस लौटाएं (या इसे बनाएं यदि यह अभी तक मौजूद नहीं है)।


यह आपके सिंगलटन में अकेले 'इनिट' छोड़ने से बेहतर तरीका है, और उपयोगकर्ता को यह बताने के लिए दस्तावेज़ीकरण पर निर्भर है कि वे 'साझा' के माध्यम से एक्सेस करने वाले हैं। लोग आमतौर पर डॉक्स नहीं पढ़ते हैं जब तक कि वे पहले से ही एक मुद्दे को जानने की कोशिश में कई मिनट बर्बाद नहीं करते हैं।
ग्रेग मैलिक

3

इसे हैडर फ़ाइल में रखें

- (id)init UNAVAILABLE_ATTRIBUTE;

यह अनुशंसित नहीं है। ऐप्पल के आधुनिक उद्देश्य-सी दस्तावेजों में कहा गया है कि आईआईटी को इंस्टीट्यूशन वापस करना चाहिए, आईडी नहीं। developer.apple.com/library/ios/releasenotes/ObjectiveC/…
lehn0058

5
और यह है: - (instancetype) init NS_UNAVAILABLE;
बंदगीजैसा

3

आप किसी भी विधि का उपयोग करके उपलब्ध नहीं होने की घोषणा कर सकते हैं NS_UNAVAILABLE

तो आप इन लाइनों को अपने @interface के नीचे रख सकते हैं

- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

अपने उपसर्ग शीर्षक में एक मैक्रो को बेहतर ढंग से परिभाषित करें

#define NO_INIT \
- (instancetype)init NS_UNAVAILABLE; \
+ (instancetype)new NS_UNAVAILABLE;

तथा

@interface YourClass : NSObject
NO_INIT

// Your properties and messages

@end

2

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

उस ने कहा, उद्देश्य-सी में एक निजी विधि बनाने का सबसे आम तरीका कार्यान्वयन फ़ाइल में एक श्रेणी बनाना है , और वहां सभी "छिपे हुए" तरीकों की घोषणा करना है। याद रखें कि यह वास्तव में कॉल को initचलने से नहीं रोक पाएगा , लेकिन यदि कोई ऐसा करने का प्रयास करता है तो संकलक चेतावनी से बाहर निकल जाएगा।

MyClass.m

@interface MyClass (PrivateMethods)
- (NSString*) init;
@end

@implementation MyClass

- (NSString*) init
{
    // code...
}

@end

इस विषय के बारे में MacRumors.com पर एक अच्छा सूत्र है


3
दुर्भाग्य से, इस मामले में, श्रेणी दृष्टिकोण वास्तव में मदद नहीं करेगा। आम तौर पर यह आपको संकलन-समय की चेतावनी देता है कि विधि को कक्षा में परिभाषित नहीं किया जा सकता है। हालाँकि, जब से MyClass को रूट क्लैस में से एक से विरासत में मिला होगा और वे init को परिभाषित करते हैं, कोई चेतावनी नहीं होगी।
बैरी वार्क

2

अच्छी तरह से समस्या है कि आप इसे "निजी / अदृश्य" क्यों नहीं बना सकते हैं क्योंकि यह init विधि आईडी को भेजा जाता है (जैसा कि एक आईडी आवंटित करता है) YourClass को नहीं

ध्यान दें कि संकलक (चेकर) के बिंदु से एक आईडी कभी भी टाइप किए गए किसी भी चीज का जवाब दे सकती है (यह जांच नहीं सकती है कि रनटाइम में वास्तव में आईडी में क्या जाता है), इसलिए आप केवल तभी छिपा सकते हैं जब कुछ भी नहीं होगा (सार्वजनिक रूप से = शीर्ष लेख) एक विधि init का उपयोग करता है, जैसा कि संकलन से पता चलेगा, कि init का जवाब देने के लिए id का कोई तरीका नहीं है, क्योंकि कहीं भी init नहीं है (आपके स्रोत में, सभी लिबास आदि ...)

इसलिए आप उपयोगकर्ता को init पास करने के लिए मना नहीं कर सकते हैं और संकलक द्वारा तोड़ दिया जा सकता है ... लेकिन आप क्या कर सकते हैं, उपयोगकर्ता को init कॉल करके वास्तविक उदाहरण प्राप्त करने से रोकना है

बस init लागू करने से, जो nil को लौटाता है और एक (निजी / अदृश्य) इनिशियलाइज़र होता है, जिसका नाम किसी और को नहीं मिलेगा (जैसे initOnce, initWithSpecial ...)

static SomeClass * SInstance = nil;

- (id)init
{
    // possibly throw smth. here
    return nil;
}

- (id)initOnce
{
    self = [super init];
    if (self) {
        return self;
    }
    return nil;
}

+ (SomeClass *) shared 
{
    if (nil == SInstance) {
        SInstance = [[SomeClass alloc] initOnce];
    }
    return SInstance;
}

नोट: कि कोई ऐसा कर सकता है

SomeClass * c = [[SomeClass alloc] initOnce];

और यह वास्तव में एक नया उदाहरण लौटाएगा, लेकिन अगर initOnce हमारी परियोजना में कहीं भी सार्वजनिक रूप से (हेडर में) घोषित नहीं किया जाएगा, तो यह एक चेतावनी उत्पन्न करेगा (आईडी शायद जवाब नहीं दे ...) और वैसे भी इस का उपयोग करने वाले व्यक्ति को आवश्यकता होगी। वास्तव में पता करने के लिए कि वास्तविक इनिशियलाइज़र initOnce है

हम इसे आगे भी रोक सकते हैं, लेकिन इसकी कोई आवश्यकता नहीं है


0

मुझे इस बात का उल्लेख करना है कि उपवर्ग में तरीकों को छिपाने के लिए जोर देना और अपवादों को उठाना अच्छी तरह से नियत करने के लिए एक बुरा जाल है।

मैं उपयोग करने की सिफारिश करूंगा __unavailableक्योंकि जैनो ने अपने पहले उदाहरण के लिए समझाया

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

- (SuperClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
    ...bla bla...
    return self;
}

- (SuperClass *)initWithLessParameters:(Type1 *)arg1
{
    self = [self initWithParameters:arg1 optional:DEFAULT_ARG2];
    return self;
}

कल्पना कीजिए कि क्या होता है -initWithLessParameters, अगर मैं उपवर्ग में ऐसा करता हूं:

- (SubClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
    [self release];
    [super doesNotRecognizeSelector:_cmd];
    return nil;
}

इसका तात्पर्य है कि आपको निजी (छिपी हुई) विधियों का उपयोग करना चाहिए, विशेषकर आरंभिक तरीकों में, जब तक कि आप उन तरीकों की योजना नहीं बनाते हैं। लेकिन, यह एक और विषय है, क्योंकि आपके पास सुपरक्लास के कार्यान्वयन में हमेशा पूर्ण नियंत्रण नहीं है। (यह मुझे खराब अभ्यास के रूप में __attribute ((objc_designated_initializer) के उपयोग पर प्रश्न बनाता है, हालांकि मैंने इसका गहराई से उपयोग नहीं किया है।)

इसका अर्थ यह भी है कि आप उन विधियों में कथनों और अपवादों का उपयोग कर सकते हैं जिन्हें उपवर्गों में ओवरराइड किया जाना चाहिए। ( उद्देश्य-सी में एक सार वर्ग बनाने के रूप में "अमूर्त" तरीके )

और, नए वर्ग विधि के बारे में मत भूलना।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.