Respuestas:
Puede crear una categoría con un -addSomeClass:
método para permitir la verificación de tipos estáticos en tiempo de compilación (para que el compilador pueda informarle si intenta agregar un objeto que sabe que es una clase diferente a través de ese método), pero no hay una forma real de hacer cumplir eso una matriz solo contiene objetos de una clase determinada.
En general, no parece haber una necesidad de tal restricción en Objective-C. Creo que nunca he escuchado a un programador experimentado de Cocoa desear esa función. Las únicas personas que parecen serlo son programadores de otros lenguajes que todavía piensan en esos lenguajes. Si solo desea objetos de una clase determinada en una matriz, solo coloque objetos de esa clase allí. Si desea probar que su código se está comportando correctamente, pruébelo.
Nadie ha puesto esto aquí todavía, ¡así que lo haré!
Esto ahora es oficialmente compatible con Objective-C. A partir de Xcode 7, puede utilizar la siguiente sintaxis:
NSArray<MyClass *> *myArray = @[[MyClass new], [MyClass new]];
Nota
Es importante tener en cuenta que estas son solo advertencias del compilador y, técnicamente, aún puede insertar cualquier objeto en su matriz. Hay scripts disponibles que convierten todas las advertencias en errores que impedirían la construcción.
nonnull
en XCode 6 y, por lo que recuerdo, se introdujeron al mismo tiempo. Además, ¿el uso de tales conceptos depende de la versión de XCode o de la versión de iOS?
@property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects;
Parece un poco torpe, ¡pero funciona!
Esta es una pregunta relativamente común para las personas que realizan la transición de lenguajes de tipos fuertes (como C ++ o Java) a lenguajes de tipos más débiles o dinámicos como Python, Ruby u Objective-C. En Objective-C, la mayoría de los objetos heredan de NSObject
(type id
) (el resto hereda de otra clase raíz como NSProxy
y también puede ser type id
), y cualquier mensaje puede enviarse a cualquier objeto. Por supuesto, enviar un mensaje a una instancia que no reconoce puede causar un error de tiempo de ejecución (y también generará una advertencia del compiladorcon 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 a menudo se denomina "tipeo 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 sea]".
Puede probar si una instancia responde a un selector en tiempo de ejecución con el -(BOOL)respondsToSelector:(SEL)selector
método. 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 (por lo que no puede simplemente usar NSArray
's -[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 de 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 declaración. En este uso, a @protocol
es análogo a una interfaz Java o una clase base abstracta de C ++. Luego, puede probar la conformidad con todo el protocolo en lugar de la respuesta a cada método. En el ejemplo anterior, no habría mucha diferencia, pero si estuviera llamando 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 MyProtocol
declara myMethod
. Se prefiere este segundo enfoque porque aclara la intención del código más que el primero.
A menudo, uno de estos enfoques lo libera de preocuparse de si todos los objetos de una matriz son de un tipo determinado. Si aún le importa, el enfoque estándar del lenguaje dinámico es la prueba unitaria, la prueba unitaria, la prueba unitaria. Debido a que una regresión en este requisito producirá un error (probablemente irrecuperable) en tiempo de ejecución (no en tiempo de compilación), debe tener cobertura de prueba para verificar el comportamiento de modo que no libere un bloqueador en la naturaleza. En este caso, realice una operación que modifique la matriz y luego verifique que todas las instancias de la matriz pertenezcan a una clase determinada. Con la cobertura de prueba adecuada, ni siquiera necesita la sobrecarga adicional de tiempo de ejecución de verificar la identidad de la instancia. Tiene una buena cobertura de pruebas unitarias, ¿no?
id
s raw excepto cuando sea necesario, como tampoco lo hacen los codificadores Java para pasar Object
s. Por qué no? ¿No lo necesita si tiene pruebas unitarias? Porque está ahí y hace que su código sea más fácil de mantener, al igual que las matrices escritas. Parece que las personas que invirtieron en la plataforma no desean conceder un punto y, por lo tanto, inventan razones por las que esta omisión es de hecho un beneficio.
Puede crear una subclase NSMutableArray
para hacer cumplir la seguridad de tipos.
NSMutableArray
es un clúster de clases , por lo que la subclasificación no es trivial. Terminé heredando NSArray
y reenviando invocaciones a una matriz dentro de esa clase. El resultado es una clase llamada ConcreteMutableArray
que es fácil de subclasificar. Esto es lo que se me ocurrió:
Actualización: consulte esta publicación de blog de Mike Ash sobre la subclasificación de un grupo de clases.
Incluya esos archivos en su proyecto, luego genere los tipos que desee 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
otros pensamientos
NSArray
para admitir serialización / deserializaciónDependiendo de su gusto, es posible que desee anular / ocultar métodos genéricos como
- (void) addObject:(id)anObject
Eche un vistazo a https://github.com/tomersh/Objective-C-Generics , una implementación de genéricos en tiempo de compilación (implementada por preprocesador) para Objective-C. Esta publicación de blog tiene una buena descripción general. Básicamente, obtiene verificación en tiempo de compilación (advertencias o errores), pero sin penalización en tiempo de ejecución para genéricos.
Este Proyecto Github implementa exactamente esa funcionalidad.
Luego puede usar los <>
corchetes, tal como lo haría en C #.
De sus ejemplos:
NSArray<MyClass>* 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 sencillo pensar dos veces en la necesidad real de un NSArray escrito.
Creé una subclase NSArray que utiliza un objeto NSArray como ivar de respaldo para evitar problemas con la naturaleza de grupo de clases de NSArray. Se necesitan bloques para aceptar o rechazar la adición de un objeto.
para permitir solo objetos NSString, puede definir un AddBlock
as
^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 el filtrado, agregarlo a otra matriz o, esto es predeterminado, generar una excepción.
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
Ú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 una aplicación del mundo real. para hacerlo, probablemente necesite implementar un método más NSArray.
Si mezcla c ++ y objetivo-c (es decir, usando el tipo de archivo mm), puede forzar la escritura usando par o tupla. Por ejemplo, en el siguiente método, puede crear un objeto C ++ de tipo std :: pair, convertirlo en un objeto de tipo contenedor OC (contenedor de std :: pair que necesita definir) y luego pasarlo a algún otro método OC, dentro del cual debe convertir el objeto OC de nuevo a un objeto C ++ para poder usarlo. El método OC solo acepta el tipo de envoltura OC, lo que garantiza la seguridad del tipo. Incluso puede usar tuplas, plantillas variadas y listas de tipos para aprovechar las funciones de C ++ más avanzadas para facilitar la seguridad de los 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];
}