Devo usare proprietà o riferimenti diretti quando accedo internamente a variabili di istanza?

Dì che ho una class come questa:

@interface MyAwesomeClass : NSObject { @private NSString *thing1; NSString *thing2; } @property (retain) NSString *thing1; @property (retain) NSString *thing2; @end @implementation MyAwesomeClass @synthesize thing1, thing1; @end 

Quando si accede a thing1 e thing2 internamente (cioè, all’interno dell’implementazione di MyAwesomeClass ), è meglio usare la proprietà, o semplicemente fare riferimento direttamente alla variabile di istanza (supponendo casi in cui non si fa alcun lavoro in un accesso o in un mutatore “personalizzato”). , cioè, abbiamo appena impostato e ottenuto la variabile). Pre-Objective C 2.0, di solito accediamo direttamente agli ivars, ma qual è il solito stile di codifica / best practice adesso? E questa raccomandazione cambia se una variabile di istanza / proprietà è privata e non accessibile al di fuori della class? Dovresti creare una proprietà per ogni ivar, anche se sono private o solo per dati pubblici? Cosa succede se la mia app non utilizza le funzionalità di codifica dei valori-chiave (poiché KVC triggers solo l’accesso alle proprietà)?

Sono interessato a guardare oltre i dettagli tecnici di basso livello. Ad esempio, dato un codice (sub-ottimale) come:

 @interface MyAwesomeClass : NSObject { id myObj; } @proprety id myObj; @end @implementation MyAwesomeClass @synthesize myObj; @end 

So che myObj = anotherObject è funzionalmente uguale a self.myObj = anotherObj .

Ma le proprietà non sono semplicemente syntax di fantasia per istruire il compilatore a scrivere accessori e mutatori per te, naturalmente; sono anche un modo per incapsulare meglio i dati, ovvero, è ansible modificare l’implementazione interna della class senza riscrivere le classi che si basano su tali proprietà. Sono interessato alle risposte che affrontano l’importanza di questo problema di incapsulamento quando si tratta del codice interno della class. Inoltre, le proprietà scritte correttamente possono triggersre le notifiche KVC, ma l’accesso diretto a ivar non lo farà; questo importa se la mia app non sta utilizzando le funzionalità di KVC ora , nel caso in cui potrebbe in futuro?

Non penso che ogni modo sia ‘migliore’. Vedete entrambi gli stili di uso comune, quindi non c’è nemmeno una solita / migliore pratica ora. Nella mia esperienza, lo stile utilizzato ha un impatto minimo sul modo in cui digerisco alcuni file di implementazione che sto cercando. Sicuramente vuoi sentirti a tuo agio con entrambi gli stili (e tutti gli altri) quando guardi il codice di altre persone.

Usare una proprietà per ogni ivar interno potrebbe andare leggermente fuori bordo, in termini di manutenzione. L’ho fatto, e ho aggiunto una quantità di lavoro non banale che non ritengo abbia pagato per me. Ma se hai un forte desiderio / OCD per vedere un codice coerente come self.var ovunque, e lo hai nel fondo della tua mente ogni volta che guardi un corso, allora self.var . Non sottovalutare l’effetto che una sensazione fastidiosa può avere sulla produttività.

Eccezioni – Ovviamente, per i getter personalizzati (ad esempio la creazione pigra), non hai molta scelta. Inoltre, creo e utilizzo una proprietà per i setter interni quando lo rende più conveniente (ad esempio l’impostazione di oggetti con la semantica della proprietà).

“just in case”, “might” non è una ragione convincente per fare qualcosa senza più dati, poiché il tempo necessario per implementarlo è diverso da zero. Una domanda migliore potrebbe essere, qual è la probabilità che tutti gli ivars privati ​​di alcune classi richiederanno notifiche KVC in futuro, ma non ora? Per la maggior parte delle mie classi, la risposta è estremamente bassa, quindi ora evito una dura regola sulla creazione di proprietà per ogni ivar privato.

Ho scoperto che quando si ha a che fare con le implementazioni interne, ottengo rapidamente una buona padronanza su come ogni ivar debba essere consultato a prescindere.

Se sei interessato, il mio approccio è questo:

  • Lettura di ivars: accesso diretto, a meno che non vi sia un getter personalizzato (ad es. Creazione pigro)
  • Scrivere ivars: direttamente in alloc/dealloc . Altrove, attraverso una proprietà privata, se ne esiste una.

Se passi del tempo sulla mailing list cocoa-dev, scoprirai che questo è un argomento molto controverso.

Alcune persone pensano che gli ivar dovrebbero sempre essere usati internamente e che le proprietà non dovrebbero mai (o raramente) essere usate se non esternamente. Vi sono varie preoccupazioni con le notifiche KVO e gli effetti collaterali di accesso.

Alcune persone pensano che dovresti sempre (o soprattutto) usare le proprietà al posto di ivar. Il vantaggio principale qui è che la gestione della memoria è ben contenuta all’interno dei metodi di accesso invece che sparsi nella logica di implementazione. Le notifiche KVO e gli effetti collaterali di accesso possono essere superati creando proprietà separate che puntano allo stesso ivar.

Guardando il codice di esempio di Apple rivelerà che sono tutti sul posto su questo argomento. Alcuni campioni usano proprietà internamente, altri usano ivars.

Direi, in generale, che questa è una questione di gusti e che non esiste un modo giusto per farlo. Io stesso uso un mix di entrambi gli stili.

L’unica differenza in un compito di thing1 = something; e self.thing1 = something; è che se si desidera che l’operazione di assegnazione di proprietà ( retain , copy , ecc.) venga eseguita sull’object assegnato, è necessario utilizzare una proprietà. Assegnare senza proprietà sarà proprio questo, assegnando un riferimento all’object fornito .

Penso che la definizione di una proprietà per i dati interni non sia necessaria. Definire solo le proprietà per ivars a cui si accede spesso e che richiedono un comportamento specifico del mutatore.

Se thing1 viene usato con KVO, è una buona idea usare self.thing1= quando lo si imposta. Se thing1 è @public , allora è meglio presumere che qualcuno un giorno vorrà usarlo con KVO.

Se thing1 ha una semantica dell’insieme complesso che non si desidera ripetere ovunque lo si imposta (ad esempio nonatomic , o non- nonatomic ), quindi utilizzare tramite self.thing1= è una buona idea.

Se il benchmarking mostra che chiamare setThing1: richiede molto tempo, allora potresti voler pensare ai modi per impostarlo senza usare self.thing1= – forse nota che non può essere KVO, o vedi se implementare manualmente KVO è meglio (ad esempio se lo si imposta 3000 volte in un loop da qualche parte, si potrebbe essere in grado di impostarlo tramite self->thing1 3000 volte e fare 2 chiamate KVO sul valore che sta per cambiare e aver cambiato).

Questo lascia il caso di un setter insignificante su una variabile privata in cui sai che non stai usando KVO. A quel punto smette di essere un problema tecnico e rientra nello stile del codice. Almeno finché l’accessor non viene visualizzato come collo di bottiglia nel profiler. In quel momento tendo ad usare l’accesso diretto a ivar (a meno che non pensi che in futuro avrò quel valore in KVO, o potrei voler renderlo pubblico e quindi pensare che altri potrebbero volerlo fare a KVO).

Tuttavia, quando ho impostato le cose con accesso diretto a ivar, provo a farlo solo tramite self->thing1= , che rende molto più semplice trovarle tutte e modificarle se mai trovo la necessità di usare KVO o renderlo pubblico , o per rendere un accessor più complesso.

Altre cose menzionate qui vanno bene. Alcune cose che mancano le altre risposte sono:

Per prima cosa, tieni sempre presente le implicazioni che gli accessori / mutatori sono virtuali (come tutti i metodi Objective-C.) In generale, è stato detto che si dovrebbe evitare di chiamare i metodi virtuali in init e dealloc, perché non si sa cosa sia un la sottoclass farà ciò che potrebbe rovinarti. Per questo motivo, in genere cerco di accedere agli iVars direttamente in init e dealloc e di accedervi tramite gli accessor / mutators ovunque. D’altra parte, se non si utilizzano in modo coerente gli accessor in tutti gli altri luoghi, le sottoclassi che le sovrascrivono potrebbero essere interessate.

In correlazione, le garanzie di atomicità delle proprietà (ovvero le @properties sono dichiarate atomiche) non possono essere mantenute per nessuno se si accede a iVar direttamente da qualsiasi posizione al di fuori di init & dealloc. Se hai bisogno di qualcosa per essere atomico, non buttare via l’atomicità accedendo direttamente a iVar. Allo stesso modo, se non hai bisogno di quelle garanzie, dichiara la tua proprietà non anatomica (per le prestazioni).

Ciò riguarda anche la questione KVO. In init, nessuno può ancora osservarti (legittimamente), e in dealloc, ogni osservatore rimasto ha un riferimento stentato (cioè falso). Lo stesso ragionamento vale anche per le garanzie di atomicità delle proprietà. (cioè come accadrebbe gli accessi simultanei prima che i ritorni di init e gli accessi che accadono durante dealloc siano intrinsecamente errori).

Se si mescolano e si abbinano all’uso diretto e di accesso / mutatore, si rischia di eseguire controlli non solo su KVO e atomicità, ma anche su sottoclassi.