Existe-t-il des collections fortement typées en Objective-C?

140

Je suis nouveau dans la programmation Mac / iPhone et Objective-C. En C # et Java, nous avons des "génériques", des classes de collection dont les membres ne peuvent être que du type déclaré. Par exemple, en C #

Dictionary<int, MyCustomObject>

ne peut contenir que des clés qui sont des entiers et des valeurs de type MyCustomObject. Existe-t-il un mécanisme similaire en Objective-C?

Riches
la source
Je commence tout juste à en apprendre davantage sur ObjC moi-même. Peut-être pouvez-vous utiliser ObjC ++ pour faire le gros du travail?
Toybuilder
Vous pourriez être intéressé par les réponses à cette question: existe-t-il un moyen d'imposer la saisie sur NSArray, NSMutableArray, etc.? . Les arguments expliquent pourquoi ce n'est pas une pratique courante en Objective-C / Cocoa.
mouviciel
2
ObjC ++ n'est pas vraiment un langage ... juste plus d'un moyen de référencer la capacité d'ObjC à gérer le C ++ en ligne exactement comme il le ferait en C. Vous ne devriez pas faire cela à moins que vous ne deviez, cependant pour utiliser une bibliothèque tierce qui a été écrite en C ++).
Marc W
À peu près une copie exacte de stackoverflow.com/questions/649483/…
Barry Wark
@ Mark W - "ne devrait pas faire ça" pourquoi pas? J'ai utilisé ObjC ++ et cela fonctionne très bien. Je peux faire #import <map> et @property std :: map <int, NSString *> myDict; Je peux utiliser l'API Cocoa complète ET avoir des collections fortement typées. Je ne vois aucun inconvénient.
John Henckel

Réponses:

211

Dans Xcode 7, Apple a introduit des «génériques légers» dans Objective-C. En Objective-C, ils généreront des avertissements du compilateur en cas d'incompatibilité de type.

NSArray<NSString*>* arr = @[@"str"];

NSString* string = [arr objectAtIndex:0];
NSNumber* number = [arr objectAtIndex:0]; // Warning: Incompatible pointer types initializing 'NSNumber *' with an expression of type 'NSString *'

Et dans le code Swift, ils produiront une erreur de compilation:

var str: String = arr[0]
var num: Int = arr[0] //Error 'String' is not convertible to 'Int'

Les génériques légers sont destinés à être utilisés avec NSArray, NSDictionary et NSSet, mais vous pouvez également les ajouter à vos propres classes:

@interface GenericsTest<__covariant T> : NSObject

-(void)genericMethod:(T)object;

@end

@implementation GenericsTest

-(void)genericMethod:(id)object {}

@end

Objective-C se comportera comme avant avec les avertissements du compilateur.

GenericsTest<NSString*>* test = [GenericsTest new];

[test genericMethod:@"string"];
[test genericMethod:@1]; // Warning: Incompatible pointer types sending 'NSNumber *' to parameter of type 'NSString *'

mais Swift ignorera complètement les informations génériques. (Ce n'est plus vrai dans Swift 3+.)

var test = GenericsTest<String>() //Error: Cannot specialize non-generic type 'GenericsTest'

Mis à part ces classes de collection Foundation, les génériques légers Objective-C sont ignorés par Swift. Tous les autres types utilisant des génériques légers sont importés dans Swift comme s'ils n'étaient pas paramétrés.

Interagir avec les API Objective-C

Connor
la source
Depuis que j'ai des questions sur les génériques et les types retournés dans les méthodes, j'ai posé ma question dans un fil différent, pour que tout reste clair: stackoverflow.com/questions/30828076
...
2
@rizzes. Oui, il vient d'être présenté.
Connor
Une mise en garde ici est que Swift n'ignore pas entièrement les annotations de type dans votre classe ObjC générique. Si vous spécifiez des contraintes, par exemple MyClass <Foo: id<Bar>>, votre code Swift supposera que les valeurs sont le type de votre contrainte, ce qui vous donne quelque chose avec quoi travailler. Cependant, les sous-classes spécialisées de MyClassverraient leurs types spécialisés ignorés (cela serait considéré comme un générique MyClass). Voir github.com/bgerstle/LightweightGenericsExample
Brian Gerstle
Est-ce que cela compile pour les systèmes d'exploitation 10.10, 10.9 et antérieurs?
p0lAris
Cela devrait aussi longtemps que vous définissez votre objectif de déploiement pour les prendre en charge
Connor
91

Cette réponse est dépassée mais reste pour une valeur historique. À partir de Xcode 7, la réponse de Connor du 8 juin 15 est plus précise.


Non, il n'y a pas de génériques en Objective-C à moins que vous ne souhaitiez utiliser des modèles C ++ dans vos propres classes de collection personnalisées (ce que je déconseille fortement).

Objective-C a le typage dynamique comme fonctionnalité, ce qui signifie que le runtime ne se soucie pas du type d'un objet puisque tous les objets peuvent recevoir des messages. Lorsque vous ajoutez un objet à une collection intégrée, ils sont simplement traités comme s'ils étaient de type id. Mais ne vous inquiétez pas, envoyez simplement des messages à ces objets comme d'habitude; cela fonctionnera bien (à moins bien sûr qu'un ou plusieurs objets de la collection ne répondent pas au message que vous envoyez) .

Les génériques sont nécessaires dans des langages tels que Java et C # car ce sont des langages forts et typés statiquement. Jeu de balle totalement différent de la fonction de frappe dynamique d'Objective-C.

Marc W
la source
88
Je ne suis pas d'accord avec "ne vous inquiétez pas, envoyez simplement des messages à ces objets". Si vous placez le mauvais type d'objets dans la collection, qui ne répondent pas à ces messages, cela produira des erreurs d'exécution. L'utilisation de génériques dans d'autres langages évite ce problème avec les vérifications de temps de compilation.
henning77
8
@ henning77 Oui, mais Objective-C est un langage plus dynamique que ces langages. Si vous voulez une sécurité de type forte, utilisez ces langages.
Raffi Khatchadourian
36
Je suis également en désaccord avec la philosophie de ne pas s'inquiéter - par exemple, si vous retirez le premier élément d'un NSArray et le convertissez en NSNumber mais que cet élément était vraiment une NSString, vous êtes foutu ...
jjxtra
13
@RaffiKhatchadourian - pas beaucoup de choix si vous écrivez une application iOS. S'il était simple d'en écrire une avec Java et d'obtenir tous les avantages de l'écriture d'une application native, croyez-moi: je le ferais.
ericsoco
11
Le plus gros reproche que j'ai à ce sujet n'est pas lié aux langages dynamiques par rapport à la vérification du temps de compilation, mais à une simple communication avec les développeurs. Je ne peux pas simplement regarder une déclaration de propriété et savoir quel type d'objets elle va renvoyer à moins qu'elle ne soit documentée quelque part.
devios1
11

Non, mais pour être plus clair, vous pouvez le commenter avec le type d'objet que vous souhaitez stocker, j'ai vu cela plusieurs fois lorsque vous avez besoin d'écrire quelque chose en Java 1.4 de nos jours) par exemple:

NSMutableArray* /*<TypeA>*/ arrayName = ....

ou

NSDictionary* /*<TypeA, TypeB>*/ dictionaryName = ...
Mark Rhodes
la source
Je suppose que c'est un bon moyen de le documenter, au cas où quelqu'un d'autre lirait votre code. Quoi qu'il en soit, le nom de la variable doit être aussi clair que possible pour savoir quels objets elle contient.
htafoya
6

Il n'y a pas de génériques dans Objective-C.

À partir des documents

Les tableaux sont des collections ordonnées d'objets. Cocoa fournit plusieurs classes de tableaux, NSArray, NSMutableArray (une sous-classe de NSArray) et NSPointerArray.

Matthew Vines
la source
Le lien vers le document dans la réponse est mort - "Désolé, cette page est introuvable" .
Pang
5

Cela a été publié dans Xcode 7 (enfin!)

Notez que dans le code Objective C, il s'agit simplement d'une vérification à la compilation; il n'y aura pas d'erreur d'exécution juste pour mettre le mauvais type dans une collection ou l'assigner à une propriété typée.

Déclarer:

@interface FooClass <T> : NSObject
@property (nonatomic) T prop;
@end

Utilisation:

FooClass<NSString *> *foo = [[FooClass alloc] init];
NSArray<FooClass<NSString *> *> *fooAry = [NSArray array];

Soyez prudent avec ces *art.

Kevin
la source
4

Les NSArrays génériques peuvent être réalisés en sous-classant NSArrayet en redéfinissant toutes les méthodes fournies avec des méthodes plus restrictives. Par exemple,

- (id)objectAtIndex:(NSUInteger)index

devrait être redéfini en

@interface NSStringArray : NSArray

comme

- (NSString *)objectAtIndex:(NSUInteger)index

pour qu'un NSArray ne contienne que des NSStrings.

La sous-classe créée peut être utilisée comme remplacement instantané et apporte de nombreuses fonctionnalités utiles: avertissements du compilateur, accès aux propriétés, meilleure création de code et achèvement dans Xcode. Toutes ces fonctionnalités sont à la compilation, il n'est pas nécessaire de redéfinir l'implémentation réelle - les méthodes de NSArray peuvent toujours être utilisées.

Il est possible d'automatiser cela et de le réduire à seulement deux instructions, ce qui le rapproche des langages prenant en charge les génériques. J'ai créé une automatisation avec WMGenericCollection , où les modèles sont fournis sous forme de macros de préprocesseur C.

Après avoir importé le fichier d'en-tête contenant la macro, vous pouvez créer un NSArray générique avec deux instructions: une pour l'interface et une pour l'implémentation. Il vous suffit de fournir le type de données que vous souhaitez stocker et les noms de vos sous-classes. WMGenericCollection fournit de tels modèles pour NSArray, NSDictionaryetNSSet , ainsi que leurs homologues mutables.

Un exemple: List<int>pourrait être réalisé par une classe personnalisée appelée NumberArray, qui est créée avec l'instruction suivante:

WMGENERICARRAY_INTERFACE(NSNumber *, // type of the value class
                         // generated class names
                         NumberArray, MutableNumberArray)

Une fois que vous avez créé NumberArray, vous pouvez l'utiliser partout dans votre projet. Il n'a pas la syntaxe de <int>, mais vous pouvez choisir votre propre schéma de dénomination pour les étiqueter comme des classes en tant que modèles.

wm
la source
notez que la même chose existe dans CoreLib: github.com/core-code/CoreLib/blob/master/CoreLib/CoreLib.h#L105
user1259710
2

Maintenant, les rêves deviennent réalité - il y a des génériques dans Objective-C depuis aujourd'hui (merci, WWDC). Ce n'est pas une blague - sur la page officielle de Swift:

Les nouvelles fonctionnalités de syntaxe vous permettent d'écrire un code plus expressif tout en améliorant la cohérence dans le langage. Les SDK ont utilisé de nouvelles fonctionnalités Objective-C telles que les génériques et l'annotation de nullabilité pour rendre le code Swift encore plus propre et plus sûr. Voici juste un échantillon des améliorations de Swift 2.0.

Et l'image qui le prouve:Génériques Objective-C

htzfun
la source
2

Je veux juste sauter ici. J'ai écrit un article de blog ici sur les génériques.

Ce que je veux apporter, c'est que les génériques peuvent être ajoutés à n'importe quelle classe , pas seulement aux classes de collection comme Apple l'indique.

J'ai ensuite ajouté avec succès à une variété de classes car elles fonctionnent exactement de la même manière que les collections d'Apple. c'est à dire. vérification du temps de compilation, complétion du code, permettant la suppression des transtypages, etc.

Prendre plaisir.

Drekka
la source
-2

Les classes Collections fournies par les frameworks Apple et GNUStep sont semi-génériques en ce qu'elles supposent qu'on leur donne des objets, certains qui sont triables et d'autres qui répondent à certains messages. Pour les primitives comme les floats, les entiers, etc., toute la structure des tableaux C est intacte et peut être utilisée, et il existe des objets wrapper spéciaux pour eux à utiliser dans les classes de collection générales (par exemple NSNumber). De plus, une classe Collection peut être sous-classée (ou spécifiquement modifiée via des catégories) pour accepter des objets de n'importe quel type, mais vous devez écrire vous-même tout le code de gestion de type. Les messages peuvent être envoyés à n'importe quel objet mais doivent renvoyer null si cela ne convient pas à l'objet, ou le message doit être transmis à un objet approprié. Les erreurs de type vrai doivent être détectées au moment de la compilation et non au moment de l'exécution. Au moment de l'exécution, ils doivent être traités ou ignorés. Enfin, Objc fournit des fonctionnalités de réflexion à l'exécution pour gérer les cas délicats et la réponse de message, le type spécifique, et les services peuvent être vérifiés sur un objet avant qu'il ne soit envoyé un message ou placé dans une collection inappropriée. Attention, des bibliothèques et des frameworks disparates adoptent des conventions différentes quant à la façon dont leurs objets se comportent lors de l'envoi de messages pour lesquels ils n'ont pas de réponse de code, donc RTFM. À part les programmes jouets et les builds de débogage, la plupart des programmes ne devraient pas avoir à planter à moins qu'ils ne se trompent vraiment et n'essayent d'écrire de mauvaises données sur la mémoire ou le disque, d'effectuer des opérations illégales (par exemple, diviser par zéro, mais vous pouvez aussi attraper cela), ou accéder ressources système hors limites. Le dynamisme et le temps d'exécution d'Objective-C permettent aux choses d'échouer gracieusement et doivent être intégrés à votre code. (ASTUCE) si vous rencontrez des problèmes de généricité dans vos fonctions, essayez une certaine spécificité. Écrivez les fonctions avec des types spécifiques et laissez le runtime sélectionner (c'est pourquoi ils sont appelés sélecteurs!) La fonction membre appropriée au moment de l'exécution.

Example:
    -(id) sort (id) obj;  // too generic. catches all.
     // better
    -(id) sort: (EasilySortableCollection*) esc;
    -(id) sort: (HardToSortCollection*) hsc; 
    ...
    [Sorter  sort: MyEasyColl];
    [Sorter  sort: MyHardColl];
Chris Reid
la source