NSObject + load and + initialize - O que eles fazem?

115

Estou interessado em entender as circunstâncias que levam um desenvolvedor a substituir + inicializar ou + carregar. A documentação deixa claro que esses métodos são chamados para você pelo tempo de execução Objective-C, mas isso é realmente tudo o que fica claro na documentação desses métodos. :-)

Minha curiosidade vem de olhar para o código de exemplo da Apple - MVCNetworking. Sua classe modelo tem um +(void) applicationStartupmétodo. Ele faz alguma manutenção no sistema de arquivos, lê NSDefaults, etc etc ... e, depois de tentar grok os métodos de classe de NSObject, parece que este trabalho de zeladoria pode ser colocado + carregar.

Eu modifiquei o projeto MVCNetworking, removendo a chamada no App Delegate para + applicationStartup e colocando os bits de manutenção em + carga ... meu computador não pegou fogo, mas isso não significa que está correto! Espero obter uma compreensão de todas as sutilezas, pegadinhas e outras coisas em torno de um método de configuração personalizado que você precisa chamar versus + carregar ou + inicializar.


Para + carregar a documentação diz:

A mensagem de carregamento é enviada para classes e categorias que são carregadas dinamicamente e vinculadas estaticamente, mas apenas se a classe ou categoria carregada recentemente implementar um método que possa responder.

Esta frase é confusa e difícil de analisar se você não souber o significado preciso de todas as palavras. Socorro!

  • O que significa "carregado dinamicamente e vinculado estaticamente?" Algo pode ser carregado dinamicamente E vinculado estaticamente ou são mutuamente exclusivos?

  • "... a classe ou categoria carregada recentemente implementa um método que pode responder" Que método? Responder como?


Quanto a + inicializar, a documentação diz:

inicializá-lo é invocado apenas uma vez por classe. Se você deseja realizar inicialização independente para a classe e para categorias da classe, você deve implementar métodos de carregamento.

Eu entendo que isso significa, "se você está tentando configurar a classe ... não use inicializar." OK tudo bem. Quando ou por que eu substituiria a inicialização?

edelaney05
fonte

Respostas:

184

A loadmensagem

O tempo de execução envia a loadmensagem para cada objeto de classe, logo após o objeto de classe ser carregado no espaço de endereço do processo. Para classes que fazem parte do arquivo executável do programa, o tempo de execução envia a loadmensagem bem no início do ciclo de vida do processo. Para classes que estão em uma biblioteca compartilhada (carregada dinamicamente), o tempo de execução envia a mensagem de carregamento logo após a biblioteca compartilhada ser carregada no espaço de endereço do processo.

Além disso, o tempo de execução só envia loadpara um objeto de classe se esse objeto de classe em si implementar o loadmétodo. Exemplo:

@interface Superclass : NSObject
@end

@interface Subclass : Superclass
@end

@implementation Superclass

+ (void)load {
    NSLog(@"in Superclass load");
}

@end

@implementation Subclass

// ... load not implemented in this class

@end

O tempo de execução envia a loadmensagem para o Superclassobjeto de classe. Ele não envia a loadmensagem para o Subclassobjeto de classe, embora Subclassherde o método deSuperclass .

O tempo de execução envia a loadmensagem para um objeto de classe após ter enviado a loadmensagem para todos os objetos da superclasse da classe (se esses objetos da superclasse implementarem load) e todos os objetos da classe nas bibliotecas compartilhadas às quais você se vincula. Mas você ainda não sabe quais outras classes em seu próprio executável receberam load.

Cada classe que seu processo carrega em seu espaço de endereço receberá uma loadmensagem, se implementar o loadmétodo, independentemente de seu processo fazer qualquer outro uso da classe.

Você pode ver como o tempo de execução procura o loadmétodo como um caso especial no _class_getLoadMethodde objc-runtime-new.mm, e chama-lhe directamente a partir call_class_loadsdeobjc-loadmethod.mm .

O tempo de execução também executa o loadmétodo de cada categoria que carrega, mesmo se várias categorias na mesma classe implementarem load. Isso é incomum. Normalmente, se duas categorias definem o mesmo método na mesma classe, um dos métodos “vencerá” e será usado, e o outro método nunca será chamado.

O initializemétodo

O tempo de execução chama o initializemétodo em um objeto de classe antes de enviar a primeira mensagem (diferente de loadou initialize) para o objeto de classe ou qualquer instância da classe. Esta mensagem é enviada usando o mecanismo normal, portanto, se sua classe não implementa initialize, mas herda de uma classe que o faz, então sua classe usará o da superclasse initialize. O tempo de execução iráenviar o initializepara todas as superclasses de uma classe primeiro (se as superclasses ainda não foram enviadasinitialize ).

Exemplo:

@interface Superclass : NSObject
@end

@interface Subclass : Superclass
@end

@implementation Superclass

+ (void)initialize {
    NSLog(@"in Superclass initialize; self = %@", self);
}

@end

@implementation Subclass

// ... initialize not implemented in this class

@end

int main(int argc, char *argv[]) {
    @autoreleasepool {
        Subclass *object = [[Subclass alloc] init];
    }
    return 0;
}

Este programa imprime duas linhas de saída:

2012-11-10 16:18:38.984 testApp[7498:c07] in Superclass initialize; self = Superclass
2012-11-10 16:18:38.987 testApp[7498:c07] in Superclass initialize; self = Subclass

Visto que o sistema envia o initializemétodo preguiçosamente, uma classe não receberá a mensagem a menos que seu programa realmente envie mensagens para a classe (ou uma subclasse, ou instâncias da classe ou subclasses). E no momento em que você recebe initialize, todas as aulas em seu processo já devem ter recebidoload (se apropriado).

A maneira canônica de implementar initializeé esta:

@implementation Someclass

+ (void)initialize {
    if (self == [Someclass class]) {
        // do whatever
    }
}

O objetivo desse padrão é evitar Someclassa reinicialização quando ele tem uma subclasse que não implementa initialize.

O tempo de execução envia a initializemensagem na _class_initializefunção em objc-initialize.mm. Você pode ver que ele usa objc_msgSendpara enviar, que é a função normal de envio de mensagens.

Leitura adicional

Confira as perguntas e respostas de Mike Ash sobre esse tópico.

rob mayoff
fonte
25
Você deve observar que +loadé enviado separadamente para as categorias; ou seja, cada categoria em uma classe pode conter seu próprio +loadmétodo.
Jonathan Grynspan de
1
Observe também que initialize será chamado corretamente por um loadmétodo, se necessário, devido à loadreferência feita à entidade não inicializada. Isso pode (estranhamente, mas sensatamente) levar a uma initializecorrida antes load! Isso é o que tenho observado, de qualquer maneira. Isso parece ser contrário a "E no momento em que você receber initialize, todas as aulas em seu processo já devem ter recebido load(se apropriado)."
Benjohn
5
Você recebe loadprimeiro. Você pode então receber initializeenquanto loadainda está em execução.
rob mayoff
1
@robmayoff não precisamos adicionar [superinicializar] e [supercarga] linhas, dentro dos respectivos métodos?
dia
1
Isso geralmente é uma má ideia, porque o tempo de execução já enviou essas duas mensagens para todas as suas superclasses antes de enviá-las para você.
rob mayoff de
17

Isso significa que não substitua +initializeem uma categoria, você provavelmente quebrará algo.

+loadé chamado uma vez por classe ou categoria que implementa +load, assim que essa classe ou categoria é carregada. Quando diz "estaticamente vinculado", significa compilado no binário do seu aplicativo. Os +loadmétodos nas classes assim compiladas serão executados quando seu aplicativo for iniciado, provavelmente antes de entrar main(). Quando diz "carregado dinamicamente", significa carregado por meio de pacotes de plug-ins ou uma chamada para dlopen(). Se você estiver no iOS, pode ignorar esse caso.

+initializeé chamado na primeira vez que uma mensagem é enviada para a turma, logo antes de lidar com essa mensagem. Isso (obviamente) só acontece uma vez. Se você substituir +initializeem uma categoria, uma das três coisas acontecerá:

  • sua implementação de categoria é chamada e a implementação da classe não
  • a implementação da categoria de outra pessoa é chamada; nada que você escreveu faz
  • sua categoria ainda não foi carregada e sua implementação nunca é chamada.

É por isso que você nunca deve substituir +initializeem uma categoria - na verdade, é muito perigoso tentar substituir qualquer método em uma categoria porque você nunca tem certeza do que está substituindo ou se sua própria substituição será substituída por outra categoria.

BTW, outra questão a ser considerada +initializeé que se alguém criar uma subclasse de você, você será potencialmente chamado uma vez para sua classe e uma vez para cada subclasse. Se estiver fazendo algo como configurar staticvariáveis, você vai querer se proteger contra isso: com dispatch_once()ou por meio de testes self == [MyClass class].


fonte