OC Runtime guide document reading

Get into the habit of writing together! This is the third day of my participation in the "Nuggets Daily New Plan · April Update Challenge", click to view the details of the event .

OC underlying principle exploration document summary

OC Runtime guide document reading

main content:

  1. message sending
  2. Dynamic method resolution
  3. message forwarding
  4. type code
  5. property declaration

It is not completely translated word by word, but comprehensible reading, because I feel that it is better to directly read the English text when translating word by word.

Official documentation: Objective-C Runtime Programming Guide

1 Introduction

OC languages ​​defer as many decisions as possible from compile and link time to runtime. Whenever possible, OC will always solve problems in a dynamic way, so OC not only needs a compiler, but also a runtime system to perform compiled code.

This document describes 1) the NSObject class and how OC programs interact with the runtime system, 2) in particular focuses on the process of loading new classes and forwarding messages to other objects at runtime, 3) it also provides information on how to Find information about an object when the program runs

In earlier versions, changing the layout of instance variables in a class required recompiling all subclasses of that class. In the current version, if you change the layout of instance variables in a class, you do not need to recompile any subclasses of that class.

2. Interaction with the runtime system

There are three ways to interact with the runtime system

2.1 Via OC source code

In most cases, the runtime system runs automatically in the background, and we only need to write and compile the OC source code. When compiling OC classes and methods, some C/C++ data structures and functions will be automatically created during compilation to communicate with the system. These data structures and functions can realize the dynamic characteristics of the language by interacting with each other. These data structures contain information defined by classes and protocols, classifications, objects, etc., and functions are used to process messages.

2.2 Through the methods of the NSObject class in the Foundation framework

  • Some methods of the NSObject class provide information for querying the runtime system, that is to say, the NSObject class simply obtains information from the runtime system and can be used to perform certain self-checks on the object.
  • Most of the classes in the program are subclasses of the NSObject class, so most of them inherit the methods of the NSObject class, thus inheriting the behavior of NSObject (NSProxy is an exception)
  • 在一些情况下,NSObject类指定以了一个模板,用于说明应该完成什么事,但是没有具体实现,也就是没有说应该怎么完成。
  • 通过调用NSObject方法,会间接的调用runtime

常见方法

method.png

2.3 通过直接调用运行时系统的函数

  • 运行时系统是一个有公开接口的动态库,由一些数据结构和函数的集合组成
  • 这些数据结构和函数的声明头文件在/usr/include/objc中
  • 这些函数使用纯C的函数实现OC的功能
  • 这些函数使得访问运行时系统接口和提供开发工具成为可能

直接调用运行时C函数

runtime function.png

3、消息发送

本章介绍如何将消息表达式转换为 objc_msgSend函数调⽤,以及如何按名称引⽤⽅法。然后解释如何利⽤objc_msgSend,以及如果需要,如何绕过动态绑定。

3.1 objc_msgSend函数

在Objective-C中,消息直到运⾏时才绑定到⽅法实现。编译器转换消息表达式为 [receiver message]

调⽤消息传递函数 objc_msgSend。此函数将接收⽅和消息中提到的⽅法的名称(即⽅法选择器)作为其两个主要参数。 objc_msgSend(receiver, selector)

消息中传递的任何参数也将传递给objc_msgSend:

objc_msgSend(receiver, selector, arg1, arg2, ...)

动态绑定

消息传递函数为动态绑定:

  • 先绑定查找选择器引⽤的过程(⽅法实现)。由于同⼀⽅法可以由不同的类实现,因此它找到的精确过程取决于接受者。
  • 它然后调⽤过程,将接收对象(指向其数据的指针)以及为⽅法。
  • 最后,它传递过程的返回值作为⾃⼰的回报值。

3.1 获取函数指针imp

消息传递的关键在于编译器为每个类和对象构建的结构。每个类结构都包含以下两个基本元素:

  • 指向超类的指针。
  • ⼀个类调度表。此表中的条⽬将⽅法选择器与其标识的⽅法的类特定地址相关联。setOrigin::⽅法的选择器与setOrigin::(实现的过程)的地址相关联,display⽅法的选择器与display的地址关联,依此类推。

创建新对象时,将为其分配内存,并初始化其实例变量。对象变量中的第⼀个变量是指向其类结构的指针。这个名为isa的指针让对象访问其类,并通过该类访问它继承的所有类。

示意图:

message sending.png

说明:

  1. 当消息发送到对象时,消息传递函数跟随对象的isa指针指向类结构,在该类结构中查找调度表中的⽅法选择器。
  2. 如果在那⾥找不到选择器,objc_msgSend会跟随指向超类的指针并尝试在其调度表中查找选择器。
  3. 连续的失败导致objc_msgSend爬升类层次结构,直到到达NSObject类。
  4. ⼀旦找到选择器,函数就会调⽤表中输⼊的⽅法,并将接收对象的数据结构传递给它。

快速查找流程

为了加快消息传递过程,运⾏时系统在使⽤⽅法时缓存选择器和地址。每个类都有⼀个单独的缓存,它可以包含继承⽅法的选择器以及类中定义的⽅法的选择器。在搜索调度表之前, 消息传递例程⾸先检查接收对象类的缓存(理论上,曾经使⽤过⼀次的⽅法可能会再次使 ⽤)。如果⽅法选择器在缓存中,则消息传递只⽐函数调⽤稍慢。⼀旦⼀个程序运⾏⾜够长的时间来“预热”它的缓存,它发送的⼏乎所有消息都会找到⼀个缓存⽅法。缓存动态增长以适应程序运⾏时的新消息

3.3 使用隐藏函数

当 objc_msgSend找到实现⽅法的过程时,它调⽤该过程并将消息中的所有参数传递给它。它还向过程传递两个隐藏参数:

  • 接收对象
  • 方法选择器

这些参数为每个⽅法实现提供关于调⽤它的消息表达式的两部分的显式信息。它们被称为 “隐藏”,因为它们没有在定义⽅法的源代码中声明。它们在代码编译时被插⼊到实现中。

虽然这些参数没有显式声明,但是源代码仍然可以引⽤它们(就像它可以引⽤接收对象的实例变量⼀样)。⽅法将接收对象引⽤为self,并将其⾃⼰的选择器引⽤为_cmd。在下⾯的⽰例中,_cmd表⽰异常⽅法的选择器,self指向接收到异常消息的对象。

代码:

- strange
{
//_cmd:表示异常的选择器
//self:指向接收到异常消息的对象
id	target = getTheReceiver(); SEL method = getTheMethod();

if ( target == self || method == _cmd ) return nil;
return [target performSelector:method];
}
复制代码

获取方法地址

规避动态绑定的唯⼀⽅法是获取⽅法的地址,然后像函数⼀样直接调⽤它。当⼀个特定的⽅法将被连续执⾏很多次,并且您希望避免每次执⾏该⽅法时消息传递的开销,这种情况可能 ⽐较合适。 使⽤NSObject类中定义的⽅法methodForSelector:,可以请求指向实现⽅法的过程的指针, 然后使⽤该指针调⽤该过程。methodForSelector:返回的指针必须谨慎地转换为正确的函数类型。类型转换中应包括返回类型和参数类型。

代码:

void (*setter)(id, SEL, BOOL); int i;

setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
setter(targetList[i], @selector(setFilled:), YES);
复制代码

说明:

  • 传递给过程的前两个参数是接收对象(self)和⽅法选择器(_cmd)。这些参数隐藏在⽅法语法中,但必须在⽅法作为函数调⽤时显式。
  • 使⽤methodForSelector:绕过动态绑定可以节省消息传递所需的⼤部分时间。但是,只有在特定的消息被重复多次时,节省的空间才会显著,如上⾯所⽰的for循环中。

4、动态方法解析

本章描述如何动态地提供⽅法的实现。

可以实现 resolveInstanceMethod:和 resolveClassMethod: ⽅法,分别为实例和类⽅法的给定选择器动态提供实现。 Objective-C⽅法只是⼀个C函数,它⾄少有两个参数self和_cmd。可以使⽤函数class_ addMethod将函数作为⽅法添加到类中。因此,考虑到以下功能:

void dynamicMethodIMP(id self, SEL _cmd) {
// implementation ....
}
复制代码

可以使⽤resolveInstanceMethod将其作为⽅法(称为ResolveThisMethodDynamic)动态添加到类中,如下所⽰:

@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSEL];
}
@end
复制代码

总结: 转发⽅法(如消息转发中所述)和动态⽅法解析在很⼤程度上是正交的。在转发机制⽣效之 前,类有机会动态解析⽅法。如果调⽤了respondsToSelector:或instanceRespondToSelector:, 则动态⽅法解析器将有机会⾸先为选择器提供IMP。如果实现resolveInstanceMethod:但希望 通过转发机制实际转发特定的选择器,则为这些选择器返回NO。

动态加载

Objective-C程序可以在运⾏时加载和链接新的类和类别。新代码被合并到程序中,并与开始时加载的类和类别相同。 动态加载可以⽤来做很多不同的事情。例如,系统⾸选项应⽤程序中的各个模块是动态加载的。 在Cocoa环境中,通常使⽤动态加载来定制应⽤程序。其他⼈可以编写程序在运⾏时加载的模块,就像Interface Builder加载⾃定义调⾊板和OSX系统⾸选项应⽤程序加载⾃定义⾸选项模块⼀样。可加载模块扩展了应⽤程序的功能。他们以你所允许的⽅式为之做出贡献,但却 ⽆法预料或定义你⾃⼰。您提供框架,但其他⼈提供代码

5、消息转发

向不处理该消息的对象发送消息是错误的。但是,在宣布错误之前,运⾏时系统会给接收对象第⼆次处理消息的机会。

转发

如果将消息发送到不处理该消息的对象,则在宣布错误之前,运⾏时会向该对象发送⼀个forwardInvocation:message,其中NSInvocation对象作为其唯⼀参数,NSInvocation对象将封装原始消息及其传递的参数。 您可以实现forwardInvocation:⽅法来给消息提供默认响应,或者以其他⽅式避免错误。顾名思义,forwardInvocation:通常⽤于将消息转发到另⼀个对象。 要了解转发的范围和意图,请设想以下场景:⾸先,假设您正在设计⼀个可以响应名为negotiate的消息的对象,并且希望其响应包含另⼀种对象的响应。通过将协商消息传递给所实现的协商⽅法主体中的其他对象,可以很容易地完成此操作。 更进⼀步,假设您希望对象对negotiate 消息的响应与在另⼀个类中实现的响应完全相同。实现这⼀点的⼀种⽅法是让您的类从另⼀个类继承该⽅法。然⽽,这样安排事情可能是不可能的。您的类和实现negotiate的类位于继承层次结构的不同分⽀中可能有很好的原因。 即使您的类不能继承协商⽅法,您仍然可以通过实现该⽅法的⼀个版本来“借⽤”该⽅法,该 ⽅法只需将消息传递给另⼀个类的实例:

- (id)negotiate
{
if ( [someOtherObject respondsTo:@selector(negotiate)] ) return [someOtherObject negotiate];
return self;
}
复制代码

这种⽅式可能会有点⿇烦,特别是如果有很多消息需要对象传递给另⼀个对象。你必须实现 ⼀个⽅法来覆盖你想从另⼀个类借⽤的每个⽅法。⽽且,你可能不想知道你在哪⾥写了完整的代码。该集合可能依赖于运⾏时的事件,并且在将来实现新⽅法和类时可能会发⽣变化。

forwardInvocation提供的第⼆个机会是:message为这个问题提供了⼀个不那么特别的解决 ⽅案,⽽且是动态的,⽽不是静态的。它的⼯作原理是这样的:当⼀个对象因为没有与消息中的选择器匹配的⽅法⽽⽆法响应消息时,运⾏时系统通过发送forwardInvocation:message 通知对象。每个对象都从NSObject类继承⼀个forwardInvocation:⽅法。但是,NSObject的 ⽅法版本只是调⽤doesNotRecognizeSelector:。通过重写NSObject的版本并实现⾃⼰的版本,您可以利⽤forwardInvocation:message提供的机会将消息转发到其他对象。

若要转发消息,forwardInvocation:⽅法只需:

  • 确定消息的位置
  • 并将位置与原始消息⼀起发送到那⾥

可以使⽤invokeWithTarget:⽅法发送消息:

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([someOtherObject respondsToSelector: [anInvocation selector]])
[anInvocation invokeWithTarget:someOtherObject];
else
[super forwardInvocation:anInvocation];
}
复制代码

所转发的消息的返回值返回给原始发送⽅。所有类型的返回值都可以传递给发送⽅,包括 id、结构和双精度浮点数。

forwardInvocation:⽅法可以充当未识别消息的分发中⼼,将它们分发给不同的接收⽅。或者它可以是⼀个中转站,将所有消息发送到同⼀个⽬的地。它可以将⼀条消息转换成另⼀条消息,或者简单地“吞下”⼀些消息,这样就不会有响应,也不会出错。forwardInvocation: ⽅法还可以将多个消息合并到单个响应中。forwardInvocation:做什么取决于实现者。然 ⽽,它为链接转发链中的对象提供了机会,为程序设计提供了可能性。

注意:forwardInvocation:⽅法只有在消息没有调⽤名义接收⽅中的现有⽅法时才能处理它们。例如,如果您希望您的对象将negotiate消息转发到另⼀个对象,则它不能有⾃⼰的negotiate⽅法。如果是这样,消息就永远不会到达forwardInvocation:.。

转发和多重继承

可以用消息转发来模拟多重继承,一个对象通过转发来响应消息,看起来就像是继承了其他类而使用其方法

消息转发和多继承.png

说明: Warrior通过消息转发调用了Diplomat的negotiate方法,看上去像是继承自Diplomat一样

代理对象

消息转发可以以一个轻量级的对象(消息代理对象)代表更多的对象进行消息处理。

同时也存在着其它类型的消息代理对象。例如,假设您有个对象需要操作大量的数据——它可能需要创建 一个复杂的图片或者需要从磁盘上读一个文件的内容。创建一个这样的对象是很费时的,您可能希望能推 迟它的创建时间——直到它真正需要时,或者系统资源空闲时。同时,您又希望至少有一个预留的对象和 程序中其它对象交互。 在这种情况下,你可以为该对象创建一个轻量的代理对象。该代理对象可以有一些自己的功能,例如响应 数据查询消息,但是它主要的功能是代表某个对象,当时间到来时,将消息转发给被代表的对象。当代理 对象的 forwardInvocation:方法收到需要转发给被代表的对象的消息时,代理对象会保证所代表的 对象已经存在,否则就创建它。所有发到被代表的对象的消息都要经过代理对象,对程序来说,代理对象 和被代表的对象是一样的。

转发和继承

虽然转发模仿继承,但NSObject类从不混淆两者。例如在 NSObject 类中,方法 respondsToSelector:和 isKindOfClass:只会出现在继承链中,而不是消息转发链中,此时我们如果想要使得这些方法对外透明,也就是消息转发的方法也返回YES,就需要重写这些方法,判断后返回YES。

转发

6、类型编码

为了和运行时系统协作,编译器将方法的返回类型和参数类型都编码成一个字符串,并且和方法选标关联在一起。

编码的获取:使用@encode(类型名)

常见编码格式:

类型编码格式.png

7、属性声明

When the compiler encounters a property declaration, the compiler generates some descriptive metadata associated with the class, class, or protocol in which the property resides. These descriptive metadata are attributes about the Property. You can access metadata through functions, which exist in some classes or protocols, such as getting the type encoding of an attribute via @encode, returning an attribute's attribute as an array of C strings, etc. In order to distinguish between property and attribute, I call property as attribute and attribute as feature

I only write about understanding and use here. For detailed description examples of attribute characteristics, you can directly read the original text.

7.1 Attribute types and related functions

The Property type defines an opaque handle to the structure objc_property that describes the property.

Structure:

typedef struct objc_property *Property;
复制代码

API:

API.png

Implementation:

函数实现.png

7.2 Attribute Type Coding

The property_getAttributes function will return the property's name, @encode encoding, and other attributes.

Implementation:

@property (nonatomic ,assign ,readonly) int age;
@property (nonatomic ,copy,readwrite) NSString *name;
复制代码

result:

结果.png

illustrate:

  1. Starts with the letter T, followed by @encode and a comma
  2. If the attribute is readonly modified, the string contains R and comma
  3. If the attribute is modified with copy or retain, the string contains C or &, respectively, followed by a comma.
  4. If the property definition has custom getter and setter methods, there is a G or S in the string followed by the corresponding method name and a comma
  5. The string ends with V followed by the property's name.

Guess you like

Origin juejin.im/post/7086262492254437407