Em Objective-C, por que devo verificar se self = não é nulo?

Eu tenho uma pergunta geral sobre como escrever methods init em Objective-C.

Eu vejo em todos os lugares (código da Apple, livros, código-fonte aberto, etc.) que um método init deve verificar se auto = [super init] não é nulo antes de continuar com a boot.

O modelo padrão da Apple para um método init é:

- (id) init { self = [super init]; if (self != nil) { // your code here } return self; } 

Por quê?

Quero dizer, quando é que o init vai devolver nada? Se eu chamei init no NSObject e tenho nulo de volta, então algo deve estar realmente errado, certo? E nesse caso, você pode nem escrever um programa …

É realmente tão comum que um método init de class possa retornar nulo? Se sim, em que caso e por quê?

Por exemplo:

 [[NSData alloc] initWithContentsOfFile:@"this/path/doesn't/exist/"]; [[NSImage alloc] initWithContentsOfFile:@"unsupportedFormat.sjt"]; [NSImage imageNamed:@"AnImageThatIsntInTheImageCache"]; 

… e assim por diante. (Nota: O NSData pode lançar uma exceção se o arquivo não existir). Existem algumas áreas onde o retorno nulo é o comportamento esperado quando ocorre um problema, e por isso é uma prática padrão verificar nulo praticamente o tempo todo, por uma questão de consistência.

Esse idioma específico é padrão porque funciona em todos os casos.

Embora incomum, haverá casos em que …

 [super init]; 

… retorna uma instância diferente, exigindo, portanto, a atribuição para si mesmo.

E haverá casos em que ele retornará nulo, exigindo, portanto, a verificação nula para que seu código não tente inicializar um slot variável de instância que não existe mais.

A linha inferior é que é o padrão correto documentado para usar e, se você não estiver usando, você está fazendo errado.

Acho que, na maioria das classs, se o valor de retorno de [super init] é nulo e você o verifica, conforme recomendado pelas práticas padrão, e retorna prematuramente se nulo, basicamente seu aplicativo ainda não funcionará corretamente. Se você pensar sobre isso, mesmo que a verificação if (self! = Nil) esteja lá, para uma operação adequada da sua class, 99,99% do tempo em que você realmente precisa ser não-nulo. Agora, suponha, por qualquer razão, [super init] retornou nulo, basicamente seu cheque contra nulo é basicamente repassar o dinheiro para o chamador da sua class, onde provavelmente ele falhará de qualquer forma, já que naturalmente assumirá que a binding foi bem sucedido.

Basicamente, o que eu estou querendo dizer é que 99,99% do tempo, o if (self! = Nil) não te compra nada em termos de maior robustez, já que você está apenas passando o dinheiro para o seu invocador. Para realmente conseguir lidar com isso de maneira robusta, você precisaria realmente inserir verificações em toda a sua hierarquia de chamadas. E mesmo assim, a única coisa que você compraria é que seu aplicativo falharia um pouco mais limpa / robustamente. Mas ainda assim falharia.

Se uma class de biblioteca arbitrariamente decidiu retornar nula como resultado de um [super init], você está bem foda de qualquer maneira, e isso é mais uma indicação de que o escritor da class de biblioteca cometeu um erro de implementação.

Acho que isso é mais uma sugestão de codificação herdada, quando os aplicativos são executados em uma memory muito mais limitada.

Mas, para o código de nível C, eu normalmente ainda verificaria o valor de retorno de malloc () em relação a um ponteiro NULL. Considerando que, para Objective-C, até que eu encontre evidências em contrário, eu acho que geralmente vou pular os cheques if (self! = Nil). Por que a discrepância?

Porque, nos níveis C e malloc, em alguns casos você pode recuperar parcialmente. Considerando que eu acho que em Objective-C, em 99,99% dos casos, se [super init] retornar nulo, você está basicamente foder, mesmo se você tentar lidar com isso. Você pode também deixar o aplicativo travar e lidar com as conseqüências.

Este é um resumo dos comentários acima.

Vamos dizer que a superclass retorna nil . O que vai acontecer?

Se você não seguir as convenções

Seu código irá travar no meio do seu método init . (a menos que o init não faça nada de significante)

Se você seguir as convenções, não sabendo que a superclass pode voltar a zero (a maioria das pessoas acaba aqui)

Seu código provavelmente irá falhar em algum momento depois, porque sua instância é nil , onde você esperava algo diferente. Ou o seu programa vai se comportar de forma inesperada sem bater. Oh céus! Queres isto? Eu não sei…

Se você seguir as convenções, permitindo voluntariamente que sua subclass retorne nula

A documentação do seu código (!) Deve indicar claramente: “retorna … ou nulo”, e o restante do seu código precisa estar preparado para lidar com isso. Agora faz sentido.

Normalmente, se sua class deriva diretamente do NSObject , não será necessário. No entanto, é um bom hábito entrar, como se sua class derivasse de outras classs, seus inicializadores retornassem nil e, em caso afirmativo, seu inicializador poderia capturá-la e comportar-se corretamente.

E sim, para registro, eu sigo as melhores práticas e as escrevo em todas as minhas aulas, mesmo aquelas derivadas diretamente do NSObject .

Você está certo, você poderia escrever apenas [super init] , mas isso não funcionaria para uma subclass de qualquer coisa. As pessoas preferem apenas memorizar uma linha de código padrão e usá-la o tempo todo, mesmo quando às vezes é necessário, e assim obtemos o padrão if (self = [super init]) , que tem a possibilidade de nulo ser retornado e a possibilidade de um object diferente de self ser devolvido em conta.

Um erro comum é escrever

 self = [[super alloc] init]; 

que retorna uma instância da superclass, que NÃO é o que você quer em um construtor / init de subclass. Você recebe de volta um object que não responde aos methods da subclass, o que pode ser confuso, e gera erros confusos sobre não responder a methods ou identificadores não encontrados, etc.

 self = [super init]; 

é necessário se a superclass tiver membros (variables ​​ou outros objects) para inicializar primeiro antes de configurar os membros das subclasss. Caso contrário, o tempo de execução do objc inicializa todos para 0 ou para zero . ( ao contrário do ANSI C, que geralmente aloca pedaços de memory sem limpá-los )

E sim, a boot da class base pode falhar devido a erros de falta de memory, componentes ausentes, falhas de aquisição de resources, etc., portanto, uma verificação de nil é recomendável e leva menos de alguns milissegundos.

Isso é para verificar que a boot funcionou, a instrução if retorna true se o método init não retornou nil, portanto, é uma maneira de verificar a criação do object funcionando corretamente. Algumas razões que eu posso pensar de que o init pode falhar talvez seja um método de reboot que a super class não conhece ou algo do tipo, eu não acho que é tão comum assim. Mas se isso acontecer, é melhor que aconteça um acidente que eu suponho, então é sempre verificado …

No OS X, não é tão provável que -[NSObject init] falhe por motivos de memory. O mesmo não pode ser dito para o iOS.

Além disso, é uma boa prática para escrever quando subclassing uma class que pode retornar nil por qualquer motivo.