2011-04-27 22 views
8

Después de revisar el desarrollador de iPhone para principiantes y leer el código de muestra en línea, me he dado cuenta de que la mayoría de los programadores de Objective C sintetizan casi todas las variables de instancia. Algunas variables son convenientes para hacer snythesize, pero la mayoría no debería hacerlo si se respeta el principio de encapsulación orientado a objetos. Lo peor son las propiedades sintetizadas marcadas como privadas. Un programador de C++ que intente usar el código de otra persona leerá los campos públicos y los métodos en el archivo de encabezado. Se saltearán las variables privadas. Este programador de C++ no sabrá que pretendía que las propiedades privadas se usen de alguna manera significativa.¿Es una propiedad sintetizada privada un oxímoron?

Echa un vistazo a esta plantilla de muestra en lazy table image loading proporcionado por Apple:

Cabecera

@interface ParseOperation : NSOperation <NSXMLParserDelegate> 

{ 
@private 
    id <ParseOperationDelegate> delegate; 
    NSData   *dataToParse; 
    NSMutableArray *workingArray; 
    AppRecord  *workingEntry; 
    NSMutableString *workingPropertyString; 
    NSArray   *elementsToParse; 
    BOOL   storingCharacterData; 
} 

Fuente

@interface ParseOperation() 
@property (nonatomic, assign) id <ParseOperationDelegate> delegate; 
@property (nonatomic, retain) NSData *dataToParse; 
@property (nonatomic, retain) NSMutableArray *workingArray; 
@property (nonatomic, retain) AppRecord *workingEntry; 
@property (nonatomic, retain) NSMutableString *workingPropertyString; 
@property (nonatomic, retain) NSArray *elementsToParse; 
@property (nonatomic, assign) BOOL storingCharacterData; 
@end 

@implementation ParseOperation 
@synthesize delegate, dataToParse, workingArray, workingEntry, workingPropertyString, elementsToParse, storingCharacterData; 

Ahora sé que esto no es C++ y nos no debe suponer que todas las prácticas de C++ deben cumplirse en el Objetivo C. B El Objetivo C debería tener buenas razones para alejarse de las prácticas de programación general.

  1. Por qué son todos los Ivars privadas sintetizados? Cuando observa el proyecto como un todo, solo NSMutableArray *workingArray es utilizado por clases externas. Entonces ninguno de los otros ivars debería tener setters y getters.
  2. ¿Por qué se sintetizan ivars muy sensibles? Por ejemplo, ahora que id delegate tiene un setter, el usuario de este objeto puede cambiar el delegado en medio del análisis XML, algo que no tiene sentido. Además, NSData *dataToParse son datos XML brutos recuperados de la red. Ahora que tiene un setter, el usuario de este objeto puede corromper los datos.
  3. ¿Cuál es el punto de marcar todo private en el encabezado? Como todos los ivars se sintetizan para tener getters/setters, son efectivamente públicos. Puede configurarlos para cualquier cosa que desee y puede obtener su valor siempre que lo desee.
+0

Hubo algunas preguntas similares a esta ayer: [Public read, private retain] (http://stackoverflow.com/questions/5788458/public-read-private-retain-property); [¿Una propiedad @ privada crea un ivar privado]? (Http://stackoverflow.com/questions/5784875/does-a-private-property-create-an-private-instance-variable) No estoy seguro de si de ellos responde tus preguntas sin embargo. –

+0

También le puede interesar esto: [iOS: ¿debe ser cada iVar realmente propiedad?] (Http://stackoverflow.com/questions/5031230/ios-must-every-ivar-really-be-property) –

Respuesta

10

Sigo el modismo modelado por este ejemplo en muchas de mis clases, así que puedo tratar de explicar mi propia justificación para esta práctica.

Las propiedades en este ejemplo se declaran en una extensión de clase en el archivo .m. Esto los hace efectivamente privados. Cualquier intento de acceder a estas propiedades desde otra clase provocará un error de "Propiedad no encontrada" en la compilación.

Para desarrolladores procedentes de otros idiomas, puede parecer extraño sintetizar getters y setters para variables de instancia privadas. De hecho, solo hay una razón por la que hago esto. Cuando se usan de forma consistente, las propiedades sintetizadas pueden simplificar la administración de la memoria y ayudar a evitar errores descuidados que pueden conducir a errores. Aquí hay un par de ejemplos:

Considera:

self.workingPropertyString = [NSMutableString string]; 

frente a esto:

workingPropertyString = [[NSMutableString string] retain]; 

Muchos desarrolladores se aseguran que estas dos tareas son funcionalmente equivalentes, pero hay una diferencia importante. La segunda asignación pierde memoria si workingPropertyString ya estaba apuntando a un objeto retenido. Para escribir código funcionalmente equivalente a la incubadora sintetizado, que tendría que hacer algo como esto:

NSMutableString *newString = [NSMutableString string]; 
if (workingPropertyString != newString) { 
    [workingPropertyString release]; 
    workingPropertyString = [newString retain]; 
} 

Este código evita fugas de cualquier objeto existente que la variable de instancia puede estar apuntando a, y maneja con seguridad la posibilidad de que puedes volver a asignar el mismo objeto a la variable de instancia. El setter sintetizado hace todo esto por ti.

Por supuesto, podemos ver que (workingPropertyString != newString) siempre será cierto en este caso, por lo que podríamos simplificar esta tarea en particular. De hecho, en la mayoría de los casos, probablemente pueda salirse con la suya con una simple asignación directa a una variable de instancia, pero, por supuesto, son los casos excepcionales los que tienden a crear la mayoría de los errores. Prefiero ir a lo seguro y establecer todas mis variables de instancia de objeto a través de setters sintetizados.Todos mis tareas objeto de instancia son sencillas de una sola línea que se ven así:

self.foo = [Foo fooWithTitle:@"The Foo"]; 

O esto:

self.foo = [[[Foo alloc] initWithTitle:@"The Foo"] autorelease]; 

Esta simplicidad y coherencia da a mi débil cerebro menos cosas en que pensar. Como resultado, casi nunca tengo errores relacionados con la gestión de la memoria. (Soy consciente de que el autorelease lenguaje podría consumir teóricamente excesivo de memoria en un bucle estrecho, pero todavía tengo que encontrar esa cuestión en la práctica. Si alguna vez hago, es un caso simple de optimizar.)

Otra cosa me gusta de esta práctica es que mis métodos dealloc todos ven así:

- (void)dealloc { 
    self.delegate = nil; 
    self.dataToParse = nil; 
    self.workingArray = nil; 
    self.workingEntry = nil; 
    self.workingPropertyString = nil; 
    self.elementsToParse = nil; 
    [super dealloc]; 
} 

EDIT: Daniel Dickison señaló algunas riesgos a la utilización de métodos de acceso en dealloc que no había considerado. Vea los comentarios .

donde cada propiedad del objeto simplemente se establece en nil. Esto libera simultáneamente cada propiedad retenida mientras se establece en cero para evitar ciertos bloqueos debido a EXC_BAD_ACCESS.

Tenga en cuenta que he configurado self.delegate = nil; aunque esa propiedad se haya declarado como (nonatomic, assign). Esta asignación no era estrictamente necesaria. De hecho, podría eliminar por completo las propiedades de mis objetos (nonatomic, assign), pero de nuevo he descubierto que aplicar esta expresión de manera consistente en todas mis variables de instancia le da menos importancia a mi cerebro, y reduce aún más las posibilidades de que cree un error a través de un error descuidado. Si es necesario, simplemente puedo voltear una propiedad desde (nonatomic, assign) hasta (nonatomic, retain) sin tener que tocar ningún código de administración de memoria. Me gusta eso.

También se podría utilizar la consistencia como argumento para sintetizar propiedades para variables escalares privadas, como su ejemplo lo ha hecho en el caso de BOOL storingCharacterData;. Esta práctica garantiza que cada asignación de variable de instancia se verá como self.foo = bar;. Normalmente no me molesto en crear propiedades escalares privadas, pero puedo ver alguna justificación para esta práctica.

+5

Buena respuesta, pero una única elección: establecer propiedades en nil en lugar de llamar a 'release' directamente en el método' dealloc' no se recomienda (hay algunos documentos de Apple que dicen eso, aunque no puedo encontrarlo en este momento). La razón es que el colocador puede activar una notificación de KVO que podría llevar a que los observadores intenten acceder a una instancia parcialmente desasignada. –

+0

Ewww. Buen punto. No he hecho mucho con KVO, así que no he encontrado ese caso, pero tiene sentido. Tendré que buscar esos documentos de Apple y posiblemente reconsiderar cómo escribo mis deallocs. Gracias por abrir mis ojos a este riesgo. – cduhn

+0

La documentación de Apple parece estar en un estado inconsistente en este punto. La documentación del Lenguaje de programación Objective C dice que si está utilizando variables de instancia sintetizadas con el tiempo de ejecución moderno, "debe invocar el método de acceso" ya que no puede acceder directamente a la variable de instancia. Sin embargo, parece que en las últimas herramientas puede acceder directamente al ivar, así que supongo que el modismo que usaré en dealloc es '[versión ivar], ivar = nil;'. Hay una buena discusión sobre esto aquí: http://stackoverflow.com/questions/1283419/valid-use-of-accessors-in-init-and-dealloc-methods – cduhn

0

¿Por qué todas las Ivars privadas sintetizados? Cuando observa el proyecto como un todo, solo NSMutableArray * workingArray se usa por clases externas. Por lo tanto, ninguno de los otros ivars debería tener setters y getters.

No real need; si va a acceder a todos los ivars directamente de todos modos, no hay necesidad de @synthesize.

¿Por qué son ivars muy sensibles sintetizados? Por un lado, ahora que Identificación del delegado tiene un setter, el usuario de este objeto puede cambiar el delegado en medio del análisis de XML, algo que no tiene sentido. Además, NSData * dataToParse es datos XML sin formato recuperados de la red. Ahora que tiene un setter , el usuario de este objeto puede corromper los datos.

Ninguno de los setter/getters está declarado públicamente. Si un cliente de la clase quería corromper las cosas al cambiar el delegado en el medio, tendrían que romper la encapsulación para hacerlo.

Por lo tanto, en última instancia, no es un problema.

¿Cuál es el punto de marcar todo privado en el encabezado? Como todos los ivars se sintetizan para tener getters/setters, son efectivamente public. Puede configurarlos para cualquier cosa que desee y puede obtener su valor cuando lo desee.

Tenga en cuenta que no hay necesidad de declarar los ivars en ese ejemplo; el compilador los sintetizará automáticamente en función de la declaración @property.

Tradicionalmente, @private estaba protegido contra alguien que manipulaba el ivar directamente de una instancia de la clase.

Tenga en cuenta que anInstance-> ivar o self-> ivar casi nunca se utiliza (y, cuando se utiliza, es casi siempre por el motivo equivocado). Hay usos para eso, pero es raro.

+0

Parece que El objetivo C debería * hacer cumplir * la encapsulación en lugar de * sugerir * la encapsulación por la forma en que se presenta el código. Después de leer todo el proyecto, puedo ver que el programador de Apple * me sugiere * no manipular los ivars privados, pero no debería tener que hacer eso. Debería poder leer el archivo de cabecera para entender cómo usar esta clase y hacer que el compilador * aplique * la encapsulación. – JoJo

+0

¿Qué pasa con 'anInstance-> publicIVar' y' self-> trulyPrivateIvar' si eso impone la encapsulación? – JoJo

+0

'anInstance-> publicIvar' no es un patrón que se utiliza; omite cualquier método de acceso y, por lo tanto, significa que las subclases no pueden anular el comportamiento en el acceso o conjunto (también significa que KVO no funcionará). 'self-> ivary' es ruido; innecesario. – bbum

Cuestiones relacionadas