¿Hay alguna forma de aplicar la escritura en NSArray, NSMutableArray, etc.?

¿Puedo hacer una instancia de NSMutableArray donde todos los elementos sean del tipo SomeClass ?

Podría hacer una categoría con un método -addSomeClass: para permitir la verificación del tipo estático en tiempo de comstackción (para que el comstackdor pueda hacerle saber si intenta agregar un objeto que sabe que es una clase diferente a través de ese método), pero no hay manera real para hacer cumplir que una matriz solo contiene objetos de una clase dada.

En general, no parece haber necesidad de tal restricción en Objective-C. No creo haber escuchado nunca a un experimentado progtwigdor de Cocoa desear esa función. Las únicas personas que parecen ser progtwigdores de otros idiomas que aún piensan en esos idiomas. Si solo quieres objetos de una clase determinada en una matriz, solo pega objetos de esa clase allí. Si desea probar que su código se comporta correctamente, pruébelo.

Nadie ha puesto esto aquí todavía, ¡así que lo haré!

Esto ahora es oficialmente compatible en Objective-C. A partir de Xcode 7, puede usar la siguiente syntax:

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

Nota

Es importante tener en cuenta que estas son solo advertencias del comstackdor y técnicamente aún puede insertar cualquier objeto en su matriz. Hay scripts disponibles que convierten todas las advertencias en errores que evitarían la construcción.

Esta es una pregunta relativamente común para las personas que hacen la transición de lenguajes de tipo fuerte (como C ++ o Java) a lenguajes de tipado más débiles o dynamics como Python, Ruby u Objective-C. En Objective-C, la mayoría de los objetos heredan de NSObject (type id ) (el rest hereda de otra clase raíz como NSProxy y también puede ser de tipo id ), y cualquier mensaje puede enviarse a cualquier objeto. Por supuesto, enviar un mensaje a una instancia que no reconoce puede causar un error en el tiempo de ejecución (y también provocará una advertencia del comstackdor con los indicadores -W apropiados). Siempre que una instancia responda al mensaje que envía, es posible que no le importe a qué clase pertenece. Esto se conoce como “tipado de pato” porque “si grazna como un pato [es decir, responde a un selector], es un pato [es decir, puede manejar el mensaje, a quién le importa qué clase es]”.

Puede probar si una instancia responde a un selector en tiempo de ejecución con el método de -(BOOL)respondsToSelector:(SEL)selector . Suponiendo que desea llamar a un método en cada instancia de una matriz pero no está seguro de que todas las instancias puedan manejar el mensaje (para que no pueda usar NSArray -[NSArray makeObjectsPerformSelector:] , algo como esto funcionaría:

 for(id o in myArray) { if([o respondsToSelector:@selector(myMethod)]) { [o myMethod]; } } 

Si controla el código fuente para las instancias que implementan los métodos que desea llamar, el enfoque más común sería definir un @protocol que contenga esos métodos y declarar que las clases en cuestión implementan ese protocolo en su statement. En este uso, un @protocol es análogo a una interfaz Java o una clase base abstracta C ++. Luego puede probar la conformidad con todo el protocolo en lugar de responder a cada método. En el ejemplo anterior, no supondría una gran diferencia, pero si llamaras a varios métodos, podría simplificar las cosas. El ejemplo sería entonces:

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

asumiendo que MyProtocol declara myMethod . Este segundo enfoque se ve favorecido porque aclara la intención del código más que el primero.

A menudo, uno de estos enfoques le libera de preocuparse de si todos los objetos de una matriz son de un tipo determinado. Si aún le importa, el enfoque del lenguaje dynamic estándar es la prueba unitaria, la prueba unitaria, la prueba unitaria. Debido a que una regresión en este requerimiento producirá un error de tiempo de ejecución (probablemente irrecuperable) (no tiempo de comstackción), necesita tener cobertura de prueba para verificar el comportamiento para que no libere un crasher en la naturaleza. En este caso, realice una operación que modifique la matriz, luego verifique que todas las instancias en la matriz pertenecen a una clase determinada. Con una cobertura de prueba adecuada, ni siquiera necesita la sobrecarga adicional de tiempo de ejecución para verificar la identidad de la instancia. Usted tiene una buena cobertura de prueba de unidad, ¿no?

Podría subclase NSMutableArray para aplicar seguridad de tipo.

NSMutableArray es un clúster de clase , por lo que la NSMutableArray subclases no es trivial. Terminé heredando de NSArray y NSArray invocaciones a una matriz dentro de esa clase. El resultado es una clase llamada ConcreteMutableArray que es fácil de subclase. Esto es lo que se me ocurrió:

  • CustomArray.h
  • CustomArray.m

Actualización: revise esta publicación de blog de Mike Ash sobre la subclasificación de un clúster de clase.

Incluya esos archivos en su proyecto, luego genere cualquier tipo que desee mediante el uso de 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 

otros pensamientos

  • Hereda de NSArray para admitir la serialización / deserialización
  • Dependiendo de su gusto, es posible que desee anular / ocultar los métodos generics como

    - (void) addObject:(id)anObject

Eche un vistazo a https://github.com/tomersh/Objective-C-Generics , una implementación de generics en tiempo de comstackción (implementada por preprocesador) para Objective-C. Esta publicación de blog tiene una buena descripción general. Básicamente, se obtienen comprobaciones en tiempo de comstackción (advertencias o errores), pero no penalización en tiempo de ejecución para generics.

Este Proyecto Github implementa exactamente esa funcionalidad.

Luego puede usar los corchetes <> , tal como lo haría en C #.

De sus ejemplos:

 NSArray* classArray = [NSArray array]; NSString *name = [classArray lastObject].name; // No cast needed 

Una posible forma podría ser subclasificar NSArray, pero Apple recomienda no hacerlo. Es más simple pensar dos veces sobre la necesidad real de un NSArray tipado.

Creé una subclase NSArray que está usando un objeto NSArray como ivar de respaldo para evitar problemas con la naturaleza del clúster de clase de NSArray. Se necesitan bloques para aceptar o rechazar agregar un objeto.

para permitir solo objetos NSString, puede definir AddBlock como

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

Puede definir un FailBlock para decidir qué hacer, si un elemento no pasó la prueba, falla correctamente para filtrar, lo agrega a otra matriz o, por defecto, genera una excepción.

VSBlockTestedObjectArray.h

 #import  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 

Úselo 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 es solo un código de ejemplo y nunca se usó en la aplicación del mundo real. para hacerlo probablemente necesite implementar el método mor NSArray.

Si combina c ++ y objective-c (es decir, utilizando el tipo de archivo mm), puede exigir el uso de tipeo o par. Por ejemplo, en el siguiente método, puede crear un objeto C ++ de tipo std :: pair, convertirlo a un objeto de tipo contenedor OC (wrapper of std :: pair que necesita definir) y luego pasarlo a algunos otro método OC, dentro del cual necesita convertir el objeto OC de nuevo a objeto C ++ para poder usarlo. El método OC solo acepta el tipo de envoltorio OC, lo que garantiza la seguridad del tipo. Incluso puede usar tupla, plantilla variadic, lista de tipos para aprovechar las características más avanzadas de C ++ para facilitar la seguridad del tipo.

 - (void) tableView:(UITableView*) tableView didSelectRowAtIndexPath:(NSIndexPath*) indexPath { std::pair tableRow(tableView, indexPath); ObjCTableRowWrapper* oCTableRow = [[[ObjCTableRowWrapper alloc] initWithTableRow:tableRow] autorelease]; [self performSelector:@selector(selectRow:) withObject:oCTableRow]; }