1. 概述
- Objective-C是一门动态性比较强的编程语言,跟C、C++等语言有着很大的不同
- Objective-C的动态性是由Runtime API来支撑的
- Runtime API提供的接口基本都是C语言的,源码由C\C++\汇编语言编写
2.Runtime之 isa指针
- 要想学习Runtime,首先需要了解底层的一些常用的数据结构,比如isa指针
- 在arm64架构之前,isa就是 一个普通的指针,储存着Class,Meta-Class对象的内存地址
- 从arm64架构开始,对isa指针进行了优化,变成了一个共用体(
union
)结构,还使用位域来存储更多的信息
2.1 示例:使用位来储存值
@interface GYPerson : NSObject
//我们不能声明一个属性,如果声明属性,系统会默认生成set和get方法
//@property (assign, nonatomic, getter=isTall) BOOL tall;
//@property (assign, nonatomic, getter=isRich) BOOL rich;
//@property (assign, nonatomic, getter=isHansome) BOOL handsome;
- (void)setTall:(BOOL)tall;
- (void)setRich:(BOOL)rich;
- (void)setHandsome:(BOOL)handsome;
- (BOOL)isTall;
- (BOOL)isRich;
- (BOOL)isHandsome;
@end
//
// GYPerson.m
// Runtime-isa
//
// Created by admin on 2020/9/18.
// Copyright © 2020 admin. All rights reserved.
//
#import "GYPerson.h"
#define GYTallMask (1<<0)
#define GYRichMask (1<<1)
#define GYHansomeMask (1<<2)
@implementation GYPerson
{
//声明一个char类型的一个字节来储存 3个Bool类型的变量值
char _tallRichHansome;
}
- (instancetype)init
{
self = [super init];
if (self) {
//初始化赋值 , 假设tall、rich、handsome 在_tallRichHansome变量中的储存位置是从右往左开始储存
_tallRichHansome = 0b00000100;
}
return self;
}
/**
位运算:
& : 与运算 在二进制下 都为1 得出的值是1, 如果有一个是0 那么值为0 (可以去除特定的位)
// 0000 0111
//&0000 0100
//------
// 0000 0100
*/
- (void)setRich:(BOOL)rich {
//1.rich是储存从右往左数第一位,那么我们如果得到右数第一位的值,这里我们需要用位运算
if (rich) {
//如果是YES 改变我们需要使用|运算 使用|运算不改边原来的值
_tallRichHansome |= GYRichMask;
} else {
_tallRichHansome &= ~GYRichMask;
}
}
- (void)setTall:(BOOL)tall {
if (tall) {
//如果是YES 改变我们需要使用|运算 使用|运算不改边原来的值
_tallRichHansome |= GYTallMask;
} else {
//如果原本是0 那我们需要去掩码的反码 在和原来的进行 &运算
/**
0000 0001 储存的值
0000 0001 掩码
1111 1110 掩码的反码
*/
_tallRichHansome &= ~GYTallMask;
}
}
- (void)setHandsome:(BOOL)handsome {
if (handsome) {
//如果是YES 改变我们需要使用|运算 使用|运算不改边原来的值
_tallRichHansome |= GYHansomeMask;
} else {
//如果原本是0 那我们需要去掩码的反码 在和原来的进行 &运算
/**
0000 0001 储存的值
0000 0001 掩码
1111 1110 掩码的反码
*/
_tallRichHansome &= ~GYHansomeMask;
}
}
- (BOOL)isRich {
//取值 使用& 运算 不管你&运算出来是多少,首先使用!把值变成0 在使用!变成1 Bool值 0和1
return !!(_tallRichHansome & GYRichMask);
}
- (BOOL)isTall {
return !!(_tallRichHansome & GYTallMask);
}
- (BOOL)isHandsome {
return !!(_tallRichHansome & GYHansomeMask);
}
@end
//测试代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
GYPerson *person = [[GYPerson alloc] init];
person.tall = NO;
person.rich = YES;
person.handsome = NO;
NSLog(@"tall:%d rich:%d hansome:%d", person.isTall, person.isRich, person.isHandsome);
}
return 0;
}
- 位运算:
- &: 与
- |: 或
- !: 非
- ~: 取反
- ^: 异或
上述实现过程我们还可以实现,但是管理起来比较麻烦,我们还可以使用结构体和位域来实现上述功能:
/**
使用位域的技术来存储值
*/
@implementation GYPerson {
// 定义一个结构体类型的数据来储存数据
struct {
//这样表示该字段占据1位 , 储存顺序是从右往左开始 储存
char tall : 1;
char rich : 1;
char handsome : 1;
} _TallRichHandsome;
}
- (void)setTall:(BOOL)tall {
_TallRichHandsome.tall = tall;
}
- (void)setRich:(BOOL)rich {
_TallRichHandsome.rich = rich;
}
- (void)setHandsonme:(BOOL)handsome {
_TallRichHandsome.handsome = handsome;
}
- (BOOL)isTall {
//如果不加!! ,且值为1的话,那么直接取值为-1 ,这是因为
//因为只占一位 而返回的BOOL类型的值是一个字节 占8位, 那么系统会自动以这个一位的值来填充其他位(如果该位是1,就是拿1来填充其他位->0b1111 1111 -> -1,(255和-1 储存都是0xff,如果是无符号就是255,有符号就是-1)), 这里有两种解决方法,一种是加上!! ,第二种是用两位来存储,这样有一位是0,那么久会以0来填充其他位
return !!_TallRichHandsome.tall;
}
- (BOOL)isRich {
return !!_TallRichHandsome.rich;
}
- (BOOL)isHandsome {
return !!_TallRichHandsome.handsome;
}
@end
union
共用体: 表示大家共用一块内存,接下来我们可以使用union
来是实现上述存储功能, 使用结构体来表示说明
#define GYTallMask (1<<0)
#define GYRichMask (1<<1)
#define GYHandsomeMask (1<<2)
@implementation GYPerson {
//使用共用体来声明, 实际上和上面 声明 char _TallRichHandsome,的作用是一样的,都是表示一个字节
union {
char bits;
//该结构体仅表示代码说明的作用(增加代码的可读性),实际上不起任何作用的, 可以删除
//加上和这个结构体,是不影响以前的存储的,因为这个结构只占一个字节,union只占一个字节,所以这个结构体是没有影响union的内存的, 而且由始至终我们一直是在访问bits,没有去访问过结构体
// 如果结构体的内存超过一个字节, 那么bits的内存也需要相应的放生变化
struct {
char tall : 1;
char rich : 1;
char handsome : 1;
};
} _TallRichHandsome;
}
- (void)setTall:(BOOL)tall
{
if (tall) {
_TallRichHandsome.bits |= GYTallMask;
} else {
_TallRichHandsome.bits &= ~GYTallMask;
}
}
- (BOOL)isTall
{
return !!(_TallRichHandsome.bits & GYTallMask);
}
- (void)setRich:(BOOL)rich
{
if (rich) {
_TallRichHandsome.bits |= GYRichMask;
} else {
_TallRichHandsome.bits &= ~GYRichMask;
}
}
- (BOOL)isRich
{
return !!(_TallRichHandsome.bits & GYRichMask);
}
- (void)setHandsonme:(BOOL)handsonme
{
if (handsonme) {
_TallRichHandsome.bits |= GYHandsomeMask;
} else {
_TallRichHandsome.bits &= ~GYHandsomeMask;
}
}
- (BOOL)isHandsome
{
return !!(_TallRichHandsome.bits & GYHandsomeMask);
}
@end
-
存储值的位置是有xxxMask,也就是掩码来决定的
-
isa 的部分源码:
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
struct objc_object {
private:
isa_t isa;
}
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
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;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
}
- 上述isa中的一些解释:
- Class、meta-class对象的地址值后三位一定都是0,arm64之后需要&一个mask(掩码)取出储存的地址值,因为在arm64之后,isa中不仅仅是存储地址值,还会储存其他的信息
- 在arm64之前isa指正是一个普通的指针,存储这class、meta-class的地址值,但是在arm64之后,对isa进行了优化,采用共用体(
union
)的形式,使用一个64位的内存空间存储这许多信息,其中33位才储存着Class、meta-class对象的地址值,而其他空间还储存着许多其他的信息
3. Runtime-Class
3.1 class
-
回顾之前学的class的结构,如下图:
-
bits & mask(掩码) ,找到 结构他class_tw_t对象,这里面包含着class的一些信息
- ro: 只读属性,储存着class中定义的基本信息,属性,方法等
- 程序运行开始,首先或将ro里面的内容赋值到 methods、properties、protocols中 这个三个属性是二维数组,是可以可写的 methods中储存是method_list_t (一维数组),这里储存的是method_t (方法对象)
- class_rw_t中的methods、properties、protocols 结构如下图
3.2 method
-
method_t的结构如下:
- name: SEL类型(方法选择器),代表方法/函数名,一般叫做选择器,底层结构跟
char*
类似- 可以通过
@selector()
和sel_registerName()
(runtimeAPI)获得 - 可以通过
sel_getName()
和NSStringFormSelector()
转成字符串 - 不同类中相同名字的方法,所对应的方法选择器是相同的
- 可以通过
- IMP:代表函数的具体实现(储存着函数的地址)
typedef id _Nullable (* IMP)(id _Nullable, SEL _Nullable, ...);
- types: 包含了函数的返回值信息,参数编码的字符串
- name: SEL类型(方法选择器),代表方法/函数名,一般叫做选择器,底层结构跟
-
type是如何通过字符来表示,返回值类型,参数的这些信息的,IOS中提供了一个@encode的指令,是通过这个编码来表示的:
-
oc中调用方法的实质,就是转换成objc_msgSend(id self, _cmd sel)方法发送,默认传递了两个参数, id类型的self 和 _cmd 类型 @selector()
3.3 cache_t cache 方法缓存对象
-
函数的底层结构就是method_t对象
-
方法调用
- 首先通过isa指针寻找class对象,在方法缓存列表中寻找方法,如果没有则去方法列表中寻找
- 如果不存在,通过superclass指针去父类中寻找,执行1操作
- 一旦找到方法调用之后, 会把该方法缓存到
自己
的cache
(方法缓存列表对象)中,下次该对象在此调用该方法时,是通过isa指针找到class对象,然后直接去class对象中的cache(方法缓存列表对象)中调用,就不用在一层一层的查找方法
-
cache
缓存对象的结构
- 散列表(哈希表)数组的长度可能会大已缓存方法数量,每一个bucket_t 对象都是一个方法对象
- 调用方法是通过
@selector(xxx
)去寻找方法的 - 第一次调用方法时 使用
@selector(xxx)
对象 & mask == 缓存在数组中的索引(通过一套算法和位运算直接算出索引), 数组的其他位置没有缓存就设置为空, 然后第二次调用,直接通过索引 在数组中找到 缓存的bucket_t(方法信息),然后直接调用, 并不是像普通的一样循环遍历,去比较key值(牺牲内存空间,换取执行效率) mask
是变化的,mask
的长度等于数组减1,初始化时,数组有一个默认长度,也就是mask有一个默认数值, 当缓存方法数组容量等于数组长度时(表示快不够缓存),会扩充容量,容量为原来旧数组容量的2倍 ,mask的值也会发生变化,扩容时首先会清空数组,重新开始缓存方法,重新计算索引值,缓存当前调用的方法- 扩容源码:
void cache_t::expand()
{
cacheUpdateLock.assertLocked();
uint32_t oldCapacity = capacity();
// 旧的数组容量 * 2
uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE;
if ((uint32_t)(mask_t)newCapacity != newCapacity) {
// mask overflow - can't grow further
// fixme this wastes one bit of mask
newCapacity = oldCapacity;
}
reallocate(oldCapacity, newCapacity);
}
- 源码寻找key ->
objc-cache
(源码类名)
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask(); //得到mask
mask_t begin = cache_hash(k, m);
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}
//模拟器下
#if __arm__ || __x86_64__ || __i386__
// objc_msgSend has few registers available.
// Cache scan increments and wraps at special end-marking bucket.
#define CACHE_END_MARKER 1
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask;
}
//真机下
#elif __arm64__
// objc_msgSend has lots of registers available.
// Cache scan decrements. No end marker needed.
#define CACHE_END_MARKER 0
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
-
由上述源码可知,如果计算出来的所以,去除的key值,不相等, 那就去前一个找,知道找到索引为0的可以还不是,那就从索引为mask(最后索引)处开始找,知道找到
-
哈希表 的核心原理 是首先通过一个函数(通过某个算法),计算出索引,然后直接寻找 ,速度比遍历数组快,牺牲空间,换取执行速度(空间换时间)
4 Runtime- objc_msgSend
4.1 OC方法的调用
- OC方法的调用:消息机制,给方法调用者发送消息
GYPerson * person = [[GYPerson alloc] init];
[person personTest];
- 上述方法调用person对象的personTest方法,底层转换成
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personTest"));
- 消息接受者(receive):
person
- 消息名称:
personTest
- 如果
objc_msgSend()
找不到合适的方法调用,最后会报错unrecognied selector sent to instance
4.2 objc_msgSend的执行流程
- OC中的方法调用,其实都是转换成objc_msgSend函数的调用
- objc_msgSend的执行流程可以分为3大阶段
4.2.1 第一阶段:消息发送
- 消息发送流程:
- 判断消息接受者(
receive
)是否为空,如果是nil,则直接退出(这也是给一个nil对象发送消息不报错的原因),否则通过isa
指针找到receiveClass
,然后再cache
中查找方法,找到方法调用结束 - 第一步没有查找到方法,则从
receiveClass
的class_rw_t
中查找方法,如果方法列表已经排序,则使用二分查找,没有排序,则使用遍历查找, 找到方法,调用方法结束查找,并把该方法缓存到receiveClass
的cache
中 - 第二步没有查找方法,从
superClass
的cache
中查找方法,如果找到,调用方法结束查找,并把该方法缓存到receiveClass
的cache
中 - 第三步没有找到方法,则从
superClass
的class_rw_t
中查找方法,然后执行第二步的查找方式 - 没有找到方法,则判断 上层是否还
superClass
,如果有,则从第三步开始执行,如果没有找到方法,则进入动态方法解析阶段 - 执行流程图:
- 判断消息接受者(
4.2.2 第二阶段:动态方法解析
4.2.2.1 动态方法解析流程
- 如果消息发送没有找到方法,就会进入动态方法解析阶段
- 是否进行动态解析,如果有,直接进入消息转发阶段
- 没有,则调用
+ (BOOL)resolveInstanceMethod:(SEL)sel
或则+ (BOOL)resolveClassMethod:(SEL)sel
方法- 开发者可以实现上述两个方法,来实现动态方法的添加,前者是针对实力方法实现,后者针对类方法
- 方法的返回值,没有任何作用,系统要求添加动态方法,返回YES,源码中根据返回值只是做了打印,并没有任何实质性的操作
- 该方法中添加的动态方法是添加到方法列表中(methods),并在添加完方法之后,在重新走
objc_msgSend()
流程,也就是从第一个阶段,消息发送阶段流程开始,寻找方法,调用方法
- 动态方法解析,不管有没有做操作,都会标记为YES ,下一次就不会再走动态解析方法了,动态方法解析完成,会在次调用到消息发送阶段
- 动态消息解析流程图:
4.2.2.2 动态添加方法
- 动态添加一个OC方法
- (void)otherMethod{
NSLog(@"%s",__func__);
}
/// 当类对象调用方法没有实现时,会进入动态解析方法
/// @param sel <#sel description#>
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(personTest)) {
//获取我们需要添加的方法
Method method = class_getInstanceMethod(self, @selector(otherMethod));
//需要给类对象动态添加方法
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
return YES;
}
return [super resolveInstanceMethod:sel];
}
- (void)otherMethod{
NSLog(@"%s",__func__);
}
// 我们知道方法列表中的方法对象是method_t
struct method_t {
SEL sel;
char*types;
IMP imp;
};
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(personTest)) {
//typedef struct objc_method *Method;
//method_t == objc_method 这两个实质上的等价的 只不过method_t这个结构体是底层结构没有暴露出来 我们可以自己定义一个结构体method_t
// Method 可以理解为等价于 struct method_t *
struct method_t *methd = (struct method_t *)class_getInstanceMethod(self, @selector(otherMethod));
//动态增加方法
class_addMethod(self, sel, methd->imp, methd->types);
}
return [super resolveInstanceMethod:sel];
}
- 动态添加一个C语言方法:
void c_otherMethod(id self, SEL _cmd) {
NSLog(@"c_otherMethod=====%@ -----%@",self,NSStringFromSelector(_cmd));
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(personTest)) {
//C语言中 函数的名称就是函数的地址
class_addMethod(self, sel, (IMP)c_otherMethod, "v16@0:8");
}
return [super resolveInstanceMethod:sel];
}
- 如果是调用类方法(+ 方法)和上述流程类似
4.2.3 第三阶段:消息转发
- 消息转发: 将消息转发给别人,自己做不了事情交给别人做
- 消息发送阶段没有找到方法,动态方法解析也没做,那么会进入第三阶段,消息转发阶段。
- 消息转发阶段:
- 进入消息转发阶段会调用
forwardingTargetForSelector:(SEL)aSelector
方法,根据调用方法不同,实现实例方法和类方法, 在此方法中我们可以返回一个对象,让该对象来执行对应的方法,就实现了简单的消息转发流程 - 如果没有实现
forwardingTargetForSelector:(SEL)aSelector
方法, 那么系统会调用methodSignatureForSelector:(SEL)aSelector:
,如果此方法的返回值不为nil
,则会调用forwardInvocation:(NSInvocation *)anInvocation
方法, 前者是返回一个方法签名(我们需要调用方法的方法签名),后者把放回的方法签名封装在一个NSInvocation
对象中,让我们来实现调用, 如果返回值为nil
则会调用doesNotRecognizeSelector
方法打印错误信息,程序会报错unrecognized selector sent to instance
- 能走到
forwardInvocation:(NSInvocation *)anInvocation
方法时,我们可以在这个方法里面做任何事情,也可以空实现,实质上调用方法,只要进入消息转发阶段,该方法的实现内容,取决于forwardInvocation:(NSInvocation *)anInvocation
的实现内容 - 如果是类方法的调用,也是有消息转发的,和调用实例方法类似,只是调用的方法把
-
变成+
,然后实现类似,但是在工具中+
方法是没有提示的。
- 进入消息转发阶段会调用
/// 进入消息转发阶段 可以指定另外一个类来执行该方法
/// @param aSelector <#aSelector description#>
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(personTest)) {
//返回这个对象之后,实质是通过objc_msgSend([[GYForwardTest alloc] init], aSelector) 给这个对象发送一个aSelector消息
return [[GYForwardTest alloc] init];
}
return [super forwardingTargetForSelector:aSelector];
}
/// 返回一个方法签名, 方法签名中包含返回值类型,参数类型
/// @param aSelector <#aSelector description#>
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(personTest)) {
//返回一个方法签名, 根据一个编码字符串返回一个方法签名
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
/// 把methodSignatureForSelector:方法返回的方法签名封装成一个NSInvocation对象,
/// @param anInvocation <#anInvocation description#>
- (void)forwardInvocation:(NSInvocation *)anInvocation {
//指定消息需要转发的对象, 也就是实际执行方法的对象->方法调用者
anInvocation.target = [[GYForwardTest alloc] init];
//anInvocation.selector: 封装了 调用方法对象, 也可以修改
//[anInvocation getArgument:<#(nonnull void *)#> atIndex:<#(NSInteger)#>] 获取方法参数
//最后需要调用方法
[anInvocation invoke];
}
- 消息转发执行流程:
4.2.4 消息发送的核心源码
objc_msgSend()就是查找方法的过程,下面是查找方法的源码
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.read();
if (!cls->isRealized()) {
// Drop the read-lock and acquire the write-lock.
// realizeClass() checks isRealized() again to prevent
// a race while the lock is down.
runtimeLock.unlockRead();
runtimeLock.write();
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
retry:
runtimeLock.assertReading();
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists.
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
// 没有找到方法,进入第二个阶段->方法动态解析
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
}
//动态方法解析阶段 调用的方法
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
4.3 @dynamic关键字
@dynamic
关键字标记的属性,是告诉编译器不用自动生成getter
和setter
的实现,等到运行时在动态添加方法实现@synthesize
:关联属性 给属性生成实例变量、setter
方法、getter
方法 (久远以前使用, 现在不用 )@propery
y以前只会自动声明setter
和getter
方法声明,现在方法的实现也会自动实现,实例变量也会自动生成
@interface GYPerson : NSObject
/** <#des#> */
@property (nonatomic, assign) int age;
@end
#import "GYPerson.h"
#import <objc/runtime.h>
@implementation GYPerson
@dynamic age;
void setAge(id self, SEL _cmd, int age) {
NSLog(@"age is === %d",age);
}
int age() {
return 120;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(setAge:)) {
class_addMethod(self, sel, (IMP)setAge, "v20@0:8i16");
return YES;
} else if (sel == @selector(age)) {
class_addMethod(self, sel, (IMP)age, "i16@0:8");
}
return [super resolveInstanceMethod:sel];
}
@end
//测试代码
GYPerson *person = [[GYPerson alloc] init];
person.age = 10;
NSLog(@"age====%d",person.age);
//打印结果:
/**
2020-10-24 10:19:12.132827+0800 Runtime-objc_msgSend2[1710:34347] age is === 10
2020-10-24 10:19:12.133272+0800 Runtime-objc_msgSend2[1710:34347] age====120
Program ended with exit code: 0
*/
4.4 super关键字
[super message]
:- 消息接受者(receive)任然是子类对象
- 从父类开始查找方法实现
@interface GYPerson : NSObject
@end
@implementation GYPerson
@end
@interface GYStudent : GYPerson
@end
#import "GYStudent.h"
@implementation GYStudent
- (instancetype)init {
if (self = [super init]) {
NSLog(@"%@----",[self class]);
NSLog(@"%@----",[super superclass]);
NSLog(@"%@----",[super class]);
NSLog(@"%@----",[super superclass]);
}
return self;
}
//测试代码;
GYStudent *student = [[GYStudent alloc] init];
//打印结果:
/**
2020-10-24 10:47:51.913618+0800 Runtime-super[2031:42837] GYStudent----
2020-10-24 10:47:51.914187+0800 Runtime-super[2031:42837] GYPerson----
2020-10-24 10:47:51.914240+0800 Runtime-super[2031:42837] GYStudent----
2020-10-24 10:47:51.914274+0800 Runtime-super[2031:42837] GYPerson----
Program ended with exit code: 0
*/
- 首先我们查看GYStudent.m的OC转换C++代码
struct __rw_objc_super {
struct objc_object *object;
struct objc_object *superClass;
__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
};
// @implementation GYStudent
static instancetype _I_GYStudent_init(GYStudent * self, SEL _cmd) {
if (self = ((GYStudent *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("GYStudent"))}, sel_registerName("init"))) {
}
return self;
}
// @end
/**
* Sends a message with a simple return value to the superclass of an instance of a class.
* 第一个参数是一个结构体,发送一个消息,里面包含的这个实例对象就是消息接受者(self),消息从superclass开始查找消息
* @param super A pointer to an \c objc_super data structure. Pass values identifying the
* context the message was sent to, including the instance of the class that is to receive the
* message and the superclass at which to start searching for the method implementation.
* @param op A pointer of type SEL. Pass the selector of the method that will handle the message.
* @param ...
* A variable argument list containing the arguments to the method.
*
* @return The return value of the method identified by \e op.
*
* @see objc_msgSend
*/
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
- 由以上代码可以知,super关键字的调用转换成C++代码
objc_msgSendSuper
函数的调用(结构体中第二个成员直接传递的是父类对象),而最终底层代码是调用objc_msgSendSuper2
函数(结构体中第二个成员传递当前class对象,内部用过superClass
拿到父类对象,),该函数接受两个参数:struct objc_super2
,该结构体中有两个成员,第一个receive
(消息接受者),第二个当前类的Class对象(作用:告诉编译器从哪里开始寻找方法(当前也就是从父类开始寻找方法))SEL
,消息名称(方法名称)
- 该结构体的真正底层代码:
struct objc_super2 {
id receive;
Class current_class;
};
receive
: 消息接受者current_class
是receive
的Class
对象
4.5 OC代码如何转成中间代码
- 我现在产看底层几乎是把OC代码转换成C++ 代码,虽然很接近底层,但是有些细节在真正的底层代码还是有细微的区别,如果想要查看真正的底层,可以查看直接查看汇编代码,或则转换成中间代码来看,下面是转换成中间代码的步骤:
5. Runtime-API
5.1 Runtime- API-类
// 动态创建一个类(参数:父类Class对象、类名、额外的储存空间(一般填0))
Class _Nullable objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, size_t extraBytes)
//注册一个类(要在类注册之前添加成员变量)
void objc_registerClassPair(Class _Nonnull cls)
//销毁一个类
void objc_disposeClassPair(Class _Nonnull cls)
//获取isa指向的Class
Class _Nullable object_getClass(id _Nullable obj)
//设置isa指向的Class
Class _Nullable object_setClass(id _Nullable obj, Class _Nonnull cls)
//判断一个OC对象是否为Class
BOOL object_isClass(id _Nullable obj)
//判断一个Class是否为元类
BOOL class_isMetaClass(Class _Nullable cls)
//获取父类Class对象
Class _Nullable class_getSuperclass(Class _Nullable cls)
5.1.1 Runtime动态创建类
void test(id self, SEL _cmd) {
NSLog(@"%@----%@",self,NSStringFromSelector(_cmd));
}
//动态创建一个类 参数: 父类class对象、类名、额外内存空间
Class newClass = objc_allocateClassPair([NSObject class], "GYStudent", 0);
//添加实例变量 参数: class对象、实例变量名、实力变量所占空间大小,对齐空间大小、实力变量类型
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_weight", 4, 1, @encode(int));
//给这个类添加一个方法
class_addMethod(newClass, @selector(test), (IMP)test, "v@:");
//注册类
objc_registerClassPair(newClass);
//增加成员变量不可以在注册类之后在添加,因为实例变量是储存在ro结构中 是只读属性,不可以修改, ro中储存的是类中定义好的成员变量不可以修改的
//增加方法是可以在注册类之后修改的,因为方法是添加到rw中的methods方法列表中,这是个可读可写的属性
//创建并调用方法
id student = [[newClass alloc] init];
[student setValue:@10 forKey:@"_age"];
[student setValue:@20 forKey:@"_weight"];
[student test];
NSLog(@"_age==%@ _weight+====%@",[student valueForKey:@"_age"],[student valueForKey:@"_weight"]);
//打印结果:
/*
2020-10-26 11:26:47.186273+0800 Runtime-API[3814:85734] <GYStudent: 0x100488340>----test
2020-10-26 11:26:47.201017+0800 Runtime-API[3814:85734] _age==10 _weight+====20
*/
//动态设置对象的isa指向的class对象
GYPerson *person = [[GYPerson alloc] init];
//设置isa指向的Class对象
object_setClass(person, newClass);
[person test];
//打印结果:
/**
2020-10-26 11:55:15.901719+0800 Runtime-API[4013:96984] _age==10 _weight+====20
2020-10-26 11:55:15.902936+0800 Runtime-API[4013:96984] <GYStudent: 0x10071c120>----test
*/
5.1.2 获取Class对象
GYPerson *person = [[GYPerson alloc] init];
Class class = [person class];
Class class2 = [GYPerson class];
Class class3 = object_getClass(person);
Class class4 = object_getClass(class2);//如果传递的是calss对象,那返回的是meta-class(元类对象)
Class class5 = [class2 class];
NSLog(@"class--通过实例获取class对象====%p",class);
NSLog(@"class--通过类名获取class对象====%p",class2);
NSLog(@"class--通过Runtime获取class对象====%p",class3);
NSLog(@"class--通过Runtime获取meta-class对象====%p",class4);
NSLog(@"class--通过class获取meta-class====%p",class5);//不准,建议通过runtime来获取meta-class对象
- 打印结果:
5.2 Runtim-API成员变量
//获取一个成员变量
Ivar _Nullable class_getInstanceVariable(Class _Nullable cls, const char * _Nonnull name)
// 拷贝成员变量列表(最后需要调用free释放)
Ivar _Nonnull * _Nullable class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount)
// 设置和获取成员变量的值
void object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value)
id _Nullable object_getIvar(id _Nullable obj, Ivar _Nonnull ivar)
//动态添加成员变量(已经注册的类是不能动态添加成员变量的)参数: class对象、实例变量名、实力变量所占空间大小,对齐空间大小、实力变量类型
BOOL class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size, uint8_t alignment, const char * _Nullable types)
//获取成员变量相关信息
const char * _Nullable ivar_getName(Ivar _Nonnull v) //返回成员变量的名称
const char * _Nullable ivar_getTypeEncoding(Ivar _Nonnull v)//返回成员变量的类型信息
- 测试代码:
//获取成员变量
Ivar nameVar = class_getInstanceVariable([GYPerson class], "_name");
NSLog(@"name===%s type====%s",ivar_getName(nameVar),ivar_getTypeEncoding(nameVar));//返回都是const char * C语言字符串 用%s 来打印
// 打印结果name===_name type====@"NSString"
GYPerson *person = [[GYPerson alloc] init];
//设置成员变量的值
object_setIvar(person, nameVar, @"test");
Ivar ageIave = class_getInstanceVariable([GYPerson class], "_age");
//给基本数据类型赋值一个NSNumber类型的数据,会发生一些问题,赋值之后,值并不准确, 我们需要直接设置值
//object_setIvar(person, ageIave, @10);
object_setIvar(person, ageIave, (__bridge id)(void *)10);//Runtime的底层函数不用包装,传什么值就直接传, void *则为“无类型指针”,void *可以指向任何类型, 数字是不能直接转换成对象类型的,首先转换成指针类型(为什么转指针类型可以转成功,因为指针存放的就是地址值,存值的, 所以可以认为10就是个地址值),然后再桥接转换成id类型
//获取得成员变量的值
id obj = object_getIvar(person, nameVar);
NSLog(@"name=====%@ obj======%@ age=====%d",person.name,obj,person.age);
//name=====test obj======test age=====987880663 // (转换类型之后)age=====10
//拷贝成员变量列表 参数: class对象、 int类型的指针
unsigned int count; //表示成员变量的数量
//调用完该方法后,会给count复制
Ivar *copyIvar = class_copyIvarList([GYPerson class], &count);//返回的是一个数组
for (int i = 0; i< count; i++) {
Ivar tempVar = copyIvar[i];
NSLog(@"name===%s type====%s",ivar_getName(tempVar),ivar_getTypeEncoding(tempVar));
}
/**
name===_age type====i
name===_name type====@"NSString"
*/
//完成操作时 记得释放
free(copyIvar);
5.3 Runtime-API -属性
//获取一个属性
bjc_property_t _Nullable class_getProperty(Class _Nullable cls, const char * _Nonnull name)
//拷贝属性列表(最后徐亚调用free释放)
objc_property_t _Nonnull * _Nullable class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)
//动态添加属性
BOOL class_addProperty(Class _Nullable cls, const char * _Nonnull name,const objc_property_attribute_t * _Nullable attributes,unsigned int attributeCount)
//动态替换属性
void
class_replaceProperty(Class _Nullable cls, const char * _Nonnull name,const objc_property_attribute_t * _Nullable attributes,unsigned int attributeCount)
//获取属性的一些信息
const char * _Nonnull property_getName(objc_property_t _Nonnull property)//获取属性名称
const char * _Nullable property_getAttributes(objc_property_t _Nonnull property) //获取属性中的一些属性
5.4 RunTime - API-方法
- 获取一个实例方法、类方法
Method _Nullable (Class _Nullable cls, SEL _Nonnull name)
Method _Nullable class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
//交换两个方法的实现
void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
- 方法实现及相关操作
//参数 class对象, SEL方法对象
IMP _Nullable class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name)
// 设置方法的 指向地址
IMP _Nonnullmethod_setImplementation(Method _Nonnull m, IMP _Nonnull imp)
- 拷贝方法列表(最后需要用到free释放)
Method _Nonnull * _Nullable class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount)
- 动态添加、替换方法
BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,const char * _Nullable types)
//动态替换
IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,const char * _Nullable types)
- 获取方法相关信息(带有copy的需要调用free释放)
SEL _Nonnull method_getName(Method _Nonnull m)//根据名称获取SEL对象
IMP _Nonnull method_getImplementation(Method _Nonnull m) //根据一个method对象得到方法函数地址IMP对象
const char * _Nullable method_getTypeEncoding(Method _Nonnull m)//获取method的类型(参数,返回值)
unsigned int method_getNumberOfArguments(Method _Nonnull m) //获取method对象的参数个数
char * _Nonnull method_copyReturnType(Method _Nonnull m)//拷贝method的返回值
char * _Nullable method_copyArgumentType(Method _Nonnull m, unsigned int index)//拷贝method对象的参数类型
//根据选择器,返回选择器的名称
const char * _Nonnull sel_getName(SEL _Nonnull sel)
// 根据一个字符串得到一个选择器
SEL _Nonnull sel_registerName(const char * _Nonnull str)
- 用block作为实现方法:
IMP _Nonnull imp_implementationWithBlock(id _Nonnull block)
id _Nullable imp_getBlock(IMP _Nonnull anImp)
BOOL imp_removeBlock(IMP _Nonnull anImp)
method_exchangeImplementations
本质:方法交换的是把Class
对象中的class_rw_t
中的methods
中的method_t
(方法对象)中的IMP(指向函数的指针(函数地址))进行一个交换,一但调用这个方法,就回清空方法缓存列表,重新缓存;需要注意的是,Founction
框架中有些类并不是看到的类名并不是真实的类型,例如NSMutableArray
的Class对象是__NSArrayM
6. 面试题
6.1 OC的消息机制
- OC中的方法调用都是转成了
objc_msgSend
函数调用,给receive(方法调用者)发送一条消息(selector方法名) - objc_msgSend底层有三大阶段,消息发送(当前类,父类查找)、动态方法解析、消息转发(消息发送的三大阶段请看上述第
4
章节)。
6.2 消息转发机制
- 当消息发送没有找到方法,并且在动态解析方法阶段没有做任何处理,就会进入消息转发阶段
- 具体消息转发流程,请参考本文
4.2.3
章节
6.3 isMemberOfClass和isKindOfClass
- 如果是实例方法,那么传递的参数是class对象,底层比较的是class是否相等,如果是类(+)方法传递的参数应该是
meta-class
对象,底层比较的是元类对象是否相等, - isMemberOfClass: 是判断调用者是否是传递参数的类型
- isKindOfClass: 是判断调用者是否是传递参数的类型,或则子类类型; 在类(+)方法情况下,有一点需要注意的是,如果传递的参数是
[NSObject class]
,那么不管是谁调用此方法返回都是YES,因为我们知道NSObject对象的meta-class的isa指针是指向class对象的 - 两个方法源码:
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
6.4 看代码回答问题(零散记载-自己还未完全明白)
-
代码能执行成功,打印结果是
my name's <viewController:0xxxxxxx>
(可以参考super关键字) -
方法为什么可以调用成功, 因为 在内存中的结构,和普通对象在内存中的结构是一样的,普通对象调用方法, 通过指针找到对象的那块内存,取出最前面的8个字节,因为最前面的8个字节就是isa指针,就能找到类对象,在去缓存和方法列表中寻找方法
-
如下图
-
栈控件内存地址分配,是从高地质到低地址的顺序开始分配 而结构体成员越往后地址值越高 ,结构体的地址值就是第一个成员的地址值
-
寻找成员变量是找到对象的那快内存,然后跳过最前面那8个字节,然后开始往下寻找 所以打印结果会多变,可能是viewcontoller
-
访问成员变量的本质,找到那个对象的那个内存,然后找到对象内存中的成员变量,成员变量就是结构体中的成员变量,每一个实例对象本质都是一个结构体,找实例对象的成员变量就是结构体中的成员,找结构体中的成员,无非就是跳过前面那些成员,然后找下一个成员,比如跳过isa指针
6.5 什么是Runtime?平时项目中有用过么?
- 什么是Runtime:
- OC是一门动态性比较强的编程语言,允许很多操作推迟到程勋运行时在进行
- OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
- 平时编写的OC代码,底层都是转换成了RuntimeAPI运行调用
- 具体应用:
- 利用关联对象(objc_xxxAssociatedObject),给分类添加属性
- 遍历是有成员变量(修改UITextFiled站位文字颜色、字典转模型、自动归档解档)
- 交换方法实现(交换系统方法)
- 利用消息转发机制解决方法找不到的异常问题