Respostas:
Você poderia criar uma categoria com um -addSomeClass:
método para permitir a verificação de tipo estático em tempo de compilação (então o compilador poderia avisá-lo se você tentar adicionar um objeto que ele sabe que é uma classe diferente através desse método), mas não há nenhuma maneira real de impor isso uma matriz contém apenas objetos de uma determinada classe.
Em geral, não parece haver necessidade de tal restrição em Objective-C. Acho que nunca ouvi um programador experiente do Cocoa desejar esse recurso. As únicas pessoas que parecem ser programadores de outras linguagens que ainda pensam nessas linguagens. Se você quiser apenas objetos de uma determinada classe em um array, coloque apenas objetos dessa classe ali. Se você quiser testar se seu código está se comportando corretamente, teste-o.
Ninguém colocou isso aqui ainda, então eu coloco!
Tthis agora é oficialmente suportado em Objective-C. A partir do Xcode 7, você pode usar a seguinte sintaxe:
NSArray<MyClass *> *myArray = @[[MyClass new], [MyClass new]];
Nota
É importante observar que esses são apenas avisos do compilador e você ainda pode tecnicamente inserir qualquer objeto em seu array. Existem scripts disponíveis que transformam todos os avisos em erros que impediriam a construção.
nonnull
no XCode 6 e, pelo que me lembro, eles foram introduzidos ao mesmo tempo. Além disso, o uso de tais conceitos depende da versão do XCode ou da versão do iOS?
@property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects;
Parece um pouco desajeitado, mas funciona!
Esta é uma pergunta relativamente comum para pessoas em transição de linguagens de tipo forte (como C ++ ou Java) para linguagens de tipo mais fraco ou dinâmico como Python, Ruby ou Objective-C. Em Objective-C, a maioria dos objetos herda de NSObject
(tipo id
) (o resto herda de outra classe raiz, como NSProxy
e também pode ser tipo id
) e qualquer mensagem pode ser enviada para qualquer objeto. Claro, enviar uma mensagem para uma instância que ela não reconhece pode causar um erro de tempo de execução (e também irá causar um aviso do compiladorcom sinalizadores -W apropriados). Contanto que uma instância responda à mensagem que você envia, você pode não se importar a qual classe ela pertence. Isso é frequentemente referido como "digitação de pato" porque "se ele grasna como um pato [ou seja, responde a um seletor], é um pato [ou seja, pode lidar com a mensagem; quem se importa com a classe]".
Você pode testar se uma instância responde a um seletor em tempo de execução com o -(BOOL)respondsToSelector:(SEL)selector
método. Supondo que você queira chamar um método em cada instância em uma matriz, mas não tenha certeza de que todas as instâncias podem manipular a mensagem (então você não pode apenas usar NSArray
's -[NSArray makeObjectsPerformSelector:]
, algo assim funcionaria:
for(id o in myArray) {
if([o respondsToSelector:@selector(myMethod)]) {
[o myMethod];
}
}
Se você controlar o código-fonte das instâncias que implementam o (s) método (s) que deseja chamar, a abordagem mais comum seria definir um @protocol
que contenha esses métodos e declarar que as classes em questão implementam esse protocolo em sua declaração. Nesse uso, a @protocol
é análogo a uma interface Java ou uma classe base abstrata C ++. Você pode então testar a conformidade com todo o protocolo, em vez de responder a cada método. No exemplo anterior, não faria muita diferença, mas se você estivesse chamando vários métodos, isso poderia simplificar as coisas. O exemplo seria:
for(id o in myArray) {
if([o conformsToProtocol:@protocol(MyProtocol)]) {
[o myMethod];
}
}
assumindo MyProtocol
declara myMethod
. Essa segunda abordagem é favorecida porque esclarece a intenção do código mais do que a primeira.
Freqüentemente, uma dessas abordagens o livra de se preocupar se todos os objetos em uma matriz são de um determinado tipo. Se você ainda se importa, a abordagem de linguagem dinâmica padrão é teste de unidade, teste de unidade, teste de unidade. Como uma regressão neste requisito produzirá um erro de tempo de execução (provavelmente irrecuperável) (não de tempo de compilação), você precisa ter cobertura de teste para verificar o comportamento, de modo que não libere um penetra na selva. Nesse caso, execute uma operação que modifique o array e, em seguida, verifique se todas as instâncias do array pertencem a uma determinada classe. Com a cobertura de teste adequada, você nem mesmo precisa da sobrecarga de tempo de execução adicional para verificar a identidade da instância. Você tem uma boa cobertura de teste de unidade, não é?
id
s brutos, exceto quando necessário, mais do que os codificadores Java passam Object
s. Por que não? Não precisa disso se você tiver testes de unidade? Porque está lá e torna seu código mais fácil de manter, assim como os arrays digitados. Parece que as pessoas que investiram na plataforma não querem ceder um ponto e, portanto, inventam razões pelas quais essa omissão é de fato um benefício.
Você pode NSMutableArray
criar uma subclasse para reforçar a segurança de tipo.
NSMutableArray
é um cluster de classes , portanto, a criação de subclasses não é trivial. Acabei herdando NSArray
e encaminhando invocações para um array dentro dessa classe. O resultado é uma classe chamada ConcreteMutableArray
que é fácil de subclassificar. Aqui está o que eu inventei:
Atualização: verifique esta postagem do blog de Mike Ash sobre como criar subclasses de um cluster de classe.
Inclua esses arquivos em seu projeto e, em seguida, gere quaisquer tipos que desejar usando macros:
MyArrayTypes.h
CUSTOM_ARRAY_INTERFACE(NSString)
CUSTOM_ARRAY_INTERFACE(User)
MyArrayTypes.m
CUSTOM_ARRAY_IMPLEMENTATION(NSString)
CUSTOM_ARRAY_IMPLEMENTATION(User)
Uso:
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
outros pensamentos
NSArray
para oferecer suporte à serialização / desserializaçãoDependendo do seu gosto, você pode querer substituir / ocultar métodos genéricos como
- (void) addObject:(id)anObject
Dê uma olhada em https://github.com/tomersh/Objective-C-Generics , uma implementação genérica em tempo de compilação (pré-processador implementada) para Objective-C. Esta postagem do blog tem uma boa visão geral. Basicamente, você obtém verificação em tempo de compilação (avisos ou erros), mas nenhuma penalidade de tempo de execução para genéricos.
Este projeto Github implementa exatamente essa funcionalidade.
Você pode usar os <>
colchetes, assim como faria em C #.
Dos exemplos deles:
NSArray<MyClass>* classArray = [NSArray array];
NSString *name = [classArray lastObject].name; // No cast needed
Uma maneira possível seria criar uma subclasse do NSArray, mas a Apple recomenda não fazer isso. É mais simples pensar duas vezes sobre a necessidade real de um NSArray digitado.
Criei uma subclasse NSArray que está usando um objeto NSArray como ivar de apoio para evitar problemas com a natureza de cluster de classe do NSArray. Leva blocos para aceitar ou recusar a adição de um objeto.
para permitir apenas objetos NSString, você pode definir um AddBlock
como
^BOOL(id element) {
return [element isKindOfClass:[NSString class]];
}
Você pode definir um FailBlock
para decidir o que fazer, se um elemento falhar no teste - falhar normalmente para filtragem, adicioná-lo a outro array ou - este é o padrão - gerar uma exceção.
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
Use-o como:
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);
Este é apenas um código de exemplo e nunca foi usado em aplicativos do mundo real. para fazer isso, provavelmente será necessário implementar o método mais NSArray.
Se você misturar c ++ e objetivo-c (ou seja, usando o tipo de arquivo mm), você pode forçar a digitação usando par ou tupla. Por exemplo, no método a seguir, você pode criar um objeto C ++ do tipo std :: pair, convertê-lo em um objeto do tipo OC wrapper (wrapper de std :: pair que você precisa definir) e, em seguida, passá-lo para algum outro método OC, dentro do qual você precisa converter o objeto OC de volta em objeto C ++ para usá-lo. O método OC aceita apenas o tipo de wrapper OC, garantindo assim a segurança do tipo. Você pode até usar tupla, modelo variável, lista de tipos para aproveitar recursos C ++ mais avançados para facilitar a segurança de tipos.
- (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];
}