Perché e non ?

In Objective-C, perché [object doSomething] ? Non sarebbe [*object doSomething] do [*object doSomething] dato che stai chiamando un metodo sull’object ?, il che significa che dovresti dereferenziare il puntatore?

La risposta richiama le radici C dell’Obiettivo-C. Objective-C è stato originariamente scritto come pre-processore del compilatore per C. Cioè, Objective-C non è stato compilato tanto quanto è stato trasformato in C dritto e quindi compilato.

Inizia con la definizione del tipo id . È dichiarato come:

 typedef struct objc_object { Class isa; } *id; 

Cioè, un id è un puntatore a una struttura il cui primo campo è di tipo Class (che, a sua volta, è un puntatore a una struttura che definisce una class). Ora, considera NSObject :

 @interface NSObject  { Class isa; } 

Si noti che il layout di NSObject e il layout del tipo puntato da id sono identici. Questo perché, in realtà, un’istanza di un object Objective-C è in realtà solo un puntatore a una struttura il cui primo campo, sempre un puntatore, punta alla class che contiene i metodi per quell’istanza (insieme ad altri metadati ).

Quando si sottoclassi NSObject e si aggiungono alcune variabili di istanza si è, a tutti gli effetti, semplicemente creando una nuova struttura C che contiene le variabili di istanza come slot in quella struttura concatenata negli slot per le variabili di istanza per tutte le superclassi. (Il runtime moderno funziona in modo leggermente diverso in modo che una superclass possa avere ivars aggiunti senza richiedere la ricompilazione di tutte le sottoclassi).

Ora, considera la differenza tra queste due variabili:

 NSRect foo; NSRect *bar; 

(NSRect è una semplice struttura in C – nessun ObjC coinvolto). foo viene creato con la memoria in pila. Non sopravviverà una volta che il frame dello stack è stato chiuso, ma non è necessario liberare memoria. bar è un riferimento a una struttura NSRect che è stata, molto probabilmente, creata nell’heap usando malloc() .

Se provi a dire:

 NSArray foo; NSArray *bar; 

Il compilatore si lamenterà del primo, dicendo che qualcosa in linea con gli oggetti basati sullo stack non è permesso in Objective-C . In altre parole, tutti gli oggetti Objective-C devono essere allocati dall’heap (più o meno– ci sono una o due eccezioni, ma sono relativamente esoterici in questa discussione) e, di conseguenza, si fa sempre riferimento a un object attraverso l’indirizzo di detto object sull’heap; si lavora sempre con i puntatori agli oggetti (e il tipo di id realtà è solo un puntatore a qualsiasi vecchio object).

Tornando alle radici del preprocessore C della lingua, è ansible convertire ogni chiamata di metodo in una riga equivalente di C. Ad esempio, le seguenti due righe di codice sono identiche:

 [myArray objectAtIndex: 42]; objc_msgSend(myArray, @selector(objectAtIndex:), 42); 

Allo stesso modo, un metodo dichiarato come questo:

 - (id) objectAtIndex: (NSUInteger) a; 

È equivalente alla funzione C dichiarata in questo modo:

 id object_at_index(id self, SEL _cmd, NSUInteger a); 

E, osservando objc_msgSend() , il primo argomento è dichiarato di tipo id :

 OBJC_EXPORT id objc_msgSend(id self, SEL op, ...); 

E questo è esattamente il motivo per cui non si usa *foo come objective di una chiamata di metodo. [myArray objectAtIndex: 42] la traduzione tramite i moduli sopra riportati: la chiamata a [myArray objectAtIndex: 42] viene [myArray objectAtIndex: 42] nella chiamata di funzione C sopra la quale quindi deve chiamare qualcosa con l’equivalente dichiarazione di chiamata di funzione C (tutto mascherato nella syntax del metodo).

Il riferimento all’object viene portato perché dà l’accesso alla class a messenger – objc_msgSend () per poi trovare l’implementazione del metodo – così come quel riferimento che diventa il primo parametro – il sé – del metodo che alla fine è eseguito.

Se vuoi davvero andare in profondità, inizia qui . Ma non preoccuparti fino a quando non ne hai colto completamente.

Non dovresti davvero pensare a questi come puntatori agli oggetti. È una specie di dettaglio di un’implementazione storica che sono puntatori e che li si usa in questo modo nella syntax dell’invio dei messaggi (vedi la risposta di @bbum). In realtà, sono solo “identificatori di object” (o riferimenti). Facciamo un po ‘di riavvolgimento per vedere la logica concettuale.

Objective-C è stato inizialmente proposto e discusso in questo libro: Programmazione orientata agli oggetti: un approccio evolutivo . Non è immensamente pratico per i programmatori di Cocoa moderni, ma le motivazioni per il linguaggio sono lì dentro.

Si noti che nel libro a tutti gli oggetti viene dato un id tipo. Non vedi affatto gli Object * più specifici nel libro; quelli sono solo una perdita nell’astrazione quando parliamo del “perché”. Ecco cosa dice il libro:

Gli identificatori di oggetti devono identificare in modo univoco quanti più oggetti possano coesistere nel sistema in qualsiasi momento. Sono memorizzati in variabili locali, passati come argomenti nelle espressioni di messaggi e in chiamate di funzione, mantenuti in variabili di istanza (campi all’interno di oggetti) e in altri tipi di strutture di memoria. In altre parole, possono essere utilizzati in modo fluido come i tipi predefiniti del linguaggio di base.

Il modo in cui un identificatore di object identifica effettivamente l’object è un dettaglio di implementazione per il quale molte scelte sono plausibili. Una scelta ragionevole, sicuramente una delle più semplici, e quella che viene usata in Objective-C, è quella di usare l’indirizzo fisico dell’object in memoria come suo identificatore. Objective-C rende nota questa decisione a C generando un’istruzione typedef in ogni file. Questo definisce un nuovo tipo, id, in termini di un altro tipo che C comprende già, cioè i puntatori alle strutture. […]

Un id consuma una quantità fissa di spazio. […] Questo spazio non è uguale allo spazio occupato dai dati privati ​​nell’object stesso.

(pp58-59, 2a ed.)

Quindi la risposta alla tua domanda è duplice:

  1. Il linguaggio design specifica che l’identificatore di un object non è lo stesso di un object stesso, e l’identificatore è la cosa a cui invii i messaggi, non l’object stesso.
  2. Il design non impone, ma suggerisce, l’implementazione che abbiamo ora, in cui i puntatori agli oggetti sono usati come identificatori.

La syntax rigorosamente tipizzata in cui si dice “un object specifico del tipo NSString” e quindi si utilizza NSString * è un cambiamento più moderno, ed è fondamentalmente una scelta di implementazione, equivalente a id .

Se questa sembra una risposta di alto livello a una domanda sulla dereferenziazione del puntatore, è importante tenere presente che gli oggetti in Objective-C sono “speciali” per la definizione della lingua. Sono implementati come strutture e fatti passare come riferimenti alle strutture, ma sono concettualmente diversi.

Perché objc_msgSend () è dichiarato in questo modo:

 id objc_msgSend(id theReceiver, SEL theSelector, ...) 
  1. Non è un puntatore, è un riferimento a un object.
  2. Non è un metodo, è un messaggio.

Non si mai dereferenziare i puntatori agli oggetti, punto. Il fatto che siano digitati come puntatori e non solo come “tipi di object” è un artefatto dell’eredità C del linguaggio. È esattamente equivalente al sistema di tipi di Java, in cui gli oggetti sono sempre accessibili tramite riferimenti. Non si è mai dereferenziate un object in Java, infatti non è ansible. Non dovresti pensarli come dei puntatori, perché semanticamente non lo sono. Sono solo riferimenti a oggetti.

Parte del motivo è che otterresti eccezioni puntatore nullo a sinistra ea destra. L’invio di un messaggio a nil è consentito e spesso perfettamente legittimo (non fa nulla e non genera un errore).

Ma si può pensare ad esso come analogo alla notazione di C ++: Esegue il metodo e dereferenzia il puntatore in un pezzo di zucchero sintattico.

Direi in questo modo: ciò che un linguaggio associa a una serie di alfabeti è solo una convenzione. Le persone che hanno progettato Objective-C l’hanno deciso

 [x doSomething]; 

per significare “inviare il messaggio doSomething all’object puntato da x”. Lo hanno definito in questo modo, tu segui la regola 🙂 Una particolarità di Objective-C, rispetto ad esempio al C ++, è che non ha una syntax per contenere un object stesso, non un puntatore all’object. Così,

 NSString* string; 

va bene, ma

 NSString string; 

è illegale. Se quest’ultimo fosse ansible, ci dovrebbe essere un modo per “inviare il messaggio capitalizedString a una stringa string “, non “inviare il messaggio capitalizedString a una stringa puntata da una string “. Ma in realtà, invii sempre un messaggio a un object puntato da una variabile nel tuo codice sorgente.

Quindi, se i progettisti di Objective-C avessero seguito la tua logica, dovresti scrivere

 [*x doSomething]; 

ogni volta che mandi un messaggio … vedi, * deve apparire sempre dopo la parentesi principale [ , formando la combinazione [* . A quel punto, credo che tu sia d’accordo sul fatto che sia meglio ridisegnare la lingua in modo tale che tu debba solo scrivere [ anziché [* , cambiando il significato della sequenza di lettere [x doSomething] .

Un object in Objective-C è essenzialmente una struct . Il primo membro della struct è la Class isa (la dimensione totale della struct può essere determinata usando isa ). I membri successivi della struct possono includere variabili di istanza.

Quando dichiari un object Objective-C, lo dichiari sempre come un tipo di puntatore perché il runtime fornisce il tuo object ad altri metodi e funzioni; se questi cambiano i membri della struct (modificando le variabili di istanza, ecc.) essi “si applicano” a tutti i riferimenti al proprio object, e non solo localmente all’interno del metodo o della funzione.

Il runtime Objective-C può aver bisogno di rimbalzare l’object attorno a un paio di funzioni diverse, quindi vuole il riferimento all’object e non l’object stesso.