Perché Objective-C non supporta i metodi privati?


123

Ho visto una serie di strategie per dichiarare metodi semi-privati ​​in Objective-C , ma non sembra esserci un modo per creare un metodo veramente privato. Lo accetto. Ma perché è così? Ogni spiegazione che ho essenzialmente dice: "non puoi farlo, ma qui c'è una grande approssimazione".

Ci sono un certo numero di parole chiave applicata al ivars(membri) che controllano la loro portata, per esempio @private, @public, @protected. Perché questo non può essere fatto anche per i metodi? Sembra qualcosa che il runtime dovrebbe essere in grado di supportare. C'è una filosofia di fondo che mi manca? È intenzionale?


15
Bella domanda, btw.
bbum

5
Per le tue modifiche: sì, l'applicazione del runtime sarebbe molto costosa per un vantaggio minimo. L'applicazione in fase di compilazione sarebbe una gradita aggiunta alla lingua ed è abbastanza realizzabile. Sì, potrebbe essere aggirato in fase di esecuzione, ma va bene. Il programmatore non è un nemico. L'obiettivo dell'ambito è aiutare a proteggere il programmatore da errori.
Rob Napier,

1
Non puoi "saltare il runtime": il runtime è ciò che invia il messaggio.
Chuck

"Non ci sarebbe modo per un metodo privato di cortocircuitare questo controllo" Intendevi "aggirare"? ;-)
Constantino Tsarouhas il

Risposte:


103

La risposta è ... beh ... semplice. Semplicità e coerenza, infatti.

Objective-C è puramente dinamico al momento dell'invio del metodo. In particolare, ogni invio di metodo passa attraverso lo stesso identico punto di risoluzione del metodo dinamico di ogni altro invio di metodo. In fase di esecuzione, ogni implementazione del metodo ha la stessa identica esposizione e tutte le API fornite dal runtime Objective-C che funzionano con metodi e selettori funzionano allo stesso modo in tutti i metodi.

Come molti hanno risposto (sia qui che in altre domande), i metodi privati ​​in fase di compilazione sono supportati; se una classe non dichiara un metodo nella sua interfaccia pubblicamente disponibile, allora quel metodo potrebbe anche non esistere per quanto riguarda il codice. In altre parole, puoi ottenere tutte le varie combinazioni di visibilità desiderate al momento della compilazione organizzando il tuo progetto in modo appropriato.

C'è poco vantaggio nel duplicare la stessa funzionalità nel runtime. Aggiungerebbe un'enorme quantità di complessità e overhead. E anche con tutta quella complessità, non impedirebbe a tutti, tranne che allo sviluppatore più casuale, di eseguire i tuoi metodi presumibilmente "privati".

EDIT: Uno dei presupposti che ho notato è che i messaggi privati ​​dovrebbero passare attraverso il runtime con un conseguente sovraccarico potenzialmente grande. È assolutamente vero?

Sì. Non c'è motivo di supporre che l'implementatore di una classe non voglia utilizzare tutte le funzionalità Objective-C impostate nell'implementazione, e questo significa che deve avvenire l'invio dinamico. Tuttavia , non vi è alcun motivo particolare per cui i metodi privati ​​non possano essere inviati da una variante speciale di objc_msgSend(), dal momento che il compilatore saprebbe che sono privati; vale a dire, questo può essere ottenuto aggiungendo alla struttura una tabella di metodi solo privati Class.

Non ci sarebbe modo per un metodo privato di cortocircuitare questo controllo o saltare il runtime?

Non potrebbe saltare il runtime, ma il runtime non dovrebbe necessariamente eseguire alcun controllo per i metodi privati.

Detto questo, non c'è motivo per cui una terza parte non possa chiamare deliberatamente objc_msgSendPrivate()un oggetto, al di fuori dell'implementazione di quell'oggetto, e alcune cose (KVO, ad esempio) dovrebbero farlo. In effetti, sarebbe solo una convenzione e in pratica un po 'meglio che prefissare i selettori dei metodi privati ​​o non menzionarli nell'intestazione dell'interfaccia.

Fare ciò, tuttavia, minerebbe la pura natura dinamica del linguaggio. Ogni invio di metodo non passerà più attraverso un meccanismo di invio identico. Invece, saresti lasciato in una situazione in cui la maggior parte dei metodi si comporta in un modo e una piccola manciata è semplicemente diversa.

Ciò si estende oltre il runtime poiché ci sono molti meccanismi in Cocoa costruiti sulla base del dinamismo coerente di Objective-C. Ad esempio, sia la codifica del valore chiave che l'osservazione del valore chiave dovrebbero essere modificate molto pesantemente per supportare metodi privati ​​- molto probabilmente creando una scappatoia sfruttabile - o metodi privati ​​sarebbero incompatibili.


49
+1 Objective-C è (per certi versi) un linguaggio "big-boy" in cui ci si aspetta che i programmatori mostrino una certa dose di disciplina, piuttosto che essere schiaffeggiati dal compilatore / runtime ad ogni turno. Lo trovo rinfrescante. Per me, limitare la visibilità del metodo durante la compilazione / il collegamento è sufficiente e preferibile.
Quinn Taylor

11
Più python è "obj-c-ic" :). Guido è stato piuttosto proattivo nel mantenere Python sui sistemi NeXT, inclusa la creazione della prima versione di PyObjC. Quindi, ObjC ha influenzato in qualche modo python .
bbum

3
+1 per l'interessante racconto storico del benevolo dittatore a vita e dei suoi incroci con il mitico sistema NeXT.
pokstad

5
Grazie. Python, Java e Objective-C hanno tutti modelli di oggetti runtime che sono dannatamente simili [a SmallTalk]. Java è stato derivato direttamente da Objective-C. Python ha seguito un corso parallelo più che uno di ispirazione, ma Guido ha sicuramente studiato da vicino NeXTSTEP.
bbum

1
Tranne che non mi fido della sua licenza e preferirei decisamente clang a gcc. Ma è sicuramente un buon primo passo.
Cthutu

18

Il runtime potrebbe supportarlo ma il costo sarebbe enorme. Ogni selettore inviato dovrebbe essere controllato se è privato o pubblico per quella classe, oppure ogni classe dovrebbe gestire due tabelle di invio separate. Questo non è lo stesso per le variabili di istanza perché questo livello di protezione viene eseguito in fase di compilazione.

Inoltre, il runtime dovrebbe verificare che il mittente di un messaggio privato sia della stessa classe del destinatario. Puoi anche bypassare metodi privati; se la classe usata instanceMethodForSelector:, potrebbe dare il ritorno IMPa qualsiasi altra classe per invocare direttamente il metodo privato.

I metodi privati ​​non sono stati in grado di aggirare l'invio del messaggio. Considera il seguente scenario:

  1. Una classe AllPublicha un metodo di istanza pubblicodoSomething

  2. Un'altra classe HasPrivateha anche un metodo di istanza privato chiamatodoSomething

  3. Si crea un array contenente un numero qualsiasi di istanze di entrambi AllPubliceHasPrivate

  4. Hai il seguente ciclo:

    for (id anObject in myArray)
        [anObject doSomething];

    Se eseguissi quel ciclo dall'interno AllPublic, il runtime avrebbe dovuto interrompere l'invio doSomethingsulle HasPrivateistanze, tuttavia questo ciclo sarebbe utilizzabile se fosse all'interno della HasPrivateclasse.


1
Sono d'accordo che ci sarebbe un costo. Forse non è qualcosa che desideri su un dispositivo portatile. Puoi sostenere l'enorme qualificazione altrimenti? Il costo sarebbe davvero importante su un sistema desktop?
Rob Jones,

1
Non l'ho pensato, ma potresti avere ragione. Objective-C ha l'invio di messaggi in fase di esecuzione rispetto alla chiamata al metodo in fase di compilazione di C ++, quindi si parlerebbe di un controllo in fase di compilazione rispetto a un controllo in fase di esecuzione.
Remus Rusanu

Sembra davvero strano che il motivo principale per non supportare i metodi privati ​​sia dovuto alle prestazioni. Penso che Apple semplicemente non pensasse che i metodi privati ​​fossero molto necessari, indipendentemente dal fatto che ci fosse o meno un calo delle prestazioni. Altrimenti avrebbero dovuto scegliere una lingua diversa per la loro azienda multimiliardaria.
pokstad

1
L'aggiunta di metodi privati ​​non complicherebbe solo l'implementazione del runtime, ma complicherebbe la semantica del linguaggio. Il controllo degli accessi è un concetto semplice nei linguaggi statici, ma è una cattiva corrispondenza per l'invio dinamico che avrebbe un sovraccarico concettuale e porterebbe a molti, molti "perché non funziona?" domande.
Jens Ayton

7
Cosa ti comprerebbero i metodi privati, davvero? Objective-C è, dopo tutto, un linguaggio basato su C. Pertanto, se qualcuno ha del codice in esecuzione nel tuo processo, può facilmente p0wnz il tuo processo indipendentemente da quanto possa essere privata o offuscata un'API.
bbum

13

Le risposte pubblicate finora rispondono bene alla domanda da un punto di vista filosofico, quindi pongo una ragione più pragmatica: cosa si otterrebbe cambiando la semantica della lingua? È abbastanza semplice da "nascondere" efficacemente i metodi privati. A titolo di esempio, immagina di avere una classe dichiarata in un file di intestazione, in questo modo:

@interface MyObject : NSObject {}
- (void) doSomething;
@end

Se hai bisogno di metodi "privati", puoi anche metterlo nel file di implementazione:

@interface MyObject (Private)
- (void) doSomeHelperThing;
@end

@implementation MyObject

- (void) doSomething
{
    // Do some stuff
    [self doSomeHelperThing];
    // Do some other stuff;
}

- (void) doSomeHelperThing
{
    // Do some helper stuff
}

@end

Certo, non è proprio la stessa cosa dei metodi privati ​​C ++ / Java, ma è abbastanza vicino, quindi perché alterare la semantica del linguaggio, così come il compilatore, il runtime, ecc., Per aggiungere una funzionalità che è già emulata in un accettabile modo? Come notato in altre risposte, la semantica del passaggio di messaggi - e la loro dipendenza dalla riflessione in fase di esecuzione - renderebbe la gestione dei messaggi "privati" non banale.


1
Il problema con questo approccio è che qualcuno può sovrascrivere i metodi privati ​​(anche inconsapevolmente) quando sottoclasse la tua classe. Suppongo che essenzialmente devi scegliere nomi che è improbabile che vengano sovrascritti.
dreamlax

3
Il che mi sembra un trucco in cima a un trucco.
Rob Jones,

1
@ Mike: Apple ha dichiarato che i nomi dei metodi con trattini bassi iniziali sono esplicitamente riservati solo ad Apple: developer.apple.com/mac/library/documentation/Cocoa/Conceptual/… . L'alternativa consigliata è di far precedere due lettere maiuscole e poi un trattino basso.
dreamlax

8
Inoltre, inserisci il tuo SSN dopo il metodo in modo che gli altri programmatori sappiano chi lo ha scritto. = D Namespaces, ne hai bisogno !!!
pokstad

2
Se sei preoccupato che i metodi che definisci vengano ignorati, non hai abbracciato adeguatamente i livelli intrinseci di verbosità che Objective-C incoraggia ...
Kendall Helmstetter Gelner

7

La soluzione più semplice è semplicemente dichiarare alcune funzioni C statiche nelle classi Objective-C. Questi hanno solo l'ambito del file secondo le regole C per la parola chiave statica e per questo possono essere utilizzati solo dai metodi in quella classe.

Nessuna confusione.


Devono essere definiti al di fuori della sezione @implementation?
Rob Jones,

@Rob - no, possono (e molto probabilmente dovrebbero essere) definiti all'interno dell'implementazione delle classi.
Cromulento

6

Sì, può essere fatto senza influire sul runtime utilizzando una tecnica già impiegata dai compilatori per la gestione di C ++: name-mangling.

Non è stato fatto perché non è stato stabilito che risolverebbe alcune notevoli difficoltà nello spazio dei problemi di codifica che altre tecniche (ad esempio, prefisso o sottolineatura) sono in grado di aggirare sufficientemente. IOW, hai bisogno di più dolore per superare abitudini radicate.

Potresti contribuire con patch a clang o gcc che aggiungono metodi privati ​​alla sintassi e generano nomi alterati che da solo riconoscevano durante la compilazione (e prontamente dimenticati). Quindi altri nella comunità di Objective-C sarebbero stati in grado di determinare se fosse effettivamente utile o meno. È probabile che sia più veloce in questo modo che cercare di convincere gli sviluppatori.


2
La modifica del nome in fase di compilazione è molto più difficile di quanto si possa supporre in quanto è necessario manipolare tutti i selettori di metodo, inclusi quelli che possono apparire nei file XIB e quelli che potrebbero essere passati a cose come NSSelectorFromString (), KeyValueCoding e amici ...
bbum

1
Sì, sarebbe più coinvolgente se volessi fornire supporto per metodi privati ​​attraverso la toolchain. La tabella dei simboli del compilatore dovrebbe essere archiviata e accessibile tramite alcune API della toolchain. Non sarebbe la prima volta che Apple ha dovuto implementare in modo incrementale funzionalità per gli sviluppatori sufficientemente importanti da consentire il tempo e la stabilità. Tuttavia, per quanto riguarda i metodi privati: è discutibile se sia una vittoria sufficiente per intraprendere lo sforzo. È ancora più sospetto che dovrebbero essere accessibili al di fuori della classe stessa da questi altri meccanismi.
Huperniketes

1
In che modo un'applicazione solo in fase di compilazione + una modifica del nome impedirebbe a qualcuno di percorrere l'elenco dei metodi di una classe e di trovarlo lì?
dreamlax

Non lo sarebbe. La mia risposta affronta solo la domanda posta dal PO.
Huperniketes

Ho pensato di provare ad aggiungere metodi privati ​​a Clang, una ragione per cui pensavo che i metodi privati ​​sarebbero stati utili è che potrebbero essere chiamati direttamente come semplici funzioni c e quindi potresti avere tutte le solite ottimizzazioni delle funzioni C. Non mi sono mai preoccupato a causa del tempo e puoi già farlo usando solo c.
Nathan Day

4

Essenzialmente, ha a che fare con la forma di passaggio di messaggi delle chiamate ai metodi di Objective-C. Qualsiasi messaggio può essere inviato a qualsiasi oggetto e l'oggetto sceglie come rispondere al messaggio. Normalmente risponderà eseguendo il metodo chiamato dopo il messaggio, ma potrebbe rispondere anche in molti altri modi. Questo non rende i metodi privati ​​completamente impossibili - Ruby lo fa con un sistema di passaggio di messaggi simile - ma li rende un po 'imbarazzanti.

Anche l'implementazione di metodi privati ​​di Ruby crea un po 'di confusione per le persone a causa della stranezza (puoi inviare all'oggetto qualsiasi messaggio tu voglia, eccetto quelli in questa lista !). Essenzialmente, Ruby lo fa funzionare proibendo ai metodi privati ​​di essere chiamati con un ricevitore esplicito. In Objective-C richiederebbe ancora più lavoro poiché Objective-C non ha questa opzione.


1

È un problema con l'ambiente di runtime di Objective-C. Mentre C / C ++ si compila in codice macchina illeggibile, Objective-C mantiene ancora alcuni attributi leggibili dall'uomo come i nomi dei metodi come stringhe . Ciò conferisce a Objective-C la capacità di eseguire funzioni riflettenti .

EDIT: Essere un linguaggio riflessivo senza metodi privati ​​rigorosi rende Objective-C più "pitonico" in quanto ti fidi di altre persone che usano il tuo codice piuttosto che limitare i metodi che possono chiamare. L'uso di convenzioni di denominazione come i doppi trattini bassi ha lo scopo di nascondere il codice a un programmatore client occasionale, ma non impedirà ai programmatori di dover svolgere un lavoro più serio.


E se i consumatori della tua libreria potessero riflettere i tuoi metodi privati, non potrebbero chiamarli anche loro?
Jared Updike

Se sanno dove cercare, non è così che le persone hanno utilizzato le biblioteche non documentate su iPhone? Il problema con l'iPhone è che rischi di non far accettare la tua app per l'utilizzo di API private.
pokstad

Se accedono a metodi privati ​​attraverso la riflessione, ottengono ciò che meritano. Eventuali errori non sono colpa tua.
gnasher729

1

Ci sono due risposte a seconda dell'interpretazione della domanda.

Il primo consiste nel nascondere l'implementazione del metodo dall'interfaccia. Viene utilizzato, in genere con una categoria senza nome (ad esempio @interface Foo()). Ciò consente all'oggetto di inviare quei messaggi ma non altri, anche se uno potrebbe comunque sovrascriverlo accidentalmente (o altrimenti).

La seconda risposta, supponendo che si tratti di prestazioni e inlining, è resa possibile ma come funzione C locale. Se volessi un NSString *argmetodo 'private foo ( )', lo faresti void MyClass_foo(MyClass *self, NSString *arg)e lo chiameresti come una funzione C come MyClass_foo(self,arg). La sintassi è diversa, ma agisce con il tipo sano di caratteristiche prestazionali dei metodi privati ​​di C ++.

Sebbene questo risponda alla domanda, devo sottolineare che la categoria senza nome è di gran lunga il modo più comune di Objective-C per farlo.


0

Objective-C non supporta i metodi privati ​​perché non ne ha bisogno.

In C ++, ogni metodo deve essere visibile nella dichiarazione della classe. Non puoi avere metodi che qualcuno che include il file di intestazione non può vedere. Quindi se vuoi metodi che il codice al di fuori della tua implementazione non dovrebbe usare, non hai scelta, il compilatore deve darti uno strumento in modo che tu possa dirgli che il metodo non deve essere usato, questa è la parola chiave "private".

In Objective-C, puoi avere metodi che non sono nel file di intestazione. Quindi si ottiene lo stesso scopo molto facilmente non aggiungendo il metodo al file di intestazione. Non sono necessari metodi privati. Objective-C ha anche il vantaggio di non dover ricompilare ogni utente di una classe perché hai cambiato i metodi privati.

Ad esempio, sono disponibili le variabili che dovevi dichiarare nel file di intestazione (non più), @private, @public e @protected.


0

Una risposta mancante qui è: perché i metodi privati ​​sono una cattiva idea dal punto di vista dell'evolvibilità. Potrebbe sembrare una buona idea rendere privato un metodo quando lo si scrive, ma è una forma di associazione anticipata. Il contesto potrebbe cambiare e un utente successivo potrebbe voler utilizzare un'implementazione diversa. Un po 'provocatorio: "Gli sviluppatori Agile non usano metodi privati"

In un certo senso, proprio come Smalltalk, Objective-C è per programmatori adulti. Apprezziamo sapere quale dovrebbe essere l'interfaccia dello sviluppatore originale e ci assumiamo la responsabilità di affrontare le conseguenze se dobbiamo modificare l'implementazione. Quindi sì, è filosofia, non implementazione.


Ho votato in alto perché non merita il voto negativo. C'è, come dice Stephan, un argomento secondo cui i metodi "privati" non sono necessari, ed è simile agli argomenti sulla digitazione dinamica rispetto a quella statica, in quanto, qualunque sia la tua conclusione preferita, entrambe le parti hanno un punto.
alastair
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.