Tempo de execução e encaminhamento de mensagens no iOS

No início dos anos 80, Xiao Li e Xiao Wang formavam um casal de longa distância. Sob a liderança do corno da reforma, Xiao Wang escolheu resolutamente uma cidade no sul para lutar. Naquela época, não havia telefone celular. Confie em escrever cartas. No entanto, como Xiao Wang viaja com frequência, seu endereço residencial muda com frequência. Portanto, toda vez que Xiao Li envia uma resposta a Xiao Wang, Xiao Wang pode não recebê-la por causa da mudança de endereço. Mais tarde, eles pensaram em uma boa maneira de resolver esse problema. O método específico é o seguinte:

Encaminhamento de mensagens na década de 80

 

Na verdade, a imagem acima pode expressar basicamente a função do Runtime no iOS e o mecanismo de encaminhamento de mensagens do iOS. A característica do Runtime é principalmente a entrega de mensagens (métodos) .Se a mensagem (método) não puder ser encontrada no objeto, ela será encaminhada. Como realizar. Exploramos o mecanismo de implementação do Runtime a partir dos seguintes aspectos.

Introdução ao tempo de execução

Objective-C estende a linguagem C e adiciona recursos orientados a objetos e mecanismo de mensagens no estilo Smalltalk. O núcleo desta extensão é uma biblioteca Runtime escrita em linguagem C e compilada. É a base dos mecanismos orientados a objetos e dinâmicos do Objective-C.

Objective-C é uma linguagem dinâmica, o que significa que não requer apenas um compilador, mas também um sistema de tempo de execução para criar classes e objetos dinamicamente e realizar a passagem e o encaminhamento de mensagens. Entender o mecanismo de tempo de execução do Objective-C pode nos ajudar a entender melhor a linguagem e, quando apropriado, podemos expandir a linguagem para resolver alguns problemas técnicos ou de design no projeto no nível do sistema. Para entender o Runtime, devemos primeiro entender seu core-messaging (Messaging).

Para se tornar um arquivo executável, uma linguagem de programação de alto nível precisa ser compilada em linguagem assembly e, em seguida, montada em linguagem de máquina. A linguagem de máquina também é a única linguagem que o computador pode reconhecer, mas OC não pode ser diretamente compilado em linguagem assembly, mas deve ser transcrito em puro C. A linguagem é então compilada e montada, e a transição da linguagem OC para a linguagem C é realizada em tempo de execução. No entanto, usamos OC para desenvolvimento orientado a objetos, e a linguagem C é um desenvolvimento mais orientado a processos, o que requer a conversão de classes orientadas a objetos em estruturas orientadas a processos.

Acima são todas as interpretações de documentos oficiais, que são um pouco obscuras e enfadonhas, então vamos usar o código para explicar em detalhes.

Mensagens em tempo de execução

Para um método de objeto  [teste de obj] , o compilador o converte em uma mensagem para enviar objc_msgSend (obj, teste) . O processo executado em Runtime é assim:

1. Primeiro, encontre sua classe por meio do indicador isa de obj ;

2. Encontre teste na lista de método de classe ;

3. Se não houver teste na classe , continue procurando sua superclasse ;

4. Assim que a função de teste for encontrada , execute sua implementação IMP

Obviamente, devido a problemas de eficiência, não é razoável percorrer o objc_method_list uma vez para cada mensagem. Portanto, é necessário armazenar em cache as funções frequentemente chamadas para melhorar a eficiência da consulta de função. Isso é o que objc_cache, outro membro importante de objc_class, faz - depois de encontrar o teste, salve o method_name do teste como a chave e method_imp como o valor. Quando a mensagem de teste é recebida novamente, ela pode ser encontrada diretamente no cache para evitar atravessar a objc_method_list. No código-fonte anterior, você pode ver que objc_cache existe na estrutura objc_class.

O método de objec_msgSend:

OBJC_EXPORTidobjc_msgSend (idself, SEL op, ...)

Vamos dar uma olhada nas estruturas de objetos, classes e métodos:

Objeto de classe (objc_class)

A classe Objective-C é representada pelo tipo Class, que é na verdade um ponteiro para a estrutura objc_class

A estrutura struct objc_class define muitas variáveis. A estrutura salva o ponteiro para a classe pai, o nome da classe, a versão, o tamanho da instância, a lista de variáveis ​​de instância, a lista de métodos, o cache, a lista de protocolos de conformidade, etc. Pode-se ver que o objeto de classe é uma estrutura struct objc_class, esta estrutura Os dados armazenados no corpo são metadados.

Instância (objc_object)

 

Os metadados no objeto de classe armazenam as informações sobre como criar uma instância, que é criada a partir da estrutura apontada pelo ponteiro isa. O ponteiro isa do objeto de classe aponta para a metaclasse (metaclasse)

Todas as informações necessárias para criar objetos de classe e métodos de classe são salvas na metaclasse, portanto, toda a estrutura deve ser conforme mostrado na figura a seguir:

Objeto de instância, objeto de classe e diagrama de metaclasse

O ponteiro isa da estrutura struct objc_object aponta para o objeto de classe;

O ponteiro isa do objeto de classe aponta para a metaclasse;

O ponteiro super_class aponta para o objeto de classe da classe pai;

O ponteiro super_class da metaclasse aponta para a metaclasse da classe pai;

Parece um trava-língua, por isso pode ser representado por um mapa divino na Internet:

Figura 6 Loop auto-fechado de objeto de instância, objeto de classe e metaclasse

Na figura acima, podemos ver que todo o sistema constitui um loop autofechado.Se ele for herdado de NSObject, a classe Root na figura acima é NSObject.

 

c1 é uma classe obtida através de um objeto de instância. O objeto de instância pode obter seu objeto de classe. O nome da classe representa o objeto de classe quando é o destinatário da mensagem. Portanto, o objeto de classe obtém a própria classe.

Se quisermos obter o objeto do ponteiro ISA, podemos usar as duas funções a seguir

OBJC_EXPORTBOOLclass_isMetaClass (Classcls) OBJC_AVAILABLE (10.5, 2.0, 9.0, 1.0);

OBJC_EXPORTClassobject_getClass (idobj) OBJC_AVAILABLE (10.5, 2.0, 9.0, 1.0);

class_isMetaClass é usado para determinar se o objeto Class é uma metaclasse, e object_getClass é usado para obter o objeto apontado pelo ponteiro isa do objeto.

 

Pode-se ver no código que a Classe obtida por um objeto de instância por meio do método de classe é o objeto de classe apontado por seu ponteiro isa, enquanto o objeto de classe não é uma metaclasse, e o objeto apontado pelo ponteiro isa do objeto de classe é uma metaclasse.

Então, sobre a parte do Runtime, vamos resumir: primeiro, o objeto de instância é uma estrutura. Esta estrutura tem apenas uma variável de membro, apontando para o objeto de classe que a construiu. Este objeto de classe armazena todas as informações necessárias para o objeto de instância, incluindo variáveis ​​de instância, Métodos de instância, etc., enquanto objetos de classe são criados por meio de metaclasses, variáveis ​​de classe e métodos de classe são armazenados na metaclasse, o que explica perfeitamente como toda a classe e instância são mapeadas para a estrutura. Portanto, entender o Runtime é entender o armazenamento de dados do iOS e o relacionamento e função entre suas classes, instâncias, objetos de classe e metaclasses em tempo de execução.

Mecanismo de encaminhamento de mensagens

 O acima mencionou muitos conceitos e conhecimentos básicos do Runtime. O que isso tem a ver com o encaminhamento de mensagens e como usá-lo? Trata-se do mecanismo de encaminhamento de mensagens do iOS.

Na análise final, todas as chamadas de método em Objective-C são essencialmente o envio de mensagens para objetos.

1. Crie um método na classe- (void) todoSomething;

2. O sistema iOS cria um número para esse método, a saber: SEL (todoSomething) e o adiciona à lista de métodos. (Seletor é uma instância de SEL, que é diferente de IMP, que é um ponteiro para o endereço de memória do programa de implementação final)

3. Ao chamar este método: [Object todoSomething]; O sistema vai até a lista de métodos para inserir este número de método e executa-o quando encontrado.

Nota: Quando escrevemos código C, costumamos usar sobrecarga de função, ou seja, o nome da função é o mesmo, mas os parâmetros são diferentes, mas isso não é viável em Objective-C, porque o seletor lembra apenas o nome do método e nenhum parâmetro. Portanto, não há como distinguir entre métodos diferentes.

Portanto, se um método for chamado, a mensagem será enviada uma vez e a lista de métodos será pesquisada no objeto de classe relacionado. Se não for encontrado, ele pesquisará a árvore de herança para encontrar a raiz da árvore de herança (geralmente NSObject). Se ele falhar e o encaminhamento de mensagens falhar, execute o método doesNotRecognizeSelector: e relate um erro de seletor não reconhecido. Então, o que exatamente é o encaminhamento de mensagens? A seguir, apresentaremos as três últimas oportunidades, uma por uma.

1. Análise de método dinâmico

Objective-C chamará + resolveInstanceMethod: ou + resolveClassMethod: em tempo de execução para lhe dar a oportunidade de fornecer uma implementação de função. Se você adicionar uma função e retornar YES, o sistema runtime irá reiniciar o processo de envio de mensagens. Exemplo conforme mostrado abaixo

Impresso "Doing foo"

Pode ser visto que embora a função foo: não seja implementada, nós adicionamos dinamicamente a função fooMethod através de class_addMethod e executamos o IMP da função fooMethod. A partir dos resultados impressos, foi realizado com sucesso.

Se o método de resolução retornar NÃO, o tempo de execução passa para a próxima etapa: forwardingTargetForSelector.

Destinatário alternativo

Se o objeto de destino implementar -forwardingTargetForSelector :, o Runtime chamará esse método neste momento para dar a você a oportunidade de encaminhar esta mensagem para outros objetos.

Um exemplo de implementação de um receptor alternativo é o seguinte:

Você pode ver que encaminhamos o método ViewController atual para Person para execução por meio de forwardingTargetForSelector. O resultado impresso também prova que realizamos com sucesso o encaminhamento.

Encaminhamento de mensagem completo

Se a mensagem desconhecida não puder ser processada na etapa anterior, a única coisa que pode ser feita é habilitar o mecanismo completo de encaminhamento de mensagens.

Primeiro, ele enviará a mensagem -methodSignatureForSelector: para obter o parâmetro e o tipo de valor de retorno da função. Se -methodSignatureForSelector: retornar nulo, o Runtime emitirá a mensagem -doesNotRecognizeSelector: e o programa travará neste momento. Se uma assinatura de função for retornada, o Runtime criará um objeto NSInvocation e enviará uma mensagem -forwardInvocation: ao objeto de destino.

Também imprime "Doing foo"

Este é o processo de três encaminhamento do Runtime. Vamos falar sobre a aplicação prática do Runtime

 

Quando as funções do método interno do sistema não são suficientes, você pode estender algumas funções ao método interno do sistema e manter as funções originais. Por exemplo, quero saber se o URL atual está vazio. Se eu julgar todas as vezes, será muito problemático. Se eu criar uma extensão para escrever, não sei como ela é implementada internamente.

1. O método de troca de tempo de execução pode ser usado.

Dois, você também pode adicionar métodos dinamicamente

Três, adicione atributos à classificação

 

Quatro, realização KVO

A implementação de KVO depende do poderoso Runtime de Objective-C. Quando um objeto A é observado, o mecanismo KVO cria dinamicamente uma subclasse da classe atual do objeto A e substitui o método setter da propriedade observada keyPath para esta nova subclasse. O método setter é então responsável por notificar o status alterado das propriedades do objeto observado.

Quinto, encaminhamento de mensagens (atualização automática) para resolver o bug (JSPatch)

Quanto ao encaminhamento de mensagens, o encaminhamento de mensagens é dividido em três níveis, podemos implementar a função de substituição em cada nível para conseguir o encaminhamento de mensagens, de forma a não causar um travamento. O JSPatch pode não apenas realizar o encaminhamento de mensagens, mas também realizar uma série de funções de adição e substituição de métodos.

Seis, perceba o arquivamento e desarquivamento automáticos do NSCoding

Descrição do princípio: Use as funções fornecidas pelo runtime para percorrer todos os atributos do próprio modelo e realizar operações de codificação e decodificação nos atributos.

Método principal: reescreva o método na classe base de Model:

 

Resumo: Em toda a operação Objective-C, todas as chamadas de método são o processo de envio ou encaminhamento de mensagens. Finalmente, a primeira imagem pode ser aproximadamente transformada na seguinte, que é fácil de entender

 



Autor: Jaren_lei
link: https: //www.jianshu.com/p/45db86af7b60

Acho que você gosta

Origin blog.csdn.net/wangletiancsdn/article/details/97939901
Recomendado
Clasificación