Est-il possible de rendre la méthode -init privée dans Objective-C?

Je dois cacher (rendre privé) la méthode -init de ma classe dans Objective-C.

Comment puis je faire ça?

Objective-C, comme Smalltalk, n’a pas de concept de méthodes “privées” versus “publiques”. Tout message peut être envoyé à n’importe quel object à tout moment.

Ce que vous pouvez faire est de lancer une NSInternalInconsistencyException si votre méthode -init est appelé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 dans la pratique – est de faire en -init que -init fasse quelque chose de sensible pour votre classe si possible.

Si vous essayez de le faire parce que vous essayez de vous assurer qu’un object singleton est utilisé, ne vous en faites pas. Plus précisément, ne vous préoccupez pas de la méthode de création de singletons “override +allocWithZone: -init , -retain , -release “. Il est pratiquement toujours inutile et ne fait que compliquer les choses sans avantage significatif.

Au lieu de cela, écrivez simplement votre code de sorte que votre méthode +sharedWhatever vous +sharedWhatever comment accéder à un singleton, et documentez-la comme moyen d’obtenir l’instance 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; 

Ceci est une version courte de l’atsortingbut indisponible. Il est apparu pour la première fois dans MacOS 10.7 et iOS 5 . Il est défini dans NSObjCRuntime.h sous la forme #define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE .

Il existe une version qui désactive la méthode uniquement pour les clients Swift , pas pour le code ObjC:

 - (instancetype)init NS_SWIFT_UNAVAILABLE; 

unavailable

Ajoutez l’atsortingbut unavailable à l’en-tête pour générer une erreur de compilation sur tout appel à init.

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

erreur de compilation

Si vous n’avez pas de raison, tapez simplement __atsortingbute__((unavailable)) , ou même __unavailable :

 -(instancetype) __unavailable init; 

doesNotRecognizeSelector:

Utilisez doesNotRecognizeSelector: pour générer une exception NSInvalidArgumentException. «Le système d’exécution appelle cette méthode chaque fois qu’un object 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 lancer NSInternalInconsistencyException et afficher un message:

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

raise:format:

Utilisez raise:format: pour lancer votre propre exception:

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

[self release] est nécessaire car l’object était déjà alloc . Lorsque vous utilisez ARC, le compilateur l’appellera pour vous. Dans tous les cas, ne vous inquiétez pas lorsque vous êtes sur le point d’arrêter intentionnellement l’exécution.

objc_designated_initializer

Si vous souhaitez désactiver init pour forcer l’utilisation d’un initialiseur désigné, il existe un atsortingbut pour cela:

 -(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER; 

Cela génère un avertissement, sauf si une autre méthode d’initialisation appelle myOwnInit 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 d’initialisation:

 - (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, vous ne pouvez pas. Il est hérité de NSObject et chaque classe y répondra sans avertissements.

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 générer une exception si elle est appelée ou (mieux) appeler votre private -initMyClass avec certaines valeurs par défaut.

L’une des principales raisons pour lesquelles les gens semblent vouloir cacher init est pour les objects singleton . Si tel est le cas, vous n’avez pas besoin de masquer -init, renvoyez simplement l’object singleton (ou créez-le s’il n’existe pas encore).

Mettez ceci dans le fichier d’en-tête

 - (id)init UNAVAILABLE_ATTRIBUTE; 

bien le problème pourquoi vous ne pouvez pas le rendre “privé / invisible” est parce que la méthode init est envoyée à id (car alloc renvoie un identifiant) pas à YourClass

Notez que du sharepoint vue du compilateur (vérificateur), un identifiant pourrait répondre à tout ce qui a été saisi (il ne peut pas vérifier ce qui se passe au moment de l’exécution). header) utiliser une méthode init, que la comstack connaîtrait, qu’il n’y a aucun moyen pour id de répondre à init, puisqu’il n’y a pas d’initial où que ce soit (dans votre source, toutes les libs etc …)

donc vous ne pouvez pas interdire à l’utilisateur de passer init et se faire écraser par le compilateur … mais ce que vous pouvez faire, c’est empêcher l’utilisateur d’obtenir une instance réelle en appelant un init

Simplement en implémentant init, qui retourne nil et possède un initialiseur (privé / invisible) dont le nom sera ignoré (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; } 

Note: que quelqu’un pourrait le faire

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

et cela renverrait en fait une nouvelle instance, mais si initOnce ne serait nulle part dans notre projet déclaré publiquement (dans l’en-tête), il générerait un avertissement (l’identifiant pourrait ne pas répondre …) et de toute façon la personne qui l’utiliserait aurait besoin savoir exactement que le véritable initialiseur est l’initOnce

nous pourrions empêcher cela encore plus, mais il n’y a pas besoin

Cela dépend de ce que vous entendez par “rendre privé”. En Objective-C, l’appel d’une méthode sur un object peut mieux être décrit comme l’envoi d’un message à cet object. Il n’y a rien dans le langage qui empêche un client d’appeler une méthode donnée sur un object. 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, il s’exécutera quand même à l’exécution.

Cela dit, la méthode la plus courante pour créer une méthode privée dans Objective-C consiste à créer une catégorie dans le fichier d’implémentation et à y déclarer toutes les méthodes “cachées”. Rappelez-vous que cela n’empêchera pas vraiment les appels à init de s’exécuter, mais le compilateur crachera des avertissements si quelqu’un essaie de le faire.

MyClass.m

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

Il y a un fil décent sur MacRumors.com à ce sujet.

Vous pouvez déclarer que toute méthode n’est pas disponible avec 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 cacher des méthodes dans la sous-classe a un mauvais piège pour le bien intentionné.

Je recommanderais d’utiliser __unavailable comme Jano l’a expliqué pour son premier exemple .

Les méthodes peuvent être remplacées dans des sous-classes. Cela signifie que si une méthode de la superclasse utilise une méthode qui déclenche une exception dans la sous-classe, cela ne fonctionnera probablement pas comme prévu. En d’autres termes, vous venez de casser ce qui fonctionnait. Cela est également vrai avec les méthodes d’initialisation. Voici un exemple d’implémentation assez commune:

 - (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 le fais 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, à moins que vous ne souhaitiez que les méthodes soient remplacées. Mais, c’est un autre sujet, puisque vous n’avez pas toujours le contrôle total sur l’implémentation de la superclasse. (Cela me fait douter de l’utilisation de __atsortingbute ((objc_designated_initializer)) en tant que mauvaise pratique, même si je ne l’ai pas utilisée 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 “abstract” comme dans Création d’une classe abstraite dans Objective-C )

Et n’oubliez pas la méthode + new class.