Vous pouvez créer une catégorie avec une -addSomeClass:méthode pour permettre la vérification du type statique au moment de la compilation (afin que le compilateur puisse vous faire savoir si vous essayez d'ajouter un objet qu'il sait être une classe différente via cette méthode), mais il n'y a pas de moyen réel de faire appliquer cela 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, n'y insérez que les objets de cette classe. Si vous souhaitez tester que votre code se comporte correctement, testez-le.
Je pense que les «programmeurs Cocoa expérimentés» ne savent tout simplement pas ce qui leur manque - l'expérience avec Java montre que les variables de type améliorent la compréhension du code et rendent plus de refactorisations possibles.
tgdavies
11
Eh bien, le support des génériques de Java est fortement cassé en soi, car ils ne l'ont pas mis en place depuis le début ...
dertoni
28
Je suis d'accord avec @tgdavies. Les capacités d'intellisense et de refactoring que j'avais avec C # me manquent. Lorsque je veux un typage dynamique, je peux l'obtenir en C # 4.0. Quand je veux taper fortement des trucs, je peux avoir ça aussi. J'ai trouvé qu'il y a un temps et un lieu pour ces deux choses.
Steve
18
@charkrit Qu'est-ce qui rend l'Objective-C «inutile»? Avez-vous pensé que c'était nécessaire lorsque vous utilisiez C #? J'entends beaucoup de gens dire que vous n'en avez pas besoin en Objective-C, mais je pense que ces mêmes personnes pensent que vous n'en avez besoin dans aucune langue, ce qui en fait une question de préférence / style, pas de nécessité.
bacar
17
N'est-ce pas pour permettre à votre compilateur de vous aider à trouver des problèmes? Bien sûr, vous pouvez dire «Si vous ne voulez que des objets d'une classe donnée dans un tableau, ne collez que les objets de cette classe». Mais si les tests sont le seul moyen de faire respecter cela, vous êtes désavantagé. Plus vous trouvez un problème loin de l'écriture du code, plus ce problème est coûteux.
GreenKiwi
145
Personne n'a encore mis ça ici, alors je vais le faire!
Ceci est désormais officiellement pris en charge dans Objective-C. À partir de Xcode 7, vous pouvez utiliser la syntaxe suivante:
Il est important de noter qu'il ne s'agit que d'avertissements du compilateur et que vous pouvez techniquement toujours insérer n'importe quel objet dans votre tableau. Il existe des scripts disponibles qui transforment tous les avertissements en erreurs qui empêcheraient la construction.
Je suis paresseux ici, mais pourquoi est-ce uniquement disponible dans XCode 7? Nous pouvons utiliser le nonnulldans XCode 6 et pour autant que je me souvienne, ils ont été introduits en même temps. De plus, l'utilisation de tels concepts dépend-elle de la version XCode ou de la version iOS?
Guven
@Guven - nullability est arrivé en 6, vous avez raison, mais les génériques ObjC n'ont pas été introduits avant Xcode 7.
Logan
Je suis sûr que cela dépend uniquement de la version de Xcode. Les génériques sont uniquement des avertissements du compilateur et ne sont pas indiqués lors de l'exécution. Je suis à peu près sûr que vous pouvez compiler sur tout ce que vous voulez.
Logan
2
@DeanKelly - Vous pourriez faire ça comme ça: ça a l'air @property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects; un peu maladroit, mais ça fait l'affaire!
Logan le
1
@Logan, il n'y a pas que l'ensemble des scripts, qui empêchent la construction en cas d'avertissement détecté. Xcode a un mécanisme parfait nommé "Configuration". Découvrez boredzo.org/blog/archives/2009-11-07/warnings
adnako
53
C'est une question relativement courante pour les personnes qui passent de langages fortement typés (comme C ++ ou Java) à des langages plus faiblement ou dynamiquement typés 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 NSProxyet peut également être de type id), et tout message peut être envoyé à n'importe quel objet. Bien sûr, l'envoi d'un message à une instance qu'elle ne reconnaît pas peut provoquer une erreur d'exécution (et entraînera également un avertissement du compilateuravec 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. Ceci est souvent appelé "frappe de canard" parce que "s'il charlatine comme un canard [c'est-à-dire qu'il répond à un sélecteur], c'est un canard [c'est-à-dire qu'il peut gérer le message; peu importe de quelle classe il s'agit]".
Vous pouvez tester si une instance répond à un sélecteur au moment de l'exécution avec la -(BOOL)respondsToSelector:(SEL)selectorméthode. 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:], quelque chose comme ça 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 consiste à définir un @protocolcontenant ces méthodes et à déclarer que les classes en question implémentent ce protocole dans leur déclaration. Dans cet usage, a @protocolest 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 pourrait simplifier les choses. L'exemple serait alors:
for(id o in myArray){if([o conformsToProtocol:@protocol(MyProtocol)]){[o myMethod];}}
en supposant MyProtocoldéclare myMethod. Cette seconde approche est privilégiée car elle clarifie davantage l'intention du code que la première.
Souvent, l'une de ces approches vous évite de vous soucier de savoir si tous les objets d'un tableau sont d'un type donné. Si vous vous en souciez toujours, l'approche standard du langage dynamique est le test unitaire, le test unitaire, le test unitaire. Étant donné qu'une régression dans cette exigence produira une erreur d'exécution (probablement irrécupérable) (pas au moment de la compilation), vous devez disposer d'une couverture de test pour vérifier le comportement afin de ne pas libérer un crasher 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 supplémentaire de la vérification de l'identité de l'instance. Vous avez une bonne couverture des tests unitaires, n'est-ce pas?
Les tests unitaires ne remplacent pas un système de type décent.
tba
8
Ouais, qui a besoin de l'outillage que les tableaux typés pourraient se permettre. Je suis sûr que @BarryWark (et toute autre personne qui a touché à toute base de code dont il a besoin pour utiliser, lire, comprendre et prendre en charge) a une couverture de code à 100%. Cependant, je parie que vous n'utilisez pas de raw ids sauf lorsque cela est nécessaire, pas plus que les codeurs Java ne passent de Objects. Pourquoi pas? Vous n'en avez pas besoin si vous avez des tests unitaires? Parce qu'il est là et rend votre code plus maintenable, comme le feraient les tableaux typés. On dirait que des personnes investies dans la plateforme ne souhaitent pas concéder de point, et donc inventent des raisons pour lesquelles cette omission est en fait un avantage.
funkybro
"Duck typing" ?? c'est hilarant! Je n'ai jamais entendu ça.
John Henckel
11
Vous pouvez créer NSMutableArrayune sous-classe pour appliquer la sécurité de type.
NSMutableArrayest un cluster de classes , donc le sous- classement n'est pas trivial. J'ai fini par hériter NSArrayet transféré des appels à un tableau à l'intérieur de cette classe. Le résultat est une classe appelée ConcreteMutableArrayqui est facile à sous- classer . Voici ce que j'ai trouvé:
Bien mais pour l'instant il manque un typage fort en remplaçant certaines méthodes. Actuellement, ce n'est qu'un typage faible.
Cœur
7
Jetez un œil à https://github.com/tomersh/Objective-C-Generics , une implémentation générique au moment de la compilation (implémentée par un préprocesseur) pour Objective-C. Ce billet de blog a une belle vue d'ensemble. En gros, vous obtenez une vérification à la compilation (avertissements ou erreurs), mais aucune pénalité d'exécution pour les génériques.
Un moyen 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 au besoin réel d'un NSArray typé.
La vérification du type statique au moment de la compilation permet de gagner du temps, l'édition est encore meilleure. Particulièrement utile lorsque vous écrivez lib pour une utilisation à long terme.
pinxue
0
J'ai créé une sous-classe NSArray qui utilise un objet NSArray comme support ivar pour éviter les problèmes avec la nature de cluster de classe de NSArray. Il faut des blocs pour accepter ou refuser l'ajout d'un objet.
seulement permettre NSString, vous pouvez définir un AddBlockcomme
Vous pouvez définir a FailBlockpour décider quoi faire, si un élément a échoué au test - échouer correctement pour le filtrage, l'ajouter à un autre tableau, ou - c'est par défaut - lever une exception.
VSBlockTestedObjectArray*stringArray =[[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element){return[element isKindOfClass:[NSStringclass]];}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:[NSNumberclass]];}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 n'est qu'un exemple de code et n'a jamais été utilisé dans une application du monde réel. pour ce faire, il a probablement besoin de la méthode NSArray plus implémentée.
Si vous mélangez c ++ et objective-c (c'est-à-dire en utilisant le type de fichier mm), vous pouvez imposer le typage en utilisant paire ou tuple. Par exemple, dans la méthode suivante, vous pouvez créer un objet C ++ de type std :: pair, le convertir en un objet de type wrapper OC (wrapper de std :: pair que vous devez définir), puis le transmettre à certains 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 wrapper OC, garantissant ainsi la sécurité du type. Vous pouvez même utiliser un tuple, un modèle variadique, une liste de types pour tirer parti des fonctionnalités C ++ plus avancées afin de faciliter la sécurité des types.
Réponses:
Vous pouvez créer une catégorie avec une
-addSomeClass:
méthode pour permettre la vérification du type statique au moment de la compilation (afin que le compilateur puisse vous faire savoir si vous essayez d'ajouter un objet qu'il sait être une classe différente via cette méthode), mais il n'y a pas de moyen réel de faire appliquer cela 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, n'y insérez que les objets de cette classe. Si vous souhaitez tester que votre code se comporte correctement, testez-le.
la source
Personne n'a encore mis ça ici, alors je vais le faire!
Ceci est désormais officiellement pris en charge dans Objective-C. À partir de Xcode 7, vous pouvez utiliser la syntaxe suivante:
Remarque
Il est important de noter qu'il ne s'agit que d'avertissements du compilateur et que vous pouvez techniquement toujours insérer n'importe quel objet dans votre tableau. Il existe des scripts disponibles qui transforment tous les avertissements en erreurs qui empêcheraient la construction.
la source
nonnull
dans XCode 6 et pour autant que je me souvienne, ils ont été introduits en même temps. De plus, l'utilisation de tels concepts dépend-elle de la version XCode ou de la version iOS?@property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects;
un peu maladroit, mais ça fait l'affaire!C'est une question relativement courante pour les personnes qui passent de langages fortement typés (comme C ++ ou Java) à des langages plus faiblement ou dynamiquement typés comme Python, Ruby ou Objective-C. En Objective-C, la plupart des objets héritent de
NSObject
(typeid
) (le reste hérite d'une autre classe racine telle queNSProxy
et peut également être de typeid
), et tout message peut être envoyé à n'importe quel objet. Bien sûr, l'envoi d'un message à une instance qu'elle ne reconnaît pas peut provoquer une erreur d'exécution (et entraînera également un avertissement du compilateuravec 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. Ceci est souvent appelé "frappe de canard" parce que "s'il charlatine comme un canard [c'est-à-dire qu'il répond à un sélecteur], c'est un canard [c'est-à-dire qu'il peut gérer le message; peu importe de quelle classe il s'agit]".Vous pouvez tester si une instance répond à un sélecteur au moment de l'exécution avec la
-(BOOL)respondsToSelector:(SEL)selector
méthode. 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 utiliserNSArray
's-[NSArray makeObjectsPerformSelector:]
, quelque chose comme ça fonctionnerait: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 consiste à 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 cet usage, a@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 pourrait simplifier les choses. L'exemple serait alors:en supposant
MyProtocol
déclaremyMethod
. Cette seconde approche est privilégiée car elle clarifie davantage l'intention du code que la première.Souvent, l'une de ces approches vous évite de vous soucier de savoir si tous les objets d'un tableau sont d'un type donné. Si vous vous en souciez toujours, l'approche standard du langage dynamique est le test unitaire, le test unitaire, le test unitaire. Étant donné qu'une régression dans cette exigence produira une erreur d'exécution (probablement irrécupérable) (pas au moment de la compilation), vous devez disposer d'une couverture de test pour vérifier le comportement afin de ne pas libérer un crasher 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 supplémentaire de la vérification de l'identité de l'instance. Vous avez une bonne couverture des tests unitaires, n'est-ce pas?
la source
id
s sauf lorsque cela est nécessaire, pas plus que les codeurs Java ne passent deObject
s. Pourquoi pas? Vous n'en avez pas besoin si vous avez des tests unitaires? Parce qu'il est là et rend votre code plus maintenable, comme le feraient les tableaux typés. On dirait que des personnes investies dans la plateforme ne souhaitent pas concéder de point, et donc inventent des raisons pour lesquelles cette omission est en fait un avantage.Vous pouvez créer
NSMutableArray
une sous-classe pour appliquer la sécurité de type.NSMutableArray
est un cluster de classes , donc le sous- classement n'est pas trivial. J'ai fini par hériterNSArray
et transféré des appels à un tableau à l'intérieur de cette classe. Le résultat est une classe appeléeConcreteMutableArray
qui est facile à sous- classer . Voici ce que j'ai trouvé:Mise à jour: consultez ce 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 tous les types que vous souhaitez en utilisant des macros:
MyArrayTypes.h
MyArrayTypes.m
Usage:
d'autres pensées
NSArray
pour prendre en charge la sérialisation / désérialisationSelon votre goût, vous voudrez peut-être remplacer / masquer des méthodes génériques telles que
- (void) addObject:(id)anObject
la source
Jetez un œil à https://github.com/tomersh/Objective-C-Generics , une implémentation générique au moment de la compilation (implémentée par un préprocesseur) pour Objective-C. Ce billet de blog a une belle vue d'ensemble. En gros, vous obtenez une vérification à la compilation (avertissements ou erreurs), mais aucune pénalité d'exécution pour les génériques.
la source
Ce projet Github implémente exactement cette fonctionnalité.
Vous pouvez ensuite utiliser les
<>
crochets, comme vous le feriez en C #.D'après leurs exemples:
la source
Un moyen 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 au besoin réel d'un NSArray typé.
la source
J'ai créé une sous-classe NSArray qui utilise un objet NSArray comme support ivar pour éviter les problèmes avec la nature de cluster de classe de NSArray. Il faut des blocs pour accepter ou refuser l'ajout d'un objet.
seulement permettre NSString, vous pouvez définir un
AddBlock
commeVous pouvez définir a
FailBlock
pour décider quoi faire, si un élément a échoué au test - échouer correctement pour le filtrage, l'ajouter à un autre tableau, ou - c'est par défaut - lever une exception.VSBlockTestedObjectArray.h
VSBlockTestedObjectArray.m
Utilisez-le comme:
Ceci n'est qu'un exemple de code et n'a jamais été utilisé dans une application du monde réel. pour ce faire, il a probablement besoin de la méthode NSArray plus implémentée.
la source
Si vous mélangez c ++ et objective-c (c'est-à-dire en utilisant le type de fichier mm), vous pouvez imposer le typage en utilisant paire ou tuple. Par exemple, dans la méthode suivante, vous pouvez créer un objet C ++ de type std :: pair, le convertir en un objet de type wrapper OC (wrapper de std :: pair que vous devez définir), puis le transmettre à certains 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 wrapper OC, garantissant ainsi la sécurité du type. Vous pouvez même utiliser un tuple, un modèle variadique, une liste de types pour tirer parti des fonctionnalités C ++ plus avancées afin de faciliter la sécurité des types.
la source
mes deux cents pour être un peu "plus propre":
utilisez des typedefs:
dans le code, nous pouvons faire:
la source