web-dev-qa-db-fra.com

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

Existe-t-il un moyen de forcer NSMutableArray à contenir 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 le tableau roues à contenir les objets Roue uniquement avec du code? (et absolument pas d'autres objets)

66
Tuyen Nguyen

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, par exemple, NSMutableArray<NSString>; mais hélas, ce 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, notez le "léger" ... Notez alors que la partie initialisation de la déclaration ci-dessus ne contient aucune notation générique. Alors que Apple a introduit des collections paramétriques et en ajoutant directement une chaîne non-chaîne au tableau ci-dessus, onlyStrings, comme par exemple:

[onlyStrings addObject:@666]; // <- Warning: Incompatible pointer types...

illicitement l'avertissement comme indiqué, le type de sécurité est à peine superficiel. 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 que Apple ont mis en œuvre est essentiellement un système d'indices pour aider à l'interopération automatique avec Swift, qui a une saveur de génériques de type sûr. Cependant du côté Objective-C, tandis que le compilateur fournit quelques conseils supplémentaires que le système est "léger" et que l'intégrité du type est finalement au programmeur - tout comme la méthode Objective-C.

Alors, que devez-vous utiliser? Les nouveaux génériques légers/pseudo, ou concevoir vos propres modèles pour votre code? Il n'y a vraiment pas de bonne réponse, déterminez ce qui a du sens dans votre scénario et utilisez-le.

Par exemple: si vous ciblez l'interopérabilité avec Swift vous devez utiliser les génériques légers! Cependant, si l'intégrité du type d'une collection est importante dans votre scénario, vous pouvez combiner les génériques légers avec votre propre code du côté Objective-C qui applique l'intégrité de type que Swift sera de son côté.

Le reste de la réponse de 2011

Comme autre option, voici une sous-classe générale rapide de NSMutableArray que vous lancez avec le type d'objet que vous voulez dans votre tableau monomorphe. Cette option ne vous donne pas de vérification de type statique (autant que vous l'obtenez dans Obj-C), vous obtenez des exceptions d'exécution lors de l'insertion du mauvais type, tout comme vous obtenez des exceptions d'exécution pour l'index hors limites, etc.

Ceci n'est pas minutieusement testé 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

Utilisé 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"];
92
CRD

Avec XCode 7, les génériques sont désormais disponibles en Objective-C!

Vous pouvez donc déclarer votre NSMutableArray comme:

NSMutableArray <Wheel*> *wheels = [[NSMutableArray alloc] initWithArray:@[[Wheel new],[Wheel new]];

Le compilateur vous avertira si vous essayez de mettre un objet non Wheel dans votre tableau.

28
andreacipriani

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

NSArray<Protocol Name>

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

9
Gravedigga

comme je le sais .. avant d'ajouter un objet dans roues mutableArray, vous devez ajouter une coche. Est-ce que l'objet que j'ajoute est la classe "roue". si c'est le cas, ajoutez-le, sinon.

Exemple:

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

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

5
harshit2811

J'espère que cela vous aidera (et fonctionnera ...: P)

Wheel.h fichier:

@protocol Wheel
@end

@interface Wheel : NSObject
@property ...
@end

Car.h fichier:

#import "Wheel.h"
@interface Car:NSObject  

{  
   NSString *model;  
   NSString *make;  
   NSMutableArray<Wheel, Optional> *wheels;  
}  
@end

Car.m fichier:

#import "Car.h"
@implementation Car

-(id)init{
   if (self=[super init]){
   self.wheels = (NSMutableArray<Wheel,Optional>*)[NSMutableArray alloc]init];
   }
return self;
}
@end
3
Aviram Net

Xcode 7 vous permet de définir des tableaux, des dictionnaires et même vos propres classes comme ayant des génériques. La syntaxe du tableau est la suivante:

NSArray<NSString*>* array = @[@"hello world"];
2
Brian Trzupek

Je ne pense pas qu'il y ait moyen de le faire avec NSMutableArray hors de la boîte. Vous pouvez probablement appliquer cela en sous-classant et en remplaçant tous les constructeurs et méthodes d'insertion, mais cela n'en vaut probablement pas la peine. Qu'espérez-vous réaliser avec cela?

1
Jim

protocole peut-être une bonne idée:

@protocol Person <NSObject>
@end

@interface Person : NSObject <Person>
@end

utiliser:

NSArray<Person>*  personArray;
0
lbsweek

Voici quelque chose que j'ai fait pour éviter de sous-classer NSMutableArray: utilisez une catégorie. De cette façon, vous pouvez avoir l'argument et les types de retour souhaités. Notez la convention de dénomination: remplacez le mot "objet" dans chacune des méthodes que vous utiliserez par le nom de la classe d'élément. "objectAtIndex" devient "wheelAtIndex" et ainsi de suite. De cette façon, il n'y a pas de conflit de nom. Très ordonné.

typedef NSMutableArray WheelList;
@interface NSMutableArray (WheelList) 
- (wheel *) wheelAtIndex: (NSUInteger) index;
- (void) addWheel: (wheel *) w;
@end

@implementation NSMutableArray (WheelList)

- (wheel *) wheelAtIndex: (NSUInteger) index 
{  
    return (wheel *) [self objectAtIndex: index];  
}

- (void) addWheel: (wheel *) w 
{  
    [self addObject: w];  
} 
@end


@interface Car : NSObject
@property WheelList *wheels;
@end;


@implementation Car
@synthesize wheels;

- (id) init 
{
    if (self = [super init]) {
        wheels = [[WheelList alloc] initWithCapacity: 4];
    }
    return self;
}

@end
0
Charles Gillingham

Ce n'est pas possible; un NSArray (qu'il soit modifiable ou non) contiendra n'importe quel type d'objet. Ce que vous pouvez faire est de créer vos propres sous-classes personnalisées comme l'a déjà suggéré Jim. Alternativement, si vous vouliez filtrer un tableau pour supprimer des objets qui n'étaient pas du type que vous souhaitez, alors vous pourriez faire:

- (void)removeObjectsFromArray:(NSMutableArray *)array otherThanOfType:(Class)type
{
    int c = 0;
    while(c < [array length])
    {
        NSObject *object = [array objectAtIndex:c];
        if([object isKindOfClass:type])
          c++;
        else
          [array removeObjectAtIndex:c];
    }
}

...
[self removeObjectsFromArray:array otherThanOfType:[Car class]];

Ou porter d'autres jugements sur la base du résultat de isKindOfClass:, par ex. pour diviser un tableau contenant un mélange de voitures et de roues en deux tableaux, chacun contenant un seul type d'objet.

0
Tommy

Il existe un projet de fichier à un en-tête qui permet ceci: Objective-C-Generics

Utilisation :

Copiez ObjectiveCGenerics.h dans votre projet. Lors de la définition d'une nouvelle classe, utilisez la macro GENERICSABLE.

#import "ObjectiveCGenerics.h"

GENERICSABLE(MyClass)

@interface MyClass : NSObject<MyClass>

@property (nonatomic, strong) NSString* name;

@end

Vous pouvez maintenant utiliser des génériques avec des tableaux et des ensembles comme vous le faites normalement en Java, C #, etc.

Code:  enter image description here

0
Misha

Vous pouvez utiliser la nsexception si vous n'avez pas d'objet spécifique.

for (int i = 0; i<items.count;i++) {
 if([[items objectAtIndex:i] isKindOfClass:[Wheel class]])
 {
  // do something..!
 }else{
  [NSException raise:@"Invalid value" format:@"Format of %@ is invalid", items];
  // do whatever to handle or raise your exception.
 }
}
0
Lalith B