Objective-C Sucintamente Métodos

En este capítulo, exploraremos los métodos de Objective-C con mucho más detalle que en los capítulos anteriores. Esto incluye una discusión en profundidad de los métodos de instancia, los métodos de clase, los métodos incorporados importantes, la herencia, las convenciones de nomenclatura y los patrones de diseño comunes..


Instancia vs. Métodos de clase

Hemos estado trabajando con métodos de instancia y de clase a lo largo de este libro, pero tomemos un momento para formalizar las dos categorías principales de métodos en Objective-C:

  • Metodos de instancia - Funciones vinculadas a un objeto. Los métodos de instancia son los "verbos" asociados con un objeto..
  • Métodos de clase - Funciones ligadas a la propia clase. No pueden ser utilizados por instancias de la clase. Estos son similares a los métodos estáticos en C #.

Como hemos visto muchas veces, los métodos de instancia están denotados por un guión antes del nombre del método, mientras que los métodos de clase tienen el prefijo más. Por ejemplo, tomemos una versión simplificada de nuestra Persona.h expediente:

Persona de la interfaz: NSObject @property (copy) NSString * name; - (void) sayHello; + (Persona *) personWithName: (NSString *) nombre; @fin

Del mismo modo, los métodos de implementación correspondientes también deben ir precedidos por un guión o un signo más. Por lo tanto, un mínimo Persona.m podría parecer algo como:

#import "Person.h" @implementation Person @synthesize name = _name; - (void) sayHello NSLog (@ "HELLO");  + (Persona *) personWithName: (NSString *) nombre Persona * persona = [[Asignación de la persona] init]; person.name = nombre; persona de retorno  @final

los di hola método puede ser llamado por instancias del Persona clase, mientras que la personaCon nombre El método solo puede ser llamado por la propia clase:

Persona * p1 = [Persona personWithName: @ "Frank"]; // Método de clase. [p1 sayHello]; // método de instancia.

La mayor parte de esto ya debe ser familiar para usted, pero ahora tenemos la oportunidad de hablar sobre algunas de las convenciones únicas en Objective-C.


La super palabra clave

En cualquier entorno orientado a objetos, es importante poder acceder a los métodos de la clase principal. Objective-C usa un esquema muy similar a C #, excepto en lugar de base, usa el súper palabra clave. Por ejemplo, la siguiente implementación de di hola mostraría HOLA en el panel de salida, y luego llame a la versión de la clase padre di hola:

- (void) sayHello NSLog (@ "HELLO"); [super sayHello]; 

A diferencia de C #, los métodos de anulación no necesitan ser marcados explícitamente como tales. Verás esto con los dos en eso y Dealloc Métodos discutidos en la siguiente sección. A pesar de que estos se definen en la NSObject clase, el compilador no se queja cuando creas tu propio en eso y Dealloc metodos en subclases.


Métodos de Inicialización

Los métodos de inicialización son necesarios para todos los objetos; un objeto recién asignado no se considera "listo para usar" hasta que se haya llamado a uno de sus métodos de inicialización. Son el lugar para establecer los valores predeterminados para las variables de instancia y, de lo contrario, configurar el estado del objeto. los NSObject clase define un valor por defecto en eso Método que no hace nada, pero a menudo es útil crear el tuyo. Por ejemplo, una costumbre. en eso implementación para nuestro Enviar clase podría asignar valores por defecto a una variable de instancia llamada _ammo:

- (id) init self = [super init]; if (self) _ammo = 1000;  devuélvete a ti mismo; 

Esta es la forma canónica de definir una costumbre. en eso método. los yo palabra clave es el equivalente de C # 's esta-se utiliza para referirse a la instancia que llama al método, lo que hace posible que un objeto se envíe mensajes a sí mismo. Como puedes ver, todos en eso Se requieren métodos para devolver la instancia. Esto es lo que hace posible utilizar el [[Ship alloc] init] Sintaxis para asignar la instancia a una variable. También note que porque el NSObject interfaz declara la en eso método, no hay necesidad de añadir un en eso declaración a Nave.h.

Mientras que simple en eso Los métodos como el que se muestra en la muestra anterior son útiles para configurar valores de variable de instancia predeterminados, a menudo es más conveniente pasar parámetros a un método de inicialización:

- (id) initWithAmmo: (unsigned int) theAmmo self = [super init]; if (self) _ammo = theAmmo;  devuélvete a ti mismo; 

Si viene de un fondo de C #, puede sentirse incómodo con el initWithAmmo nombre del método Probablemente esperaría ver el Munición parámetro separado del nombre del método real como inicio del vacío (munición uint); sin embargo, la denominación del método Objective-C se basa en una filosofía completamente diferente.

Recuerde que el objetivo de Objective-C es forzar una API para que sea lo más descriptiva posible, asegurando que no haya absolutamente ninguna confusión en cuanto a lo que hará una llamada a un método. No se puede pensar en un método como una entidad separada de sus parámetros, son una sola unidad. Esta decisión de diseño se refleja realmente en la implementación de Objective-C, que no hace distinción entre un método y sus parámetros. Internamente, un nombre de método es en realidad el lista de parámetros concatenados.

Por ejemplo, considere las siguientes tres declaraciones de métodos. Tenga en cuenta que el segundo y el tercero no son métodos integrados de NSObject, vos tambien hacer Necesito agregarlos a la interfaz de la clase antes de implementarlos..

- (id) init; - (id) initWithAmmo: (unsigned int) theAmmo; - (id) initWithAmmo: (sin firma int) el capitán de la Munición: (Persona *) theCaptain;

Si bien esto parece una sobrecarga de métodos, técnicamente no. Estas no son variaciones en el en eso Método: todos son métodos completamente independientes con distintos nombres de métodos. Los nombres de estos métodos son los siguientes:

init initWithAmmo: initWithAmmo: capitán:

Esta es la razón por la que ves notación como indexOfObjectWithOptions: passingTest: y indexOfObjectAtIndexes: options: passingTest: para consultar métodos en la documentación oficial de Objective-C (tomada de NSArray).

Desde un punto de vista práctico, esto significa que el primer parámetro de sus métodos siempre debe ser descrito por el nombre del método "primario". Los métodos ambiguos como los siguientes generalmente son mal vistos por los programadores de Objective-C:

- (id) disparar: (Ship *) aShip;

En su lugar, debe usar una preposición para incluir el primer parámetro en el nombre del método, de esta manera:

- (id) shootOtherShip: (Ship *) aShip;

Incluyendo ambos Otro envío y un barco en la definición del método puede parecer redundante, pero recuerde que la un barco El argumento solo se usa internamente. Alguien que llame al método va a escribir algo como shootOtherShip: discoveryOne, dónde descubrimiento uno Es la variable que contiene la nave que quieres disparar. Este es exactamente el tipo de verbosidad que los desarrolladores de Objective-C se esfuerzan por.

Inicialización de clase

Además de en eso método para inicializar instancias, Objective-C también proporciona una manera de configurar clases. Antes de llamar a cualquier método de clase o crear una instancia de cualquier objeto, el tiempo de ejecución de Objective-C llama al inicializar Método de clase de la clase en cuestión. Esto le da la oportunidad de definir cualquier variable estática antes de que alguien use la clase. Uno de los casos de uso más comunes para esto es configurar singletons:

barco estático * _sharedShip; + (void) inicializa if (self == [clase de envío]) _sharedShip = [[self alloc] init];  + (Enviar *) sharedShip return _sharedShip; 

Antes de la primera vez [Ship sharedShip] se llama, el tiempo de ejecución llamará [Inicializar barco], lo que hace que el singleton esté definido. El modificador de variable estática cumple el mismo propósito que en C #: crea una variable de nivel de clase en lugar de una variable de instancia. los inicializar El método solo se llama una vez, pero se llama en todas las súper clases, por lo que debe tener cuidado de no inicializar las variables de nivel de clase varias veces. Por eso incluimos el auto == [clase de nave] condicional para asegurarse _shareShip sólo se asigna en el Enviar clase.

También tenga en cuenta que dentro de un método de clase, el yo palabra clave se refiere a la clase en sí, no a una instancia. Asi que, [auto asignar] en el último ejemplo es el equivalente de [Asignar nave].


Métodos de desasignación

La contraparte lógica del método de inicialización de una instancia es el Dealloc método. Este método se llama a un objeto cuando su recuento de referencia llega a cero y su memoria subyacente está a punto de ser desasignada.

Desalojamiento en MMR

Si está utilizando la administración de memoria manual (no recomendado), debe liberar las variables de instancia que su objeto asignó en el Dealloc método. Si no libera variables de instancia antes de que su objeto quede fuera del alcance, tendrá punteros colgantes a sus variables de instancia, lo que significa que se perderá memoria cada vez que se libere una instancia de la clase. Por ejemplo, si nuestro Enviar clase asignada una variable llamada _pistola en su en eso Método, tendrías que lanzarlo en Dealloc. Esto se demuestra en el siguiente ejemplo (Gun.h contiene una interfaz vacía que simplemente define la Pistola clase):

#import "Ship.h" #import "Gun.h" @implementation Ship BOOL _gunIsReady; Gun * _gun;  - (id) init self = [super init]; if (self) _gun = [[Gun alloc] init];  devuélvete a ti mismo;  - (void) dealloc NSLog (@ "Deallocating a Ship"); [_ lanzamiento de pistola]; [super dealloc];  @final

Puedes ver el Dealloc Método en acción mediante la creación de una Enviar y soltándolo, así:

int main (int argc, const char * argv []) @autoreleasepool Ship * ship = [[Ship alloc] init]; [barco autorelease]; NSLog (@ "Ship aún debería existir en autoreleasepool");  NSLog (@ "La nave debería estar desasignada por ahora"); devuelve 0; 

Esto también demuestra cómo funcionan los objetos auto-lanzados. los Dealloc Método no será llamado hasta el final de la @autoreleasepool bloque, por lo que el código anterior debe dar salida a lo siguiente:

El barco todavía debe existir en autoreleasepool Deallocating a Ship El barco debe estar desasignado a estas alturas

Tenga en cuenta que la primera NSLog () mensaje en principal() se visualiza antes de el de la Dealloc Método, aunque se llamara después la autorelease llamada.

Desalojamiento en ARC

Sin embargo, si está utilizando el recuento automático de referencias, todas las variables de su instancia se desasignarán automáticamente, y [super dealloc] también será llamado para usted (nunca debe llamarlo explícitamente). Por lo tanto, lo único de lo que tiene que preocuparse son las variables sin objeto, como los búferes creados con C malloc ().

Me gusta en eso, no tienes que implementar un Dealloc Método si su objeto no necesita ningún manejo especial antes de ser lanzado. Este es a menudo el caso de los entornos de conteo automático de referencias..


Métodos privados

Un gran obstáculo para los desarrolladores de C # que hacen la transición a Objective-C es la aparente falta de métodos privados. A diferencia de C #, todos los métodos en una clase de Objective-C son accesibles a terceros; sin embargo, es posible emular El comportamiento de los métodos privados..

Recuerde que los clientes solo importan la interfaz de una clase (es decir, los archivos de encabezado), nunca deben ver la implementación subyacente. Así, añadiendo nuevos métodos dentro de la implementación archivo sin incluirlos en el interfaz, Podemos ocultar efectivamente métodos de otros objetos. Si bien, esto es más basado en convenciones que métodos "verdaderos", pero esencialmente es la misma funcionalidad: intentar llamar a un método que no está declarado en una interfaz dará como resultado un error del compilador.

Intentando llamar a un método "privado"

Por ejemplo, digamos que necesitabas agregar un privado prepareToShoot método para el Enviar clase. Todo lo que tienes que hacer es omitirlo. Nave.h mientras se agrega a Nave.m:

// Ship.h @interface Ship: NSObject @property (débil) Person * captain; - (vacío) disparar; @fin

Esto declara un método público llamado disparar, que utilizará el privado prepareToShoot método. La implementación correspondiente podría verse algo como:

// Ship.m #import "Ship.h" @implementation Ship BOOL _gunIsReady;  @synthesize captain = _captain; - (void) dispara if (! _gunIsReady) [self prepareToShoot]; _gunIsReady = YES;  NSLog (@ "¡Disparando!");  - (void) prepareToShoot // Ejecuta alguna funcionalidad privada. NSLog (@ "Preparando el arma principal ...");  @final

A partir de Xcode 4.3, puede definir métodos privados. en cualquier sitio en la implementación. Si usa el método privado antes de que el compilador lo haya visto (como en el ejemplo anterior), el compilador verifica el resto del bloque de implementación para la definición del método. Antes de Xcode 4.3, tenía que definir un método privado antes de se usó en otro lugar en el archivo, o lo declara hacia adelante con una extensión de clase.

Las extensiones de clase son un caso especial de categorías, que se presentan en el próximo capítulo. Así como no hay manera de marcar un método como privado, no hay manera de marcar un método como protegido; Sin embargo, como veremos en el siguiente capítulo, las categorías ofrecen una poderosa alternativa a los métodos protegidos..


Selectores

Los selectores son los métodos de representación de Objective-C. Le permiten "seleccionar" dinámicamente uno de los métodos de un objeto, que se puede usar para referirse a un método en tiempo de ejecución, pasar un método a otra función y determinar si un objeto tiene un método particular. Por motivos prácticos, puede pensar en un selector como un nombre alternativo para un método.

Representación de los desarrolladores de un método frente a la representación de Objective-C

Internamente, Objective-C usa un número único para identificar cada nombre de método que utiliza su programa. Por ejemplo, un método llamado di hola podría traducirse a 4984331082. Este identificador se llama selector, y es una forma mucho más eficiente para que el compilador se refiera a los métodos que a su representación de cadena completa. Es importante entender que un selector solo representa el método. nombre-No es un método específico de implementación. En otras palabras, un di hola método definido por el Persona clase tiene el mismo selector que una di hola método definido por el Enviar clase.

Las tres herramientas principales para trabajar con selectores son:

  • @selector() - Devuelve el selector asociado con un nombre de método de código fuente.
  • NSSelectorFromString () - Devuelve el selector asociado con la representación de cadena de un nombre de método. Esta función hace posible definir el nombre del método en tiempo de ejecución, pero es menos eficiente que @selector().
  • NSStringFromSelector () - Devuelve la representación de cadena de un nombre de método desde un selector.

Como puede ver, hay tres formas de representar un nombre de método en Objective-C: como código fuente, como cadena o como selector. Estas funciones de conversión se muestran gráficamente en la siguiente figura:

Conversión entre código fuente, cadenas y selectores

Los selectores se almacenan en un tipo de datos especial llamado SEL. El siguiente fragmento de código muestra el uso básico de las tres funciones de conversión que se muestran en la figura anterior:

int main (int argc, const char * argv []) @autoreleasepool SEL selector = @selector (sayHello); NSLog (@ "% @", NSStringFromSelector (selector)); if (selector == NSSelectorFromString (@ "sayHello")) NSLog (@ "Los selectores son iguales!");  devolver 0; 

Primero, usamos el @selector() Directiva para averiguar el selector para un método llamado di hola, que es una representación de código fuente de un nombre de método. Tenga en cuenta que puede pasar alguna nombre del método para @selector() -no tiene que existir en ninguna otra parte de tu programa. A continuación, utilizamos el NSStringFromSelector () Función para convertir el selector de nuevo en una cadena para que podamos mostrarlo en el panel de salida. Finalmente, el condicional muestra que los selectores tienen una correspondencia uno a uno con los nombres de los métodos, independientemente de si los encuentra a través de nombres de métodos o cadenas codificados.

Nombres de los métodos y selectores

El ejemplo anterior utiliza un método simple que no toma parámetros, pero es importante poder pasar métodos que hacer aceptar parametros Recuerde que el nombre de un método consiste en el nombre del método primario concatenado con todos los nombres de los parámetros. Por ejemplo, un método con el firma

- (void) sayHelloToPerson: (Person *) aPerson withGreeting: (NSString *) aGreeting;

tendría un método nombre de:

sayHelloToPerson: withGreeting:

Esto es lo que pasarías a @selector() o NSSelectorFromString () para devolver el identificador para ese método. Los selectores solo funcionan con método nombres (no firmas), por lo que hay no una correspondencia uno a uno entre los selectores y las firmas. Como resultado, el método nombre en el último ejemplo, también se hará coincidir una firma con diferentes tipos de datos, incluidos los siguientes:

- (void) sayHelloToPerson: (NSString *) aName withGreeting: (BOOL) useGreeting;

La verbosidad de las convenciones de nomenclatura de Objective-C evita las situaciones más confusas; sin embargo, los selectores para los métodos de un solo parámetro pueden ser complicados porque la adición de dos puntos al nombre del método en realidad lo convierte en un completamente diferente método. Por ejemplo, en el siguiente ejemplo, el primer nombre del método no toma un parámetro, mientras que el segundo sí lo hace:

dile hola dime hola

Una vez más, las convenciones de nomenclatura contribuyen en gran medida a eliminar la confusión, pero aún debe asegurarse de saber cuándo es necesario agregar dos puntos al final del nombre de un método. Este es un problema común si usted es nuevo en los selectores, y puede ser difícil de depurar, ya que los dos puntos posteriores aún crean un nombre de método perfectamente válido..

Realización de selectores

Por supuesto, grabando un selector en un SEL La variable es relativamente inútil sin la capacidad de ejecutarla más adelante. Dado que un selector es simplemente un método nombre (no es una implementación), siempre debe estar emparejado con un objeto antes de poder llamarlo. los NSObject clase define un realizarSelector: método para este propósito.

[joe performSelector: @selector (sayHello)];

Este es el equivalente de llamar di hola directamente en joe:

[joe sayHello];

Para los métodos con uno o dos parámetros, puede utilizar los performSelector: withObject: y performSelector: withObject: withObject: metodos El siguiente método de implementación:

- (void) sayHelloToPerson: (Person *) aPerson NSLog (@ "Hello,% @", [aPerson name]); 

podría ser llamado dinámicamente pasando la una persona argumento a la performSelector: withObject: Método, como se demuestra aquí:

[joe performSelector: @selector (sayHelloToPerson :) withObject: bill];

Esto es equivalente a pasar el parámetro directamente al método:

[joe sayHelloToPerson: bill];

Del mismo modo, el performSelector: withObject: withObject: El método le permite pasar dos parámetros al método de destino. La única advertencia con estos es que todos los parámetros y el valor de retorno del método deben ser objetos, no funcionan con tipos de datos C primitivos como En t, flotador, etc. Si necesita esta funcionalidad, puede marcar el tipo primitivo en una de las muchas clases de envoltorios de Objective-C (por ejemplo,., NSNumber) o utilice el objeto NSInvocation para encapsular una llamada de método completa.

Comprobando la existencia de selectores

No es posible realizar un selector en un objeto que no haya definido el método asociado. Pero a diferencia de las llamadas a métodos estáticos, no es posible determinar en tiempo de compilación si realizarSelector: planteará un error. En su lugar, debe verificar si un objeto puede responder a un selector en tiempo de ejecución usando el nombre adecuado respondsToSelector: método. Simplemente vuelve o NO Dependiendo de si el objeto puede realizar el selector:

SEL methodToCall = @selector (sayHello); if ([joe respondsToSelector: methodToCall]) [joe performSelector: methodToCall];  else NSLog (@ "Joe no sabe cómo realizar% @.", NSStringFromSelector (methodToCall)); 

Si sus selectores se generan dinámicamente (por ejemplo, si methodToCall se selecciona de una lista de opciones) o no tiene control sobre el objeto de destino (por ejemplo,., joe puede ser uno de varios tipos diferentes de objetos), es importante ejecutar esta comprobación antes de intentar llamar realizarSelector:.

Usando selectores

La idea principal detrás de los selectores es ser capaz de pasar a los métodos de la misma manera que pasa a los objetos. Esto se puede usar, por ejemplo, para definir dinámicamente una "acción" para un Persona Objeto para ejecutar más adelante en el programa. Por ejemplo, considere la siguiente interfaz:

Ejemplo de código incluido: selectores

Persona de la interfaz: NSObject @property (copy) NSString * name; @property (débil) Persona * amigo; acción @property SEL; - (void) sayHello; - (vacío) diga adiós; - (void) coerceFriend; @fin

Junto con la implementación correspondiente:

#import "Person.h" @implementation Person @synthesize name = _name; @synthesize friend = _friend; @synthesize action = _action; - (void) sayHello NSLog (@ "Hola, dice% @.", _name);  - (void) sayGoodbye NSLog (@ "Goodbye, dice% @.", _name);  - (void) coerceFriend NSLog (@ "% @ está a punto de hacer que% @ haga algo.", _name, [_friend name]); [_friend performSelector: _action];  @final

Como puedes ver, llamando al coerce amigo método forzará a diferente Objeto para realizar alguna acción arbitraria. Esto le permite configurar una amistad y un comportamiento temprano en su programa y esperar a que ocurra un evento en particular antes de desencadenar la acción:

#importar  #import "Person.h" NSString * askUserForAction () // En el mundo real, esto capturaría algunos // comentarios del usuario para determinar a qué método llamar. NSString * theMethod = @ "sayGoodbye"; devuelve el Método;  int main (int argc, const char * argv []) @autoreleasepool // Crea una persona y determina una acción a realizar. Persona * joe = [[Asignación de persona] init]; joe.name = @ "Joe"; Persona * factura = [[Asignación de persona] init]; bill.name = @ "Bill"; joe.friend = bill; joe.action = NSSelectorFromString (askUserForAction ()); // Espera un evento ... // Realiza la acción. [joe coerceFriend];  devuelve 0; 

Esto es casi exactamente cómo se implementan los componentes de la interfaz de usuario en iOS. Por ejemplo, si tuviera un botón, lo configuraría con un objeto de destino (por ejemplo,., amigo), y una acción (por ejemplo,., acción). Luego, cuando el usuario finalmente presiona el botón, puede usar realizarSelector: para ejecutar el método deseado en el objeto apropiado. Permitiendo tanto el objeto y el método para variar de forma independiente proporciona una flexibilidad significativa: el botón podría, literalmente, realizar cualquier acción con cualquier objeto sin alterar la clase del botón de ninguna manera. Esto también forma la base del patrón de diseño Target-Action, que se basa en gran medida en el iOS sucintamente libro de acompañamiento.


Resumen

En este capítulo, cubrimos los métodos de instancia y clase, junto con algunos de los métodos incorporados más importantes. Trabajamos estrechamente con los selectores, que son una forma de referirnos a los nombres de los métodos como código fuente o cadenas. También hicimos una breve vista previa del patrón de diseño Target-Action, que es un aspecto integral de la programación de iOS y OS X.

El siguiente capítulo trata sobre una forma alternativa de crear métodos privados y protegidos en Objective-C.

Esta lección representa un capítulo de Objective-C Sucintamente, un libro electrónico gratuito del equipo de Syncfusion.