voy a responder a sus preguntas más adelante, pero tal vez la mejor manera de aprender estas cosas es leer algunas notas fáciles de usar destinados a personas nuevas a la lengua, como the Learn Objective-C tutorial encima en cocoadevcentral.
Un ejemplo
Me gustaría ayudar a responder a sus preguntas con un ejemplo (Me encanta aprender con el ejemplo). Digamos que eres un profesor que escribe un programa que les pregunta a los alumnos una pregunta específica de sí/no, y realiza un seguimiento de cuántos lo hacen correctamente y cuántos alumnos ha solicitado.
Aquí es una posible interfaz para esta clase:
@interface Question : NSObject {
NSString* questionStr;
int numTimesAsked;
int numCorrectAnswers;
}
@property (nonatomic, retain) NSString* questionStr;
@property (nonatomic, readonly) int numTimesAsked;
@property (nonatomic) int numCorrectAnswers;
@property (nonatomic) int numWrongAnswers;
- addAnswerWithTruthValue: (BOOL) isCorrect;
@end
Las tres variables dentro de las llaves son variables de instancia, y cada instancia de la clase tendrán sus propios valores para cada una de esas variables. Todo lo que está fuera de las llaves pero antes de @end
es una declaración de un método (incluidas las declaraciones @property
).
(Nota al margen:. Para muchos objetos, es útil tener retain
propiedades, ya que desea evitar la sobrecarga de copiar el objeto, y asegúrese de que no se libera mientras se está usando Es legal retain
un NSString
como en este ejemplo, pero it is often considered good practice to use copy
instead of retain
ya que un NSString*
en realidad podría apuntar a un objeto NSMutableString
, que más tarde puede cambiar cuando el código de espera que se mantenga el mismo.)
lo que hace @property
Cuando se declara una @property
, que está haciendo dos cosas:
- Declarar un método setter y getter en la interfaz de la clase, y
- que indican cómo el setter y getter se comportan.
Para el primero, es suficiente para saber que esta línea:
@property (nonatomic, retain) NSString* questionStr;
es básicamente el mismo que esto:
- (NSString*) questionStr; // getter
- (void) setQuestionStr: (NSString) newQuestionStr; // setter
en la cabecera. Literalmente estás declarando esos dos métodos; puede llamarlos directamente o usar la notación de puntos como un atajo para llamarlos por usted.
La parte "básicamente" en "básicamente lo mismo" es la información adicional proporcionada por palabras clave como nonatomic
y retain
.
La palabra clave nonatomic
indica que no son necesariamente seguros para subprocesos.La palabra clave retain
común indica que el objeto conserva cualquier valor establecido y libera los valores anteriores a medida que se liberan.
Por ejemplo:
// The correct answer to both questions is objectively YES.
Question* myQuestion = [[Question alloc] init];
NSString* question1 = [[NSString alloc] initWithString:@"Is pizza tasty?"];
// question1 has retain count of 1, from the call to alloc
myQuestion.questionStr = question1;
// question1 now has a retain count of 2
NSString* question2 = [[NSString alloc] initWithString:@"Free iPhone?"];
myQuestion.questionStr = question2;
// question1 has a retain count of 1, and question2 has retain count of 2
Si la declaración de @property
questionStr
había sido assign
lugar, a continuación, todas las declaraciones myQuestion.questionStr =
no habría hecho ningún cambio en absoluto a los recuentos de retener.
Puede read a little more about properties here.
Qué hacer IBOutlet
y IBAction
Estos son, básicamente, las palabras no-op que actúan sólo como una manera de contar Interface Builder que las piezas del archivo de cabecera que presten atención. IBOutlet
se convierte literalmente en una cadena vacía cuando el compilador la examina, y IBAction
se convierte en el valor de retorno void
. Sin embargo, los necesitamos para trabajar con Interface Builder, por lo que son importantes, solo que no para el compilador.
Nota rápida sobre estructuras C y flecha vs notación de puntos
Por cierto, la parte de datos de un objeto de Objective-C es muy similar a una estructura C. Si usted tiene un puntero a una estructura C, puede utilizar la flecha notación ->
para referirse a una parte específica de la estructura, así:
struct MyStructType {
int i;
BOOL b;
};
struct MyStructType* myStruct;
myStruct->i = 3;
myStruct->b = TRUE; // or YES in Objective-C.
Esta misma sintaxis funciona de la misma manera en Objective-C:
Question* question = [[Question alloc] init];
question->questionStr = @"Is this a long answer?"; // YES
Pero al hacer esto, no hay ninguna llamada método sucediendo detrás de las escenas, a diferencia de la notación de punto. Con la notación de puntos, que está llamando la incubadora (o getter si no hay = después), y estas dos líneas es el mismo:
question.questionStr = @"Chocolate?";
[question setQuestionStr:@"Chocolate?"];
A menudo es una buena idea para evitar la notación flecha en favor de la la notación de puntos, ya que la notación de puntos le permite aplicar un estado válido; por ejemplo, que los punteros de su clase siempre se conservan. Incluso puede prohibir que otros utilicen la notación de flecha al declarar las variables de instancia como @private
; aún pueden usar el getter y el setter para acceder a él, si declara @property
para ello.
Lo que hace @synthesize
Ahora, cuando llegue a su alrededor para la aplicación real de su clase, @synthesize
dice algo así como "asegúrese de que el getter y setter son implementadas para esta propiedad." Hace no diga "implementar ambos para mí", porque el compilador es lo suficientemente educado como para verificar primero su propia implementación y solo completar las piezas que ha perdido. No tiene que usar @synthesize
en absoluto, incluso si usa @property
fuera del wazoo; siempre podría proporcionar sus implementaciones para sus instaladores y buscadores, si le gusta ese tipo de cosas.
Usted probablemente ha notado en la interfaz Question
por encima de que hay una propiedad que es no una variable de instancia (numWrongAnswers
), lo cual está bien porque estás declarando métodos. En el código de ejemplo aquí, se puede ver cómo funciona realmente:
@implementation Question
@synthesize questionStr, numTimesAsked, numCorrectAnswers;
- (void) setNumCorrectAnswers: (int) newCorrectAnswers {
// We assume the # increases, and represents new answers.
int numNew = newCorrectAnswers - numCorrectAnswers;
numTimesAsked += numNew;
numCorrectAnswers = newCorrectAnswers;
}
- (int) numWrongAnswers {
return numTimesAsked - numCorrectAnswers;
}
- (void) setNumWrongAnswers: (int) newWrongAnswers {
int numNew = newWrongAnswers - self.numWrongAnswers;
numTimesAsked += numNew;
}
- (void) addAnswerWithTruthValue: (BOOL) isCorrect {
if (isCorrect) {
self.numCorrectAnswers++;
} else {
self.numWrongAnswers++;
}
}
@end
Una cosa que está pasando aquí es que estamos fingiendo una variable de instancia llamada numWrongAnswers
, lo que sería la información redundante si se guardó en la clase. Como conocemos numWrongAnswers
+ numCorrectAnswers
= numTimesAsked
en todo momento, solo necesitamos almacenar dos de estos tres puntos de datos, y siempre podemos pensar en términos del otro utilizando los dos valores que conocemos. El punto aquí es comprender que una declaración @property
realmente se trata de declarar un método setter y getter, que generalmente corresponde a una variable de instancia real, pero no siempre. La palabra clave @synthesize
de forma predeterminada hace que corresponda a una variable de instancia real, de modo que sea fácil para el compilador completar la implementación por usted.
Razones para tener separados .h
y .m
archivos
Por cierto, todo el punto de métodos que declaran en un solo archivo (el archivo .h
cabecera) y la definición de su ejecución en otro (el archivo .m
o métodos) es ayudar a desacoplar el código. Por ejemplo, si solo actualiza un archivo .m
en su proyecto, no tiene que volver a compilar los otros archivos .m
, ya que su código objeto permanecerá igual, esto ahorra tiempo. Otra ventaja es que puede usar una biblioteca que solo incluye archivos de encabezado y código de objeto precompilado, o incluso bibliotecas dinámicas donde necesita el archivo de encabezado para que el compilador sepa qué métodos existen, pero esos métodos ni siquiera están vinculados en con tu archivo ejecutable. Estas ventajas son difíciles de apreciar la primera vez que se comienza a codificar, pero solo el desglose lógico y la encapsulación de la implementación se vuelven útiles después de un tiempo breve.
¡Espero que sea útil!
Algunas críticas constructivas de una publicación impresionante en general: (1) Las propiedades de NSString realmente deberían marcarse como retener, (2) Creo que no se necesita el bit sobre por qué quieres los archivos .m y .h en este contexto, ya que es mejor seguir con lo que está pasando. Me gustó que hablaste sobre el propósito de IBOutlet e IBAction. –
En realidad, las propiedades de NSString deben marcarse como copia. – bbum
Vaya, eso es lo que quise decir, el código de ejemplo ya lo tiene como retener ...gracias por corregir mi error! –