Em quais situações precisamos escrever o qualificador de propriedade __autoreleasing sob ARC?

118

Estou tentando resolver o quebra-cabeça.

__strongé o padrão para todos os ponteiros de objetos retíveis do Objective-C como NSObject, NSString, etc. É uma referência forte. O ARC o equilibra com um -releaseno final do escopo.

__unsafe_unretainedé igual à velha maneira. É usado para um ponteiro fraco sem reter o objeto retentível.

__weaké semelhante, __unsafe_unretainedexceto que é uma referência fraca de zeragem automática, o que significa que o ponteiro será definido como nulo assim que o objeto referenciado for desalocado. Isso elimina o perigo de ponteiros pendentes e erros EXC_BAD_ACCESS.

Mas para que serve exatamente __autoreleasing? Estou tendo dificuldade em encontrar exemplos práticos de quando preciso usar este qualificador. Acredito que seja apenas para funções e métodos que esperam um ponteiro-ponteiro, como:

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

ou

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

que sob ARC deve ser declarado desta forma:

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

Mas isso é muito vago e gostaria de entender o porquê . Os trechos de código que encontro colocam o __autoreleasing entre as duas estrelas, o que me parece estranho. O tipo é NSError**(um ponteiro para NSError), então por que colocá-lo __autoreleasingentre as estrelas e não simplesmente na frente delas NSError**?

Além disso, pode haver outras situações nas quais devo confiar __autoreleasing.

Membro Orgulhoso
fonte
1
Eu tenho a mesma pergunta e as respostas abaixo não são totalmente convincentes ... por exemplo, por que o sistema não fornece interfaces que levam argumentos NSError ** declarados com o decorador __autoreleasing como você e as notas de versão de transição para Arc dizem que deveria estar? por exemplo, qualquer uma das muitas dessas rotinas em NSFileManager.h ??
Pai de

Respostas:

67

Você está certo. Conforme explica a documentação oficial:

__autoreleasing para denotar argumentos que são passados ​​por referência (id *) e são liberados automaticamente no retorno.

Tudo isso é muito bem explicado no guia de transição ARC .

Em seu exemplo NSError, a declaração significa __strong, implicitamente:

NSError * e = nil;

Será transformado em:

NSError * __strong error = nil;

Quando você chama seu savemétodo:

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

O compilador terá então que criar uma variável temporária, definida em __autoreleasing. Assim:

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

Será transformado em:

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

Você pode evitar isso declarando o objeto de erro como __autoreleasing, diretamente.

Macmade
fonte
3
Não, __autoreleasingsó é usado para argumentos passados ​​por referência. Este é um caso especial, pois você tem um ponteiro para o ponteiro de um objeto. Esse não é o caso de coisas como construtores de conveniência, já que eles apenas retornam um ponteiro para um objeto e o ARC o trata automaticamente.
Macmade
7
Por que o qualificador __autoreleasing é colocado entre as estrelas, e não apenas na frente de NSError **? Isso me parece estranho, pois o tipo é NSError **. Ou é porque isso está tentando indicar que o ponteiro NSError * apontado deve ser qualificado como apontando para um objeto liberado automaticamente?
Membro orgulhoso de
1
Membro @Proud em relação ao seu primeiro comentário - incorreto (se bem entendi) - veja a resposta de Glen Low abaixo. O objeto de erro é criado e atribuído a uma variável de liberação automática (aquela que você passou) dentro da função salvar. Esta atribuição faz com que o objeto seja retido e liberado automaticamente naquele momento. A declaração da função save nos impede de enviá-la qualquer coisa que não seja uma variável de liberação automática porque é disso que ela precisa - é por isso que o compilador cria uma variável temporária se tentarmos.
Colin de
2
Então, por que nenhuma das interfaces da Apple parece ter isso? por exemplo, tudo em NSFileManager.h?
Pai
1
@Macmade: Por acaso notei que sua resposta foi editada (por stackoverflow.com/users/12652/comptrol ) e tenho a impressão de que pelo menos as alterações em seu primeiro exemplo ("implicitamente ... serão transformadas em ...) estão errados, porque o qualificador __strong foi movido da segunda linha para a primeira linha. Talvez você possa verificar isso.
Martin R
34

Seguindo a resposta do Macmade e a pergunta de acompanhamento do membro orgulhoso nos comentários (também teria postado isso como um comentário, mas excede a contagem máxima de caracteres):

É aqui que o qualificador variável de __autoreleasing é colocado entre as duas estrelas.

Para começar, a sintaxe correta para declarar um ponteiro de objeto com um qualificador é:

NSError * __qualifier someError;

O compilador vai perdoar isso:

__qualifier NSError *someError;

mas não está correto. Consulte o guia de transição do Apple ARC (leia a seção que começa com "Você deve decorar as variáveis ​​corretamente ...").

Para responder à questão em questão: Um ponteiro duplo não pode ter um qualificador de gerenciamento de memória ARC porque um ponteiro que aponta para um endereço de memória é um ponteiro para um tipo primitivo, não um ponteiro para um objeto. No entanto, quando você declara um ponteiro duplo, o ARC deseja saber quais são as regras de gerenciamento de memória para o segundo ponteiro. É por isso que as variáveis ​​de ponteiro duplo são especificadas como:

SomeClass * __qualifier *someVariable;

Portanto, no caso de um argumento de método que é um ponteiro NSError duplo, o tipo de dados é declarado como:

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

que em inglês diz "ponteiro para um ponteiro de objeto __autoreleasing NSError".

Binyamin Bauman
fonte
15

A especificação ARC definitiva diz que

Para objetos __autoreleasing, o novo ponteiro é retido, liberado automaticamente e armazenado no lvalue usando semântica primitiva.

Por exemplo, o código

NSError* __autoreleasing error = someError;

na verdade é convertido para

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

... é por isso que funciona quando você tem um parâmetro NSError* __autoreleasing * errorPointer, o método chamado atribuirá o erro a *errorPointere a semântica acima será ativada.

Você poderia usar __autoreleasingem um contexto diferente para forçar um objeto ARC no pool de autorelease, mas isso não é muito útil, uma vez que o ARC só parece usar o pool de autorelease no retorno do método e já controla isso automaticamente.

Glen Low
fonte