Mise à jour en 2015
Cette réponse a été écrite pour la première fois au début de 2011 et a commencé :
Ce que nous voulons vraiment, c'est un polymorphisme paramétrique afin que vous puissiez déclarer, disons, NSMutableArray<NSString>
mais, hélas, elle n'est pas disponible.
En 2015, Apple a apparemment changé cela avec l'introduction de "génériques légers" dans Objective-C et maintenant vous pouvez déclarer :
NSMutableArray<NSString *> *onlyStrings = [NSMutableArray new];
Mais tout n'est pas tout à fait ce qu'il semble, remarquez le "lightweight"... Remarquez ensuite que la partie initialisation de la déclaration ci-dessus ne contient aucune notation générique. Alors qu'Apple a introduit des collections paramétriques, et que l'ajout d'un élément non-variable directement au tableau ci-dessus, onlyStrings
, comme dans say :
[onlyStrings addObject:@666]; // <- Warning: Incompatible pointer types...
suscitera l'avertissement comme indiqué, la sécurité de type est à peine superficielle. Considérez la méthode :
- (void) push:(id)obj onto:(NSMutableArray *)array
{
[array addObject:obj];
}
et le fragment de code dans une autre méthode de la même classe :
NSMutableArray<NSString *> *oops = [NSMutableArray new];
[self push:@"asda" onto:oops]; // add a string, fine
[self push:@42 onto:oops]; // add a number, no warnings...
Ce qu'Apple a mis en place est essentiellement un système d'indications pour faciliter l'interopérabilité automatique avec Swift, qui a une saveur de génériques à sécurité de type. Toutefois, du côté de l'Objective-C, si le compilateur fournit quelques indications supplémentaires, le système est "léger" et l'intégrité des types reste en fin de compte du ressort du programmeur - comme c'est le cas en Objective-C.
Alors, lequel utiliser ? Les nouveaux génériques légers/pseudo génériques, ou concevoir vos propres modèles pour votre code ? Il n'y a pas vraiment de bonne réponse, déterminez ce qui a du sens dans votre scénario et utilisez-le.
Par exemple : Si vous visez l'interopérabilité avec Swift, vous devez utiliser les génériques légers ! Cependant, si l'intégrité de type d'une collection est importante dans votre scénario, vous pouvez combiner les génériques légers avec votre propre code côté Objective-C qui applique l'intégrité de type que Swift appliquera de son côté.
Le reste de la réponse 2011
Comme autre option, voici une sous-classe générale rapide de NSMutableArray que vous initez avec le type d'objet que vous voulez dans votre tableau monomorphe. Cette option ne vous donne pas de contrôle de type statique (dans la mesure où vous l'obtenez jamais en Obj-C), vous obtenez des exceptions d'exécution sur l'insertion du mauvais type, tout comme vous obtenez des exceptions d'exécution pour l'index hors limites, etc.
C'est no testé minutieusement et suppose que la documentation sur le remplacement de NSMutableArray est correcte...
@interface MonomorphicArray : NSMutableArray
{
Class elementClass;
NSMutableArray *realArray;
}
- (id) initWithClass:(Class)element andCapacity:(NSUInteger)numItems;
- (id) initWithClass:(Class)element;
@end
Et la mise en œuvre :
@implementation MonomorphicArray
- (id) initWithClass:(Class)element andCapacity:(NSUInteger)numItems
{
elementClass = element;
realArray = [NSMutableArray arrayWithCapacity:numItems];
return self;
}
- (id) initWithClass:(Class)element
{
elementClass = element;
realArray = [NSMutableArray new];
return self;
}
// override primitive NSMutableArray methods and enforce monomorphism
- (void) insertObject:(id)anObject atIndex:(NSUInteger)index
{
if ([anObject isKindOfClass:elementClass]) // allows subclasses, use isMemeberOfClass for exact match
{
[realArray insertObject:anObject atIndex:index];
}
else
{
NSException* myException = [NSException
exceptionWithName:@"InvalidAddObject"
reason:@"Added object has wrong type"
userInfo:nil];
@throw myException;
}
}
- (void) removeObjectAtIndex:(NSUInteger)index
{
[realArray removeObjectAtIndex:index];
}
// override primitive NSArray methods
- (NSUInteger) count
{
return [realArray count];
}
- (id) objectAtIndex:(NSUInteger)index
{
return [realArray objectAtIndex:index];
}
// block all the other init's (some could be supported)
static id NotSupported()
{
NSException* myException = [NSException
exceptionWithName:@"InvalidInitializer"
reason:@"Only initWithClass: and initWithClass:andCapacity: supported"
userInfo:nil];
@throw myException;
}
- (id)initWithArray:(NSArray *)anArray { return NotSupported(); }
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag { return NotSupported(); }
- (id)initWithContentsOfFile:(NSString *)aPath { return NotSupported(); }
- (id)initWithContentsOfURL:(NSURL *)aURL { return NotSupported(); }
- (id)initWithObjects:(id)firstObj, ... { return NotSupported(); }
- (id)initWithObjects:(const id *)objects count:(NSUInteger)count { return NotSupported(); }
@end
A utiliser comme :
MonomorphicArray *monoString = [[MonomorphicArray alloc] initWithClass:[NSString class] andCapacity:3];
[monoString addObject:@"A string"];
[monoString addObject:[NSNumber numberWithInt:42]]; // will throw
[monoString addObject:@"Another string"];