69 votes

NSMutableArray - Force le tableau à contenir uniquement un type d'objet spécifique.

Existe-t-il un moyen de forcer NSMutableArray à ne contenir qu'un seul type d'objet spécifique ?

J'ai des définitions de classes comme suit :

@interface Wheel:NSObject  
{    
  int size;  
  float diameter;  
}  
@end  

@interface Car:NSObject  
{  
   NSString *model;  
   NSString *make;  
   NSMutableArray *wheels;  
}  
@end

Comment puis-je forcer roues pour contenir Roue objets seulement avec le code ? (et absolument pas d'autres objets)

101voto

CRD Points 21578

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"];

10voto

Gravedigga Points 66

Je peux me tromper (je suis un noob), mais je pense que si vous créez un protocole personnalisé et que vous vous assurez que les objets que vous ajoutez au tableau suivent le même protocole, alors lorsque vous déclarez le tableau vous utilisez

NSArray<Protocol Name>

Cela devrait empêcher l'ajout d'objets qui ne respectent pas ledit protocole.

5voto

harshit2811 Points 153

D'après ce que je sais, avant d'ajouter un objet dans un tableau mutable, il faut cocher une case. L'objet que j'ajoute est-il de la classe "wheel" ? Si oui, ajoutez-le, sinon non.

Ejemplo:

if([id isClassOf:"Wheel"] == YES)
{
[array addObject:id) 
}

Quelque chose comme ça. Je ne me souviens pas de la syntaxe exacte.

1voto

Jim Points 39574

Je ne crois pas qu'il y ait un moyen de le faire avec NSMutableArray en dehors de la boîte. Vous pourriez probablement faire respecter cette règle en sous-classant et en surchargeant tous les constructeurs et les méthodes d'insertion, mais cela n'en vaut probablement pas la peine. Qu'espérez-vous obtenir avec ceci ?

0voto

lbsweek Points 528

Le protocole peut être une bonne idée :

@protocol Person <NSObject>
@end

@interface Person : NSObject <Person>
@end

à utiliser :

NSArray<Person>*  personArray;

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X