No Objective-C, por que devo verificar se self = [super init] não é nulo?

165

Eu tenho uma pergunta geral sobre como escrever métodos init no 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 self = [super init] não é nulo antes de continuar com a inicialização.

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 o init nunca retornará nada? Se eu chamei init no NSObject e não obtive nada, então algo deve estar realmente ferrado, certo? E, nesse caso, você também pode nem escrever um programa ...

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

Jasarien
fonte
1
Wil Shipley postou um artigo relacionado a isso há algum tempo. [self = [init estúpido];] ( wilshipley.com/blog/2005/07/self-stupid-init.html ) Leia também os comentários, algumas coisas boas.
Ryan Townshend
6
Você poderia perguntar a Wil Shipley ou Mike Ash ou Matt Gallagher . De qualquer maneira, é um tópico debatido. Mas geralmente é bom ficar com os idiomas da Apple ... afinal, são os frameworks deles.
jbrennan
1
Parece que Wil estava argumentando mais por não se redesignar cegamente durante o init, sabendo que [super init] pode não retornar o receptor.
Jasarien
3
Wil mudou de idéia desde que o post foi feito originalmente.
bbum
5
Eu já tinha visto essa pergunta há algum tempo e a encontrei novamente. Perfeito. +1
Dan Rosenstark

Respostas:

53

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 gerar uma exceção se o arquivo não existir). Existem algumas áreas em que retornar nulo é o comportamento esperado quando um problema ocorre e, por causa disso, é prática comum verificar nulo praticamente o tempo todo, por uma questão de consistência.

iKenndac
fonte
10
Sim, mas este não é DENTRO do método init da respectiva classe. NSData herda de NSObject. O NSData verifica se [super init] retorna nulo? É o que estou perguntando aqui. Desculpe se eu não estava claro ...
Jasarien
9
Se você subclasse essas classes, haveria uma chance bastante real de que [super init] retorne zero. Nem todas as classes são subclasses diretas de NSObject. Nunca há garantia de que ele não retornará nada. É apenas uma prática de codificação defensiva que geralmente é incentivada.
213 Chuck
7
Duvido que o NSObject init possa retornar nulo em qualquer caso, sempre. Se você estiver com falta de memória, a alocação falhará, mas se for bem-sucedida, duvido que o init possa falhar - o NSObject nem sequer possui variáveis ​​de instância, exceto Class. No GNUStep, é implementado como apenas "return self", e desmontar no Mac é o mesmo. Tudo isso, é claro, é irrelevante - basta seguir o idioma padrão e você não terá que se preocupar se ele pode ou não.
Peter N Lewis
9
Não estou decidido a seguir as melhores práticas. Gostaria, no entanto, de saber por que elas são as melhores práticas em primeiro lugar. É como ser dito para pular de uma torre. Você não apenas faz isso, a menos que saiba o porquê. Existe um travesseiro grande e macio para pousar no fundo, com uma recompensa em dinheiro maciça? Se eu soubesse que eu pularia. Se não, eu não faria. Eu não quero apenas seguir cegamente uma prática sem saber por que a estou seguindo ...
Jasarien
4
Se alocação retornar nulo, o init será enviado para nulo, o que sempre resultará em nulo, e isso acabará sendo auto nulo.
T.
50

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, assim, a atribuição a si próprio.

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

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

bbum
fonte
3
Isso ainda é verdade à luz dos especificadores de nulidade? Se o inicializador da minha superclasse não for nulo, será que ainda vale a pena desorganizar ainda mais? (Embora o NSObject em si pareça não ter nada a ver com seu -initafeto…) #
natevw 17/12/15
Existe sempre um caso em que [super init]retorna nulo quando a superclasse direta é NSObject? Não é este o caso em que "tudo está quebrado?"
Dan Rosenstark
1
@DanRosenstark Não, se NSObjecté a superclasse direta. Mas ... mesmo se você declarar NSObjectcomo superclasse direta, algo poderia ter sido modificado em tempo de execução, de modo que NSObjecta implementação de initnão seja o que realmente é chamado.
bbum 9/07
1
Muito obrigado @bbum, isso realmente me ajudou a orientar em algumas correções de bugs. É bom descartar algumas coisas!
Dan Rosenstark
26

Acho que, na maioria das classes, se o valor de retorno de [super init] for nulo e você o verificar, conforme recomendado pelas práticas padrão, e retornar prematuramente se for nulo, basicamente seu aplicativo ainda não funcionará corretamente. Se você pensar sobre isso, apesar de que, se (auto! = Nil) verificação está lá, para o funcionamento adequado de sua classe, 99,99% do tempo que você realmente fazer necessidade de auto para ser não-nulo. Agora, suponha que, por qualquer motivo, [super init] tenha retornado nulo, basicamente seu cheque contra nulo está basicamente passando a quantia para o chamador de sua classe, onde provavelmente falharia de qualquer maneira, já que naturalmente assumirá que a chamada foi bem sucedido.

Basicamente, o que eu entendo é que 99,99% do tempo, o if (self! = Nil) não compra nada para você em termos de maior robustez, já que você está apenas passando o dinheiro para o invocador. Para realmente ser capaz de lidar com isso de maneira robusta, você realmente precisa verificar a hierarquia de chamadas inteira. E mesmo assim, a única coisa que você compraria é que seu aplicativo falharia um pouco mais limpa / robusta. Mas ainda falharia.

Se uma classe de biblioteca decidiu arbitrariamente retornar nulo como resultado de um [superinicial], você está praticamente fodido de qualquer maneira, e isso é mais uma indicação de que o escritor da classe 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 muito mais memória limitada.

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

Porque, nos níveis C e malloc, em alguns casos você pode recuperar parcialmente. Enquanto eu penso no Objective-C, em 99,99% dos casos, se o [super init] retornar nulo, você está basicamente fodido, mesmo que tente lidar com isso. Você também pode deixar o aplicativo falhar e lidar com as consequências.

Gino
fonte
6
Bem dito. Eu concordo.
Roger CS Wernersson
3
+1 Eu concordo totalmente. Apenas uma observação secundária: não acredito que o padrão seja resultado de momentos em que a alocação falhou com mais frequência. A alocação geralmente já é feita no momento em que o init é chamado. Se a alocação falhar, o init nem será chamado.
Nikolai Ruhe
8

Este é um resumo dos comentários acima.

Digamos que a superclasse retorne nil. O que vai acontecer?

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

Seu código falhará no meio do seu initmétodo. (a menos initque nada tenha significado)

Se você seguir as convenções, sem saber que a superclasse pode retornar nula (a maioria das pessoas acaba aqui)

Seu código provavelmente travará em algum momento depois, porque sua instância é nilonde você esperava algo diferente. Ou seu programa se comportará inesperadamente sem travar. Oh céus! Queres isto? Eu não sei...

Se você seguir as convenções, permitindo que sua subclasse retorne nulo

Sua documentação de 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.

user123444555621
fonte
5
O ponto interessante aqui, eu acho, é que a opção 1 é claramente preferível à opção 2. Se houver genuinamente circunstâncias nas quais você possa querer que o init da sua subclasse retorne nulo, será preferível o número 3. Se a única razão para isso acontecer é devido a um erro no seu código, use o número 1. O uso da opção 2 apenas atrasa a explosão do aplicativo até um momento posterior e, assim, torna seu trabalho quando você depura o erro com muito mais força. É como capturar exceções silenciosamente e continuar sem lidar com elas.
Mark Amery
Ou você alternar para rápidos e usar apenas opcionais
AndrewSB
7

Normalmente, se sua classe deriva diretamente NSObject, você não precisará. No entanto, é um bom hábito entrar, como se sua classe deriva de outras classes, os inicializadores podem retornar nile, nesse caso, o inicializador pode capturar isso e se comportar corretamente.

E sim, para constar, eu sigo as melhores práticas e as escrevo em todas as minhas aulas, mesmo nas que derivam diretamente NSObject.

John Rudy
fonte
1
Com isso em mente, seria uma boa prática verificar nulo depois de inicializar uma variável e antes de chamar funções nela? por exemploFoo *bar = [[Foo alloc] init]; if (bar) {[bar doStuff];}
dev_does_software
Herdar de NSObjectnão garante o que -initvocê oferece NSObjecttambém, se contar tempos de execução exóticos como versões mais antigas do GNUstep em (que retorna GSObject), então não importa o que seja, uma verificação e uma atribuição.
Maxthon Chan
3

Você está certo, muitas vezes poderia escrever [super init], mas isso não funcionaria para uma subclasse 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 leva tanto a possibilidade de retorno nulo quanto a possibilidade de um objeto que não selfseja retornado. em conta.

andyvn22
fonte
3

Um erro comum é escrever

self = [[super alloc] init];

que retorna uma instância da superclasse, que NÃO é o que você deseja em um construtor de subclasse / init. Você recebe de volta um objeto que não responde aos métodos da subclasse, que pode ser confuso, e gera erros confusos sobre não responder a métodos ou identificadores não encontrados, etc.

self = [super init]; 

é necessário se a superclasse tiver membros (variáveis ​​ou outros objetos) para inicializar primeiro antes de configurar os membros das subclasses. Caso contrário, o tempo de execução objc inicializa todos eles para 0 ou para zero . ( ao contrário do ANSI C, que geralmente aloca pedaços de memória sem limpá-los )

E sim, a inicialização da classe base pode falhar devido a erros de falta de memória, componentes ausentes, falhas na aquisição de recursos, etc., portanto, uma verificação de zero é sensata e leva menos de alguns milissegundos.

Chris Reid
fonte
2

Isso é para verificar se a intialização funcionou, a instrução if retorna true se o método init não retornar nulo, portanto, é uma maneira de verificar a criação do objeto que funcionou corretamente. Poucas razões pelas quais consigo pensar que o init pode falhar, talvez seja um método de inicialização anulado que a superclasse não conhece ou algo do tipo, eu não acho que isso seja tão comum. Mas se isso acontecer, não é melhor que aconteça uma falha que suponha, para que seja sempre verificada ...

Daniel
fonte
é, mas como são reunidos, o que acontece se alocar problemas?
Daniel
Eu imagino que, se a alocação falhar, o init será enviado a zero, em vez de uma instância de qualquer classe que você está chamando init. Nesse caso, nada acontecerá e nenhum código será executado para testar se o [super init] retornou ou não.
Jasarien 17/08/09
2
A alocação de memória nem sempre é feita em + alocação. Considere o caso de clusters de classe; um NSString não sabe qual subclasse específica usar até que o inicializador seja chamado.
bbum
1

No OS X, não é provável que haja -[NSObject init]falha devido a motivos de memória. O mesmo não pode ser dito para o iOS.

Além disso, é uma boa prática escrever quando subclassificar uma classe que possa retornar nilpor qualquer motivo.

MaddTheSane
fonte
2
No iOS e no Mac OS, -[NSObject init]é muito improvável que falhe devido a motivos de memória, pois não aloca nenhuma memória.
Nikolai Ruhe
Eu acho que ele quis dizer alloc, não o init :)
Thomas Tempelmann