Runtime简介
运行时最主要的是消息机制
- 对于C语言,函数调用在编译的时候会决定调用哪个函数
- 对于OC函数,属于
动态调用过程
,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用, - 在编译阶段,oc可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错
在编译阶段,C语言调用未实现的函数就会报错 - 如果向某个对象传递消息,在底层所有的方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用哪个方法则完全取决于运行期决定,甚至可能在运行期改变,这些特性使得OC变成一门真正的动态语言
- 在Runtime中,对象可以使用C语言中的结构体表示,而方法可以用C函数实现,另外在加上了额外的特性,这些结构体和函数被Runtime函数封装后,让OC的面向对象编程变为可能
Objective-C中的数据结构
1.id
运行时系统如何知道某个对象的类型呢?对象类型并不是在编译期就知道了,而是要在运行期查找,OC有个特殊类型id,它可以表示OC的任意对象类型,id类型定义在Runtime的头文件中:
struct obje_object {
Class isa;
} *id;
由此可见,每个对象结构体的首个成员变量是Class类的isa,该变量定义了对象所属的类,通常指isa指针
objc_object
objc_object 是一个表示一个类实例的结构体,它的定义如下(objc/objc.h):
struct objc_object{
Class isa OBJC_ISA_AVAILABILITY;
};
typedef struce objc_object *id;
可以看到,这个结构体只有一个实体,基指向其类的isa指针。这样,当我们向一个OC对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类,Runtime库会在类的方法列表以及父类的方法列表中寻找与消息对应的selector指向的方法,找到后即运行这个方法。
2.Class
Class对象也定义在Runtime的头文件中,查看objc/runtime.h中的objc_class结构体:Objective-c中,类是由Class类型来表示的,它实际是一个指向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;//类的版本信息,默认为0
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;
下面说下Class 结构体的几个主要变量:
1.isa:结构体的首个变量也是isa指针,这说明Class本身也是OC中的对象。isa指针非常重要,对象需要通过isa指针找到它的类,类需要isa找到元类,这在调用实例方法和类方法的时候起到重要作用。
2.super_class:结构体里还有个变量是super_class,它定义了本类的超类。类对象所属类型(isa指针所指向的类型)是另一个类,叫做元类。
3.ivars:成员变量列表,类的成员都在ivars里面。
4.methodLists:方法列表,类的实例方法都在methodLists里,类方法在元类的methodLists里面。methodLists是一个指针的指针,通过修改该指针指向指针的值,就可以动态的为某一个类添加成员方法。这就是Category实现的原理,同时也说明Category只可以为对象添加成员方法,不能添加成员变量。
5.cache:方法缓存列表,objc_msgSend(下文详解)每调用一次方法后,就会把该方法缓存到cache列表中,下次调用的时候,会优先从cache列表中寻找,如果cache没有,才从methodLists中查找方法,提高效率。
元类(Meta Class)
meta-class 是一个类对象的类。在上面我们提到,所有的类自身也是一个对象,我们可以向这个对象发送消息(即调用类方法)。既然是对象,那么它也是一个objc_object指针,它包含一个指向其类的一个isa指针,那么,这个isa指针指向什么呢?为了调用类方法,这个类的isa指针必须指向一个包含这个类方法的一个objc_class结构体。这就引出了meta-class的概念,meta-class中存储着一个类的所有类方法。所以,调用类方法的这个类对象的isa指针指向的就是meta-class 当我们向一个对象发送消息时,runtime会在这个对象所属的这个类的方法列表中查找方法;而向一个类发送消息时,会在这个类的meta-class的方法列表中查找。
再深入一下,meta-class 也是一个类,也可以向它发送一个消息,那么它的isa又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C的设计者让所有的meta-class的isa指向基类的meta-class,以此作为他们的所属类。
即,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。
通过上面的描述,再加上对objc_class结构体中super_class指针的分析,我们就可以描绘出类及相应meta-class类的一个继承体系了,如下代码
上图 superclass指针代表继承关系,isa指针代表实例所属的类。类也是一个对象,它是另一个类的实例,这个就是”元类“,元类里面保存了类方法的列表,类里面保存了实例方法的列表。实例对象的isa指向类,类对象的isa指向元类,元类对象的isa指向一个根元类(root metaclass)。所有子类的元类都继承父类的元类,换而言之,类对象和元类对象有同样的继承关系。
Class是一个指向objc_class结构体的指针,而id是一个指向objc_object结构体的指针,其中的isa是一个指向objc_class结构体的指针。其中的id就是我们说的对象,Class就是我们所说的类。isa指针不总是指向实例对象所属的类,不能依靠它来确定类型,而是应该用isKindOfClass:方法来确定实例对象的类。因为KVO的实现机制就是将被观察对象的isa指针指向一个中间类而不是真实的类。
Category
Category是表示一个指向分类的结构体的指针,其定义如下:
/// An opaque type that represents a category.
typedef struct objc_category *Category;
struct objc_category {
char * _Nonnull category_name OBJC2_UNAVAILABLE;
char * _Nonnull class_name OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
}
这个结构体主要包含了分类定义的实例方法与类方法,其中instance_methods列表是objc_class中方法列表的一个子集,而class_methods列表是元类方法列表的一个子集。可发现,类别中没有ivar成员变量指针,也就意味着:类别中不能够添加实例变量和属性
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
3.SEL
-
方法交换(method swizzing)
在Objctive-C中,对象收到消息后,究竟会调用哪种方法需要在运行期才能解析出来。查找消息的唯一依据是选择子(selector),选择子(selector)与相应的方法(IMP)对应,利用Objective-C的动态特性,可以实现在运行时偷换选择子(selector)对应的方法实现,这就是方法交换 (method swizzing).
每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。
我们可以新增选择子,也可以改变某个选择子所对应的IMP,还可以交换两个选择子所映射到的指针。
- Objective-C中提供了三种API来动态替换类方法或实例方法的实现:
1.class_replaceMethod
替换类方法的定义。
class_replaceMethod(Class cls,SEL name,IMP imp,const char *types)
2.method_exchangeImplementations
交换两个方法的实现。
method_exchangeImplementations(Method m1,Method m2)
3.method_setImplementation
设置一个方法的实现
method_setImplementation(Method m,IMP imp)
先说这三个方法的区别:
-
class_replaceMethod
:当类中没有想替换的原方法时,该方法调用class_addMethod
来为类增加一个新方法,也正因如此,class_replaceMethod
在调用时需传入types参数,而其余两个缺不需要。 -
method_exchangeImplementations
: 内部实现就是调用了两次method_setImplemetation
方法。
再来看看他们的使用场景:
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSelector = @selector(willMoveToSuperview:);
SEL swizzledSelector = @selector(myWillMoveToSuperview:);
Method originalMethod = class_getInstanceMethod(self, originalSelector);
Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
BOOL didAddMethod = class_addMethod(self,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(self,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)myWillMoveToSuperview:(UIView *)newSuperview
{
NSLog(@"WillMoveToSuperview: %@", self);
[self myWillMoveToSuperview:newSuperview];
}
总结
class_replaceMethod
,当需要替换的方法有可能不存在是,可以考虑使用该方法。
method_exchangeImplementations
,当需要交换两个方法时使用
method_setImplementation
是最简单的用法,当仅仅需要为一个方法设置其实现方式时实现。
4.Ivar
ivar 代表类中实例变量的类型,在Runtime的头文件中的定义如下:
typedef struct objc_ivar *Ivar;
objc_ivar的定义如下:
struct objc_ivar {
char * _Nullable ivar_name OBJC2_UNAVAILABLE;
char * _Nullable ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
class_copyIvarList(Class cls,unsigned int *outCount)
可以使用这个方法获取某个类的成员变量列表
5.objc_property_t
objc_property_t是属性,在Runtime的头文件中的定义如下:
typedef struct objc_property *objc_property_t;
class_copyPropertyList(Class cls, unsigned int *outCount)
可以使用这个方法获取某个类的属性列表。
Runtime原理探究