Runtime底层学习
Objective-C作为一门高级编程语言,想要成为可执行文件需要先编译成汇编语言,在汇编成机器语言,机器语言也是计算机能识别的唯一语言。但是Objective-C并不能直接编译成汇编语言,需要先转写为C语言在进行编译和汇编的操作。从Objective-C到C语言的过渡就是由Runtime来实现的。
Objective-C的语言特性是动态性比较强,这种动态性就是由Runtime API来支撑的。想要了解Runtime的原理,首先需要知道OC语言的isa
、Cache缓存
、class_rw_t
。
isa详解
要想学习Runtime,首先要了解它底层的一些常用的数据结构,比如isa指针。
在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址。
从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息。共用体如下
union isa_t
{
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
};
我们来看一段代码
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, assign, getter=isTall) BOOL tall;
@property (nonatomic, assign, getter=isRich) BOOL rich;
@property (nonatomic, assign, getter=isHandsome) BOOL handsome;
@end
#import "Person.h"
@implementation Person
@end
#import <Foundation/Foundation.h>
#import "Person.h"
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.rich = NO;
person.tall = YES;
person.handsome = NO;
NSLog(@"%zd",class_getInstanceSize([Person class]));
}
return 0;
}
在main.m函数中,系统给Person
对象分配了16个字节的内存,其中Person
的isa指针占用8个字节,三个属性占用3个字节。
既然Person
的属性是BOOL类型,可以考虑使用共用体。
共用体:共用体内的成员变量共用一块内存。
则Person
类的.m文件内,可以修改成如下:
#import "Person.h"
@interface Person()
{
union{
char bits;
struct{
char tall : 1;
char rich : 1;
char handsome : 1;
};
}_tallRichHandsome;
}
@end
@implementation Person
- (void)setTall:(BOOL)tall{
_tallRichHandsome.bits = tall;
}
- (BOOL)isTall{
return !!(_tallRichHandsome.bits );
}
- (void)setRich:(BOOL)rich{
_tallRichHandsome.bits = rich;
}
- (BOOL)isRich{
return !!(_tallRichHandsome.bits);
}
- (void)setHandsome:(BOOL)handsome{
_tallRichHandsome.bits = handsome;
}
- (BOOL)isHandsome{
return !!( _tallRichHandsome.bits);
}
@end
在结构体union中
struct{
char tall : 1;
char rich : 1;
char handsome : 1;
};
只是为了方便阅读代码,struct
内的成员变量,都存储在union
的char
中。所以回到开头的union isa_t
中,struct
是为了阅读方便,共用体union
中的信息都存储在uintptr_t bits;
中。
Class的结构
objc_class的结构-
class_rw_t
里面的methods
、properties
、protocols
是二维数组,是可读可写的,包含了类的初始内容、分类的内容。
class_rw_t方法结构图
method_array_t
是一个二维数组,每一个元素是一个分类或者类的方法数组method_list_t
。在method_list_t
数组中包含的才是分类或者类的方法信息。 -
class_ro_t
里面的baseMethodList
、baseProtocols
、ivars
、baseProperties
是一维数组,是只读的,包含了类的初始内容。
class_ro_t结构图 -
method_t
是对方法\函数的封装
method_t -
IMP
代表函数的具体实现
IMP -
SEL
代表方法\函数名,一般叫做选择器。底层结构跟char *
类似
可以通过@selector()
和sel_registerName()
获得。
方法缓存
Class内部结构中有个方法缓存(cache_t),用散列表来缓存曾经调用的方法,可以提高方法的查找速度。
buckets
是散列表,是数组。bucket_t
- 散列表的存储方式
散列表的存储方式
在上图中,当Person
类中有方法- (void)personTest;
- 首先,通过
@selector(personTest)
&_Mask
,得出一个值,比如得到2,则将这个方法personTest
存储到下表为2的数组中。一般存储的位置从高到低。如果当前被存储内容,这得到数值减1,往下存储。 - 当调用这个方法时,通过
@selector(personTest)
&_Mask
,得到同样的值,然后直接找到这个方法的地址值,直接调用。如果取值时,当前的cache_key_t
不是所需要的,这得到数值减1,往下取值。 - 缺点:用空间换时间。
objc_msgSend
先来看段代码
#import <Foundation/Foundation.h>
@interface Person : NSObject
- (void)personTest;
@end
#import "Person.h"
@implementation Person
- (void)personTest{
}
@end
#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
[person personTest];
}
return 0;
}
当调用- (void)personTest;
方法时,是在底层转化为C语言调用
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personTest"));
}
return 0;
}
- OC中的方法调用,其实都是转化为
objc_msgSend
函数的调用,给receiver(方法接收者)发送了一条消息(selector方法名); -
objc_msgSend
的执行流程可以分为3大阶段:
✔️ 消息发送
- 1、判断消息接收者
receiver
是否为nil。如果是nil,这直接退出; - 2、如果
receiver
不为nil,receiver
通过isa指针找到receiverClass(接收者类对象)
。从receiverClass
的cache
中查找方法。如果找到方法,调用方法,结束查找; - 3、如果没有找到方法,则从
reveiverClass
的class_rw_t
中查找方法。如果找到了方法,调用方法,结束查找,并将方法缓存到receiverClass
的cache
中; - 4、如果没找到方法,从superClass的cache中查找方法,如果找到方法,调用方法,结束查找,并将方法缓存到
receiverClass
的cache
中; - 5、如果没有找到方法,从superClass的
class_rw_t
中查找方法。如果找到方法,调用方法,结束查找,并将方法缓存到receiverClass
的cache
中; - 6、如果没找到方法,则判断是否还有superclass。如果有,则执行第4步。如果没找到方法,则动态方法解析。
✔️动态方法解析
- 是否曾经存在过动态方法解析,如果是,就进入消息转发阶段。
- 如果没有动态方法解析过,则调用
+ (BOOL)resolveInstanceMethod:(SEL)sel
方法(实例方法调用)或者+(BOOL)resolveClassMethod:(SEL)sel
(类方法调用)判断是否有动态绑定方法,并标记为已经动态方法解析,然后进入消息发送阶段。
✔️消息转发
- 通过动态方法解析不成功,则调用
- (id)forwardingTargetForSelector:(SEL)aSelector
方法,把这条消息转给其他接收者来处理,如果有其他接收者来处理,返回不为nil,则调用objc_msgSend(返回值,SEL)
; - 如果返回值为nil,则调用
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
方法,生成方法签名,然后系统用这个方法签名生成NSInvocation对象,如果这个方法返回的nil,则崩溃报错unrecognized selector sent to instance
; - 如果
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
方法返回值不为nil,则调用- (void)forwardInvocation:(NSInvocation *)anInvocation
方法。
注意:
1、如果调用的是类方法,上述流程中,方法换成类方法(+开头)。
2、dynamic告诉编译器不要自动生成setter方法和getter方法的实现,等到运行时在添加方法的实现。
3、@synthesize age = _age;
是为age
属性生成_age
,并且自动生成setter方法和getter方法,并赋值。
有关super
[super message]的底层实现
objc_msgSendSuper(self,[Person class],@selector(message));
[super class]
返回的是当前类
- (class)class{
return object_getClass(self);
}
[super superClass]
返回的是父类
- (Class)superclass{
return class_getSuperclass(object_getClass(self));
}
- 1、消息接收者仍然是子类对象;
- 2、从父类开始查找方法的实现;
Runtime的应用API
类相关
- 1、动态创建一个类(参数:父类,类名,额外的内存空间)创建之后要注册这个类
Class objc_allocateClassPair(Class superClass,const char *name,size_t extraBytes) - 2、注册一个类(要在类注册之前添加成员变量)
Void objc_registerClassPair(Class cls) - 3、销毁一个类
Void objc_disposeClassPair(Class cls) - 4、获取isa指向的Class,或者类对象(元类对象)
Class object_getClass(id obj) - 5、设置isa指向的class,此isa指向其他的类
Class object_setClass(id obj,class cls) - 6、判断一个OC对象是否为class,传入实例对象,BOOL为0,传入类对象,BOOL为1
BOOL object_isClass(id obj) - 7、判断一个Class是否为元素
BOOL class_isMetaClass(Class cls) - 8、获取父类
Class class_getSuperclass(class cls)
成员变量相关
- 9、获取成员变量信息(获取不到值)
Ivar class_getInstanceVariable(Class cls,const char *name) - 10、拷贝实例变量列表(最后需要调用free释放)
Ivar *class_copyIvarList(Class cls,unsigned int *outCount) - 11、设置和获取成员变量的值
Void object_setIvar(id obj,Ivar ivar,id value)
Id object_getIvar(id obj,Ivar ivar) - 12、动态添加成员变量
BOOL class_addIvar(Class cls,const char *name,size_t size,unit8_t alignment,
const char *types) - 13、获取成员变量的相关信息
const char *ivar_getName(Ivar v)
const char *ivar_getTypeEncoding(Ivar v)
属性相关
- 14、获取一个属性
- 15、拷贝属性列表(最后需要调用free释放)
objc_property_t *class_copyPropertyList(Class cls,unsigned int *outCount) - 16、动态添加属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t
*attributes, unsigned int attributeCount) - 17、动态替换属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t
*attributes,unsigned int attributeCount) - 18、获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
方法相关
- 19、获得一个实例方法、类方法
Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name) - 20、方法实现相关操作
IMP class_getMethodImplementation(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2) - 21、拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount) - 22、动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types) - 23、动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types) - 24、获取方法的相关信息(带有copy的需要调用free去释放)
SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index) - 25、选择器相关
const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str) - 26、用block作为方法实现
IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)