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.