下面讲讲消息传递用到的一些概念:
- isa
- 类对象(objc_class)
- 实例(objc_object)
- 元类(Meta Class)
- Method(objc_method)
- SEL(objc_selector)
- IMP
- 类缓存(objc_cache)
- Category(objc_category)
isa
类对象(objc_class)
Objective-C
类是由Class
类型来表示的,它实际上是一个指向objc_class
结构体的指针。
typedef struct objc_class *Class;
查看objc/runtime.h
中objc_class
结构体的定义如下:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
struct objc_class
结构体定义了很多变量,通过命名不难发现,
结构体里保存了指向父类的指针、类的名字、版本、实例大小、实例变量列表、方法列表、缓存、遵守的协议列表等,
一个类包含的信息也不就正是这些吗?没错,类对象就是一个结构体struct objc_class
,这个结构体存放的数据称为元数据(metadata
),
该结构体的第一个成员变量也是isa
指针,这就说明了Class
本身其实也是一个对象,因此我们称之为类对象,类对象在编译期产生用于创建实例对象,是单例。
实例(objc_object)
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
类对象中的元数据存储的都是如何创建一个实例的相关信息,那么类对象和类方法应该从哪里创建呢?
就是从isa
指针指向的结构体创建,类对象的isa
指针指向的我们称之为元类(metaclass
),
元类中保存了创建类对象以及类方法所需的所有信息,因此整个结构应该如下图所示:
[图片上传失败...(image-c1da91-1616930561295)]
元类(Meta Class)
通过上图我们可以看出整个体系构成了一个自闭环,struct objc_object
结构体实例
它的isa
指针指向类对象,
类对象的isa
指针指向了元类,super_class
指针指向了父类的类对象,
而元类的super_class
指针指向了父类的元类,那元类的isa
指针又指向了自己。
元类(Meta Class)是一个类对象的类。
在上面我们提到,所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。
为了调用类方法,这个类的isa
指针必须指向一个包含这些类方法的一个objc_class
结构体。这就引出了meta-class
的概念,元类中保存了创建类对象以及类方法所需的所有信息。
任何NSObject
继承体系下的meta-class
都使用NSObject
的meta-class
作为自己的所属类,而基类的meta-class
的isa
指针是指向它自己。
Method(objc_method)
先看下定义
runtime.h
/// An opaque type that represents a method in a class definition.代表类定义中一个方法的不透明类型
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
Method
和我们平时理解的函数是一致的,就是表示能够独立完成一个功能的一段代码,比如:
- (void)logName
{
NSLog(@"name");
}
这段代码,就是一个函数。
我们来看下objc_method
这个结构体的内容:
- SEL method_name 方法名
- char *method_types 方法类型
- IMP method_imp 方法实现
在这个结构体重,我们已经看到了SEL
和IMP
,说明SEL
和IMP
其实都是Method
的属性。
我们接着来看SEL
。
SEL(objc_selector)
先看下定义
Objc.h
/// An opaque type that represents a method selector.代表一个方法的不透明类型
typedef struct objc_selector *SEL;
objc_msgSend
函数第二个参数类型为SEL
,它是selector
在Objective-C
中的表示类型(Swift
中是Selector
类)。selector
是方法选择器,可以理解为区分方法的 ID
,而这个 ID
的数据结构是SEL
:
@property SEL selector;
可以看到selector
是SEL
的一个实例。
A method selector is a C string that has been registered (or “mapped“) with the Objective-C runtime. Selectors generated by the compiler are automatically mapped by the runtime when the class is loaded.
其实selector
就是个映射到方法的C
字符串,你可以用 Objective-C
编译器命令@selector()
或者 Runtime
系统的sel_registerName
函数来获得一个 SEL
类型的方法选择器。
selector
既然是一个string
,我觉得应该是类似className+method
的组合,命名规则有两条:
- 同一个类,selector不能重复
- 不同的类,selector可以重复
这也带来了一个弊端,我们在写C
代码的时候,经常会用到函数重载,就是函数名相同,参数不同,但是这在Objective-C
中是行不通的,因为selector
只记了method
的name
,没有参数,所以没法区分不同的method
。
比如:
- (void)caculate(NSInteger)num;
- (void)caculate(CGFloat)num;
是会报错的。
我们只能通过命名来区别:
- (void)caculateWithInt(NSInteger)num;
- (void)caculateWithFloat(CGFloat)num;
在不同类中相同名字的方法所对应的方法选择器是相同的,即使方法名字相同而变量类型不同也会导致它们具有相同的方法选择器。
IMP
看下IMP
的定义
/// A pointer to the function of a method implementation. 指向一个方法实现的指针
typedef id (*IMP)(id, SEL, ...);
#endif
就是指向最终实现程序的内存地址的指针。
在iOS
的Runtime
中,Method
通过selector
和IMP
两个属性,实现了快速查询方法及实现,相对提高了性能,又保持了灵活性。
类缓存(objc_cache)
当Objective-C
运行时通过跟踪它的isa
指针检查对象时,它可以找到一个实现许多方法的对象。然而,你可能只调用它们的一小部分,并且每次查找时,搜索所有选择器的类分派表没有意义。所以类实现一个缓存,每当你搜索一个类分派表,并找到相应的选择器,它把它放入它的缓存。所以当objc_msgSend
查找一个类的选择器,它首先搜索类缓存。这是基于这样的理论:如果你在类上调用一个消息,你可能以后再次调用该消息。
为了加速消息分发, 系统会对方法和对应的地址进行缓存,就放在上述的objc_cache
,所以在实际运行中,大部分常用的方法都是会被缓存起来的,Runtime
系统实际上非常快,接近直接执行内存地址的程序速度。
Category(objc_category)
Category
是表示一个指向分类的结构体的指针,其定义如下:
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
};
name:是指 class_name 而不是 category_name。
cls:要扩展的类对象,编译期间是不会定义的,而是在Runtime阶段通过name对 应到对应的类对象。
instanceMethods:category中所有给类添加的实例方法的列表。
classMethods:category中所有添加的类方法的列表。
protocols:category实现的所有协议的列表。
instanceProperties:表示Category里所有的properties,这就是我们可以通过objc_setAssociatedObject和objc_getAssociatedObject增加实例变量的原因,不过这个和一般的实例变量是不一样的。
从上面的category_t
的结构体中可以看出,分类中可以添加实例方法,类方法,甚至可以实现协议,添加属性,不可以添加成员变量。