¿Hay colecciones fuertemente tipadas en Objective-C?

140

Soy nuevo en la programación de Mac / iPhone y Objective-C. En C # y Java tenemos "genéricos", clases de colección cuyos miembros solo pueden ser del tipo declarado. Por ejemplo, en C #

Dictionary<int, MyCustomObject>

solo puede contener claves que son enteros y valores que son de tipo MyCustomObject. ¿Existe un mecanismo similar en Objective-C?

Rico
fuente
Empezando a aprender sobre ObjC yo mismo. ¿Quizás pueda usar ObjC ++ para hacer el trabajo pesado?
Toybuilder
Es posible que le interesen las respuestas a esta pregunta: ¿Hay alguna forma de exigir la escritura en NSArray, NSMutableArray, etc.? . Se dan argumentos por los que no es una práctica común en Objective-C / Cocoa.
mouviciel
2
ObjC ++ no es realmente un lenguaje ... es solo una forma de hacer referencia a la capacidad de ObjC para manejar C ++ en línea de la misma manera que manejaría C. Sin embargo, no debe hacer esto a menos que tenga que hacerlo (como si necesita para usar una biblioteca de terceros que fue escrita en C ++).
Marc W
Prácticamente un duplicado exacto de stackoverflow.com/questions/649483/…
Barry Wark
@ Mark W - "no debería hacer esto" ¿por qué no? He usado ObjC ++ y funciona muy bien. Puedo hacer #import <map> y @property std :: map <int, NSString *> myDict; Puedo usar la API de cacao completa Y tener colecciones fuertemente tipadas. No veo ningún inconveniente.
John Henckel

Respuestas:

211

En Xcode 7, Apple ha introducido 'Lightweight Generics' en Objective-C. En Objective-C, generarán advertencias del compilador si hay una falta de coincidencia de tipos.

NSArray<NSString*>* arr = @[@"str"];

NSString* string = [arr objectAtIndex:0];
NSNumber* number = [arr objectAtIndex:0]; // Warning: Incompatible pointer types initializing 'NSNumber *' with an expression of type 'NSString *'

Y en el código Swift, producirán un error de compilación:

var str: String = arr[0]
var num: Int = arr[0] //Error 'String' is not convertible to 'Int'

Los Genéricos Ligeros están diseñados para usarse con NSArray, NSDictionary y NSSet, pero también puede agregarlos a sus propias clases:

@interface GenericsTest<__covariant T> : NSObject

-(void)genericMethod:(T)object;

@end

@implementation GenericsTest

-(void)genericMethod:(id)object {}

@end

Objective-C se comportará como antes con las advertencias del compilador.

GenericsTest<NSString*>* test = [GenericsTest new];

[test genericMethod:@"string"];
[test genericMethod:@1]; // Warning: Incompatible pointer types sending 'NSNumber *' to parameter of type 'NSString *'

pero Swift ignorará por completo la información genérica. (Ya no es cierto en Swift 3+.)

var test = GenericsTest<String>() //Error: Cannot specialize non-generic type 'GenericsTest'

Aparte de estas clases de colección Foundation, Swift ignora los genéricos livianos Objective-C. Cualquier otro tipo que use genéricos livianos se importa a Swift como si no estuviera parametrizado.

Interactuando con las API de Objective-C

Connor
fuente
Dado que tengo preguntas sobre los genéricos y los tipos devueltos en los métodos, hice mi pregunta en diferentes hilos, para mantener todo claro: stackoverflow.com/questions/30828076/…
lvp
2
@rizzes. Sí, se acaba de presentar.
Connor
Una advertencia aquí es que Swift no ignora por completo las anotaciones de tipo en su clase genérica de ObjC. Si especifica restricciones, por ejemplo MyClass <Foo: id<Bar>>, su código Swift asumirá que los valores son el tipo de su restricción, lo que le brinda algo con lo que trabajar. Sin embargo, las subclases especializadas de MyClasstendrían sus tipos especializados ignorados (se verá efectivamente lo mismo que un genérico MyClass). Ver github.com/bgerstle/LightweightGenericsExample
Brian Gerstle
Entonces, ¿esto compila para 10.10, 10.9 y sistemas operativos anteriores?
p0lAris
Siempre y cuando establezca su objetivo de implementación para apoyarlos
Connor
91

Esta respuesta está desactualizada pero sigue teniendo valor histórico. A partir de Xcode 7, la respuesta de Connor del 8 de junio de 2015 es más precisa.


No, no hay genéricos en Objective-C a menos que desee utilizar plantillas de C ++ en sus propias clases de colección personalizadas (lo cual desaconsejo encarecidamente).

Objective-C tiene la tipificación dinámica como una característica, lo que significa que al tiempo de ejecución no le importa el tipo de objeto, ya que todos los objetos pueden recibir mensajes. Cuando agrega un objeto a una colección integrada, solo se tratan como si fueran de tipo id. Pero no se preocupe, solo envíe mensajes a esos objetos como de costumbre; funcionará bien (a menos que, por supuesto, uno o más de los objetos de la colección no respondan al mensaje que está enviando) .

Se necesitan genéricos en lenguajes como Java y C # porque son lenguajes fuertes y de tipo estático. Juego de pelota totalmente diferente a la característica de tipeo dinámico de Objective-C.

Marc W
fuente
88
No estoy de acuerdo con "no te preocupes, solo envía mensajes a esos objetos". Si coloca el tipo incorrecto de objetos en la colección, que no responden a estos mensajes, esto generará errores de tiempo de ejecución. El uso de genéricos en otros idiomas evita este problema con las comprobaciones de tiempo de compilación.
henning77
8
@ henning77 Sí, pero Objective-C es un lenguaje más dinámico que estos lenguajes. Si desea una fuerte seguridad de escritura, use esos idiomas.
Raffi Khatchadourian
36
También estoy en desacuerdo con la filosofía de no preocuparse, por ejemplo, si saca el primer elemento de un NSArray y lo arroja a un NSNumber pero ese elemento era realmente un NSString, está jodido ...
jjxtra
13
@RaffiKhatchadourian: no hay muchas opciones si está escribiendo una aplicación para iOS. Si fuera simple escribir uno con Java y obtener todos los beneficios de escribir una aplicación nativa, créame: lo haría.
ericsoco
11
La mayor queja que tengo sobre esto no tiene que ver con los lenguajes dinámicos versus la verificación del tiempo de compilación, sino con la simple comunicación del desarrollador. No puedo simplemente mirar una declaración de propiedad y saber qué tipo de objetos va a devolver a menos que esté documentado en alguna parte.
devios1
11

No, pero para que quede más claro, puede comentarlo con el tipo de objeto que desea almacenar, lo he visto algunas veces cuando necesita escribir algo en Java 1.4 hoy en día), por ejemplo:

NSMutableArray* /*<TypeA>*/ arrayName = ....

o

NSDictionary* /*<TypeA, TypeB>*/ dictionaryName = ...
Mark Rhodes
fuente
Supongo que esta es una buena manera de documentarlo, en caso de que alguien más lea su código. De todos modos, el nombre de la variable debe ser lo más claro posible para saber qué objetos contiene.
htafoya
6

No hay genéricos en Objective-C.

De los documentos

Las matrices son colecciones ordenadas de objetos. Cocoa proporciona varias clases de matriz, NSArray, NSMutableArray (una subclase de NSArray) y NSPointerArray.

Matthew Vines
fuente
El enlace al documento en respuesta está muerto: "Lo siento, esa página no se puede encontrar" .
Pang
5

Esto fue lanzado en Xcode 7 (¡finalmente!)

Tenga en cuenta que en el código del Objetivo C, es solo una verificación en tiempo de compilación; no habrá ningún error en tiempo de ejecución solo por colocar el tipo incorrecto en una colección o asignarlo a una propiedad escrita.

Declarar:

@interface FooClass <T> : NSObject
@property (nonatomic) T prop;
@end

Utilizar:

FooClass<NSString *> *foo = [[FooClass alloc] init];
NSArray<FooClass<NSString *> *> *fooAry = [NSArray array];

Ten cuidado con esos *s.

Kevin
fuente
4

Las matrices NSA genéricas se pueden realizar subclasificando NSArrayy redefiniendo todos los métodos proporcionados con métodos más restrictivos. Por ejemplo,

- (id)objectAtIndex:(NSUInteger)index

tendría que ser redefinido en

@interface NSStringArray : NSArray

como

- (NSString *)objectAtIndex:(NSUInteger)index

para que un NSArray contenga solo NSStrings.

La subclase creada se puede usar como un reemplazo directo y trae muchas características útiles: advertencias del compilador, acceso a la propiedad, mejor creación de código y finalización en Xcode. Todas estas son características de tiempo de compilación, no hay necesidad de redefinir la implementación real; los métodos de NSArray aún se pueden usar.

Es posible automatizar esto y reducirlo a solo dos declaraciones, lo que lo acerca a los idiomas que admiten genéricos. Creé una automatización con WMGenericCollection , donde las plantillas se proporcionan como Macros de preprocesador C.

Después de importar el archivo de encabezado que contiene la macro, puede crear un NSArray genérico con dos declaraciones: una para la interfaz y otra para la implementación. Solo necesita proporcionar el tipo de datos que desea almacenar y los nombres de sus subclases. WMGenericCollection proporciona tales plantillas para NSArray, NSDictionaryy NSSet, así como sus contrapartes mutables.

Un ejemplo: List<int>podría realizarse mediante una clase personalizada llamada NumberArray, que se crea con la siguiente instrucción:

WMGENERICARRAY_INTERFACE(NSNumber *, // type of the value class
                         // generated class names
                         NumberArray, MutableNumberArray)

Una vez que haya creado NumberArray, puede usarlo en todas partes en su proyecto. Carece de la sintaxis de <int>, pero puede elegir su propio esquema de nombres para etiquetarlos como clases como plantillas.

wm
fuente
tenga en cuenta que lo mismo existe en CoreLib: github.com/core-code/CoreLib/blob/master/CoreLib/CoreLib.h#L105
user1259710
2

Ahora los sueños se hacen realidad: hay genéricos en Objective-C desde hoy (gracias, WWDC). No es una broma, en la página oficial de Swift:

Las nuevas características de sintaxis le permiten escribir código más expresivo mientras mejora la consistencia en todo el lenguaje. Los SDK han empleado nuevas características de Objective-C, como genéricos y anotaciones de nulabilidad para hacer que el código Swift sea aún más limpio y seguro. Aquí hay solo una muestra de las mejoras de Swift 2.0.

Y una imagen que prueba esto:Genéricos de Objective-C

htzfun
fuente
2

Solo quiero saltar aquí. He escrito una publicación de blog aquí sobre Genéricos.

Lo que quiero contribuir es que los genéricos se pueden agregar a cualquier clase , no solo a las clases de colección como indica Apple.

Luego he agregado con éxito a una variedad de clases, ya que funcionan exactamente igual que las colecciones de Apple. es decir. comprobación del tiempo de compilación, finalización del código, habilitación para la eliminación de moldes, etc.

Disfrutar.

drekka
fuente
-2

Las clases de Colecciones proporcionadas por los marcos de Apple y GNUStep son semi-genéricas porque suponen que se les dan objetos, algunos que se pueden ordenar y otros que responden a ciertos mensajes. Para primitivas como flotantes, ints, etc., toda la estructura de los arreglos en C está intacta y se puede usar, y hay objetos de envoltura especiales para usar en las clases de colección general (por ejemplo, NSNumber). Además, una clase de Colección puede subclasificarse (o modificarse específicamente a través de categorías) para aceptar objetos de cualquier tipo, pero debe escribir todo el código de manejo de tipos usted mismo. Los mensajes pueden enviarse a cualquier objeto, pero deberían volverse nulos si no es apropiado para el objeto, o el mensaje debe reenviarse a un objeto apropiado. Los errores de tipo verdadero deben detectarse en tiempo de compilación, no en tiempo de ejecución. En tiempo de ejecución deben ser manejados o ignorados. Finalmente, Objc proporciona funciones de reflexión en tiempo de ejecución para manejar casos difíciles y la respuesta de mensajes, el tipo específico y los servicios se pueden verificar en un objeto antes de enviar un mensaje o colocarlo en una colección inapropiada. Tenga en cuenta que bibliotecas y marcos dispares adoptan diferentes convenciones sobre cómo se comportan sus objetos cuando envían mensajes para los que no tienen respuestas de código, por lo que RTFM. Además de los programas de juguete y las compilaciones de depuración, la mayoría de los programas no deberían tener que bloquearse a menos que realmente se arruinen e intenten escribir datos incorrectos en la memoria o el disco, realicen operaciones ilegales (por ejemplo, divídalo por cero, pero también puede atraparlo) o acceder recursos del sistema fuera de límites. El dinamismo y el tiempo de ejecución de Objective-C permiten que las cosas fallen con gracia y deben integrarse en su código. (SUGERENCIA) si tiene problemas con la genérica en sus funciones, prueba alguna especificidad. Escriba las funciones con tipos específicos y deje que el tiempo de ejecución seleccione (¡por eso se llaman selectores!) La función miembro apropiada en tiempo de ejecución.

Example:
    -(id) sort (id) obj;  // too generic. catches all.
     // better
    -(id) sort: (EasilySortableCollection*) esc;
    -(id) sort: (HardToSortCollection*) hsc; 
    ...
    [Sorter  sort: MyEasyColl];
    [Sorter  sort: MyHardColl];
Chris Reid
fuente