W jakich sytuacjach musimy wpisać __autoreasing kwalifikator własności w ramach ARC?

118

Próbuję rozwiązać zagadkę.

__strongjest wartością domyślną dla wszystkich zachowywalnych wskaźników obiektów Objective-C, takich jak NSObject, NSString itp. Jest to silne odniesienie. ARC równoważy to z a -releasena końcu zakresu.

__unsafe_unretainedrówna się starej drodze. Służy do słabego wskaźnika bez zatrzymywania obiektu możliwego do zachowania.

__weakjest podobna __unsafe_unretainedz tą różnicą, że jest to słabe odniesienie z automatycznym zerowaniem, co oznacza, że ​​wskaźnik zostanie ustawiony na zero, gdy tylko obiekt, do którego się odwołuje, zostanie zwolniony. Eliminuje to niebezpieczeństwo zwisających wskaźników i błędów EXC_BAD_ACCESS.

Ale do czego dokładnie się __autoreleasingprzydaje? Trudno mi znaleźć praktyczne przykłady, kiedy muszę używać tego kwalifikatora. Uważam, że jest to tylko dla funkcji i metod, które oczekują wskaźnika-wskaźnika, takiego jak:

- (BOOL)save:(NSError**);

lub

NSError *error = nil;
[database save:&error];

które zgodnie z ARC należy zadeklarować w ten sposób:

- (BOOL)save:(NSError* __autoreleasing *);

Ale to jest zbyt niejasne i chciałbym w pełni zrozumieć, dlaczego . Fragmenty kodu, które znajduję, umieszczają __autoreleasing pomiędzy dwiema gwiazdkami, co dla mnie wygląda dziwnie. Typ to NSError**(wskaźnik-wskaźnik do NSError), więc po co umieszczać je __autoreleasingpomiędzy gwiazdami, a nie tylko przed nimi NSError**?

Mogą też istnieć inne sytuacje, na których muszę polegać __autoreleasing.

Dumny członek
źródło
1
Mam to samo pytanie, a odpowiedzi poniżej nie są do końca przekonujące ... na przykład, dlaczego system nie zapewnia interfejsów, które przyjmują argumenty NSError ** zadeklarowane za pomocą dekoratora __autoreleasing, takiego jak ty, a informacje o przejściu do wydania łuku mówią, że tak Powinien być? np. dowolna z wielu z tych procedur w NSFileManager.h?
Tata

Odpowiedzi:

67

Masz rację. Jak wyjaśnia oficjalna dokumentacja:

__autoreleasing w celu oznaczenia argumentów, które są przekazywane przez referencję (id *) i są automatycznie zwalniane po powrocie.

Wszystko to jest bardzo dobrze wyjaśnione w przewodniku przejścia ARC .

W Twoim przykładzie NSError deklaracja oznacza __strongniejawnie:

NSError * e = nil;

Zostanie przekształcony w:

NSError * __strong error = nil;

Kiedy wywołujesz swoją savemetodę:

- ( BOOL )save: ( NSError * __autoreleasing * );

Kompilator będzie musiał wtedy utworzyć zmienną tymczasową ustawioną na __autoreleasing. Więc:

NSError * error = nil;
[ database save: &error ];

Zostanie przekształcony w:

NSError * __strong error = nil;
NSError * __autoreleasing tmpError = error;
[ database save: &tmpError ];
error = tmpError;

Możesz tego uniknąć, deklarując __autoreleasingbezpośrednio obiekt błędu jako .

Macmade
źródło
3
Nie, __autoreleasingjest używany tylko dla argumentów przekazywanych przez odwołanie. Jest to szczególny przypadek, ponieważ masz wskaźnik do wskaźnika obiektu. Tak nie jest w przypadku takich rzeczy, jak wygodne konstruktory, ponieważ zwracają one tylko wskaźnik do obiektu, a ARC obsługuje go automatycznie.
Macmade
7
Dlaczego kwalifikator __autoreleasing jest umieszczony pomiędzy gwiazdami, a nie tylko przed NSError **? Wydaje mi się to dziwne, ponieważ typ to NSError **. A może dlatego, że ma to na celu wskazanie, że wskaźnik wskazujący na NSError * musi zostać zakwalifikowany jako wskazujący na obiekt autoreleased?
Dumny członek
1
@Proud Member, jeśli chodzi o Twój pierwszy komentarz - to jest niepoprawne (jeśli dobrze cię rozumiem) - zobacz odpowiedź Glen Low poniżej. Obiekt błędu jest tworzony i przypisywany do zmiennej autouzupełniania (tej, którą przekazałeś) wewnątrz funkcji składowania . To przypisanie powoduje, że obiekt zostaje w tym czasie zachowany i automatycznie wydany. Deklaracja funkcji save uniemożliwia nam wysłanie jej czegokolwiek innego niż zmienna autoreleasingowa, ponieważ tego właśnie potrzebuje - dlatego kompilator tworzy zmienną tymczasową, jeśli spróbujemy.
Colin,
2
Dlaczego więc żaden z interfejsów Apple nie wydaje się mieć tego? np. wszystko w NSFileManager.h?
Tata
1
@Macmade: Przypadkiem zauważyłem, że Twoja odpowiedź została zredagowana (przez stackoverflow.com/users/12652/comptrol ) i mam wrażenie, że przynajmniej zmiany w Twoim pierwszym przykładzie („implicite… zostaną przekształcone w ...) są błędne, ponieważ kwalifikator __strong został przeniesiony z drugiej linii do pierwszej. Być może mógłbyś to sprawdzić.
Martin R
34

W związku z odpowiedzią Macmade i pytaniem uzupełniającym Dumnym członka w komentarzach (opublikowałby to również jako komentarz, ale przekracza maksymalną liczbę znaków):

Oto dlaczego kwalifikator zmiennej __autoreleasing jest umieszczony między dwiema gwiazdami.

Przedmowa, poprawna składnia deklarowania wskaźnika obiektu z kwalifikatorem to:

NSError * __qualifier someError;

Kompilator to wybaczy:

__qualifier NSError *someError;

ale to nie jest poprawne. Zobacz przewodnik przejścia na Apple ARC (przeczytaj sekcję rozpoczynającą się od „Powinieneś poprawnie dekorować zmienne ...”).

Aby odpowiedzieć na aktualne pytanie: Podwójny wskaźnik nie może mieć kwalifikatora zarządzania pamięcią ARC, ponieważ wskaźnik wskazujący na adres pamięci jest wskaźnikiem do typu pierwotnego, a nie wskaźnikiem do obiektu. Jednak gdy deklarujesz podwójny wskaźnik, ARC chce wiedzieć, jakie są zasady zarządzania pamięcią dla drugiego wskaźnika. Dlatego zmienne z podwójnym wskaźnikiem są określane jako:

SomeClass * __qualifier *someVariable;

Zatem w przypadku argumentu metody, który jest podwójnym wskaźnikiem NSError, typ danych jest deklarowany jako:

- (BOOL)save:(NSError* __autoreleasing *)errorPointer;

który w języku angielskim mówi „wskaźnik do __autoreleasing wskaźnika obiektu NSError”.

Binyamin Bauman
źródło
15

Ostateczna specyfikacja ARC mówi, że

W przypadku obiektów __autoreleasing nowy wskaźnik jest zachowywany, automatycznie zwalniany i przechowywany w lvalue przy użyciu semantyki prymitywnej.

Na przykład kod

NSError* __autoreleasing error = someError;

faktycznie zostanie przekonwertowany na

NSError* error = [[someError retain] autorelease];

... dlatego działa, gdy masz parametr NSError* __autoreleasing * errorPointer, wywołana metoda przypisze błąd do*errorPointer i uruchomi się powyższa semantyka.

Możesz użyć __autoreleasingw innym kontekście, aby zmusić obiekt ARC do puli autorelease, ale nie jest to zbyt użyteczne, ponieważ ARC wydaje się używać puli autorelease tylko podczas powrotu metody i już obsługuje to automatycznie.

Glen Low
źródło