Réponses:
Vous pouvez créer une catégorie avec une -addSomeClass:
méthode pour permettre la vérification du type statique au moment de la compilation (afin que le compilateur puisse vous faire savoir si vous essayez d'ajouter un objet qu'il sait être une classe différente via cette méthode), mais il n'y a pas de moyen réel de faire appliquer cela un tableau ne contient que des objets d'une classe donnée.
En général, une telle contrainte ne semble pas nécessaire en Objective-C. Je ne pense pas avoir jamais entendu un programmeur expérimenté de Cocoa souhaiter cette fonctionnalité. Les seules personnes qui semblent être des programmeurs d'autres langues qui pensent encore dans ces langues. Si vous ne voulez que des objets d'une classe donnée dans un tableau, n'y insérez que les objets de cette classe. Si vous souhaitez tester que votre code se comporte correctement, testez-le.
Personne n'a encore mis ça ici, alors je vais le faire!
Ceci est désormais officiellement pris en charge dans Objective-C. À partir de Xcode 7, vous pouvez utiliser la syntaxe suivante:
NSArray<MyClass *> *myArray = @[[MyClass new], [MyClass new]];
Remarque
Il est important de noter qu'il ne s'agit que d'avertissements du compilateur et que vous pouvez techniquement toujours insérer n'importe quel objet dans votre tableau. Il existe des scripts disponibles qui transforment tous les avertissements en erreurs qui empêcheraient la construction.
nonnull
dans XCode 6 et pour autant que je me souvienne, ils ont été introduits en même temps. De plus, l'utilisation de tels concepts dépend-elle de la version XCode ou de la version iOS?
@property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects;
un peu maladroit, mais ça fait l'affaire!
C'est une question relativement courante pour les personnes qui passent de langages fortement typés (comme C ++ ou Java) à des langages plus faiblement ou dynamiquement typés comme Python, Ruby ou Objective-C. En Objective-C, la plupart des objets héritent de NSObject
(type id
) (le reste hérite d'une autre classe racine telle que NSProxy
et peut également être de type id
), et tout message peut être envoyé à n'importe quel objet. Bien sûr, l'envoi d'un message à une instance qu'elle ne reconnaît pas peut provoquer une erreur d'exécution (et entraînera également un avertissement du compilateuravec les indicateurs -W appropriés). Tant qu'une instance répond au message que vous envoyez, vous ne vous souciez peut-être pas de la classe à laquelle elle appartient. Ceci est souvent appelé "frappe de canard" parce que "s'il charlatine comme un canard [c'est-à-dire qu'il répond à un sélecteur], c'est un canard [c'est-à-dire qu'il peut gérer le message; peu importe de quelle classe il s'agit]".
Vous pouvez tester si une instance répond à un sélecteur au moment de l'exécution avec la -(BOOL)respondsToSelector:(SEL)selector
méthode. En supposant que vous souhaitiez appeler une méthode sur chaque instance d'un tableau mais que vous ne soyez pas sûr que toutes les instances puissent gérer le message (vous ne pouvez donc pas simplement utiliser NSArray
's -[NSArray makeObjectsPerformSelector:]
, quelque chose comme ça fonctionnerait:
for(id o in myArray) {
if([o respondsToSelector:@selector(myMethod)]) {
[o myMethod];
}
}
Si vous contrôlez le code source des instances qui implémentent la ou les méthodes que vous souhaitez appeler, l'approche la plus courante consiste à définir un @protocol
contenant ces méthodes et à déclarer que les classes en question implémentent ce protocole dans leur déclaration. Dans cet usage, a @protocol
est analogue à une interface Java ou à une classe de base abstraite C ++. Vous pouvez ensuite tester la conformité à l'ensemble du protocole plutôt que la réponse à chaque méthode. Dans l'exemple précédent, cela ne ferait pas beaucoup de différence, mais si vous appeliez plusieurs méthodes, cela pourrait simplifier les choses. L'exemple serait alors:
for(id o in myArray) {
if([o conformsToProtocol:@protocol(MyProtocol)]) {
[o myMethod];
}
}
en supposant MyProtocol
déclare myMethod
. Cette seconde approche est privilégiée car elle clarifie davantage l'intention du code que la première.
Souvent, l'une de ces approches vous évite de vous soucier de savoir si tous les objets d'un tableau sont d'un type donné. Si vous vous en souciez toujours, l'approche standard du langage dynamique est le test unitaire, le test unitaire, le test unitaire. Étant donné qu'une régression dans cette exigence produira une erreur d'exécution (probablement irrécupérable) (pas au moment de la compilation), vous devez disposer d'une couverture de test pour vérifier le comportement afin de ne pas libérer un crasher dans la nature. Dans ce cas, effectuez une opération qui modifie le tableau, puis vérifiez que toutes les instances du tableau appartiennent à une classe donnée. Avec une couverture de test appropriée, vous n'avez même pas besoin de la surcharge d'exécution supplémentaire de la vérification de l'identité de l'instance. Vous avez une bonne couverture des tests unitaires, n'est-ce pas?
id
s sauf lorsque cela est nécessaire, pas plus que les codeurs Java ne passent de Object
s. Pourquoi pas? Vous n'en avez pas besoin si vous avez des tests unitaires? Parce qu'il est là et rend votre code plus maintenable, comme le feraient les tableaux typés. On dirait que des personnes investies dans la plateforme ne souhaitent pas concéder de point, et donc inventent des raisons pour lesquelles cette omission est en fait un avantage.
Vous pouvez créer NSMutableArray
une sous-classe pour appliquer la sécurité de type.
NSMutableArray
est un cluster de classes , donc le sous- classement n'est pas trivial. J'ai fini par hériter NSArray
et transféré des appels à un tableau à l'intérieur de cette classe. Le résultat est une classe appelée ConcreteMutableArray
qui est facile à sous- classer . Voici ce que j'ai trouvé:
Mise à jour: consultez ce billet de blog de Mike Ash sur le sous-classement d'un cluster de classes.
Incluez ces fichiers dans votre projet, puis générez tous les types que vous souhaitez en utilisant des macros:
MyArrayTypes.h
CUSTOM_ARRAY_INTERFACE(NSString)
CUSTOM_ARRAY_INTERFACE(User)
MyArrayTypes.m
CUSTOM_ARRAY_IMPLEMENTATION(NSString)
CUSTOM_ARRAY_IMPLEMENTATION(User)
Usage:
NSStringArray* strings = [NSStringArray array];
[strings add:@"Hello"];
NSString* str = [strings get:0];
[strings add:[User new]]; //compiler error
User* user = [strings get:0]; //compiler error
d'autres pensées
NSArray
pour prendre en charge la sérialisation / désérialisationSelon votre goût, vous voudrez peut-être remplacer / masquer des méthodes génériques telles que
- (void) addObject:(id)anObject
Jetez un œil à https://github.com/tomersh/Objective-C-Generics , une implémentation générique au moment de la compilation (implémentée par un préprocesseur) pour Objective-C. Ce billet de blog a une belle vue d'ensemble. En gros, vous obtenez une vérification à la compilation (avertissements ou erreurs), mais aucune pénalité d'exécution pour les génériques.
Ce projet Github implémente exactement cette fonctionnalité.
Vous pouvez ensuite utiliser les <>
crochets, comme vous le feriez en C #.
D'après leurs exemples:
NSArray<MyClass>* classArray = [NSArray array];
NSString *name = [classArray lastObject].name; // No cast needed
Un moyen possible pourrait être de sous-classer NSArray mais Apple recommande de ne pas le faire. Il est plus simple de réfléchir à deux fois au besoin réel d'un NSArray typé.
J'ai créé une sous-classe NSArray qui utilise un objet NSArray comme support ivar pour éviter les problèmes avec la nature de cluster de classe de NSArray. Il faut des blocs pour accepter ou refuser l'ajout d'un objet.
seulement permettre NSString, vous pouvez définir un AddBlock
comme
^BOOL(id element) {
return [element isKindOfClass:[NSString class]];
}
Vous pouvez définir a FailBlock
pour décider quoi faire, si un élément a échoué au test - échouer correctement pour le filtrage, l'ajouter à un autre tableau, ou - c'est par défaut - lever une exception.
VSBlockTestedObjectArray.h
#import <Foundation/Foundation.h>
typedef BOOL(^AddBlock)(id element);
typedef void(^FailBlock)(id element);
@interface VSBlockTestedObjectArray : NSMutableArray
@property (nonatomic, copy, readonly) AddBlock testBlock;
@property (nonatomic, copy, readonly) FailBlock failBlock;
-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock Capacity:(NSUInteger)capacity;
-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock;
-(id)initWithTestBlock:(AddBlock)testBlock;
@end
VSBlockTestedObjectArray.m
#import "VSBlockTestedObjectArray.h"
@interface VSBlockTestedObjectArray ()
@property (nonatomic, retain) NSMutableArray *realArray;
-(void)errorWhileInitializing:(SEL)selector;
@end
@implementation VSBlockTestedObjectArray
@synthesize testBlock = _testBlock;
@synthesize failBlock = _failBlock;
@synthesize realArray = _realArray;
-(id)initWithCapacity:(NSUInteger)capacity
{
if (self = [super init]) {
_realArray = [[NSMutableArray alloc] initWithCapacity:capacity];
}
return self;
}
-(id)initWithTestBlock:(AddBlock)testBlock
FailBlock:(FailBlock)failBlock
Capacity:(NSUInteger)capacity
{
self = [self initWithCapacity:capacity];
if (self) {
_testBlock = [testBlock copy];
_failBlock = [failBlock copy];
}
return self;
}
-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock
{
return [self initWithTestBlock:testBlock FailBlock:failBlock Capacity:0];
}
-(id)initWithTestBlock:(AddBlock)testBlock
{
return [self initWithTestBlock:testBlock FailBlock:^(id element) {
[NSException raise:@"NotSupportedElement" format:@"%@ faild the test and can't be add to this VSBlockTestedObjectArray", element];
} Capacity:0];
}
- (void)dealloc {
[_failBlock release];
[_testBlock release];
self.realArray = nil;
[super dealloc];
}
- (void) insertObject:(id)anObject atIndex:(NSUInteger)index
{
if(self.testBlock(anObject))
[self.realArray insertObject:anObject atIndex:index];
else
self.failBlock(anObject);
}
- (void) removeObjectAtIndex:(NSUInteger)index
{
[self.realArray removeObjectAtIndex:index];
}
-(NSUInteger)count
{
return [self.realArray count];
}
- (id) objectAtIndex:(NSUInteger)index
{
return [self.realArray objectAtIndex:index];
}
-(void)errorWhileInitializing:(SEL)selector
{
[NSException raise:@"NotSupportedInstantiation" format:@"not supported %@", NSStringFromSelector(selector)];
}
- (id)initWithArray:(NSArray *)anArray { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfFile:(NSString *)aPath{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfURL:(NSURL *)aURL{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(id)firstObj, ... { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(const id *)objects count:(NSUInteger)count { [self errorWhileInitializing:_cmd]; return nil;}
@end
Utilisez-le comme:
VSBlockTestedObjectArray *stringArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
return [element isKindOfClass:[NSString class]];
} FailBlock:^(id element) {
NSLog(@"%@ can't be added, didn't pass the test. It is not an object of class NSString", element);
}];
VSBlockTestedObjectArray *numberArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
return [element isKindOfClass:[NSNumber class]];
} FailBlock:^(id element) {
NSLog(@"%@ can't be added, didn't pass the test. It is not an object of class NSNumber", element);
}];
[stringArray addObject:@"test"];
[stringArray addObject:@"test1"];
[stringArray addObject:[NSNumber numberWithInt:9]];
[stringArray addObject:@"test2"];
[stringArray addObject:@"test3"];
[numberArray addObject:@"test"];
[numberArray addObject:@"test1"];
[numberArray addObject:[NSNumber numberWithInt:9]];
[numberArray addObject:@"test2"];
[numberArray addObject:@"test3"];
NSLog(@"%@", stringArray);
NSLog(@"%@", numberArray);
Ceci n'est qu'un exemple de code et n'a jamais été utilisé dans une application du monde réel. pour ce faire, il a probablement besoin de la méthode NSArray plus implémentée.
Si vous mélangez c ++ et objective-c (c'est-à-dire en utilisant le type de fichier mm), vous pouvez imposer le typage en utilisant paire ou tuple. Par exemple, dans la méthode suivante, vous pouvez créer un objet C ++ de type std :: pair, le convertir en un objet de type wrapper OC (wrapper de std :: pair que vous devez définir), puis le transmettre à certains autre méthode OC, dans laquelle vous devez reconvertir l'objet OC en objet C ++ pour pouvoir l'utiliser. La méthode OC n'accepte que le type wrapper OC, garantissant ainsi la sécurité du type. Vous pouvez même utiliser un tuple, un modèle variadique, une liste de types pour tirer parti des fonctionnalités C ++ plus avancées afin de faciliter la sécurité des types.
- (void) tableView:(UITableView*) tableView didSelectRowAtIndexPath:(NSIndexPath*) indexPath
{
std::pair<UITableView*, NSIndexPath*> tableRow(tableView, indexPath);
ObjCTableRowWrapper* oCTableRow = [[[ObjCTableRowWrapper alloc] initWithTableRow:tableRow] autorelease];
[self performSelector:@selector(selectRow:) withObject:oCTableRow];
}