¿Por qué usarías un ivar?

153

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.myPropertysintaxis.


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

Sam
fuente
2
voto a favor para una pregunta interesante, bien formulada y también una en la que me gustaría escuchar el caso de los ivars, ya que parece que me han enseñado a hacerlo a la manera de Sam.
Damo
2
Tenga en cuenta que el Conteo automático de referencia (ARC) aplica los mismos beneficios de administración de memoria a los ivars que las propiedades, por lo que en el código ARC la diferencia se trata realmente de la encapsulación.
benzado
1
Su pregunta y especialmente la parte Editar 1 en realidad es mucho más informativa que la respuesta elegida.
user523234
1
Para Edit1: creo que es posible leer Y ESCRIBIR cada propiedad, incluso cuando solo una declaración de lectura en .h, con Key-Value-Coding, por ejemplo: [object setValue: [NSNumber numberWithInt: 20] forKey: @ "propertyname "];
Binario
1
@Sam a su Edición 1: si usa una propiedad privada y usa la extensión / continuación de clase en el archivo .m, no es visible para las subclases. Debe volver a escribir el código o usar otro .h con la extensión de clase. Más fácil con @ protegido / predeterminado.
Binarian

Respuestas:

100

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 conveniencia copy,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:

if ([obj respondsToSelector:(@selector(setName:)])
  [(id)obj setName:@"Al Paca"];

Ahora pruébelo solo con un ivar y sin KVC.

justin
fuente
@ Sam gracias, y buena pregunta! re complejidad: ciertamente va en ambos sentidos. re encapsulación - actualizado
justin
@bbum RE: Ejemplo engañoso Aunque estoy de acuerdo con usted en que es la solución incorrecta, no puedo imaginar que haya muchos desarrolladores de objc experimentados que crean que simplemente no sucede; Lo he visto en otros programas y las tiendas de aplicaciones han ido tan lejos como para prohibir el uso de API privadas de Apple.
Justin
1
¿No puedes acceder a un ivar privado con object-> foo? No es tan difícil de recordar.
Nick Lockwood
1
Quise decir que puedes acceder a él usando una deferencia de puntero desde el objeto usando la sintaxis C ->. Las clases de Objective-C son básicamente estructuras debajo del capó, y dado un puntero a una estructura, la sintaxis de C para acceder a los miembros es ->, que también funciona para ivars en clases de objetivo C
Nick Lockwood
1
@NickLockwood si el ivar es @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?
justin
76

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 que retain/ copy/ autoreleaselos 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:

CFAbsoluteTime cft;
unsigned const kRuns = 1000 * 1000 * 1000;

cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
    testIVar = i;
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"1: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);

cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
    [self setTestIVar:i];
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"2: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);

Salida:

1: 23.0 picoseconds/run
2: 98.4 picoseconds/run

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 inaceptable

Actualizar 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 Accountobjetos 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 un initmétodo y un compare:método. El compare: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 AccountAy AccountB. Si observa su implementación, notará que son casi completamente idénticos, con una excepción: el compare:método. AccountAlos objetos acceden a sus propias propiedades por método (getter), mientras que los AccountBobjetos 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 AccountAobjetos y otra para AccountBobjetos, 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:

runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076

Como puede ver, ordenar la matriz de AccountBobjetos siempre es significativamente más rápido que ordenar la matriz de AccountAobjetos.

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.marchivo (el código depende de que ARC esté habilitado y asegúrese de usar la optimización al compilar para ver el efecto completo):

#import <Foundation/Foundation.h>

typedef NS_ENUM(int, Gender) {
    GenderMale,
    GenderFemale
};


@interface AccountA : NSObject
    @property (nonatomic) unsigned age;
    @property (nonatomic) Gender gender;
    @property (nonatomic) int64_t balance;
    @property (nonatomic,nonnull,copy) NSString * name;

    - (NSComparisonResult)compare:(nonnull AccountA *const)account;

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance;
@end


@interface AccountB : NSObject
    @property (nonatomic) unsigned age;
    @property (nonatomic) Gender gender;
    @property (nonatomic) int64_t balance;
    @property (nonatomic,nonnull,copy) NSString * name;

    - (NSComparisonResult)compare:(nonnull AccountB *const)account;

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance;
@end


static
NSMutableArray * allAcocuntsA;

static
NSMutableArray * allAccountsB;

static
int64_t getRandom ( const uint64_t min, const uint64_t max ) {
    assert(min <= max);
    uint64_t rnd = arc4random(); // arc4random() returns a 32 bit value only
    rnd = (rnd << 32) | arc4random();
    rnd = rnd % ((max + 1) - min); // Trim it to range
    return (rnd + min); // Lift it up to min value
}

static
void createAccounts ( const NSUInteger ammount ) {
    NSArray *const maleNames = @[
        @"Noah", @"Liam", @"Mason", @"Jacob", @"William",
        @"Ethan", @"Michael", @"Alexander", @"James", @"Daniel"
    ];
    NSArray *const femaleNames = @[
        @"Emma", @"Olivia", @"Sophia", @"Isabella", @"Ava",
        @"Mia", @"Emily", @"Abigail", @"Madison", @"Charlotte"
    ];
    const NSUInteger nameCount = maleNames.count;
    assert(maleNames.count == femaleNames.count); // Better be safe than sorry

    allAcocuntsA = [NSMutableArray arrayWithCapacity:ammount];
    allAccountsB = [NSMutableArray arrayWithCapacity:ammount];

    for (uint64_t i = 0; i < ammount; i++) {
        const Gender g = (getRandom(0, 1) == 0 ? GenderMale : GenderFemale);
        const unsigned age = (unsigned)getRandom(18, 120);
        const int64_t balance = (int64_t)getRandom(0, 200000000) - 100000000;

        NSArray *const nameArray = (g == GenderMale ? maleNames : femaleNames);
        const NSUInteger nameIndex = (NSUInteger)getRandom(0, nameCount - 1);
        NSString *const name = nameArray[nameIndex];

        AccountA *const accountA = [[AccountA alloc]
            initWithName:name age:age gender:g balance:balance
        ];
        AccountB *const accountB = [[AccountB alloc]
            initWithName:name age:age gender:g balance:balance
        ];

        [allAcocuntsA addObject:accountA];
        [allAccountsB addObject:accountB];
    }
}


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        @autoreleasepool {
            NSUInteger ammount = 1000000; // 1 Million;
            if (argc > 1) {
                unsigned long long temp = 0;
                if (1 == sscanf(argv[1], "%llu", &temp)) {
                    // NSUIntegerMax may just be UINT32_MAX!
                    ammount = (NSUInteger)MIN(temp, NSUIntegerMax);
                }
            }
            createAccounts(ammount);
        }

        // Sort A and take time
        const CFAbsoluteTime startTime1 = CFAbsoluteTimeGetCurrent();
        @autoreleasepool {
            [allAcocuntsA sortedArrayUsingSelector:@selector(compare:)];
        }
        const CFAbsoluteTime runTime1 = CFAbsoluteTimeGetCurrent() - startTime1;

        // Sort B and take time
        const CFAbsoluteTime startTime2 = CFAbsoluteTimeGetCurrent();
        @autoreleasepool {
            [allAccountsB sortedArrayUsingSelector:@selector(compare:)];
        }
        const CFAbsoluteTime runTime2 = CFAbsoluteTimeGetCurrent() - startTime2;

        NSLog(@"runTime 1: %f", runTime1);
        NSLog(@"runTime 2: %f", runTime2);
    }
    return 0;
}



@implementation AccountA
    - (NSComparisonResult)compare:(nonnull AccountA *const)account {
        // Sort by gender first! Females prior to males.
        if (self.gender != account.gender) {
            if (self.gender == GenderFemale) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Otherwise sort by name
        if (![self.name isEqualToString:account.name]) {
            return [self.name compare:account.name];
        }

        // Otherwise sort by age, young to old
        if (self.age != account.age) {
            if (self.age < account.age) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Last ressort, sort by balance, low to high
        if (self.balance != account.balance) {
            if (self.balance < account.balance) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // If we get here, the are really equal!
        return NSOrderedSame;
    }

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance
    {
        self = [super init];
        assert(self); // We promissed to never return nil!

        _age = age;
        _gender = gender;
        _balance = balance;
        _name = [name copy];

        return self;
    }
@end


@implementation AccountB
    - (NSComparisonResult)compare:(nonnull AccountA *const)account {
        // Sort by gender first! Females prior to males.
        if (_gender != account.gender) {
            if (_gender == GenderFemale) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Otherwise sort by name
        if (![_name isEqualToString:account.name]) {
            return [_name compare:account.name];
        }

        // Otherwise sort by age, young to old
        if (_age != account.age) {
            if (_age < account.age) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // Last ressort, sort by balance, low to high
        if (_balance != account.balance) {
            if (_balance < account.balance) return NSOrderedAscending;
            return NSOrderedDescending;
        }

        // If we get here, the are really equal!
        return NSOrderedSame;
    }

    - (nonnull instancetype)initWithName:(nonnull NSString *const)name
        age:(const unsigned)age gender:(const Gender)gender
        balance:(const int64_t)balance
    {
        self = [super init];
        assert(self); // We promissed to never return nil!

        _age = age;
        _gender = gender;
        _balance = balance;
        _name = [name copy];

        return self;
    }
@end
Mecki
fuente
3
Explicación extremadamente informativa y realista. Voto a favor para la muestra de código
Philip007
1
Uno de los calificadores clave que veo en su publicación es "... de las rutas de código críticas". El punto es que use lo que hace que el código sea más fácil de leer / escribir y luego optimice lo que considera que son las rutas críticas. Esto agregará la complejidad donde sea necesario.
Sandy Chapman
1
@ViktorLexington En mi código estaba configurando uno unsigned intque 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.
Mecki
1
@Fogmeister Agregó un ejemplo de código que muestra la facilidad con que esto puede hacer una gran diferencia en un ejemplo muy simple del mundo real. Y este ejemplo no tiene nada que ver con una supercomputadora que hace billones de cálculos, se trata más bien de ordenar una tabla de datos realmente simple (un caso bastante común entre millones de aplicaciones).
Mecki
2
@malhal una propiedad marcada como copyse NO hacer una copia de su valor cada vez que se acceda al mismo. El captador de copypropiedad es como el captador de una strong/ retainpropiedad. Su código es básicamente return [[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 un strong/ retainsetter se ve así:[self->value autorelease]; self->value = [newValue retain];
Mecki
9

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).

Polvo oscuro
fuente
3
+1 Buena respuesta sobre ganancia de "rendimiento mínimo" sumando y queriendo ver todas las llamadas a métodos explícitamente. El uso de la sintaxis de puntos con propiedades definitivamente oculta una gran cantidad de trabajo que se realiza en getters / setters personalizados (especialmente si ese getter devuelve una copia de algo cada vez que se llama).
Sam
1
KVO no funciona para mí sin usar un setter. ¡Cambiar el ivar directamente no llama al observador que el valor ha cambiado!
Binario
2
KVC puede acceder a ivars. KVO no puede detectar cambios en los ivars (y en su lugar depende de los accesores para ser llamados).
Nikolai Ruhe
9

Semántica

  • Lo que @propertypuede expresar que los ivars no pueden: nonatomicy copy.
  • Lo que los ivars pueden expresar que @propertyno pueden:
    • @protected: público en subclases, exterior privado.
    • @package: público en marcos en 64 bits, privado fuera. Igual que @publicen 32 bits. Consulte Control de acceso variable de clase e instancia de 64 bits de Apple .
    • Calificadores Por ejemplo, las matrices de fuertes referencias a objetos: 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. nonatomiclas 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.

Subject: Re: when do you use properties vs. ivars?
From: John McCall <email@hidden>
Date: Sun, 17 Mar 2013 15:10:46 -0700

Las propiedades afectan el rendimiento de muchas maneras:

  1. Como ya se discutió, enviar un mensaje para hacer una carga / almacenar es más lento que simplemente hacer la carga / almacenar en línea .

  2. Enviar un mensaje para hacer una carga / almacenar también es un poco más de código que debe mantenerse en i-cache: incluso si el getter / setter agregó cero instrucciones adicionales más allá de solo la carga / store, habría una mitad sólida -docenas de instrucciones adicionales en la persona que llama para configurar el envío del mensaje y manejar el resultado.

  3. Enviar un mensaje obliga a mantener una entrada para ese selector en la caché de métodos , y esa memoria generalmente se queda en d-cache. Esto aumenta el tiempo de inicio, aumenta el uso de memoria estática de su aplicación y hace que los cambios de contexto sean más dolorosos. Dado que la memoria caché del método es específica de la clase dinámica de un objeto, este problema aumenta cuanto más use KVO en él.

  4. El envío de un mensaje obliga a que todos los valores de la función se derramen a la pila (o se mantengan en registros de guardado de llamadas, lo que solo significa derramar en un momento diferente).

  5. Enviar un mensaje puede tener efectos secundarios arbitrarios y, por lo tanto,

    • obliga al compilador a restablecer todos sus supuestos sobre la memoria no local
    • no se puede izar, hundir, reordenar, fusionar o eliminar.

  6. En ARC, el resultado del envío de un mensaje siempre será retenido , ya sea por la persona que llama o la persona que llama, incluso para devoluciones +0: incluso si el método no retiene / libera automáticamente su resultado, la persona que llama no lo sabe y tiene para intentar tomar medidas para evitar que el resultado se vuelva a publicar automáticamente. Esto nunca puede eliminarse porque los envíos de mensajes no son estáticamente analizables.

  7. En ARC, debido a que un método setter generalmente toma su argumento en +0, no hay forma de "transferir" una retención de ese objeto (que, como se discutió anteriormente, ARC generalmente tiene) en el ivar, por lo que el valor generalmente tiene que obtener retener / liberado dos veces .

Nada de esto significa que siempre son malos, por supuesto, hay muchas buenas razones para usar propiedades. Solo tenga en cuenta que, como muchas otras características del lenguaje, no son gratuitas.


Juan.

Jano
fuente
6

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.

CRD
fuente
5

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.

Justin
fuente
5

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.

dreamlax
fuente