web-dev-qa-db-fra.com

Existe-t-il un moyen d'imposer la saisie sur NSArray, NSMutableArray, etc.?

Puis-je créer une instance NSMutableArray tous les éléments sont de type SomeClass?

88
Sam Lee

Vous pouvez créer une catégorie avec une méthode -addSomeClass: afin d'autoriser la vérification de type statique au moment de la compilation (le compilateur pourrait donc vous informer que si vous essayez d'ajouter un objet dont il sait qu'il s'agit d'une classe différente via cette méthode), il n'existe aucun moyen réel d'appliquer qu'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, collez uniquement les objets de cette classe. Si vous voulez vérifier que votre code se comporte correctement, testez-le.

34
Chuck

Personne n'a encore mis ça ici, alors je vais le faire!

Ceci est maintenant officiellement supporté dans Objective-C. A partir de Xcode 7, vous pouvez utiliser la syntaxe suivante:

NSArray<MyClass *> *myArray = @[[MyClass new], [MyClass new]];

Remarque

Il est important de noter que ce ne sont que des avertissements pour le compilateur et que vous pouvez toujours techniquement insérer n'importe quel objet dans votre tableau. Il existe des scripts disponibles qui transforment tous les avertissements en erreurs susceptibles d'empêcher la construction.

135
Logan

Il s'agit d'une question relativement courante pour les personnes qui passent d'un langage à typage élevé (comme C++ ou Java) à un langage à typage plus faible ou dynamique, 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 du type id), et tout message peut être envoyé à n'importe quel objet. Bien sûr, l'envoi d'un message à une instance qu'il ne reconnaît pas peut provoquer une erreur d'exécution (et entraînera également un compilateur warning avec 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. On parle souvent de "frappe de canard" parce que "s'il se comporte comme un canard [répond à un sélecteur], il s'agit d'un canard [il peut gérer le message; peu importe sa classe]". 

Vous pouvez tester si une instance répond à un sélecteur au moment de l'exécution avec la méthode -(BOOL)respondsToSelector:(SEL)selector. 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:] , une méthode comme celle-ci 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 consisterait à 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 cette utilisation, un @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 simplifierait peut-être les choses. L'exemple serait alors:

for(id o in myArray) {
  if([o conformsToProtocol:@protocol(MyProtocol)]) {
    [o myMethod];
  }
}

en supposant que MyProtocol déclare myMethod. Cette deuxième approche est privilégiée car elle clarifie l’objet du code plus que la première.

Souvent, l’une de ces approches vous évite de vous demander si tous les objets d’un tableau sont d’un type donné. Si vous y tenez toujours, l’approche du langage dynamique standard consiste à effectuer des tests unitaires. Dans la mesure où une régression dans cette exigence génère une erreur d'exécution (probablement irrécupérable) (vous ne devez pas compiler), vous devez disposer d'une couverture de test pour vérifier le comportement de sorte que vous ne libériez pas un crash 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 liée à la vérification de l'identité de l'instance. Vous avez une bonne couverture de tests unitaires, n'est-ce pas?

52
Barry Wark

Vous pouvez sous-classe NSMutableArray pour appliquer la sécurité de type.

NSMutableArray est un cluster de classes , le sous-classement n'est donc pas trivial. J'ai fini par hériter de NSArray et transféré les invocations vers un tableau de cette classe. Le résultat est une classe appelée ConcreteMutableArray qui est facile à sous-classe. Voici ce que je suis venu avec:

Mise à jour: checkout this 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 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)

Utilisation:

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

Autres pensées

  • Il hérite de NSArray pour prendre en charge la sérialisation/désérialisation
  • Selon vos goûts, vous voudrez peut-être remplacer/masquer les méthodes génériques telles que

    - (void) addObject:(id)anObject

11
bendytree

Jetez un coup d'œil à https://github.com/tomersh/Objective-C-Generics , une implémentation générique à la compilation (implémentée par un pré-processeur) pour Objective-C. This blog post a une vue d'ensemble de Nice. En gros, vous obtenez une vérification au moment de la compilation (avertissements ou erreurs), mais aucune pénalité d'exécution pour les génériques.

7
Barry Wark

Ce projet Github implémente exactement cette fonctionnalité. 

Vous pouvez ensuite utiliser les crochets <>, comme vous le feriez en C #.

De leurs exemples:

NSArray<MyClass>* classArray = [NSArray array];
NSString *name = [classArray lastObject].name; // No cast needed
4
NSAddict

Une solution 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 à la nécessité d’un NSArray dactylographié.

0
mouviciel

Si vous mélangez c ++ et objective-c (c’est-à-dire en utilisant le type de fichier mm), vous pouvez imposer la frappe à l’aide de pair ou Tuple. Par exemple, dans la méthode suivante, vous pouvez créer un objet C++ de type std :: pair, le convertir en objet de type encapsuleur OC (encapsuleur de std :: pair à définir), puis le transmettre à 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 d'enveloppe OC, assurant ainsi la sécurité du type. Vous pouvez même utiliser Tuple, modèle variadic, liste de types pour exploiter 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];
}
0
colin

J'ai créé une sous-classe NSArray qui utilise un objet NSArray comme ivar de sauvegarde pour éviter les problèmes liés à la nature de cluster de classes de NSArray. Il faut des blocs pour accepter ou refuser l’ajout d’un objet.

pour autoriser uniquement les objets NSString, vous pouvez définir une AddBlock comme

^BOOL(id element) {
    return [element isKindOfClass:[NSString class]];
}

Vous pouvez définir une variable FailBlock pour décider quoi faire si un élément échoue au test - échouez gracieusement pour le filtrage, ajoutez-le à un autre tableau ou, ce qui est le cas par défaut, lève 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 est juste un exemple de code et n'a jamais été utilisé dans des applications réelles. pour ce faire, il faut probablement que la méthode NSArray soit implémentée.

0
vikingosegundo

mes deux cents pour être un peu "plus propre":

utiliser typedefs:

typedef NSArray<NSString *> StringArray;

dans le code on peut faire:

StringArray * titles = @[@"ID",@"Name", @"TYPE", @"DATE"];
0
ingconti