J'ai besoin de cacher (rendre privé) le -init
méthode de ma classe en Objective-C.
Comment puis je faire ça?
Objective-C, comme Smalltalk, n'a aucun concept de méthodes "privées" contre "publiques". Tout message peut être envoyé à n'importe quel objet à tout moment.
Ce que vous pouvez faire, c'est lancer un NSInternalInconsistencyException
si votre -init
la méthode est invoquée:
- (id)init {
[self release];
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:@"-init is not a valid initializer for the class Foo"
userInfo:nil];
return nil;
}
L'autre alternative - qui est probablement bien meilleure en pratique - est de faire -init
faites quelque chose de sensé pour votre classe si possible.
Si vous essayez de faire cela parce que vous essayez de "vous assurer" qu'un objet singleton est utilisé, ne vous embêtez pas. Plus précisément, ne vous embêtez pas avec le "override +allocWithZone:
, -init
, -retain
, -release
"méthode de création de singletons. Elle est pratiquement toujours inutile et ne fait qu'ajouter une complication sans réel avantage significatif.
Au lieu de cela, écrivez simplement votre code de telle sorte que votre +sharedWhatever
est la façon dont vous accédez à un singleton, et documentez cela comme le moyen d'obtenir l'instance de singleton dans votre en-tête. Cela devrait être tout ce dont vous avez besoin dans la grande majorité des cas.
NS_UNAVAILABLE
- (instancetype)init NS_UNAVAILABLE;
Il s'agit de la version courte de l'attribut indisponible. Il est apparu pour la première fois dans macOS 10.7 et iOS 5 . Il est défini dans NSObjCRuntime.h comme #define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE
.
Il existe une version qui désactive la méthode uniquement pour Swift clients , pas pour le code ObjC:
- (instancetype)init NS_Swift_UNAVAILABLE;
unavailable
Ajoutez l'attribut unavailable
à l'en-tête pour générer une erreur de compilation lors de tout appel à init.
-(instancetype) init __attribute__((unavailable("init not available")));
Si vous n'avez pas de raison, tapez simplement __attribute__((unavailable))
, ou même __unavailable
:
-(instancetype) __unavailable init;
doesNotRecognizeSelector:
Utilisez doesNotRecognizeSelector:
pour déclencher une NSInvalidArgumentException. "Le système d'exécution invoque cette méthode chaque fois qu'un objet reçoit un message aSelector auquel il ne peut ni répondre ni transmettre."
- (instancetype) init {
[self release];
[super doesNotRecognizeSelector:_cmd];
return nil;
}
NSAssert
Utilisez NSAssert
pour lever NSInternalInconsistencyException et afficher un message:
- (instancetype) init {
[self release];
NSAssert(false,@"unavailable, use initWithBlah: instead");
return nil;
}
raise:format:
Utilisez raise:format:
pour lever votre propre exception:
- (instancetype) init {
[self release];
[NSException raise:NSGenericException
format:@"Disabled. Use +[[%@ alloc] %@] instead",
NSStringFromClass([self class]),
NSStringFromSelector(@selector(initWithStateDictionary:))];
return nil;
}
[self release]
Est nécessaire car l'objet était déjà alloc
ated. Lorsque vous utilisez ARC, le compilateur l'appellera pour vous. En tout cas, pas de quoi s'inquiéter lorsque vous êtes sur le point d'arrêter intentionnellement l'exécution.
objc_designated_initializer
Dans le cas où vous avez l'intention de désactiver init
pour forcer l'utilisation d'un initialiseur désigné, il existe un attribut pour cela:
-(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER;
Cela génère un avertissement sauf si une autre méthode d'initialisation appelle myOwnInit
en interne. Les détails seront publiés dans Adopting Modern Objective-C après la prochaine version de Xcode (je suppose).
Apple a commencé à utiliser les éléments suivants dans leurs fichiers d'en-tête pour désactiver le constructeur init:
- (instancetype)init NS_UNAVAILABLE;
Cela s'affiche correctement comme une erreur de compilation dans Xcode. Plus précisément, cela est défini dans plusieurs de leurs fichiers d'en-tête HealthKit (HKUnit est l'un d'entre eux).
Si vous parlez de la méthode par défaut -init, vous ne pouvez pas. Il est hérité de NSObject et chaque classe y répondra sans avertissement.
Vous pouvez créer une nouvelle méthode, par exemple -initMyClass, et la placer dans une catégorie privée comme le suggère Matt. Définissez ensuite la méthode -init par défaut pour déclencher une exception si elle est appelée ou (mieux) appeler votre -initMyClass privée avec des valeurs par défaut.
L'une des principales raisons pour lesquelles les gens semblent vouloir cacher init est pour objets singleton . Si tel est le cas, vous n'avez pas besoin de masquer -init, renvoyez simplement l'objet singleton à la place (ou créez-le s'il n'existe pas encore).
Mettez ceci dans le fichier d'en-tête
- (id)init UNAVAILABLE_ATTRIBUTE;
eh bien, le problème pour lequel vous ne pouvez pas le rendre "privé/invisible" est que la méthode init est envoyée à id (car alloc renvoie un id) et non à YourClass
Notez que du point de vue du compilateur (vérificateur), un identifiant pourrait potentiellement répondre à tout ce qui a été tapé (il ne peut pas vérifier ce qui se trouve réellement dans l'identifiant au moment de l'exécution), vous ne pouvez donc masquer init que lorsque rien ne le ferait (publiquement = dans en-tête) utilisez une méthode init, que la compilation sait, qu'il n'y a aucun moyen pour id de répondre à init, car il n'y a d'init nulle part (dans votre source, toutes les bibliothèques etc ...)
donc vous ne pouvez pas interdire à l'utilisateur de passer init et d'être écrasé par le compilateur ... mais ce que vous pouvez faire, c'est d'empêcher l'utilisateur d'obtenir une instance réelle en appelant un init
simplement en implémentant init, qui retourne nil et a un initialiseur (privé/invisible) dont le nom ne sera pas obtenu par quelqu'un d'autre (comme 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;
}
Remarque: que quelqu'un pourrait le faire
SomeClass * c = [[SomeClass alloc] initOnce];
et il retournerait en fait une nouvelle instance, mais si l'initOnce ne serait nulle part dans notre projet déclaré publiquement (dans l'en-tête), il générerait un avertissement (id pourrait ne pas répondre ...) et de toute façon la personne qui l'utilise aurait besoin savoir exactement que l'initialiseur réel est l'initOnce
nous pourrions empêcher cela encore plus, mais il n'est pas nécessaire
Cela dépend de ce que vous entendez par "rendre privé". Dans Objective-C, appeler une méthode sur un objet pourrait mieux être décrit comme l'envoi d'un message à cet objet. Rien dans le langage n'interdit à un client d'appeler une méthode donnée sur un objet; le mieux que vous puissiez faire est de ne pas déclarer la méthode dans le fichier d'en-tête. Si un client appelle néanmoins la méthode "private" avec la bonne signature, elle s'exécutera toujours au moment de l'exécution.
Cela dit, la façon la plus courante de créer une méthode privée dans Objective-C consiste à créer un Category dans le fichier d'implémentation et à y déclarer toutes les méthodes "cachées". N'oubliez pas que cela n'empêchera pas vraiment les appels à init
de s'exécuter, mais le compilateur émettra des avertissements si quelqu'un essaie de le faire.
MyClass.m
@interface MyClass (PrivateMethods)
- (NSString*) init;
@end
@implementation MyClass
- (NSString*) init
{
// code...
}
@end
Il y a un bon thread sur MacRumors.com à ce sujet.
Vous pouvez déclarer toute méthode non disponible en utilisant NS_UNAVAILABLE
.
Vous pouvez donc mettre ces lignes sous votre @interface
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
Encore mieux définir une macro dans votre en-tête de préfixe
#define NO_INIT \
- (instancetype)init NS_UNAVAILABLE; \
+ (instancetype)new NS_UNAVAILABLE;
et
@interface YourClass : NSObject
NO_INIT
// Your properties and messages
@end
Je dois mentionner que placer des assertions et lever des exceptions pour masquer les méthodes dans la sous-classe a un piège désagréable pour les bien intentionnés.
Je recommanderais d'utiliser __unavailable
as Jano a expliqué pour son premier exemple .
Les méthodes peuvent être remplacées dans les sous-classes. Cela signifie que si une méthode de la superclasse utilise une méthode qui déclenche simplement une exception dans la sous-classe, elle ne fonctionnera probablement pas comme prévu. En d'autres termes, vous venez de casser ce qui fonctionnait auparavant. Cela est également vrai pour les méthodes d'initialisation. Voici un exemple d'une telle mise en œuvre plutôt courante:
- (SuperClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
...bla bla...
return self;
}
- (SuperClass *)initWithLessParameters:(Type1 *)arg1
{
self = [self initWithParameters:arg1 optional:DEFAULT_ARG2];
return self;
}
Imaginez ce qui arrive à -initWithLessParameters, si je fais cela dans la sous-classe:
- (SubClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
[self release];
[super doesNotRecognizeSelector:_cmd];
return nil;
}
Cela implique que vous devriez avoir tendance à utiliser des méthodes privées (masquées), en particulier dans les méthodes d'initialisation, sauf si vous prévoyez de les remplacer. Mais, c'est un autre sujet, car vous n'avez pas toujours le contrôle total de la mise en œuvre de la superclasse. (Cela me fait remettre en question l'utilisation de __attribute ((objc_designated_initializer)) comme mauvaise pratique, même si je ne l'ai pas utilisé en profondeur.)
Cela implique également que vous pouvez utiliser des assertions et des exceptions dans des méthodes qui doivent être remplacées dans des sous-classes. (Les méthodes "abstraites" comme dans Création d'une classe abstraite en Objective-C )
Et n'oubliez pas la méthode + new class.