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

Respuestas:

35

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.

Arrojar
fuente
136
Creo que los "programadores experimentados de Cocoa" simplemente no saben lo que se están perdiendo; la experiencia con Java muestra que las variables de tipo mejoran la comprensión del código y hacen posibles más refactorizaciones.
tgdavies
11
Bueno, el soporte genérico de Java está muy roto por derecho propio, porque no lo pusieron desde el principio ...
dertoni
28
Tengo que estar de acuerdo con @tgdavies. Echo de menos las capacidades de intellisense y refactorización que tenía con C #. Cuando quiero escritura dinámica, puedo obtenerla en C # 4.0. Cuando quiero cosas de tipos fuertes, también puedo tenerlas. Descubrí que hay un momento y un lugar para ambas cosas.
Steve
18
@charkrit ¿Qué tiene Objective-C que lo hace 'innecesario'? ¿Sintió que era necesario cuando usaba C #? Escucho a mucha gente decir que no lo necesitas en Objective-C, pero creo que estas mismas personas piensan que no lo necesitas en ningún idioma, lo que lo convierte en una cuestión de preferencia / estilo, no de necesidad.
bacar
17
¿No se trata de permitir que tu compilador te ayude a encontrar problemas? Seguro que puedes decir "Si solo quieres objetos de una clase determinada en una matriz, solo pega objetos de esa clase allí". Pero si las pruebas son la única forma de hacer cumplir eso, estás en desventaja. Cuanto más lejos de escribir el código encuentre un problema, más costoso será ese problema.
GreenKiwi
145

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.

Logan
fuente
Estoy siendo vago aquí, pero ¿por qué solo está disponible en XCode 7? Podemos usar el nonnullen 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?
Guven
@Guven: la nulabilidad llegó en 6, tiene razón, pero los genéricos de ObjC no se introdujeron hasta Xcode 7.
Logan
Estoy bastante seguro de que depende solo de la versión de Xcode. Los genéricos son solo advertencias del compilador y no se indican en tiempo de ejecución. Estoy bastante seguro de que podría compilar con cualquier OS que desee.
Logan
2
@DeanKelly - Podrías hacer eso así: @property (nonatomic, strong) NSArray<id<SomeProtocol>>* protocolObjects; Parece un poco torpe, ¡pero funciona!
Logan
1
@Logan, no solo está el conjunto de scripts, que impiden la construcción en caso de detectarse alguna advertencia. Xcode tiene un mecanismo perfecto llamado "Configuración". Vea esto boredzo.org/blog/archives/2009-11-07/warnings
adnako
53

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 NSProxyy 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)selectormé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 @protocolque contenga esos métodos y declarar que las clases en cuestión implementan ese protocolo en su declaración. En este uso, a @protocoles 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 MyProtocoldeclara 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?

Barry Wark
fuente
35
Las pruebas unitarias no sustituyen a un sistema de tipos decente.
tba
8
Sí, ¿quién necesita las herramientas que podrían permitirse las matrices mecanografiadas? Estoy seguro de que @BarryWark (y cualquier otra persona que haya tocado cualquier base de código que necesite usar, leer, comprender y respaldar) tiene una cobertura de código del 100%. Sin embargo, apuesto a que no usa ids raw excepto cuando sea necesario, como tampoco lo hacen los codificadores Java para pasar Objects. 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.
funkybro
"Duck typing" ?? ¡eso es hilarante! nunca escuché eso antes.
John Henckel
11

Puede crear una subclase NSMutableArraypara hacer cumplir la seguridad de tipos.

NSMutableArrayes un clúster de clases , por lo que la subclasificación no es trivial. Terminé heredando NSArrayy reenviando invocaciones a una matriz dentro de esa clase. El resultado es una clase llamada ConcreteMutableArrayque 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

  • Hereda de NSArraypara admitir serialización / deserialización
  • Dependiendo de su gusto, es posible que desee anular / ocultar métodos genéricos como

    - (void) addObject:(id)anObject

bendytree
fuente
Agradable, pero por ahora carece de una escritura fuerte al anular algunos métodos. Actualmente es solo una escritura débil.
Cœur
7

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.

Barry Wark
fuente
1
Lo probé, muy buena idea, pero lamentablemente con errores y no verifica los elementos agregados.
Binarian
4

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
IluTov
fuente
0

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.

Mouviciel
fuente
1
Ahorra tiempo al tener una verificación de tipo estática en el momento de la compilación, la edición es aún mejor. Especialmente útil cuando escribe lib para un uso a largo plazo.
pinxue
0

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 AddBlockas

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

Puede definir un FailBlockpara 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.

vikingosegundo
fuente
0

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];
}
colin
fuente
0

mis dos centavos para ser un poco "más limpio":

use typedefs:

typedef NSArray<NSString *> StringArray;

en código podemos hacer:

StringArray * titles = @[@"ID",@"Name", @"TYPE", @"DATE"];
ingconti
fuente