È ansible rendere il metodo -init privato in Objective-C?

Ho bisogno di hide (rendere privato) il metodo -init della mia class in Objective-C.

Come lo posso fare?

Objective-C, come Smalltalk, non ha alcun concetto di “privato” rispetto a metodi “pubblici”. Qualsiasi messaggio può essere inviato a qualsiasi object in qualsiasi momento.

Quello che puoi fare è lanciare un NSInternalInconsistencyException se il tuo metodo -init è invocato:

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

L’altra alternativa – che probabilmente è molto meglio nella pratica – è fare in -init fare qualcosa di sensato per la class, se ansible.

Se stai provando a farlo perché stai cercando di “garantire” che venga utilizzato un object singleton, non preoccuparti. In particolare, non preoccuparti del +allocWithZone: “override +allocWithZone: -init , -retain , -release ” per la creazione di singleton. È praticamente sempre inutile e aggiunge semplicemente complicazioni senza un reale vantaggio significativo.

Invece, scrivi semplicemente il tuo codice in modo tale che il tuo metodo +sharedWhatever sia come accedi a un singleton e come documento per ottenere l’istanza singleton nell’intestazione. Questo dovrebbe essere tutto ciò che serve nella maggior parte dei casi.

NS_UNAVAILABLE

 - (instancetype)init NS_UNAVAILABLE; 

Questa è una versione breve dell’attributo non disponibile. È apparso per la prima volta in macOS 10.7 e iOS 5 . È definito in NSObjCRuntime.h come #define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE .

Esiste una versione che disabilita il metodo solo per i client Swift , non per il codice ObjC:

 - (instancetype)init NS_SWIFT_UNAVAILABLE; 

unavailable

Aggiungi l’attributo unavailable all’intestazione per generare un errore del compilatore su qualsiasi chiamata a init.

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

errore di compilazione del tempo

Se non si dispone di un motivo, è sufficiente digitare __attribute__((unavailable)) o anche __unavailable :

 -(instancetype) __unavailable init; 

doesNotRecognizeSelector:

Usa doesNotRecognizeSelector: per aumentare NSInvalidArgumentException. “Il sistema runtime richiama questo metodo ogni volta che un object riceve un messaggio aSelettore a cui non può rispondere o inoltrare.”

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

NSAssert

Usa NSAssert per lanciare NSInternalInconsistencyException e mostra un messaggio:

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

raise:format:

Usa raise:format: per lanciare la tua eccezione:

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

[self release] è necessario perché l’object è già stato alloc . Quando si usa ARC il compilatore lo chiamerà per te. In ogni caso, non c’è nulla di cui preoccuparsi quando stai per fermare intenzionalmente l’esecuzione.

objc_designated_initializer

Nel caso in cui si intenda disabilitare init per forzare l’uso di un inizializzatore designato, esiste un attributo per questo:

 -(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER; 

Questo genera un avviso a meno che nessun altro metodo di inizializzazione richiami internamente myOwnInit . I dettagli saranno pubblicati in Adopting Modern Objective-C dopo la prossima release di Xcode (immagino).

Apple ha iniziato a utilizzare quanto segue nei loro file di intestazione per disabilitare il costruttore init:

 - (instancetype)init NS_UNAVAILABLE; 

Questo viene visualizzato correttamente come errore del compilatore in Xcode. In particolare, questo è impostato in diversi dei loro file di intestazione HealthKit (HKUnit è uno di questi).

Se stai parlando del metodo default-init allora non puoi. È ereditato da NSObject e ogni class risponderà senza avvisi.

Potresti creare un nuovo metodo, ad esempio -initMyClass, e inserirlo in una categoria privata come suggerisce Matt. Quindi definisci il metodo predefinito -init per sollevare un’eccezione se è chiamata o (meglio) chiama il tuo privato -initMyClass con alcuni valori predefiniti.

Uno dei motivi principali per cui la gente sembra voler hide init è per gli oggetti singleton . In questo caso, non è necessario hide -init, è sufficiente restituire l’object singleton (oppure crearlo se non esiste ancora).

Metti questo nel file di intestazione

 - (id)init UNAVAILABLE_ATTRIBUTE; 

beh, il problema per cui non puoi renderlo “privato / invisibile” è perché il metodo init viene inviato all’ID (come alloc restituisce un id) non a YourClass

Si noti che dal punto di vista del compilatore (correttore) un id può potenzialmente rispondere a qualsiasi cosa mai digitata (non è in grado di verificare ciò che va realmente nell’ID in fase di esecuzione), in modo da poter hide init solo quando nulla sarebbe in alcun modo (pubblicamente = in header) usa un metodo init, che la compilazione dovrebbe sapere, che non c’è modo per id di rispondere a init, dato che non c’è init ovunque (nella tua sorgente, in tutte le librerie ecc …)

quindi non puoi proibire all’utente di passare init e essere distrutto dal compilatore … ma ciò che puoi fare è impedire che l’utente ottenga un’istanza reale chiamando un init

semplicemente implementando init, che restituisce nil e ha un inizializzatore (privato / invisibile) il nome che qualcun altro non otterrà (come 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; } 

Nota: qualcuno potrebbe farlo

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

e in effetti restituirebbe una nuova istanza, ma se initOnce non fosse in grado di dichiarare pubblicamente (in header), il nostro progetto genererebbe un avvertimento (l’id potrebbe non rispondere …) e comunque la persona che lo utilizza avrebbe bisogno per sapere esattamente che il vero inizializzatore è l’initOnce

potremmo impedirlo ulteriormente, ma non ce n’è bisogno

Dipende da cosa intendi per “rendere privato”. In Objective-C, chiamare un metodo su un object potrebbe essere meglio descritto come l’invio di un messaggio a quell’object. Non c’è niente nella lingua che impedisce a un client di chiamare un determinato metodo su un object; il meglio che puoi fare non è dichiarare il metodo nel file di intestazione. Se un client chiama comunque il metodo “privato” con la firma corretta, verrà comunque eseguito in fase di runtime.

Detto questo, il modo più comune per creare un metodo privato in Objective-C è creare una categoria nel file di implementazione e dichiarare tutti i metodi “nascosti”. Ricorda che questo non impedirà realmente le chiamate a init da eseguire, ma il compilatore sputerà gli avvisi se qualcuno cerca di farlo.

MyClass.m

 @interface MyClass (PrivateMethods) - (NSString*) init; @end @implementation MyClass - (NSString*) init { // code... } @end 

C’è una discussione decente su MacRumors.com su questo argomento.

È ansible dichiarare qualsiasi metodo non disponibile utilizzando NS_UNAVAILABLE .

Quindi puoi mettere queste linee sotto la tua @interface

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

Definisci ancora meglio una macro nell’intestazione del tuo prefisso

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

e

 @interface YourClass : NSObject NO_INIT // Your properties and messages @end 

Devo dire che posizionare le asserzioni e aumentare le eccezioni per hide i metodi nella sottoclass ha una trappola sgradevole per i ben intenzionati.

Consiglierei di usare __unavailable come spiegato da Jano per il suo primo esempio .

I metodi possono essere sovrascritti in sottoclassi. Ciò significa che se un metodo nella superclass utilizza un metodo che solleva solo un’eccezione nella sottoclass, probabilmente non funzionerà come previsto. In altre parole, hai appena infranto ciò che era solito lavorare. Questo è vero anche con i metodi di inizializzazione. Ecco un esempio di implementazione piuttosto comune:

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

Immagina cosa succede a -initWithLessParameters, se lo faccio nella sottoclass:

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

Ciò implica che si dovrebbe tendere ad usare metodi privati ​​(nascosti), specialmente nei metodi di inizializzazione, a meno che non si preveda di sovrascrivere i metodi. Ma questo è un altro argomento, dal momento che non hai sempre il pieno controllo nell’implementazione della superclass. (Questo mi fa mettere in discussione l’uso di __attribute ((objc_designated_initializer)) come ctriggers pratica, anche se non l’ho usato in profondità.)

Implica anche che puoi usare asserzioni ed eccezioni in metodi che devono essere sovrascritti in sottoclassi. (I metodi “astratti” come in Creare una class astratta in Objective-C )

E, non dimenticare il + nuovo metodo di class.