iOS runtime
文章目录
- OC中类和对象的本质
- 实例对象,类,元类的关系
- 类的属性
- 类的方法
- 消息发送机制
- Runtime api的使用
- Runtime 的应用
Runtime是什么?
Runtime是一个运行时库,它提供对Objective-C语言的动态属性的支持。Runtime是一种程序在运行时候的机制,其中最主要的是消息机制。在objective-c中,消息是在程序运行的时候才绑定到方法实现的。
OC中类和对象的本质
在程序编译运行的时候,我们用OC编译的代码其实会经历以下流程
OC代码 -> C++,C语言代码 -> 汇编代码->二进制代码
OC的实现其实是通过C++和C语言去实现的(苹果官方的源码https://opensource.apple.com/tarballs/objc4/,一般包体积最大的都是最新的)
在iOS中,几乎所有的类都继承于NSObject类,所以我们先探究一下这个NSObject这个类,NSObject 这个类里面只有Class isa这个变量
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
那Class isa是什么呢?先来看看Class的定义
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
struct objc_class : objc_object {
// Class ISA;
Class superclass;//父类
cache_t cache;//缓存列表
uintptr_t data_NEVER_USE; // class_rw_t * plus custom rr/alloc flags
//
class_rw_t *data() {
return (class_rw_t *)(data_NEVER_USE & ~CLASS_FAST_FLAG_MASK);
}
方法列表...
};
Class其实就是一个继承于objc_object结构体objc_class。objc_object是一个包含了指向objc_class 指针isa指针的结构体。所以说,其实我们在OC中写的类,其实就是一个结构体。我们创建对象的时候,其实就是在创建结构体实例
对于objc_object包含一个objc_class类型的isa指针,而objc_class又是继承于
objc_object这个结构体,这是不是有点绕?
其实这个很好理解,就是类其实也是一个对象,所以objc_class会继承objc_object这个结构体。
实例对象,类,元类的关系
看一张经典的图
图3-1 实例对象,类和元类.png
这里有三个角色,实例对象,类,还有元类。对于这三个角色,有以下关系
-
实例对象是类的实例,类是元类的实例
-
类是描述实例对象的,包括对象的方法(类声明中的 - 方法),属性,变量等信息。元类是描述类对象的,包括类对象的方法(类声明中的 - 方法)等信息。所以类中的声明的方法,无论是 - 还是 + 方法,都是对象方法,区别在于是说这个对象是实例对象还是类对象。
-
元类其实也是类,与类的实现都是objc_class结构体
-
实例对象中的isa指针指向于对应的类,而类对象中的isa指针指向元类
-
objc_class中变量superclass是一个指针,指向父类的objc_class,也就是说指向父类的结构体。类的superclass指向父类的objc_class,根类(NSObject)的superclass是空。
-
元类的superclass指向父类的元类的。而元类的根类的superclass指向的是类的根类。为什么元类的根类的superclass指向的是类的根类呢?因为元类本来就是类,所以元类的根类是继承于根类NSObject。
-
关于isa指针
上面提及到,isa是一个指针指向于类或元类的指针。在arm64架构之前,isa指针是直接指向类或是元类的地址,但是在arm64架构后,对isa指针做了优化,isa指针采用了union的存储方式,用来存储类或元类地址,nonpointer,ha s_assoc,has_cxx_dtor,shiftcls,magic,weakly_referenced,deallocating,extra_rc,has_sidetable_rc等信息(isa指针占8个字节,通过将上述字段的信息存通过位运算存放到isa指针所占的8个字节不同的位中)。
类的结构
上面已经给出了objc_class的结构体实现,其中有一个class_rw_t 类型的data,实现如下
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;//实例对象占据的内存大小
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
const method_list_t * baseMethods;//类原有的方法
const protocol_list_t * baseProtocols;//类原有的协议
const ivar_list_t * ivars;//类的变量
const uint8_t * weakIvarLayout;
const property_list_t *baseProperties;//类原有的属性
};
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;//类原有的信息,即未融合category的方法和属性的类
union {
//采用共用体的方式去存储方法列表
method_list_t **method_lists; // RW_METHOD_ARRAY == 1 //包含该类所有category的方法列表
method_list_t *method_list; // RW_METHOD_ARRAY == 0 //类原本的方法列表
};
struct chained_property_list *properties;
const protocol_list_t ** protocols;//属性列表
Class firstSubclass;
Class nextSiblingClass;
};
类中的变量,属性,方法布局如下
类的结构
类的结构体中有包含了父类的指针,方法的缓存列表, class_rw_t结构的类信息。
类的信息中有一个方法的缓存列表,这个缓存列表是一个hash表。在我们调用过的方法中,会把方法的地址缓存到缓存列表中。调用方法的时候,我们会先查看方法方法列表中是否有对应的方法,没有的情况下再去类信息中查找。散列表是一种较为高效的查找方法,比遍历方法列表高效不小,所以整体上加入方法缓存可以提高调用方法的效率。
类中还包含了一个class_rw_t 类型的data,data中包含了一个class_ro_t 类型只读变量ro,这个ro包含了类的变量,属性,协议,方法等的信息。class_ro_t 既然已经包含了类的这些信息,为什么类的结构体中还要多家一层class_rw_t 类型的da ta层呢?
原因很简单,因为ro只包含了类在编译后的信息,但是我们在开发过程中会在category给类添加属性和方法等,所以需要给类添加额外的信息,而class_rw_t这一层就是承担着这种功能。
了解了类的结构后,我们可以通过runtime中的API去获取到类的变量,属性,方法列表等信息,甚至可以通过runtime去动态创建类。关于runtime的使用可以在文章底部看到
类的方法
- 方法解析
- load和initialize
- class方法
- super关键词
方法解析
在我们编写OC代码的时候,对象和类方法的调用本质是消息发送,调用objc_msgSend方法,向对象发送一个调用方法的消息。
#if !OBJC_OLD_DISPATCH_PROTOTYPES
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincompatible-library-redeclaration"
OBJC_EXPORT void
objc_msgSend(void /* id self, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
#pragma clang diagnostic pop
#else
从上面的源码可知objc_msgSend存在隐藏的默认两个参数。第一个是self,也就是方法的调用者,第二个是sel,也就是需要调用的方法的名称。省略号...这是我们写的OC方法中传入的参数。OC代码中方法调用在运行过程中最终会转换成objc_msgSend的形式。
虽然绝大部分OC代码都是通过消息发送机制去完成调用的,但不是所有的OC代码都是通过objc_msgSend去调用,如load方法,是在类加载被系统直接调用的。
load和initialize的区别
在日常开发中,会经常在类做一些初始化工作,这时候需要用到类的load和initialize方法。load方法是在把类加载到内存中时候会被调用,而initialize是类第一次接收到消息的时候被调用。
加载类的时候是按build phase(如下图所示)中的从上往下的顺序去加载。
对于load方法,当加载类到内存的时候,如果类存在父类的情况下,不管父类的编译顺序先后,会先加载父类的load方法,类中的load这个方法只会由系统调用一次。如果category存在load方法,在调用完类的load方法后,就会按编译顺序调用分类中的load方法。
对于工程中所有类而言,会按照编译顺序去调用load方法。
但如果类存在父类,或是category实现了load方法的时候,调用顺序如下
父类的load(如果存在父类) -> 类的load方法 -> 类的load方法
而且因为在加载类的时候调用load方法,是通过直接调用类或是category的lo ad方法,所以不存在覆盖问题。
对于initialize方法,在第一次给类发送消息的时候(如 [类名 allock] init]),会被调用。initialize和load方法一样,如果存在父类的会优先调用父类的initialize方法,如果子类没有实现initialize方法,则会调用父类的initialize方法。所以如果一个类有多个子类,但是子类里面没有实现initialize方法,父类的initialize方法可能会调用多次。
initialize方法是通过消息发送机制去调用的,如果category实现了initialize方法会存在方法覆盖的问题。为什么覆盖,这里留在另外一篇文章去讨论。
QQ20200523-135757@2x.pngclass方法
class 方法作用是获取类的类型,默认实现是在NSObject这个类里面,所以我们即便不实现class方法,也能调用这个方法,但是既然class方法的实现在NSObject中,那怎能保证class方法能准确的返回我们的类呢?看一下下面的源码
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
+ (Class)superclass {
return self->superclass;
}
- (Class)superclass {
return [self class]->superclass;
}
对于类对象,返回的是self,这个self不是指NSObject这个类,指得是objc_msgSend(void /* id self, SEL op, ... */ )中传入的self,也就是调用者,所以这就能保证即便是写在NSObject中的类也能返回正确的值。对于实例对象,返回的是实例对象的isa指针。从上图的3-1可知道,isa指针会指向对应的类对象。
super方法
super 这个方法是告诉应该从父类的方法开始寻找对应的方法。在转换成最终的调用时,调用的不是objc_msgSend这个函数,而是调用objc_msgSendSuper这个函数。调用这个方法会传入两个隐藏参数,分别是objc_super 和 方法名称,objc_msgSendSuper 会根据objc_super的值去查找对应的父类。objc_super是一个结构体,内容如下
struct objc_super {
id receiver;
Class class;
};
这里包含了两个变量,分别是调用者和调用者class类型。
由上可知,OC代码的方法调用基本都是由objc_msgSend去实现的,调用一个对象的方法就是给这个对象发送消息。
消息发送机制
上面提及到方法的调用本质上是消息发送机制,消息发送机制的执行流程如下
QQ20200523-154512@2x.png
如果经历了上面的步骤没找到方法的话,就会转入消息动态解析流程
QQ20200523-154830@2x.png
如果动态解析流程不处理消息的话,就会进入消息转发流程
QQ20200523-154957@2x.png
测试消息发送流程的代码如下
#import "ViewController.h"
#import <objc/runtime.h>
#import "ForWardHander.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//测试实例方法的消息发送流程
[self performSelector:@selector(testSendMessage)];
//测试类方法的消息发送流程
[ViewController performSelector:@selector(testSendMessage)];
}
void testResolveClassMethod(id self, SEL _cmd){
NSLog(@"动态添加方法");
}
//实例对象的消息发送
+(BOOL)resolveInstanceMethod:(SEL)sel{
//1 动态添加方法
if (sel == @selector(testSendMessage)) {
class_addMethod([self class], sel, (IMP)testResolveClassMethod, "v16@0:8:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
-(id)forwardingTargetForSelector:(SEL)aSelector{
//2 消息转发-转发方法给其他对象
if(aSelector == @selector(testSendMessage)){
return [[ForWardHander alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
//获取方法签名
if(aSelector == @selector(testSendMessage)){
NSMethodSignature *signature = [NSMethodSignature methodSignatureForSelector:@selector(forwardingTargetForSelector:)];
return signature;
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
//3 消息转发- 转发方法,在这里可以指定方法调用者和用什么方法代替原本调用的方法。forwardingTargetForSelector只能制定代替处理的对象
anInvocation.target = self;
anInvocation.selector = @selector(forwariTestMethodSignature);
[anInvocation invoke];
}
-(void)forwariTestMethodSignature{
NSLog(@"测试实例对象的methodSignatureForSelector");
}
//类对象的消息发发送
+(BOOL)resolveClassMethod:(SEL)sel{
return NO;
}
+(id)forwardingTargetForSelector:(SEL)aSelector{
if(aSelector == @selector(testSendMessage)){
return [[ForWardHander alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
+(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if(aSelector == @selector(testSendMessage)){
NSMethodSignature *signature = [NSMethodSignature methodSignatureForSelector:@selector(forwardingTargetForSelector:)];
return signature;
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation{
anInvocation.target = [self class];
anInvocation.selector = @selector(forwariTestMethodSignature);
[anInvocation invoke];
}
+(void)forwariTestMethodSignature{
NSLog(@"测试类的methodSignatureForSelector");
}
@end
ForWardHander.m的实现如下
#import "ForWardHander.h"
@implementation ForWardHander
-(void)testSendMessage{
NSLog(@"测试消息转发");
}
@end
这里顺道提一下class_addMethod中最后一个参数types,types传入的是一个字符指针,里面的值构成是
‘返回类型+方法参数总共占的字节数+类型+参数1的字节数开始 +类型+ 参数2的字节数开始+ ...类型+ 参数n的字节数开始’
对于方法,最终转换成objc_msgSend,默认会传入两个参数,objc_msgSend(void /* id self, SEL op, ... */ ),一个是方法调用者,一个是方法名。
所以对于空方法,如test(),types传的值是'v16@0@8'。对于有一个参数的方法,types串的是‘v20@0@8i16’
NSProxy
OC的对象中有两个大的基类,一个是NSObect,一个是NSProxy。NSObject我们在开发中经常用到,但是NSProxy是什么呢?NSProxy是专门用来做消息转发的,相当于一个中介,把你的需求交给其他类去实现。关系如下
QQ20200523-171531@2x.pngNSProxy实现消息转发跟上面消息发送流程最后一步大致一样,需要实现下面的两个方法
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
NSProxy和消息发送机制都可以用来做消息转发,但是NSProxy更高效,因为NSProxy不需要经历方法查找,动态方法解析等步骤。
Runtime Api的使用
关于类的API
//动态创建一个类(参数:父类,类名,额外的内存空间)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
//注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class cls)
//销毁一个类
void objc_disposeClassPair(Class cls)
//获取isa指向的Class
Class object_getClass(id obj)
//设置isa指向的Class
Class object_setClass(id obj, Class cls)
//判断一个OC对象是否为Class
BOOL object_isClass(id obj)
//判断一个Class是否为元类
BOOL class_isMetaClass(Class cls)
//获取父类
Class class_getSuperclass(Class cls)
关于变量的API
//获取一个实例变量信息
Ivar class_getInstanceVariable(Class cls, const char *name)
//拷贝实例变量列表(最后需要调用free释放)
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
//设置成员变量的值
void object_setIvar(id obj, Ivar ivar, id value)
//获取成员变量的值
id object_getIvar(id obj, Ivar ivar)
//动态添加成员变量(已经注册的类是不能动态添加成员变量的)
BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)
//获取成员变量的名称
const char *ivar_getName(Ivar v)
//获取成员变量的编码类型
const char *ivar_getTypeEncoding(Ivar v)
关于属性的API
//获取一个属性
objc_property_t class_getProperty(Class cls, const char *name)
//拷贝属性列表(最后需要调用free释放)
objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
//动态添加属性
BOOL class_addProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
//动态替换属性
void class_replaceProperty(Class cls, const char *name, const objc_property_attribute_t *attributes,
unsigned int attributeCount)
//获取属性的一些信息
const char *property_getName(objc_property_t property)
const char *property_getAttributes(objc_property_t property)
关于方法的API
//获得实例方法
Method class_getInstanceMethod(Class cls, SEL name)
//获得类方法
Method class_getClassMethod(Class cls, SEL name)
//获取方法实现
IMP class_getMethodImplementation(Class cls, SEL name)
//设置方法实现
IMP method_setImplementation(Method m, IMP imp)
//替换方法实现
void method_exchangeImplementations(Method m1, Method m2)
//拷贝方法列表(最后需要调用free释放)
Method *class_copyMethodList(Class cls, unsigned int *outCount)
//动态添加方法
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
//动态替换方法
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
//获取方法名
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)
//获取选择器名称
const char *sel_getName(SEL sel)
//注册一个选择器
SEL sel_registerName(const char *str)
//设置block作为方法实现
IMP imp_implementationWithBlock(id block)
//获取block
id imp_getBlock(IMP anImp)
//移除block
BOOL imp_removeBlock(IMP anImp)
Runtime的应用
- Method Swizzling
- category中属性关联对象
- 对象和模型的相互转换