Cel-C: różnica między id i void *


Odpowiedzi:


240

void * oznacza "odniesienie do jakiejś losowej porcji pamięci z nietypową / nieznaną zawartością"

id oznacza „odniesienie do jakiegoś losowego obiektu Objective-C o nieznanej klasie”

Istnieją dodatkowe różnice semantyczne:

  • W trybach GC Only lub GC Supported kompilator wyemituje bariery zapisu dla odwołań typu id, ale nie dla typu void *. Podczas deklarowania struktur może to być krytyczna różnica. Zadeklarowanie iVars podobnych do void *_superPrivateDoNotTouch;spowoduje przedwczesne zbieranie obiektów, jeśli _superPrivateDoNotTouchfaktycznie jest to obiekt. Nie rób tego.

  • próba wywołania metody na referencji void *typu spowoduje wyświetlenie ostrzeżenia kompilatora.

  • próba wywołania metody dla idtypu spowoduje ostrzeżenie tylko wtedy, gdy wywoływana metoda nie została zadeklarowana w żadnej z @interfacedeklaracji widzianych przez kompilator.

Dlatego nigdy nie należy odnosić się do obiektu jako do void *. Podobnie, należy unikać używania idzmiennej wpisanej w celu odniesienia się do obiektu. Użyj możliwie najbardziej szczegółowego odwołania do klasy. Nawet NSObject *jest lepsze niż idto, że kompilator może przynajmniej zapewnić lepszą walidację wywołań metod względem tego odwołania.

Jedynym typowym i prawidłowym zastosowaniem void *jest nieprzezroczyste odniesienie do danych, które jest przesyłane przez inny interfejs API.

Rozważ sortedArrayUsingFunction: context:metodę NSArray:

- (NSArray *)sortedArrayUsingFunction:(NSInteger (*)(id, id, void *))comparator context:(void *)context;

Funkcja sortowania zostanie zadeklarowana jako:

NSInteger mySortFunc(id left, id right, void *context) { ...; }

W tym przypadku NSArray po prostu przekazuje jako contextargument do metody wszystko, co przekażesz jako contextargument. Jeśli chodzi o NSArray, jest to nieprzezroczysta porcja danych o rozmiarze wskaźnika, a możesz jej używać w dowolnym celu.

Bez funkcji typu zamknięcia w języku jest to jedyny sposób na przeniesienie porcji danych z funkcją. Przykład; jeśli chcesz, aby mySortFunc () warunkowo sortowało z uwzględnieniem wielkości liter lub bez rozróżniania wielkości liter, jednocześnie zachowując bezpieczeństwo wątków, przekazałbyś wskaźnik rozróżniający wielkość liter w kontekście, prawdopodobnie rzucając przy wejściu i wyjściu.

Kruchy i podatny na błędy, ale jedyny sposób.

Bloki rozwiązują ten problem - Bloki są zamknięciami dla C. Są dostępne w Clang - http://llvm.org/ i są wszechobecne w Snow Leopardzie ( http://developer.apple.com/library/ios/documentation/Performance /Reference/GCD_libdispatch_Ref/GCD_libdispatch_Ref.pdf ).


Dodatkowo, jestem prawie pewien, że idzakłada się , że an odpowiada -retaini -release, podczas gdy a void*jest całkowicie nieprzezroczysty dla wywoływanego. Nie możesz przekazać dowolnego wskaźnika do -performSelector:withObject:afterDelay:(zachowuje obiekt) i nie możesz założyć, że +[UIView beginAnimations:context:]zachowa kontekst (delegat animacji powinien zachować własność kontekstu; UIKit zachowuje delegata animacji).
— tc.

3
Nie można poczynić żadnych założeń co do tego, na co idodpowiada an . An idmoże łatwo odwołać się do instancji klasy, która nie pochodzi z NSObject. Jednak praktycznie rzecz biorąc, twoje stwierdzenie najlepiej pasuje do zachowań w świecie rzeczywistym; nie możesz mieszać nie <NSObject>implementujących klas z Foundation API i zajść bardzo daleko, to na pewno!
— bbum

2
Teraz idi Classtypy są traktowane jako utrzymywalny wskaźnik obiektu w ARC. Tak więc założenie jest prawdziwe przynajmniej w przypadku ARC.
— eonil

Co to jest „przedwczesne żniwa”?
— Brad Thomas,

1
@BradThomas Gdy garbage collector zbiera pamięć przed zakończeniem programu.
— bbum

21

id jest wskaźnikiem do obiektywnego obiektu C, gdzie as void * jest wskaźnikiem do czegokolwiek.

id wyłącza również ostrzeżenia związane z wywoływaniem nieznanych metod, a więc na przykład:

[(id)obj doSomethingWeirdYouveNeverHeardOf];

nie daje zwykłego ostrzeżenia o nieznanych metodach. Oczywiście zgłosi wyjątek w czasie wykonywania, chyba że obj jest nil lub naprawdę implementuje tę metodę.

Często powinieneś używać NSObject*lub id<NSObject>zamiast tego id, co przynajmniej potwierdza, że ​​zwrócony obiekt jest obiektem Cocoa, więc możesz bezpiecznie używać na nim metod takich jak retain / release / autorelease.


2
W przypadku wywołań metod element docelowy typu (id) wygeneruje ostrzeżenie, jeśli metoda docelowa nie została nigdzie zadeklarowana. Tak więc, w twoim przykładzie, musiało zostać gdzieś zadeklarowane polecenie „wykonajSomethingWeirdYouveNeverHeardOf”, aby nie było ostrzeżenia.
— bbum

Masz rację, lepszym przykładem byłoby coś w rodzaju StoragePolicy.
— Peter N Lewis

@PeterNLewis Nie zgadzam się Often you should use NSObject*zamiast tego id. Określając NSObject*, w rzeczywistości wyraźnie mówisz, że obiekt jest obiektem NSO. Każde wywołanie metody do obiektu spowoduje ostrzeżenie, ale nie będzie wyjątku w czasie wykonywania, dopóki ten obiekt rzeczywiście odpowie na wywołanie metody. Ostrzeżenie jest oczywiście denerwujące, więc idjest lepsze. Z grubsza można wtedy być bardziej szczegółowym, na przykład mówiąc id<MKAnnotation>, co w tym przypadku oznacza, że ​​niezależnie od obiektu, musi on być zgodny z protokołem MKAnnotation.
— pnizzle

1
Jeśli zamierzasz używać id <MKAnnotation>, równie dobrze możesz użyć NSObject <MKAnnotation> *. Co następnie pozwala na użycie dowolnej metody w MKAnnotation i dowolnej metody w NSObject (tj. Wszystkich obiektów w normalnej hierarchii klas głównych NSObject) i uzyskanie ostrzeżenia o czymkolwiek innym, co jest znacznie lepsze niż brak ostrzeżenia i awaria środowiska wykonawczego.
— Peter N Lewis

8

Jeśli metoda ma zwracany typ id , możesz zwrócić dowolny obiekt Objective-C.

void oznacza, że ​​metoda nic nie zwróci.

void *jest tylko wskazówką. Nie będziesz w stanie edytować treści pod adresem wskazywanym przez wskaźnik.


2
Ponieważ dotyczy to zwracanej wartości metody, przeważnie słusznej. Jeśli chodzi o deklarowanie zmiennych lub argumentów, nie do końca. Zawsze możesz rzucić (void *) na bardziej konkretny typ, jeśli chcesz przeczytać / zapisać zawartość - nie jest to dobry pomysł, aby to zrobić.
— bbum

8

idjest wskaźnikiem do obiektu Objective-C. void *jest wskaźnikiem do czegokolwiek . Możesz użyć void *zamiast id, ale nie jest to zalecane, ponieważ nigdy nie otrzymasz ostrzeżeń kompilatora o niczym.

Możesz zobaczyć stackoverflow.com/questions/466777/whats-the-difference-between-declaring-a-variable-id-and-nsobject i unixjunkie.blogspot.com/2008/03/id-vs-nsobject-vs -id.html .


1
Nie do końca. Zmienne wpisane (void *) nie mogą w ogóle być celem wywołań metod. Powoduje to wyświetlenie z kompilatora komunikatu „ostrzeżenie: nieprawidłowy typ odbiornika 'void *'”.
— bbum

@bbum: void *typowane zmienne z całą pewnością mogą być celem wywołań metod - to ostrzeżenie, a nie błąd. Nie tylko, że można to zrobić: int i = (int)@"Hello, string!";a skontaktujemy się z: printf("Sending to an int: '%s'\n", [i UTF8String]);. To ostrzeżenie, a nie błąd (i niezupełnie zalecane ani przenośne). Ale powodem, dla którego możesz robić te rzeczy, jest podstawowe C.
— johne

1
Przepraszam. Masz rację; jest to ostrzeżenie, a nie błąd. Po prostu traktuję ostrzeżenia jako błędy zawsze i wszędzie.
— bbum

4
/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

Powyższy kod pochodzi z objc.h, więc wygląda na to, że id jest instancją struktury objc_object, a wskaźnik isa może wiązać się z dowolnym obiektem klasy Objective C, podczas gdy void * jest po prostu wskaźnikiem bez typu.


2

Rozumiem, że id reprezentuje wskaźnik do obiektu, podczas gdy void * może wskazywać na wszystko, o ile następnie rzutujesz go na typ, w którym chcesz go użyć


Jeśli rzutujesz z (void *) do jakiegoś typu obiektu, w tym id, prawdopodobnie robisz to źle. Istnieją powody, aby to zrobić, ale są one nieliczne, odległe i prawie zawsze wskazują na wadę konstrukcyjną.
— bbum

1
cytuj „są ku temu powody, ale jest ich niewiele, daleko między” prawda. To zależy od sytuacji. Jednak nie wypowiedziałbym ogólnego stwierdzenia typu „prawdopodobnie robisz to źle” bez kontekstu.
— hhafez

Złożyłbym ogólne oświadczenie; musiał wytropić i naprawić zbyt wiele przeklętych błędów z powodu rzucania na niewłaściwy typ z pustką * pomiędzy. Jedynym wyjątkiem są interfejsy API oparte na wywołaniach zwrotnych, które przyjmują argument void * context, którego kontrakt stanowi, że kontekst pozostanie nietknięty między ustawieniem wywołania zwrotnego a odebraniem go.
— bbum

0

Oprócz tego, co już zostało powiedziane, istnieje różnica między obiektami a wskaźnikami związanymi z kolekcjami. Na przykład, jeśli chcesz umieścić coś w NSArray, potrzebujesz obiektu (typu „id”) i nie możesz tam użyć wskaźnika surowych danych (typu „void *”). Możesz użyć [NSValue valueWithPointer:rawData]do przekonwertowania void *rawDdatana typ „id”, aby użyć go w kolekcji. Ogólnie „id” jest bardziej elastyczny i ma więcej semantyki związanej z dołączonymi do niego obiektami. Jest więcej przykładów wyjaśniających typ id Objective C tutaj .

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.