Dlaczego Objective-C nie obsługuje prywatnych metod?

123

Widziałem wiele strategii deklarowania metod półprywatnych w Objective-C , ale wydaje się, że nie ma sposobu na stworzenie metody prawdziwie prywatnej. Akceptuje to. Ale dlaczego tak jest? Każde wyjaśnienie, które zasadniczo mówię: „nie możesz tego zrobić, ale tutaj jest bliskie przybliżenie”.

Istnieje wiele słów kluczowych stosuje się do ivars(członków), które kontrolują ich zakres, na przykład @private, @public, @protected. Dlaczego nie można tego zrobić również w przypadku metod? Wygląda na to, że środowisko wykonawcze powinno być w stanie obsłużyć. Czy brakuje mi jakiejś podstawowej filozofii? Czy to celowe?

Rob Jones
źródło
15
Przy okazji, dobre pytanie.
bbum
5
Do twoich zmian: tak, wymuszanie w czasie wykonywania byłoby bardzo kosztowne i przynosiło bardzo niewielkie korzyści. Egzekwowanie prawa w czasie kompilacji byłoby pożądanym dodatkiem do języka i jest całkiem osiągalne. Tak, można to obejść w czasie wykonywania, ale to w porządku. Programista nie jest wrogiem. Celem zakresu jest pomoc w ochronie programisty przed błędami.
Rob Napier,
1
Nie można „pominąć środowiska wykonawczego” - to właśnie środowisko wykonawcze służy do wysyłania wiadomości.
Chuck,
„Nie byłoby sposobu, aby prywatna metoda spowodowała zwarcie tego sprawdzenia” Czy chodziło Ci o „obejście”? ;-)
Constantino Tsarouhas

Odpowiedzi:

103

Odpowiedź jest ... cóż ... prosta. A właściwie prostota i konsekwencja.

Cel-C jest czysto dynamiczny w momencie wysyłania metody. W szczególności, każda wysyłka metody przechodzi przez dokładnie ten sam dynamiczny punkt rozwiązania metody, co każda inna wysyłka metody. W czasie wykonywania każda implementacja metody ma dokładnie taką samą ekspozycję, a wszystkie interfejsy API dostarczane przez środowisko wykonawcze Objective-C, które współpracują z metodami i selektorami, działają tak samo we wszystkich metodach.

Jak wielu odpowiedziało (zarówno tutaj, jak i w innych pytaniach), obsługiwane są prywatne metody kompilacji; jeśli klasa nie deklaruje metody w swoim publicznie dostępnym interfejsie, to ta metoda równie dobrze może nie istnieć, jeśli chodzi o Twój kod. Innymi słowy, możesz osiągnąć wszystkie różne kombinacje widoczności pożądane w czasie kompilacji, odpowiednio organizując projekt.

Duplikowanie tej samej funkcjonalności w środowisku wykonawczym nie przynosi większych korzyści. Dodałoby to ogromną złożoność i narzut. I nawet przy całej tej złożoności, nadal nie przeszkodziłoby to wszystkim, z wyjątkiem najbardziej przypadkowego programisty, w wykonywaniu twoich rzekomo „prywatnych” metod.

EDYCJA: Jednym z założeń, które zauważyłem, jest to, że prywatne wiadomości musiałyby przejść przez środowisko wykonawcze, co spowodowałoby potencjalnie duży narzut. Czy to absolutna prawda?

Tak to jest. Nie ma powodu, by przypuszczać, że osoba implementująca klasę nie chciałaby używać wszystkich funkcji celu-C w implementacji, a to oznacza, że ​​musi nastąpić dynamiczne wysyłanie. Jednak nie ma żadnego szczególnego powodu, dla którego metody prywatne nie mogłyby zostać wysłane przez specjalny wariant programu objc_msgSend(), skoro kompilator wiedziałby, że są one prywatne; tj. można to osiągnąć poprzez dodanie do Classstruktury tabeli metod wyłącznie prywatnych .

Nie byłoby sposobu, aby prywatna metoda spowodowała zwarcie tego sprawdzenia lub pominięcie środowiska wykonawczego?

Nie może pominąć środowiska wykonawczego, ale środowisko wykonawcze niekoniecznie musiałoby sprawdzać metody prywatne.

To powiedziawszy, nie ma powodu, dla którego strona trzecia nie mogłaby celowo wywołać objc_msgSendPrivate()obiektu poza implementacją tego obiektu, a niektóre rzeczy (na przykład KVO) musiałyby to zrobić. W efekcie byłaby to po prostu konwencja i niewiele lepsza w praktyce niż umieszczanie przedrostków selektorów metod prywatnych lub nie wspominanie ich w nagłówku interfejsu.

Jednak takie postępowanie podważyłoby czystą dynamiczną naturę języka. Nie każda wysyłka metodą będzie już przechodzić przez identyczny mechanizm. Zamiast tego zostałbyś pozostawiony w sytuacji, w której większość metod zachowuje się w jeden sposób, a kilka jest po prostu innych.

Wykracza to poza środowisko wykonawcze, ponieważ w kakao jest wiele mechanizmów opartych na konsekwentnej dynamice Objective-C. Na przykład zarówno kodowanie wartości klucza, jak i obserwacja wartości klucza musiałyby zostać bardzo mocno zmodyfikowane, aby obsługiwały metody prywatne - najprawdopodobniej poprzez utworzenie luki, którą można wykorzystać - albo metody prywatne byłyby niekompatybilne.

bbum
źródło
49
+1 Objective-C jest (w pewnym sensie) językiem „dużego chłopca”, w którym od programistów oczekuje się wykazywania pewnej dyscypliny, zamiast bycia uderzanym w rękę przez kompilator / środowisko uruchomieniowe na każdym kroku. Uważam, że to orzeźwiające. Dla mnie ograniczenie widoczności metod podczas kompilacji / łączenia jest wystarczające i preferowane.
Quinn Taylor
11
Więcej Pythona to „obj-c-ic” :). Guido był dość proaktywny w utrzymywaniu Pythona w systemach NeXT, w tym w tworzeniu pierwszej wersji PyObjC. Zatem ObjC w pewnym stopniu wpłynęło na Pythona .
bbum
3
+1 za interesującą historyczną opowieść o życzliwym dla życia dyktatorze i jego skrzyżowaniach z mitycznym systemem NeXT.
pokstad
5
Dziękuję Ci. Python, Java i Objective-C wszystkie mają modele obiektów środowiska wykonawczego, które są dość cholernie podobne [do SmallTalk]. Java wywodzi się bezpośrednio z Objective-C. Python prowadził równoległy kurs więcej niż jednego inspiracji, ale Guido z pewnością uważnie studiował NeXTSTEP.
bbum
1
Tyle że nie ufam jego licencji i zdecydowanie wolałbym clang niż gcc. Ale to z pewnością dobry pierwszy krok.
Cthutu
18

Środowisko wykonawcze mogłoby to obsługiwać, ale koszt byłby ogromny. Każdy wysyłany selektor musiałby zostać sprawdzony, czy jest prywatny czy publiczny dla tej klasy, albo każda klasa musiałaby zarządzać dwiema oddzielnymi tabelami wysyłania. To nie jest to samo dla przykładowych zmiennych, ponieważ ten poziom ochrony jest wykonywany w czasie kompilacji.

Ponadto środowisko wykonawcze musiałoby zweryfikować, czy nadawca prywatnej wiadomości jest tej samej klasy co odbiorca. Możesz także ominąć metody prywatne; jeśli użyta klasa instanceMethodForSelector:, mogłaby zwrócić wartość zwróconą IMPdowolnej innej klasie, aby mogli bezpośrednio wywołać metodę prywatną.

Metody prywatne nie mogą ominąć wysłania wiadomości. Rozważ następujący scenariusz:

  1. Klasa AllPublicma metodę wystąpienia publicznegodoSomething

  2. Inna klasa HasPrivatema również metodę wystąpienia prywatnego o nazwiedoSomething

  3. Tworzysz tablicę zawierającą dowolną liczbę wystąpień obu AllPubliciHasPrivate

  4. Masz następującą pętlę:

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

    Jeśli uruchomiłeś tę pętlę od wewnątrz AllPublic , środowisko wykonawcze będzie musiało zatrzymać wysyłanie doSomethingna HasPrivateinstancje, jednak ta pętla byłaby użyteczna, gdyby znajdowała się wewnątrz HasPrivateklasy.

dreamlax
źródło
1
Zgadzam się, że wiązałoby się to z kosztami. Być może nie jest to coś, czego chcesz na urządzeniu przenośnym. Czy możesz inaczej wesprzeć olbrzymią kwalifikację? Czy koszt naprawdę miałby znaczenie w przypadku komputera stacjonarnego?
Rob Jones
1
Nie myśl o tym, ale możesz mieć rację. Objective-C ma wysyłanie komunikatów w czasie wykonywania w porównaniu z wywołaniem metody czasu kompilacji w C ++, więc można mówić o sprawdzaniu czasu kompilacji w porównaniu z kontrolą w czasie wykonywania.
Remus Rusanu
Wydaje się naprawdę dziwne, że głównym powodem, dla którego nie wspieramy prywatnych metod, jest wydajność. Myślę, że Apple po prostu nie uważał, że prywatne metody są bardzo potrzebne, niezależnie od tego, czy był hit wydajnościowy. W przeciwnym razie powinni wybrać inny język dla swojej wielomiliardowej firmy.
pokstad
1
Dodanie metod prywatnych nie tylko skomplikowałoby implementację środowiska wykonawczego, ale skomplikowałoby semantykę języka. Kontrola dostępu jest prostą koncepcją w językach statycznych, ale nie pasuje do dynamicznej dystrybucji, która wiązałaby się z dużym narzutem koncepcyjnym i prowadziła do wielu, wielu „dlaczego to nie działa?” pytania.
Jens Ayton
7
Co tak naprawdę kupiłyby ci prywatne metody? W końcu Objective-C jest językiem opartym na C. Tak więc, jeśli ktoś ma uruchomiony kod w twoim procesie, może łatwo p0wnz twój proces, niezależnie od tego, jak prywatny lub zaciemniony może być dowolny interfejs API.
bbum
13

Opublikowane dotychczas odpowiedzi dobrze sobie radzą z odpowiedzią na pytanie z filozoficznej perspektywy, więc zamierzam postawić bardziej pragmatyczny powód: co zyskałoby się dzięki zmianie semantyki języka? Dość łatwo jest skutecznie „ukryć” prywatne metody. Na przykład wyobraź sobie, że masz klasę zadeklarowaną w pliku nagłówkowym, na przykład:

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

Jeśli potrzebujesz "prywatnych" metod, możesz również umieścić to w pliku implementacji:

@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

Oczywiście, to nie jest zupełnie tak samo jak C ++ / Java metod prywatnych, ale to faktycznie na tyle blisko, więc po co zmieniać semantykę języka, jak również kompilatora, czas pracy, etc., aby dodać funkcję, która już emulowane w dopuszczalnym sposób? Jak zauważono w innych odpowiedziach, semantyka przekazywania wiadomości - i jej zależność od refleksji w czasie wykonywania - sprawiłaby, że obsługa wiadomości „prywatnych” byłaby nietrywialna.

mipadi
źródło
1
Problem z tym podejściem polega na tym, że ktoś może przesłonić metody prywatne (nawet nieświadomie), gdy podklasują twoją klasę. Przypuszczam, że zasadniczo musisz wybrać nazwy, które prawdopodobnie nie zostaną zastąpione.
dreamlax
3
Co dla mnie jest jak włamanie do włamania.
Rob Jones
1
@Mike: Firma Apple stwierdziła, że ​​nazwy metod z początkowymi podkreśleniami są wyraźnie zarezerwowane tylko dla Apple: developer.apple.com/mac/library/documentation/Cocoa/Conceptual/… . Zalecaną alternatywą jest przedrostek dwoma dużymi literami, a następnie podkreśleniem.
dreamlax
8
Dodaj też swój numer SSN po metodzie, aby inni programiści wiedzieli, kto go napisał. = D Przestrzenie nazw, potrzebujesz ich !!!
pokstad
2
Jeśli martwisz się, że metody, które definiujesz, zostaną zastąpione, nie
przyjąłeś
7

Najłatwiejszym rozwiązaniem jest po prostu zadeklarowanie statycznych funkcji C w klasach Objective-C. Mają one tylko zasięg plikowy zgodnie z regułami C dla słowa kluczowego static i dlatego mogą być używane tylko przez metody w tej klasie.

Bez zamieszania.

Cromulent
źródło
Czy muszą być zdefiniowane poza sekcją @implementation?
Rob Jones
@Rob - nie, nie mogą (i najprawdopodobniej powinny być) zdefiniowane wewnątrz Twojej implementacji klas.
Cromulent
6

Tak, można to zrobić bez wpływu na środowisko uruchomieniowe, wykorzystując technikę już stosowaną przez kompilatory do obsługi C ++: mangling nazw.

Nie zostało to zrobione, ponieważ nie ustalono, że rozwiązałoby to znaczną trudność w przestrzeni problemowej kodowania, którą inne techniki (np. Prefiksowanie lub podkreślanie) są w stanie obejść w wystarczającym stopniu. IOW, potrzebujesz więcej bólu, aby przezwyciężyć zakorzenione nawyki.

Mógłbyś wnieść łaty do clang lub gcc, które dodają prywatne metody do składni i wygenerują zniekształcone nazwy, które sam rozpoznał podczas kompilacji (i natychmiast zapomniał). Wtedy inni członkowie społeczności Objective-C byliby w stanie określić, czy było to rzeczywiście warte zachodu, czy nie. W ten sposób prawdopodobnie będzie szybszy niż próba przekonania programistów.

Huperniketes
źródło
2
Zniekształcanie nazw w czasie kompilacji jest znacznie trudniejsze, niż można by przypuszczać, ponieważ musisz zmienić wszystkie selektory metod, w tym te, które mogą pojawić się w plikach XIB i te, które mogą być przekazane do rzeczy takich jak NSSelectorFromString (), a także KeyValueCoding & friends ...
bbum
1
Tak, byłoby bardziej zaangażowane, gdybyś chciał zapewnić wsparcie dla prywatnych metod w całym łańcuchu narzędzi. Tablica symboli kompilatora musiałaby być przechowywana i dostępna za pośrednictwem interfejsu API łańcucha narzędzi. Nie byłby to pierwszy raz, gdy Apple musiałby stopniowo udostępniać programistom funkcje, które były wystarczająco ważne, o ile pozwalał na to czas i stabilność. Jednak jeśli chodzi o metody prywatne: wątpliwe jest, czy jest to wystarczająca wygrana, aby podjąć wysiłek. Jest jeszcze bardziej podejrzane, że powinny być dostępne poza samą klasą przez te inne mechanizmy.
Huperniketes
1
W jaki sposób wymuszanie tylko w czasie kompilacji + zniekształcanie nazw zatrzymałoby kogoś, kto przeszedłby listę metod klasy i znalazł ją tam?
dreamlax
Nie byłoby. Moja odpowiedź dotyczy tylko pytania zadanego przez PO.
Huperniketes
Pomyślałem o próbie dodania metod prywatnych do Clang, jednym z powodów, dla których uważałem, że metody prywatne byłyby dobre, jest to, że można by je wywołać bezpośrednio jak proste funkcje c, a następnie można było przeprowadzić wszystkie zwykłe optymalizacje funkcji C. Nigdy nie zawracałem sobie głowy z powodu czasu i już możesz to zrobić, używając po prostu c.
Dzień Nathana
4

Zasadniczo ma to związek z formą wywołań metod przekazywania komunikatów Objective-C. Dowolną wiadomość można wysłać do dowolnego obiektu, a obiekt sam wybiera sposób odpowiedzi na wiadomość. Zwykle zareaguje, wykonując metodę nazwaną po wiadomości, ale może również odpowiedzieć na wiele innych sposobów. Nie oznacza to, że metody prywatne są całkowicie niemożliwe - Ruby robi to z podobnym systemem przekazywania wiadomości - ale czyni je nieco niezręcznymi.

Nawet implementacja prywatnych metod w Ruby jest nieco myląca dla ludzi z powodu dziwności (możesz wysłać obiektowi dowolną wiadomość, z wyjątkiem tych z tej listy !). Zasadniczo Ruby sprawia, że ​​działa, zabraniając wywoływania metod prywatnych z jawnym odbiornikiem. W Objective-C wymagałoby to jeszcze więcej pracy, ponieważ Objective-C nie ma takiej opcji.

Głaskanie pod brodę
źródło
1

Jest to problem ze środowiskiem wykonawczym Objective-C. Podczas gdy C / C ++ kompiluje się do nieczytelnego kodu maszynowego, Objective-C nadal zachowuje pewne czytelne dla człowieka atrybuty, takie jak nazwy metod jako ciągi . To daje Objective-C możliwość wykonywania funkcji odblaskowych .

EDYCJA: Bycie językiem refleksyjnym bez ścisłych prywatnych metod sprawia, że ​​Objective-C jest bardziej „pythonowe”, ponieważ ufasz innym ludziom, którzy używają Twojego kodu, zamiast ograniczać metody, które mogą wywoływać. Stosowanie konwencji nazewnictwa, takich jak podwójne podkreślenia, ma na celu ukrycie kodu przed zwykłym programistą klienta, ale nie powstrzyma programistów przed bardziej poważną pracą.

pokstad
źródło
A jeśli konsumenci twojej lib mogą odzwierciedlać twoje prywatne metody, czy nie mogliby ich również zadzwonić?
Jared Updike
Jeśli wiedzą, gdzie szukać, czy nie w ten sposób ludzie używają nieudokumentowanych bibliotek na iPhonie? Problem z iPhonem polega na tym, że ryzykujesz, że Twoja aplikacja nie zostanie zaakceptowana do korzystania z prywatnego interfejsu API.
pokstad
Jeśli uzyskają dostęp do prywatnych metod poprzez refleksję, dostaną to, na co zasługują. Wszelkie błędy nie są twoją winą.
gnasher729
1

Istnieją dwie odpowiedzi w zależności od interpretacji pytania.

Pierwsza polega na ukryciu implementacji metody w interfejsie. Jest to używane, zazwyczaj z kategorią bez nazwy (np @interface Foo().). Pozwala to obiektowi na wysyłanie tych wiadomości, ale nie innych - chociaż nadal można je zastąpić przypadkowo (lub w inny sposób).

Druga odpowiedź, przy założeniu, że chodzi o wydajność i inlining, jest możliwa, ale zamiast tego jako lokalna funkcja C. Jeśli chciałeś „prywatny Foo ( NSString *arg)” metoda, to zrobi void MyClass_foo(MyClass *self, NSString *arg)i wywołać ją jako funkcję C podobnego MyClass_foo(self,arg). Składnia jest inna, ale działa z rozsądnym rodzajem charakterystyk wydajności prywatnych metod C ++.

Chociaż to odpowiada na pytanie, powinienem zauważyć, że kategoria bez nazwy jest zdecydowanie bardziej powszechnym sposobem realizacji celu C.

AlBlue
źródło
0

Objective-C nie obsługuje metod prywatnych, ponieważ ich nie potrzebuje.

W C ++ każda metoda musi być widoczna w deklaracji klasy. Nie możesz mieć metod, których ktoś zawierający plik nagłówkowy nie może zobaczyć. Więc jeśli chcesz, aby metody, których kod poza twoją implementacją nie były używane, nie masz wyboru, kompilator musi dać ci jakieś narzędzie, abyś mógł powiedzieć, że metody nie wolno używać, to jest słowo kluczowe „private”.

W Objective-C możesz mieć metody, których nie ma w pliku nagłówkowym. Więc bardzo łatwo osiągniesz ten sam cel, nie dodając metody do pliku nagłówkowego. Nie ma potrzeby stosowania prywatnych metod. Objective-C ma również tę zaletę, że nie musisz ponownie kompilować każdego użytkownika klasy, ponieważ zmieniłeś metody prywatne.

Na przykład zmienne, które musiałeś zadeklarować w pliku nagłówkowym (już nie), dostępne są @private, @public i @protected.

gnasher729
źródło
0

Brakuje tu odpowiedzi: ponieważ metody prywatne to zły pomysł z punktu widzenia ewolucji. Może wydawać się dobrym pomysłem ustawienie metody jako prywatnej podczas jej pisania, ale jest to forma wczesnego wiązania. Kontekst może się zmienić, a późniejszy użytkownik może chcieć użyć innej implementacji. Trochę prowokacyjne: „Zwinni programiści nie używają metod prywatnych”

W pewnym sensie, podobnie jak Smalltalk, Objective-C jest dla dorosłych programistów. Cenimy wiedzę o tym, jaki powinien być interfejs, i bierzemy odpowiedzialność za radzenie sobie z konsekwencjami, jeśli zajdzie potrzeba zmiany implementacji. Więc tak, to filozofia, a nie implementacja.

Stephan Eggermont
źródło
Głosowałem za tym, ponieważ nie zasługuje na głosowanie negatywne. Jak mówi Stephan, istnieje argument, że metody „prywatne” nie są konieczne i jest podobny do argumentów dotyczących pisania dynamicznego i statycznego, w tym sensie, że niezależnie od preferowanego wniosku, obie strony mają rację.
alastair