在学习Runtime
之前,我们了解到,OC
是一门动态性比较强的语言,这跟C
和C++
有很大的不同。而OC
动态运行时的本质就是Runtime
,OC
的底层实现都是基于Runtime
来实现的,Runtime
是开源的可以下载源码,底层由C
和C++
以及汇编语言
来实现。知道了这些,我们可以利用Runtime
来做很多事情,现在让我们来详细的了解Runtime
,以及如何在项目中使用Runtime
。
在了解Runtime
之前,首先我们要了解一个概念,什么是ISA
,了解Runtime
在底层常用的数据结构,我们先来看一下什么是ISA
。
ISA详解
在arm64架构之前,isa就是个普通指针,存储着Class,Meta-Class对象的内存地址
在arm64结构开始,对isa进行了优化,变成了一个共用体(union)结构,还是用位域来存储更多的信息
在源码中我们可以找到这个共用体:
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
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;
};
}
这些字段代表的含义:
nonpointer
0、代表普通指针,存储着Class、Meta-Class对象的内存地址
1、代表优化过,使用位域存储更多的信息
has_assoc
是否设置过关联对象,如果没有,释放时会更快
has_cxx_dtor
是否有C++的析构函数(.cxx.destruct),如果没有,释放时更快
shiftcls
存储着Class、Meta-Class对象的内存地址
magic
用于在调试时分辨对象是否未完成初始化
weakly_referenced
是否有被弱引用指向过,如果没有,释放时会更快
deallocating
对象是否正在释放
has_sidetable_rc
引用计数器是否过大无法存储在isa中
如果为1,那么引用计数会存储在一个叫SideTable的类的属性中
extra_rc
里面存储的值是引用计数器减1
想要对共用体以及isa
位域等信息有一个详细的了解,需要大家知道位运算的基本知识,在这里不做过多的介绍。大概的思想就是通过位运算,我可以取出特定的位,这样本来需要一个字节(也就是8位)来存储一个信息,现在我用到了位运算,就可以用一位来存储某个信息,这样对计算机的内存有一个优化。想要知道Runtime
,我们需要对类的结构有个了解,接下来我们了解一下Class
的结构。
struct objc_class {
Class isa;
Class super_class;
struct objc_cache * cache;//方法缓存
struct objc_data_bits_t bits;//用于获取具体的类信息
};
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_list_t methods;//方法列表
property_list_t properties;//属性列表
protocol_list_t protocols;//协议列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;//instance对象占用的内存空间
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;//类名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;//成员变量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
};
class_rw_t
- class_rw_t里面的methods、properities、protocols是二维数组,包含了类的初始内容、分类的内容
class_ro_t
- class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容
baseMethodList
里包含的数据类型是method_t
:
method_t
IMP代表函数的具体实现
SEL代表方法\函数名,一般叫选择器
1、可以通过@selector()和sel_registerName()活的
2、可以通过sel_registerName()和NSStringFromSelector()转成字符串
3、不同类中相同的名字的方法,所对应的方法选择器是相同的
types包含了函数返回值、参数编码字符串
struct method_t {
SEL name;//函数名
const char *types;//编码(返回值类型,参数类型)
IMP imp;//函数地址
};
};
方法缓存
Class内部有个方法缓存Cache_t,用散列表来缓存曾经调用过的方法,可以提高方法的查找速度
struct cache_t {
struct bucket_t *_buckets;//散列表
mask_t _mask;//散列表的长度
mask_t _occupied;//已经缓存的方法数量
}
struct bucket_t {
cache_key_t _key;//SEL作为可以
IMP _imp;//函数的内存地址
}
现在大家对类的底层实现,有了详细的了解,接下来,我们一起研究一下objc_msgSend
方法调用的流程
objc_msgSend执行流程
OC中的方法调用,其实都是转换为objc_msgSend函数调用,objc_msgSend的执行流程可以分为三大阶段
1、消息发送
2、动态方法解析
3、消息转发
objc_msgSend执行流程 - 源码跟读
objc-msg-arm64.s
ENTRY_objc_msgSend
b.le LNilOrTagged
CacheLookup NORMAL
.macro CacheLookup
.macro CheckMiss
STATIC_ENTRY _objc_msgSend_uncached
.macro MethodTableLookup
_class_lookupMethodAndLoadCache3
objc-runtime-new.mm
_class_lookupMethodAndLoadCache3
lookUpImpOrForward
getMethodNoSuper_nolock、search_method_list、log_and_fill_cache
cache_getImp、log_and_fill_cache、getMethodNoSuper_nolock、log_and_fill_cache
_class_resolveInstanceMethod
_objc_msgForward_impcache
objc-msg-arm64.s
STATIC_ENTRY_objc_msgForward_impcache
ENTRY_objc_msgForward
Core Foundation
forwarding(不开源)
2、如果消息发送不成功,就会进入动态解析方法,开发者可以实现以下方法,来动态添加方法。动态解析过后,会重新走消息发送的流程,从receiverClass
的cache
中查找放方法这一步重新开始执行。
+(BOOL)resolveClassMethod:(SEL)sel
+(BOOL)resolveInstanceMethod:(SEL)sel
3、如果消息发送,以及动态方法解析都不成功,那么开始走最后一步,消息转发阶段,消息转发调用Runtime
的方法:
- (id)forwardingTargetForSelector:(SEL)aSelector{
return [super forwardingTargetForSelector:aSelector];
}
//方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [super methodSignatureForSelector:aSelector];
}
//封装了一个方法调用,包括:方法调用者,方法名,方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation{
// anInvocation.target方法调用者
// anInvocation.selector方法名
// [anInvocation getArgument:NULL atIndex:0]
}
Runtime的使用
//动态创建一个类(参数:父类,类名,额外的内存空间)
objc_allocateClassPair(Class _Nullable __unsafe_unretained superclass, const char * _Nonnull name, size_t extraBytes);
//注册一个类(要在类注册之前添加成员变量)
objc_registerClassPair(Class _Nonnull __unsafe_unretained cls);
//销毁一个类
objc_disposeClassPair(Class _Nonnull __unsafe_unretained cls);
//获取isa指向的class
objc_getClass(const char * _Nonnull name);
//设置isa指向的class
object_setClass(id _Nullable obj, Class _Nonnull __unsafe_unretained cls);
//判断一个OC对象是否为class
object_isClass(id _Nullable obj);
//判断一个OC对象是否为meta-class
class_isMetaClass(Class _Nullable __unsafe_unretained cls);
//获取父类
class_getSuperclass(Class _Nullable __unsafe_unretained cls);
//获取一个实例变量
class_getInstanceVariable(Class _Nullable __unsafe_unretained cls, const char * _Nonnull name);
//copy实例变量列表(最后需要调用free释放)
class_copyIvarList(Class _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);
//设置和获取成员变量的值
object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value);
object_getIvar(id _Nullable obj, Ivar _Nonnull ivar);
//动态添加成员变量(已经注册的类是不能动态添加成员变量的)
class_addIvar(Class _Nullable __unsafe_unretained cls, const char * _Nonnull name, size_t size, uint8_t alignment, const char * _Nullable types);
//获取成员变量的相关信息
ivar_getName(Ivar _Nonnull v);
ivar_getTypeEncoding(Ivar _Nonnull v);
//获取一个属性
class_getProperty(Class _Nullable __unsafe_unretained cls, const char * _Nonnull name);
//拷贝属性列表
class_copyPropertyList(Class _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);
//动态添加属性
class_addProperty(Class _Nullable __unsafe_unretained cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount);
//动态替换属性
class_replaceProperty(Class _Nullable __unsafe_unretained cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount);
//获取属性的一些信息
property_getName(objc_property_t _Nonnull property);
property_getAttributes(objc_property_t _Nonnull property);
//获得一个实例方法、类方法
class_getInstanceMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name);
class_getClassMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name);
//方法实现相关操作
class_getMethodImplementation(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name);
method_setImplementation(Method _Nonnull m, IMP _Nonnull imp);
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);
//拷贝方法发列表(最后需要调用free释放)
class_copyMethodList(Class _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);
//动态添加方法
class_addMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types);
//动态替换方法
class_replaceMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types);
//获取方法的相关信息(带有copy的需要调用free释放)
method_getName(Method _Nonnull m);
method_getImplementation(Method _Nonnull m);
method_getTypeEncoding(Method _Nonnull m);
method_copyReturnType(Method _Nonnull m);
method_copyArgumentType(Method _Nonnull m, unsigned int index);
Runtime
还有许多API,在这里就不一一列举了,有兴趣的可以自己搜索,有很多相关的文章可以借鉴。利用这些API
,我们可以在项目中利用关联对象,给分类添加属性、也可以遍历所有类的成员变量(修改textfield
的站占位文字颜色,字典转模型,自动归结档等)、交换方法实现等一系列操作。