Por lo general, veo esta pregunta formulada de otra manera, como ¿Debe cada ivar ser una propiedad? (y me gusta la respuesta de bbum a esta Q).
Yo uso propiedades casi exclusivamente en mi código. De vez en cuando, sin embargo, trabajo con un contratista que ha estado desarrollando en iOS durante mucho tiempo y es un programador de juegos tradicional. Escribe un código que declara casi ninguna propiedad y se apoya en ivars. Supongo que hace esto porque 1.) está acostumbrado ya que las propiedades no siempre existieron hasta el Objetivo C 2.0 (Oct '07) y 2.) por la ganancia mínima de rendimiento de no pasar por un getter / setter.
Si bien escribe un código que no se filtra, aún prefiero que use propiedades sobre ivars. Hablamos sobre eso y él más o menos no ve razón para usar propiedades ya que no estábamos usando KVO y tiene experiencia en el cuidado de los problemas de memoria.
Mi pregunta es más ... ¿Por qué querrías usar un período ivar, con o sin experiencia? ¿Existe realmente una gran diferencia de rendimiento que justificar el uso de un ivar?
También como un punto de aclaración, anulo los setters y getters según sea necesario y uso el ivar que se correlaciona con esa propiedad dentro del getter / setter. Sin embargo, fuera de un getter / setter o init, siempre uso la self.myProperty
sintaxis.
Editar 1
Agradezco todas las buenas respuestas. Una que me gustaría abordar que parece incorrecta es que con un ivar se obtiene la encapsulación donde con una propiedad que no. Simplemente defina la propiedad en una continuación de clase. Esto ocultará la propiedad a los extraños. También puede declarar la propiedad de solo lectura en la interfaz y redefinirla como readwrite en la implementación como:
// readonly for outsiders
@property (nonatomic, copy, readonly) NSString * name;
y tener en la clase continuación:
// readwrite within this file
@property (nonatomic, copy) NSString * name;
Para tenerlo completamente "privado" solo declararlo en la continuación de clase
Respuestas:
Encapsulamiento
Si el ivar es privado, las otras partes del programa no pueden acceder a él tan fácilmente. Con una propiedad declarada, las personas inteligentes pueden acceder y mutar con bastante facilidad a través de los accesos.
Actuación
Sí, esto puede hacer la diferencia en algunos casos. Algunos programas tienen restricciones en las que no pueden usar ningún mensaje objc en ciertas partes del programa (piense en tiempo real). En otros casos, es posible que desee acceder directamente a él para mayor velocidad. En otros casos, es porque la mensajería objc actúa como un firewall de optimización. Finalmente, puede reducir sus operaciones de recuento de referencia y minimizar el uso máximo de memoria (si se hace correctamente).
Tipos no triviales
Ejemplo: si tiene un tipo C ++, el acceso directo es el mejor enfoque a veces. Es posible que el tipo no se pueda copiar o que no sea trivial copiarlo.
Multithreading
Muchos de sus ivars son codependientes. Debe garantizar la integridad de sus datos en un contexto multiproceso. Por lo tanto, puede favorecer el acceso directo a múltiples miembros en secciones críticas. Si se queda con los accesores para datos codependientes, sus bloqueos generalmente deben ser reentrantes y, a menudo, terminará haciendo muchas más adquisiciones (significativamente más a veces).
Corrección del programa
Dado que las subclases pueden anular cualquier método, eventualmente puede ver que hay una diferencia semántica entre escribir en la interfaz y administrar su estado de manera adecuada. El acceso directo para la corrección del programa es especialmente común en estados parcialmente construidos: en sus inicializadores y en
dealloc
, es mejor usar el acceso directo. También puede encontrar esto común en las implementaciones de un descriptor de acceso, un constructor de convenienciacopy
,mutableCopy
implementaciones, y archivo / serialización.También es más frecuente a medida que uno se mueve de todo lo que tiene una mentalidad pública de acceso de lectura y escritura a una que oculta bien los detalles / datos de implementación. A veces, debe evitar correctamente los efectos secundarios que una anulación de subclase puede introducir para hacer lo correcto.
Tamaño binario
Declarar todo lo que reescribe de forma predeterminada generalmente da como resultado muchos métodos de acceso que nunca necesita, cuando considera la ejecución de su programa por un momento. Por lo tanto, agregará algo de grasa a su programa y tiempos de carga también.
Minimiza la complejidad
En algunos casos, es completamente innecesario agregar + type + mantener todo ese andamiaje adicional para una variable simple como un bool privado que se escribe en un método y se lee en otro.
Eso no es para nada decir que el uso de propiedades o accesorios es malo: cada uno tiene importantes beneficios y restricciones. Al igual que muchos lenguajes OO y enfoques de diseño, también debe favorecer los accesores con visibilidad adecuada en ObjC. Habrá momentos en los que necesitarás desviarte. Por esa razón, creo que a menudo es mejor restringir los accesos directos a la implementación que declara el ivar (por ejemplo, declararlo
@private
).re Editar 1:
La mayoría de nosotros hemos memorizado cómo llamar dinámicamente a un descriptor de acceso oculto (siempre que sepamos el nombre ...). Mientras tanto, la mayoría de nosotros no hemos memorizado cómo acceder adecuadamente a los ivars que no son visibles (más allá de KVC). La continuación de la clase ayuda , pero introduce vulnerabilidades.
Esta solución es obvia:
Ahora pruébelo solo con un ivar y sin KVC.
fuente
@private
, el compilador debería prohibir el acceso de los miembros fuera de la clase y los métodos de instancia, ¿no es eso lo que ves?Para mí suele ser el rendimiento. Acceder a un ivar de un objeto es tan rápido como acceder a un miembro de estructura en C usando un puntero a la memoria que contiene dicha estructura. De hecho, los objetos Objective-C son básicamente estructuras C ubicadas en memoria asignada dinámicamente. Esto suele ser lo más rápido que puede obtener su código, ni siquiera el código de ensamblaje optimizado a mano puede ser más rápido que eso.
Acceder a un ivar a través de un getter / setting implica una llamada al método Objective-C, que es mucho más lenta (al menos 3-4 veces) que una llamada de función C "normal" e incluso una llamada de función C normal ya sería varias veces más lenta que accediendo a un miembro de estructura. Dependiendo de los atributos de su propiedad, la implementación del setter / getter generada por el compilador puede involucrar otra llamada de función C a las funciones
objc_getProperty
/objc_setProperty
, ya que éstas tendrán queretain
/copy
/autorelease
los objetos según sea necesario y realizar un bloqueo adicional para propiedades atómicas cuando sea necesario. Esto puede volverse muy costoso fácilmente y no estoy hablando de ser un 50% más lento.Intentemos esto:
Salida:
Esto es 4.28 veces más lento y este fue un int primitivo no atómico, más o menos el mejor de los casos ; La mayoría de los otros casos son aún peores (¡pruebe una
NSString *
propiedad atómica !). Entonces, si puede vivir con el hecho de que cada acceso de ivar es 4-5 veces más lento de lo que podría ser, el uso de las propiedades está bien (al menos en lo que respecta al rendimiento), sin embargo, hay muchas situaciones en las que tal caída del rendimiento es completamente inaceptableActualizar 2015-10-20
Algunas personas argumentan que este no es un problema del mundo real, el código anterior es puramente sintético y nunca lo notarás en una aplicación real. Bien entonces, probemos una muestra del mundo real.
El siguiente código define los
Account
objetos a continuación. Una cuenta tiene propiedades que describen el nombre (NSString *
), el género (enum
) y la edad (unsigned
) de su propietario, así como un saldo (int64_t
). Un objeto de cuenta tiene uninit
método y uncompare:
método. Elcompare:
método se define como: órdenes femeninas antes que masculinas, nombres ordenados alfabéticamente, órdenes jóvenes antes viejas, órdenes de saldo de menor a mayor.En realidad, existen dos clases de cuenta
AccountA
yAccountB
. Si observa su implementación, notará que son casi completamente idénticos, con una excepción: elcompare:
método.AccountA
los objetos acceden a sus propias propiedades por método (getter), mientras que losAccountB
objetos acceden a sus propias propiedades por ivar. ¡Esa es realmente la única diferencia! Ambos acceden a las propiedades del otro objeto para compararlo con getter (¡acceder a él por ivar no sería seguro! ¿Qué pasa si el otro objeto es una subclase y ha anulado al getter?). También tenga en cuenta que acceder a sus propias propiedades como ivars no interrumpe la encapsulación (los ivars aún no son públicos).La configuración de prueba es realmente simple: cree cuentas aleatorias de 1 Mio, agréguelas a una matriz y clasifique esa matriz. Eso es. Por supuesto, hay dos matrices, una para
AccountA
objetos y otra paraAccountB
objetos, y ambas matrices están llenas de cuentas idénticas (misma fuente de datos). Calculamos el tiempo que lleva ordenar los arreglos.Aquí está el resultado de varias ejecuciones que hice ayer:
Como puede ver, ordenar la matriz de
AccountB
objetos siempre es significativamente más rápido que ordenar la matriz deAccountA
objetos.Quien afirma que las diferencias de tiempo de ejecución de hasta 1.32 segundos no hacen ninguna diferencia, nunca debería hacer la programación de la interfaz de usuario. Si quiero cambiar el orden de clasificación de una tabla grande, por ejemplo, las diferencias de tiempo como estas hacen una gran diferencia para el usuario (la diferencia entre una interfaz de usuario aceptable y lenta).
También en este caso, el código de muestra es el único trabajo real realizado aquí, pero ¿con qué frecuencia su código es solo un pequeño engranaje de un reloj complicado? Y si cada marcha se ralentiza todo el proceso de esta manera, ¿qué significa eso para la velocidad de todo el reloj al final? Especialmente si un paso de trabajo depende de la salida de otro, lo que significa que todas las ineficiencias se resumirán. La mayoría de las ineficiencias no son un problema en sí mismas, es su suma total la que se convierte en un problema para todo el proceso. Y tal problema no es nada que un generador de perfiles muestre fácilmente porque un generador de perfiles se trata de encontrar puntos críticos, pero ninguna de estas ineficiencias son puntos críticos por sí mismos. El tiempo de CPU se distribuye de manera promedio entre ellos, sin embargo, cada uno de ellos solo tiene una fracción tan pequeña que parece una pérdida total de tiempo optimizarlo. Y es verdad,
E incluso si no piensa en términos de tiempo de CPU, porque cree que perder el tiempo de CPU es totalmente aceptable, después de todo "es gratis", ¿qué pasa con los costos de alojamiento del servidor causados por el consumo de energía? ¿Qué pasa con la duración de la batería de los dispositivos móviles? Si escribiría la misma aplicación móvil dos veces (por ejemplo, un navegador web móvil propio), una vez que todas las clases acceden a sus propias propiedades solo por getters y una vez donde todas las clases acceden a ellas solo por ivars, usar la primera definitivamente definitivamente agotará la batería es mucho más rápida que usar la segunda, a pesar de que son equivalentes funcionales y para el usuario, la segunda probablemente incluso se sentiría un poco más rápida.
Ahora aquí está el código para su
main.m
archivo (el código depende de que ARC esté habilitado y asegúrese de usar la optimización al compilar para ver el efecto completo):fuente
unsigned int
que nunca se retiene / libera, ya sea que use ARC o no. La retención / liberación en sí es costosa, por lo que la diferencia será menor ya que la administración de retención agrega una sobrecarga estática que siempre existe, usando directamente setter / getter o ivar; sin embargo, seguirá ahorrando la sobrecarga de una llamada de método adicional si accede directamente al ivar. No es un gran problema en la mayoría de los casos, a menos que lo haga varios miles de veces por segundo. Apple dice que usa getters / setters de forma predeterminada, a menos que esté en un método init / dealloc o haya detectado un cuello de botella.copy
se NO hacer una copia de su valor cada vez que se acceda al mismo. El captador decopy
propiedad es como el captador de unastrong
/retain
propiedad. Su código es básicamentereturn [[self->value retain] autorelease];
. Solo el setter copia el valor y se verá más o menos así[self->value autorelease]; self->value = [newValue copy];
, mientras que unstrong
/retain
setter se ve así:[self->value autorelease]; self->value = [newValue retain];
La razón más importante es el concepto OOP de ocultar información : si expone todo a través de propiedades y, por lo tanto, permite que los objetos externos echen un vistazo a los elementos internos de otro objeto, utilizará estos internos y complicará el cambio de implementación.
La ganancia de "rendimiento mínimo" puede resumirse rápidamente y luego convertirse en un problema. Lo sé por experiencia; Trabajo en una aplicación que realmente lleva los iDevices a sus límites y, por lo tanto, debemos evitar llamadas a métodos innecesarios (por supuesto, solo cuando sea razonablemente posible). Para ayudar con este objetivo, también estamos evitando la sintaxis de puntos ya que dificulta ver el número de llamadas a métodos a primera vista: por ejemplo, ¿cuántas llamadas a métodos activa la expresión
self.image.size.width
? Por el contrario, puede saber de inmediato con[[self image] size].width
.Además, con el nombre correcto de ivar, KVO es posible sin propiedades (IIRC, no soy un experto en KVO).
fuente
Semántica
@property
puede expresar que los ivars no pueden:nonatomic
ycopy
.@property
no pueden:@protected
: público en subclases, exterior privado.@package
: público en marcos en 64 bits, privado fuera. Igual que@public
en 32 bits. Consulte Control de acceso variable de clase e instancia de 64 bits de Apple .id __strong *_objs
.Actuación
Breve historia: los ivars son más rápidos, pero no es importante para la mayoría de los usos.
nonatomic
las propiedades no usan bloqueos, pero ivar directo es más rápido porque omite la llamada de acceso. Para más detalles, lea el siguiente correo electrónico de lists.apple.com.fuente
Las variables de propiedades frente a instancia son una compensación, al final la elección se reduce a la aplicación.
Encapsulación / Ocultación de información Esto es una buena cosa (TM) desde una perspectiva de diseño, interfaces estrechas y enlaces mínimos es lo que hace que el software sea mantenible y comprensible. Es bastante difícil en Obj-C ocultar cualquier cosa, pero las variables de instancia declaradas en la implementación se acercan lo más posible.
Rendimiento Si bien la "optimización prematura" es una mala cosa (TM), escribir código con un rendimiento deficiente solo porque puede es al menos igual de malo. Es difícil argumentar en contra de que una llamada al método sea más costosa que una carga o una tienda, y en el código computacional intensivo el costo pronto aumenta.
En un lenguaje estático con propiedades, como C #, el compilador a menudo puede optimizar las llamadas a los establecedores / captadores. Sin embargo, Obj-C es dinámico y eliminar esas llamadas es mucho más difícil.
Abstracción Un argumento contra las variables de instancia en Obj-C ha sido tradicionalmente la gestión de la memoria. Con las variables de instancia de MRC se requieren llamadas para retener / liberar / liberación automática para que se extiendan por todo el código, las propiedades (sintetizadas o no) mantienen el código de MRC en un lugar: el principio de abstracción, que es una buena cosa (TM). Sin embargo, con GC o ARC este argumento desaparece, por lo que la abstracción para la administración de memoria ya no es un argumento contra las variables de instancia.
fuente
Las propiedades exponen sus variables a otras clases. Si solo necesita una variable que solo sea relativa a la clase que está creando, use una variable de instancia. Aquí hay un pequeño ejemplo: las clases XML para analizar RSS y el ciclo similar a través de un montón de métodos delegados y demás. Es práctico tener una instancia de NSMutableString para almacenar el resultado de cada paso diferente del análisis. No hay ninguna razón por la cual una clase externa necesite acceder o manipular esa cadena. Entonces, simplemente lo declara en el encabezado o en privado y accede a él en toda la clase. Establecer una propiedad para ella solo puede ser útil para asegurarse de que no haya problemas de memoria, usando self.mutableString para invocar al captador / definidor.
fuente
La compatibilidad con versiones anteriores fue un factor para mí. No pude usar ninguna característica de Objective-C 2.0 porque estaba desarrollando software y controladores de impresora que tenían que funcionar en Mac OS X 10.3 como parte de un requisito. Sé que su pregunta parecía dirigida a iOS, pero pensé que aún compartiría mis razones para no usar propiedades.
fuente