Perché dovresti usare un ivar?


153

Di solito vedo questa domanda posta dall'altra parte, ad esempio ogni Ivar deve essere una proprietà? (e mi piace la risposta di bbum a questa Q).

Uso le proprietà quasi esclusivamente nel mio codice. Ogni tanto, tuttavia, lavoro con un appaltatore che ha sviluppato per lungo tempo su iOS ed è un programmatore di giochi tradizionale. Scrive codice che non dichiara quasi nessuna proprietà e si appoggia su ivar. Suppongo che lo faccia perché 1.) ci è abituato poiché le proprietà non sono sempre esistite fino all'Obiettivo C 2.0 (ottobre '07) e 2.) per il minimo guadagno prestazionale di non passare attraverso un getter / setter.

Mentre scrive codice che non perde, preferirei comunque che usasse le proprietà sugli ivar. Ne abbiamo parlato e più o meno vede non c'è motivo di usare le proprietà poiché non stavamo usando KVO ed è esperto nel prendersi cura dei problemi di memoria.

La mia domanda è più ... Perché mai dovresti voler usare un periodo di Ivar - vissuto o no. C'è davvero una così grande differenza di prestazioni che l'uso di un ivar sarebbe giustificato?

Anche come punto di chiarimento, sovrascrivo setter e getter secondo necessità e uso l'ivar che si correla con quella proprietà all'interno del getter / setter. Tuttavia, al di fuori di un getter / setter o init, utilizzo sempre la self.myPropertysintassi.


Modifica 1

Apprezzo tutte le buone risposte. Uno a cui vorrei rivolgermi che sembra errato è che con un ivar ottieni l'incapsulamento dove con una proprietà non lo fai. Basta definire la proprietà in una continuazione di classe. Questo nasconderà la proprietà agli estranei. Puoi anche dichiarare la proprietà in sola lettura nell'interfaccia e ridefinirla come readwrite nell'implementazione come:

// readonly for outsiders
@property (nonatomic, copy, readonly) NSString * name;

e nella continuazione della classe:

// readwrite within this file
@property (nonatomic, copy) NSString * name;

Per averlo completamente "privato" dichiararlo solo nella continuazione della classe.


2
voto per una domanda interessante - ben messo e anche uno che mi piacerebbe sentire il caso di Ivars in quanto sembra che mi sia stato insegnato a farlo alla maniera di Sam.
Damo,

2
Si noti che il conteggio di riferimento automatico (ARC) applica gli stessi vantaggi di gestione della memoria agli ivar delle proprietà, quindi nel codice ARC la differenza riguarda davvero l'incapsulamento.
benzado,

1
La tua domanda e soprattutto la parte Modifica 1 in realtà molto più istruttiva della risposta scelta.
user523234,

1
Per Edit1: Penso che sia possibile leggere E SCRIVERE ogni proprietà, anche quando solo una dichiarazione di sola lettura in .h, con Key-Value-Coding, ad esempio: [object setValue: [NSNumber numberWithInt: 20] forKey: @ "propertyname "];
Binarian,

1
@Sam alla tua Modifica 1: Se usi una proprietà privata e usi l'estensione / continuazione della classe nel file .m, non è visibile per le sottoclassi. Devi scrivere di nuovo il codice o utilizzare un altro .h con l'estensione di classe. Più facile con @ protetto / predefinito.
Binarian

Risposte:


100

incapsulamento

Se l'ivar è privato, le altre parti del programma non possono raggiungerlo facilmente. Con una proprietà dichiarata, le persone intelligenti possono accedere e mutare abbastanza facilmente tramite gli accessori.

Prestazione

Sì, questo può fare la differenza in alcuni casi. Alcuni programmi hanno vincoli in cui non possono utilizzare alcun messaggio objc in determinate parti del programma (pensa in tempo reale). In altri casi, ti consigliamo di accedervi direttamente per la velocità. In altri casi, è perché objc messaging funge da firewall di ottimizzazione. Infine, può ridurre le operazioni di conteggio dei riferimenti e ridurre al minimo l'utilizzo di memoria di picco (se eseguito correttamente).

Tipi non banali

Esempio: se si dispone di un tipo C ++, l'accesso diretto è talvolta l'approccio migliore. Il tipo potrebbe non essere copiabile o potrebbe non essere banale da copiare.

multithreading

Molti dei tuoi avari sono codipendenti. È necessario garantire l'integrità dei dati in un contesto multithread. Pertanto, potresti favorire l'accesso diretto a più membri in sezioni critiche. Se rimani con gli accessori per dati codipendenti, i tuoi lucchetti in genere devono rientrare e spesso finirai per fare molte più acquisizioni (molto più a volte).

Correttezza del programma

Poiché le sottoclassi possono ignorare qualsiasi metodo, alla fine potresti notare una differenza semantica tra la scrittura nell'interfaccia e la gestione appropriata del tuo stato. L'accesso diretto per la correttezza del programma è particolarmente comune negli stati parzialmente costruiti: negli inizializzatori e in dealloc, è meglio usare l'accesso diretto. È inoltre possibile trovare questo comune nelle implementazioni di una funzione di accesso, un costruttore convenienza, copy, mutableCopyimplementazioni, e l'archiviazione / serializzazione.

È anche più frequente quando ci si sposta da tutto ciò che ha una mentalità di accesso in lettura / scrittura pubblica a quella che nasconde bene i suoi dettagli / dati di implementazione. A volte è necessario aggirare correttamente gli effetti collaterali che una sostituzione della sottoclasse può introdurre per fare la cosa giusta.

Dimensione binaria

Dichiarare tutto in lettura per impostazione predefinita di solito comporta molti metodi di accesso che non sono mai necessari, se si considera l'esecuzione del programma per un momento. Quindi aggiungerà un po 'di grasso al tuo programma e caricherà anche i tempi.

Riduce al minimo la complessità

In alcuni casi, è assolutamente inutile aggiungere + tipo + mantenere tutto quel ponteggio aggiuntivo per una semplice variabile come un bool privato scritto in un metodo e letto in un altro.


Ciò non significa affatto che l'uso di proprietà o accessori sia negativo - ognuno ha importanti vantaggi e restrizioni. Come molti linguaggi OO e approcci alla progettazione, dovresti anche favorire gli accessori con un'adeguata visibilità in ObjC. Ci saranno momenti in cui devi deviare. Per questo motivo, penso che spesso sia meglio limitare gli accessi diretti all'implementazione che dichiara l'ivar (es. Dichiararlo @private).


re Modifica 1:

Molti di noi hanno memorizzato come chiamare dinamicamente un accessor nascosto (purché conosciamo il nome ...). Nel frattempo, la maggior parte di noi non ha memorizzato come accedere correttamente agli ivar che non sono visibili (oltre KVC). La continuazione della classe aiuta , ma introduce vulnerabilità.

Questa soluzione alternativa è ovvia:

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

Ora provalo solo con un ivar e senza KVC.


@Sam grazie, e bella domanda! ri complessità: va certamente in entrambe le direzioni. ri-incapsulamento - aggiornato
appena in

@bbum RE: Esempio specioso Anche se concordo con te sul fatto che si tratti della soluzione sbagliata, non riesco a immaginare che molti sviluppatori esperti possano credere che ciò non accada; L'ho visto nei programmi di altri e gli App Store sono arrivati ​​al punto di vietare l'uso di API Apple private.
justin

1
Non riesci ad accedere a un ivar privato con object-> foo? Non è così difficile da ricordare.
Nick Lockwood,

1
Volevo dire che puoi accedervi usando una deferenza del puntatore dall'oggetto usando la sintassi C ->. Le classi Objective-C sono fondamentalmente solo strutture sotto il cofano, e dato un puntatore a una struttura, la sintassi C per accedere ai membri è ->, che funziona anche per gli ivar nelle classi C oggettive.
Nick Lockwood,

1
@NickLockwood se l'ivar lo è @private, il compilatore dovrebbe vietare al membro l'accesso al di fuori dei metodi di classe e istanza - non è quello che vedi?
appena il

76

Per me è di solito una performance. Accedere a un ivar di un oggetto è veloce quanto accedere a un membro di una struttura in C usando un puntatore alla memoria contenente una tale struttura. In effetti, gli oggetti Objective-C sono sostanzialmente strutture C posizionate nella memoria allocata dinamicamente. Di solito è più veloce del tuo codice, nemmeno il codice assembly ottimizzato a mano può essere più veloce di così.

L'accesso a un ivar tramite un getter / impostazione comporta una chiamata al metodo Objective-C, che è molto più lenta (almeno 3-4 volte) di una chiamata di funzione C "normale" e anche una normale chiamata di funzione C sarebbe già più volte più lenta di accesso a un membro struct. A seconda degli attributi della propria proprietà, l'implementazione setter / getter generata dal compilatore può comportare un'altra chiamata di funzione C alle funzioni objc_getProperty/ objc_setProperty, poiché queste dovranno retain/ copy/ autoreleasegli oggetti in base alle necessità e, se necessario, eseguire ulteriori spin-lock per le proprietà atomiche. Questo può facilmente diventare molto costoso e non sto parlando di essere il 50% più lento.

Proviamo questo:

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

Produzione:

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

Questo è 4,28 volte più lento e questo era un int primitivo non atomico, praticamente il caso migliore ; la maggior parte degli altri casi è anche peggiore (prova una NSString *proprietà atomica !). Quindi, se riesci a convivere con il fatto che ogni accesso agli ivar è 4-5 volte più lento di quanto potrebbe essere, l'uso delle proprietà va bene (almeno quando si tratta di prestazioni), tuttavia, ci sono molte situazioni in cui tale calo delle prestazioni è completamente inaccettabile.

Aggiornamento 20-10-2015

Alcune persone sostengono che questo non è un problema del mondo reale, il codice sopra è puramente sintetico e non lo noterai mai in una vera applicazione. Va bene allora, proviamo un campione del mondo reale.

Il seguente codice definisce Accountoggetti. Un account ha proprietà che descrivono name ( NSString *), gender ( enum) ed age ( unsigned) del suo proprietario, nonché un saldo ( int64_t). Un oggetto account ha un initmetodo e un compare:metodo. Il compare:metodo è definito come: ordini femminili prima del maschio, nomi in ordine alfabetico, ordini giovani prima dei vecchi, ordini del saldo da basso a alto.

In realtà esistono due classi di account AccountAe AccountB. Se osservi la loro implementazione, noterai che sono quasi del tutto identici, con una sola eccezione: il compare:metodo. AccountAgli oggetti accedono alle proprie proprietà con il metodo (getter), mentre gli AccountBoggetti accedono alle proprie proprietà con ivar. Questa è davvero l'unica differenza! Entrambi accedono alle proprietà dell'altro oggetto a cui confrontare con getter (accedervi con ivar non sarebbe sicuro! E se l'altro oggetto fosse una sottoclasse e ha scavalcato il getter?). Si noti inoltre che l'accesso alle proprie proprietà come ivar non interrompe l'incapsulamento (gli ivar non sono ancora pubblici).

La configurazione del test è davvero semplice: crea 1 account casuale Mio, aggiungili a un array e ordina quell'array. Questo è tutto. Naturalmente, ci sono due array, uno per gli AccountAoggetti e uno per gli AccountBoggetti ed entrambi gli array sono riempiti con account identici (stessa origine dati). Dedichiamo il tempo necessario per ordinare le matrici.

Ecco l'output di diverse esecuzioni che ho fatto ieri:

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

Come puoi vedere, l'ordinamento dell'array di AccountBoggetti è sempre significativamente più veloce dell'ordinamento dell'array di AccountAoggetti.

Chiunque sostenga che differenze di runtime fino a 1,32 secondi non fanno alcuna differenza non dovrebbe mai fare la programmazione dell'interfaccia utente. Se voglio cambiare l'ordine di ordinamento di una tabella di grandi dimensioni, ad esempio, differenze di tempo come queste fanno un'enorme differenza per l'utente (la differenza tra un'interfaccia utente accettabile e una lenta).

Anche in questo caso il codice di esempio è l'unico vero lavoro eseguito qui, ma con quale frequenza il tuo codice è solo un piccolo ingranaggio di un orologio complicato? E se ogni marcia rallenta l'intero processo in questo modo, cosa significa per la velocità dell'intero orologio alla fine? Soprattutto se un passaggio di lavoro dipende dall'output di un altro, il che significa che tutte le inefficienze si sommeranno. La maggior parte delle inefficienze non è un problema da sola, è la loro somma che diventa un problema per l'intero processo. E un problema del genere è nulla che un profiler mostrerà facilmente perché un profiler si occupa di trovare hot spot critici, ma nessuna di queste inefficienze è hot spot da sola. Il tempo della CPU è solo mediamente ripartito tra loro, ma ognuno di essi ne ha solo una minima parte, sembra una perdita di tempo totale per ottimizzarlo. Ed è vero

E anche se non pensi in termini di tempo della CPU, perché ritieni che sprecare il tempo della CPU sia del tutto accettabile, dopo tutto "è gratis", che dire dei costi di hosting del server causati dal consumo di energia? Che dire della durata della batteria dei dispositivi mobili? Se dovessi scrivere due volte la stessa app mobile (ad es. Un browser Web mobile), una volta una versione in cui tutte le classi accedono alle proprie proprietà solo tramite i getter e una volta in cui tutte le classi accedono a esse solo tramite gli ivar, l'utilizzo costante della prima sicuramente esaurirà la batteria molto più veloce rispetto all'utilizzo della seconda, anche se sono equivalenti dal punto di vista funzionale e per l'utente la seconda potrebbe anche sembrare un po 'più veloce.

Ora ecco il codice per il tuo main.mfile (il codice si basa sull'attivazione di ARC e assicurati di utilizzare l'ottimizzazione durante la compilazione per vedere l'effetto 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

3
Spiegazione estremamente istruttiva e concreta. Upgrade per esempio di codice
Philip007,

1
Uno dei qualificatori chiave che vedo nel tuo post è "... dai percorsi di codice critico". Il punto è che usa ciò che rende il codice più facile da leggere / scrivere e quindi ottimizzare quelli che ritieni siano i percorsi critici. Ciò aggiungerà la complessità dove è necessario.
Sandy Chapman,

1
@ViktorLexington Nel mio codice stavo impostando un valore unsigned intche non viene mai mantenuto / rilasciato, sia che tu usi ARC o meno. Il mantenimento / rilascio stesso è costoso, quindi la differenza sarà minore poiché la gestione del mantenimento aggiunge un sovraccarico statico che esiste sempre, usando direttamente setter / getter o ivar; tuttavia risparmierai comunque l'overhead di una chiamata di metodo aggiuntiva se accedi direttamente a ivar. Nella maggior parte dei casi non è un grosso problema, a meno che non lo facciate diverse migliaia di volte al secondo. Apple afferma di utilizzare i getter / setter per impostazione predefinita, a meno che tu non sia in un metodo init / dealloc o abbia un collo di bottiglia.
Mecki,

1
@Fogmeister Aggiunto un esempio di codice che mostra quanto facilmente ciò possa fare un'enorme differenza in un esempio molto semplice del mondo reale. E questo esempio non ha nulla a che fare con un super computer che esegue trilioni di calcoli, si tratta più di ordinare una tabella di dati davvero semplice (un caso abbastanza comune tra milioni di app).
Mecki,

2
@malhal Una proprietà contrassegnata come NONcopy eseguirà una copia del suo valore ogni volta che vi si accede. Il getter di proprietà è come il getter di una / proprietà. Il suo codice è fondamentalmente . Solo il setter copia il valore e apparirà più o meno così , mentre un / setter avrà questo aspetto:copystrongretainreturn [[self->value retain] autorelease];[self->value autorelease]; self->value = [newValue copy];strongretain[self->value autorelease]; self->value = [newValue retain];
Mecki

9

Il motivo più importante è il concetto OOP di nascondere le informazioni : se si espone tutto tramite le proprietà e si consente quindi agli oggetti esterni di sbirciare gli interni di un altro oggetto, si farà uso di questi interni e quindi si complica la modifica dell'implementazione.

Il guadagno di "prestazioni minime" può riassumere rapidamente e quindi diventare un problema. Lo so per esperienza; Lavoro su un'app che porta davvero gli iDevices ai loro limiti e quindi abbiamo bisogno di evitare inutili chiamate di metodo (ovviamente solo dove ragionevolmente possibile). Per aiutare con questo obiettivo, stiamo anche evitando la sintassi del punto poiché rende difficile vedere il numero di chiamate di metodo a prima vista: ad esempio, quante chiamate di metodo self.image.size.widthattiva l'espressione ? Al contrario, puoi dirlo immediatamente con [[self image] size].width.

Inoltre, con una corretta denominazione dell'ivaro, KVO è possibile senza proprietà (IIRC, non sono un esperto di KVO).


3
+1 Buona risposta sul guadagno di "prestazioni minime" sommando e volendo vedere tutte le chiamate di metodo in modo esplicito. L'uso della sintassi del punto con le proprietà maschera sicuramente molto lavoro che si svolge nei getter / setter personalizzati (specialmente se quel getter restituisce una copia di qualcosa ogni volta che viene chiamato).
Sam

1
KVO non funziona per me senza usare un setter. Cambiare direttamente l'ivar non chiama l'osservatore che il valore è cambiato!
Binarian,

2
KVC può accedere agli ivar. KVO non è in grado di rilevare le modifiche agli ivar (e si basa invece sugli accessor da chiamare).
Nikolai Ruhe,

9

Semantica

  • Cosa @propertypuò esprimere ciò che gli ivar non possono: nonatomice copy.
  • Cosa possono esprimere gli ivar che @propertynon possono:

Prestazione

Breve storia: gli avari sono più veloci, ma non importa per la maggior parte degli usi. nonatomicle proprietà non usano i blocchi, ma ivar diretto è più veloce perché salta la chiamata degli accessor. Per i dettagli leggi la seguente email da 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

Le proprietà influiscono sulle prestazioni in molti modi:

  1. Come già discusso, l'invio di un messaggio per eseguire un caricamento / archivio è più lento rispetto al semplice caricamento / archivio in linea .

  2. L'invio di un messaggio per eseguire un caricamento / archivio è anche un po 'più di codice che deve essere conservato nella i-cache: anche se il getter / setter ha aggiunto zero istruzioni extra oltre al solo carico / archivio, ci sarebbe una metà solida -dici istruzioni extra nel chiamante per impostare l'invio del messaggio e gestire il risultato.

  3. L'invio di un messaggio impone che una voce per quel selettore venga mantenuta nella cache del metodo e che la memoria rimanga generalmente nella cache dei d. Ciò aumenta i tempi di avvio, aumenta l'utilizzo della memoria statica della tua app e rende più dolorosi i cambi di contesto. Poiché la cache del metodo è specifica della classe dinamica per un oggetto, questo problema aumenta quanto più si utilizza KVO su di esso.

  4. L'invio di un messaggio impone che tutti i valori nella funzione vengano riversati nello stack (o mantenuti nei registri di salvataggio della chiamata, il che significa semplicemente versare in un altro momento).

  5. L'invio di un messaggio può avere effetti collaterali arbitrari e quindi

    • impone al compilatore di ripristinare tutte le sue ipotesi sulla memoria non locale
    • non può essere issato, affondato, riordinato, coalizzato o eliminato.

  6. In ARC, il risultato dell'invio di un messaggio verrà sempre mantenuto , sia dal chiamante che dal chiamante, anche per i rendimenti +0: anche se il metodo non mantiene / rilascia automaticamente il risultato, il chiamante non lo sa e ha per provare ad agire per evitare che il risultato venga rilasciato automaticamente. Questo non può mai essere eliminato perché gli invii di messaggi non sono staticamente analizzabili.

  7. In ARC, poiché un metodo setter generalmente prende il suo argomento a +0, non c'è modo di "trasferire" un trattenimento di quell'oggetto (che, come discusso sopra, ARC di solito ha) nell'ivar, quindi il valore deve generalmente ottenere conservare / rilasciare due volte .

Niente di tutto ciò significa che sono sempre cattivi, ovviamente - ci sono molti buoni motivi per usare le proprietà. Tieni solo presente che, come molte altre funzionalità linguistiche, non sono gratuite.


John.


6

Le variabili Proprietà vs. Istanza sono un compromesso, alla fine la scelta si riduce all'applicazione.

Incapsulamento / nascondere informazioni Questa è una buona cosa (TM) dal punto di vista del design, interfacce strette e collegamento minimo sono ciò che rende il software mantenibile e comprensibile. In Obj-C è piuttosto difficile nascondere qualcosa, ma le variabili di istanza dichiarate nell'implementazione si avvicinano il più possibile.

Prestazioni Mentre "l'ottimizzazione prematura" è una cosa negativa (TM), scrivere codice con prestazioni scarse solo perché è possibile è almeno altrettanto negativo. È difficile discutere che una chiamata di metodo sia più costosa di un carico o di un archivio e nel codice intensivo computazionale il costo si somma presto.

In un linguaggio statico con proprietà, come C #, le chiamate ai setter / getter possono spesso essere ottimizzate dal compilatore. Tuttavia, Obj-C è dinamico e rimuovere tali chiamate è molto più difficile.

Astrazione Un argomento contro le variabili di istanza in Obj-C è stato tradizionalmente la gestione della memoria. Con le variabili di istanza MRC è necessario che le chiamate per conservare / rilasciare / autorelease si diffondano in tutto il codice, le proprietà (sintetizzate o meno) mantengono il codice MRC in un posto - il principio di astrazione che è una buona cosa (TM). Tuttavia, con GC o ARC questo argomento scompare, quindi l'astrazione per la gestione della memoria non è più un argomento contro le variabili di istanza.


5

Le proprietà espongono le tue variabili ad altre classi. Se hai solo bisogno di una variabile relativa solo alla classe che stai creando, usa una variabile di istanza. Ecco un piccolo esempio: le classi XML per l'analisi di RSS e simili eseguono un ciclo di metodi delegati e simili. È pratico disporre di un'istanza di NSMutableString per memorizzare il risultato di ogni diverso passaggio dell'analisi. Non c'è motivo per cui una classe esterna debba mai accedere o manipolare quella stringa. Quindi, devi semplicemente dichiararlo nell'intestazione o privatamente e accedervi in ​​tutta la classe. L'impostazione di una proprietà per essa potrebbe essere utile solo per assicurarsi che non vi siano problemi di memoria, utilizzando self.mutableString per richiamare il getter / setter.


5

La compatibilità con le versioni precedenti era un fattore per me. Non potevo usare nessuna funzionalità di Objective-C 2.0 perché stavo sviluppando software e driver di stampante che dovevano funzionare su Mac OS X 10.3 come parte di un requisito. So che la tua domanda sembrava mirata su iOS, ma ho pensato di condividere ancora i miei motivi per non usare le proprietà.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.