总资料
全是随笔 笔记。没有规律
使用Objc版本为:objc4-824
前言
Runtime 是什么
OC 是一门动态性比较强的编程语言, 运行很多操作推迟到程序运行时进行(包括动态添加方法添加类添加属性)。
OC的动态性是由runtime 支撑和实现的,runtime 是一套C的API,封装了很多动态性相关的函数
平时写的OC代码,底层都是转换成了runtime的API进行调用的。
具体使用
- 利用关联对象(AssocicatedObject)给分类添加属性
- 遍历类的所有成员变量(修改textfiled的占位颜色,字典模型转换, 归档解档)配合kvc完成。
- 交换方法实现(交换系统自带方法,如NSLog,按钮点击,)
- 搜集未实现的方法,避免崩溃
- 重写 description 方法,打印model变量的所有成员变量
OTHER
runtime
的方法 前缀,代表着是获取的是什么属性:
比如 method
开头的方法,都是获取或设置 method
的某一个特定的属性的方法。
一、 isa
- arm64 之前isa就只是简单存储 地址
- arm64 以后进行了优化,isa存储的数据比较大,只有一部分是存储的 对象地址。
1.1 基本源码构成
union 加 位域的结合。
union isa_t {
uintptr_t bits;
struct {
ISA_BITFIELD; // defined in isa.h
};
}
#isa.h __arm64__ 真机
# define ISA_BITFIELD \
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 unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
1.2 位域
- 位域 是 C语言的结构。
- 源码中
union
里面只使用了bits
成员,struct
只为了增加可读性,因为没有往struct
里面赋值 OC
代码中基本上名字里面含有MASK
字段的都是位域的处理方式。x86 # define ISA_MASK 0x00007ffffffffff8ULL
转换成 二进制位后,可以看见
4-47位都是包含范围 与isa
地址&
后就是isa的类对象或元类对象的地址。arm64 # define ISA_MASK 0x0000000ffffffff8ULL
转换成 二进制位后,可以看见
4-43位都是包含范围。
1.3 各参数描述
nonpointer
:
0:代表普通指针,直接存储Class、Meta Class的内存地址
1:代表被优化过,使用位域技术来存储更多的信息,后面的位的数据才有效has_assoc
: 是否有设置过关联对象(应该是category动态添加的属性,待验证),没有的话,释放更快has_cxx_dtor
: 是否有C++的析构函数,没有的话,释放更快shiftcls
: 真正存储的Class、Meta Class 的地址指针magic
: 用于调试时判断对象是否已经完成了初始化weakly_referenced
: 是否被弱引用过,没有的话能够更快的释放unused
: TODO待了解has_sidetable_rc
: 引用计数是否过大导致无法存储在isa中,如果为1,则引用计数会存储在SideTable
的类的属性中extra_rc
:存储引用计数的数据,值=引用计数器-1
释放更快。可以通过以下代码证明
//dealloc() -> _objc_rootDealloc(self) -> obj->rootDealloc();
//objc-object.h
inline void objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
#if ISA_HAS_CXX_DTOR_BIT
!isa.has_cxx_dtor &&
#else
!isa.getClass(false)->hasCxxDtor() &&
#endif
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this); //直接释放
}
else {
object_dispose((id)this);
}
}
1.4 对象的存储路径:
objc-runtimme-new.h
-> struct objc_class : objc_object
-> class_rw_t *-
> class_rw_ext_t(class_ro_t)
objc_class 结构体主要成员如下:
Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
获取路径如下
-
class_rw_ext_t
里面method_array_t
property_array_t
等是二维数组,存放的是可读可写的,是包含分类等动态加载进来的信息。其中method_array_t
->method_list_t
->method_t
为二维一维和具体的方法struct,struct method_t
里面存储着具体的方法信息,如下 . 总结:新版的rumtime利用了懒加载的机制,在类的methods,properties等需要修改时,才初始化class_rw_ext_t
这块“dirty+memory”
存储这些列表,这样就减少了在旧版rumtime
中90%的类在rw中直接复制ro中数据浪费的内存。更多详细信息可以参考这个 -
class_ro_t
里面的数组protocol_list_t *
property_list_t*
等存储的是一维数组,存放的是可读的,类的初始信息,即不包括分类和runtime等动态加载进来的信息。 在realizeClassWithoutSwift
方法里面由class_ro_t
为基础来创建class_rw_ext_t
。
1.5 Method_t
SEL name; //函数名字,可以当做string
const char *types; //编码。包括返回值类型,参数类型 和字节数量
MethodListIMP imp; //函数的地址指针
SEL
表示函数的名字,又叫做方法选择器
,底层类似于char *
所以不同类有相同名字的方法,对应的SEL
是相同的,地址相同
@selector(<#selector#>)
sel_registerName(<#const char * _Nonnull str#>)
sel_getName(<#SEL _Nonnull sel#>)
NSStringFromSelector(<#SEL _Nonnull aSelector#>)
可以用上面的函数进行char *
method
SEL
转换
typedef struct objc_selector *SEL;
IMP
代表了函数的具体实现
using MethodListIMP = IMP __ptrauth_objc_method_list_imp;
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
- Type Encoding: 官方文档
i24@0:8i16f20 -> 返回值+参数1+参数2+…+参数n
i:代表返回值
第一个数字24指所有参数占用的字节总数
后面的数字代表当前的参数在地址内的字节的偏移量,比如i16代表这个ini参数是第16个字节开始的。
char *buf2 = @encode(struct key);
二、Cache_t 方法缓存
cache是散列表,空间换时间
第一次调用方法的时候,会将方法缓存到类对象的cache里面。
2.1 struct 结构
//散列表里面的结构,存储了SEL 和 IMP;
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
......省略其他函数
}
/* dyld_shared_cache_builder and obj-C agree on these definitions */
struct preopt_cache_entry_t {
uint32_t sel_offs;
uint32_t imp_offs;
};
/* dyld_shared_cache_builder and obj-C agree on these definitions */
struct preopt_cache_t {
int32_t fallback_class_offset;
union {
struct {
uint16_t shift : 5;
uint16_t mask : 11;
};
uint16_t hash_params;
};
uint16_t occupied : 14; //表示已经缓存的方法的数量 (不包含空的)
uint16_t has_inlines : 1;
uint16_t bit_one : 1;
preopt_cache_entry_t entries[];
inline int capacity() const {
//桶的容量
return mask + 1; //msak的数量是总容量 - 1
}
};
struct cache_t {
private:
// explicit_atomic 代表原子性,能够保证增删查改的线程安全
// _bucketsAndMaybeMask 包含 bucket_t 的地址。或者可能存有mask的值(根据不同平台),下面有讲解
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //8字节
union {
struct {
explicit_atomic<mask_t> _maybeMask; //4字节 mask可能存在这里
#if __LP64__
uint16_t _flags; //2
#endif
uint16_t _occupied; //2 //表示已经缓存的方法的数量 (不包含空的)
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; //8
......省略其他函数
};
}
occupied
//表示已经缓存的方法的数量 (不包含空的)capacity
当前的总容量、桶容量(包含了空的内容)mask
总容量-1,用来和方法地址进行与运算获取缓存的下标bucket_t
实际存储的缓存的方法的结构体。
2.2 cache_t 各结构的平台差异
在代码中_bucketsAndMaybeMask 里面的存储方式在不同的 类型设备上是不一样的。
节选自源码,并有以下注释。
#if defined(__arm64__) && __LP64__ //armCPU 64 位
#if TARGET_OS_OSX || TARGET_OS_SIMULATOR // pc 或模拟器(应该不存在?)arm64的PC或模拟器?
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16 //iphone真机
#endif
#elif defined(__arm64__) && !__LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4 //32 位 arm(已经淘汰)
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED //其他(MAC 或 模拟器)
#endif
通过上面代码可以看见不同架构的 CACHE_MASK_STORAGE
的值不一样,也就可以在下面代码看出cache存储的逻辑也不一样。
(_originalPreoptCache 同样根据不同架构的存储逻辑不一样,可以看源码,这里未列出)
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
//(MAC 或 模拟器)
// _bucketsAndMaybeMask 是 buckets_t 的首指针
// _maybeMask 是 buckets mask
static constexpr uintptr_t bucketsMask = ~0ul; //0xffff
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
// (应该不存在?)arm64的PC或模拟器?
static constexpr uintptr_t maskShift = 48;
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << maskShift) - 1;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//iphone真机 _bucketsAndMaybeMask 和 _maybeMask 是在一起的,节省内存
// _bucketsAndMaybeMask 的低 48位是 buckets_t 的首指针, 高16位存储buckets mask
// _maybeMask 未使用
// mask 被移动的位数
static constexpr uintptr_t maskShift = 48;
// mask 后面4位 必须为零,也就是说buckets_t 的位数只有 48 - 4 = 44
static constexpr uintptr_t maskZeroBits = 4;
// mask 的最大数量 (1 << 16) - 1
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
// 实际的buckets 地址的掩码 的值 (1 << 44) - 1
static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
//32 位 arm(已经淘汰)
// _bucketsAndMaybeMask is a buckets_t pointer in the top 28 bits
// _maybeMask is unused, the mask length is stored in the low 4 bits
static constexpr uintptr_t maskBits = 4;
static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
static constexpr uintptr_t bucketsMask = ~maskMask;
static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#else
#error Unknown cache mask storage type.
#endif
2.3 insert cache 流程
先结论后看代码:
- 容量的大小扩容顺序为: 当前容量的2倍: 0->4->8->16->32
bucket_t
的初始下标为:SEL
地址 &MASK
(总容量-1)。 但是可能被占用,占用后会存在其他地址- 每次扩容时,会释放掉以前缓存的内容
- 一般情况是超过
3/4
容量的时候,就会扩容 insert()
不是线程安全的,可能会被其他线程同时写入
首先看下 初始化容量大小的数量
enum {
#if CACHE_END_MARKER || (__arm64__ && !__LP64__)
// When we have a cache end marker it fills a bucket slot, so having a
// initial cache size of 2 buckets would not be efficient when one of the
// slots is always filled with the end marker. So start with a cache size
// 4 buckets.
INIT_CACHE_SIZE_LOG2 = 2,
#else
// Allow an initial bucket size of 2 buckets, since a large number of
// classes, especially metaclasses, have very few imps, and we support
// the ability to fill 100% of the cache before resizing.
INIT_CACHE_SIZE_LOG2 = 1,
#endif
INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2),
MAX_CACHE_SIZE_LOG2 = 16,
MAX_CACHE_SIZE = (1 << MAX_CACHE_SIZE_LOG2),
FULL_UTILIZATION_CACHE_SIZE_LOG2 = 3,
FULL_UTILIZATION_CACHE_SIZE = (1 << FULL_UTILIZATION_CACHE_SIZE_LOG2),
};
真正方法
// 真正方法
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
......省略了无关内容
// Use the cache as-is if until we exceed our expected fill ratio.
//新的数量为使用数量+1, 第一位 0+1=1
mask_t newOccupied = occupied() + 1;
unsigned oldCapacity = capacity(),
//总容量进行缓存,以防扩容
unsigned capacity = oldCapacity;
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.
//初始容量为 1<<2 = 4
if (!capacity) capacity = INIT_CACHE_SIZE;
//开辟内存
reallocate(oldCapacity, capacity, /* freeOld */false);
}
else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
// Cache is less than 3/4 or 7/8 full. Use it as-is.
// CACHE_END_MARKER = 1
// cache_fill_ratio(capacity) = capacity * 3 / 4
//如果 新容量 1 <= 总容量的3/4的话 不需要做处理
}
#if CACHE_ALLOW_FULL_UTILIZATION
else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
// Allow 100% cache utilization for small buckets. Use it as-is.
// 运行 100% 容量也不处理
}
#endif
else {
//其他情况,超过 3/4 或者 达到100%了, 进行扩容
//初始为:INIT_CACHE_SIZE = 4
//后续扩容为 当前容量的2倍: 0->4->8->16->32
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
//扩容,并且不保存原始缓存,并释放
reallocate(oldCapacity, capacity, true);
}
/*
memory_order_relaxed: 只保证当前操作的原子性,不考虑线程间的同步,其他线程可能读到新值,也可能读到旧值。
bucketsMask 可以在上面代码看具体的值
struct bucket_t *cache_t::buckets() const {
uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed);
return (bucket_t *)(addr & bucketsMask); //获取bucket的地址,如ios就是在1<<44
}
*/
bucket_t *b = buckets();
// m 为 struct preopt_cache_t 里面的 mask
mask_t m = capacity - 1;
//通过哈希计算 实际bucket_t的下标的值,具体实现 见下一个 代码片段
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot.
//do 代表,先用当前下标无条件,查找一次
do {
if (fastpath(b[i].sel() == 0)) {
//如果计算出的下标,是未使用的位置,则插入这个位置,并返回
//实际缓存的数量 加一
incrementOccupied();
b[i].set<Atomic, Encoded>(b, sel, imp, cls());
return;
}
if (b[i].sel() == sel) {
//如果当前sel 已经被其他线程缓存进入其中,则返回
// The entry was added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
return;
}
//如果当前位置被占用,则每次重新循环计算下标,直到找到合适的位置,或者重新找到第一次尝试的位置 才结束。
} while (fastpath((i = cache_next(i, m)) != begin));
//缓存失败 。。。。
bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}
具体的哈希计算的方法:
// Class points to cache. SEL is key. Cache buckets store SEL+IMP.
// Caches are never built in the dyld shared cache.
static inline mask_t cache_hash(SEL sel, mask_t mask)
{
uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
value ^= value >> 7;
#endif
//mask = 总容量 - 1
return (mask_t)(value & mask);
}
2.4 realloc()
TODO:
三、objc_msgSend()
OC中所有的OC基本都会走下面的调用流程(+load
不会,它是直接地址调用).
调用的顺序总共经历了两个文件
- objc-msg-arm64.s 汇编, 频繁调用,所有 查找第一次缓存的任务放到了汇编代码里面.
- objc-runtime-new.mm 普通文件
大概流程如下:
- 调用方法时,会转换成objc_msgSend()
- 在汇编文件中,先查找cache是否有当前的方法. 有就直接调用
- 找不到的时候,会直接调用 objc-runtime-new.mm的方法
- 直接跳过缓存,查找当前class的方法列表. 有就直接返回
- 没有会在for 循环中 转到superClass中,
- 然后查到superClass的cache. 没有的话重复4-6步
- 当superClass == nil时,如果所有的都没有找到. 则会将
imp = _objc_msgForward_impcache
, 最终调用的时候,会进入到异常处理的流程 即+ (BOOL)resolveInstanceMethod:(SEL)sel
- 找到imp后,没有在缓冲中的话,会inset到cache中去.
- 然后return 到汇编中进行调用
具体调用流程图:图片有点大, 点击查看大图
一个蓝色的入口
三个绿色的出口。其他蓝色的都是关键节点。
大流程:
1). 查找方法,找到 返回
2). 动态方法解析
3).备用接收者
4).完整转发
-
_objc_forward_handler
的实现函数没有开源,不知道当前异常处理的时候,被赋予了哪个函数地址来进行调用。 -
没有处理的异常,最终的crash堆栈中会有
___forwarding___
这个函数,已有人通过反汇编模拟了 异常消息时的转发流程,也就是挽救的函数调用。
注意:
添加缓存的方法:log_and_fill_cache(cls, imp, sel, inst, curClass);
是直接添加到cls
的cache中,也就是说是缓存到调用类的缓存里面 -
(+/-) (BOOL)resolveClassMethod:(SEL)sel
-
(+/-) (id)forwardingTargetForSelector:(SEL)aSelector
-
(+/-) NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
-
(+/-) (void)forwardInvocation:(NSInvocation *)anInvocation
-
(void)doesNotRecognizeSelector:(SEL)aSelector;
-
TODO
简单使用
+ (BOOL)resolveClassMethod:(SEL)sel {
class_addMethod(object_getClass(self), sel, (IMP)(dynamicMethodIMP), "@@:");
[super resolveClassMethod:sel];
return YES;
}
---------
- (id)forwardingTargetForSelector:(SEL)aSelector{
return self.target; //将原本消息全部转发
}
--------
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:self.target];
}
四、@Synthesize VS @dynamic
- ARC 默认行为
//默认会自动生成成员变量 和get
和set
方法
@property(nonatomic, copy) NSString *str;
@sythiesize
//手动设置 _str 为成员变量的名字,_str 可改变成任何名字
@sythiesize str = _str;
@dynamic
//提醒编译器不自动生成setter
和getter
的实现,也不自动生成成员变量。 但是set和get的声明是自动生成的了。
@dynamic str;
可以在 +resolveInstanceMethod:sel
里面动态添加方法的实现。 基本上没有这么使用的。
五、 Super
继承关系
探究的问题
#import "Student.h"
@implementation Student
- (instancetype)init {
if (self = [super init]) {
NSLog(@"%@", [self class]);
NSLog(@"%@", [self superclass]);
NSLog(@"---------");
NSLog(@"%@", [super class]);
NSLog(@"%@", [super superclass]);
}
return self;
}
@end
2021-12-04 16:50:22.084478+0800 107-objc-send[15563:2249138] Student
2021-12-04 16:50:22.085260+0800 107-objc-send[15563:2249138] Person
2021-12-04 16:50:22.085438+0800 107-objc-send[15563:2249138] ---------
2021-12-04 16:50:22.085497+0800 107-objc-send[15563:2249138] Student
2021-12-04 16:50:22.085548+0800 107-objc-send[15563:2249138] Person
解析过程:
将当前文件转换成 cpp文件后,可以找出这几个源代码
static instancetype _I_Student_init(Student * self, SEL _cmd) {
if (self = ((Student *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){
(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("init"))) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9j_jxsl94c13kzbq69hbwnc6ytr0000gn_T_Student_733843_mi_0, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9j_jxsl94c13kzbq69hbwnc6ytr0000gn_T_Student_733843_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("superclass")));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9j_jxsl94c13kzbq69hbwnc6ytr0000gn_T_Student_733843_mi_2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9j_jxsl94c13kzbq69hbwnc6ytr0000gn_T_Student_733843_mi_3, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){
(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("class")));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_9j_jxsl94c13kzbq69hbwnc6ytr0000gn_T_Student_733843_mi_4, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){
(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("superclass")));
}
return self;
}
其中进行提取后
[super class]
被转换成了下面语句
objc_msgSendSuper((__rw_objc_super){self, class_getSuperclass(objc_getClass("Student"))}, sel_registerName("class"));
提取出
方法: objc_msgSendSuper()
参数1: (__rw_objc_super){self, class_getSuperclass(objc_getClass("Student"))}
, __rw_objc_super
的结构体,本质是objc_super
结构体
参数2: sel_registerName("class")
objc源码探析
结构体的定义可在头文件中
objc/message.h 的头文件中定义
struct objc_super {
__unsafe_unretained _Nonnull id receiver; //消息接受者
__unsafe_unretained _Nonnull Class super_class; //消息接受者的superClass
};
方法的说明:
/**
* Sends a message with a simple return value to the superclass of an instance of a class.
*
* @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);
简单翻译就是说,
objc_super
的结构体里面receiver
参数是消息的接受者。super_class
参数是从这里开始查找方法的实现。
op
就是需要查找的方法和处理的方法。
结论:
- 所以
[super class]
的本质是 将self
当做方法的接受者。super
当做方法开始的查找者,但是-class()
方法是定义在根类中的,所以这里无论是self
和super
开始查找都无所谓,都只会在NSObject
类中找到。 [super class]
==[self class]
- 对
self
和super
都没有定义和声明的方法,无论用self
和super
来调用都没有任何影响,因为接受者都只是self
六、 isKindOfClass 、isMemberOfClass
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
-
isMemberOfClass
判断是否是当前类型 -
isKindOfClass
判断左边类是否是右边的子类 -
(-)
调用是判断类对象
-
(+)
是判断元类对象
-
[Person isKindofClass:[NSObject class]]
是成立的,调用的是+
号,因为Person的元类
的superClass最终指向的是 NSObject的类对象
, 可参考指向图。
七、Runtime实际用处
搜集未实现的方法
在 自己实现的基类中添加 消息转发的方法,能有效避免 调用未实现的方法而导致崩溃的事情。
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person test]: unrecognized selector sent to instance 0x1010583a0'
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([self respondsToSelector:aSelector]) {
//忽略OK的方法
return [super methodSignatureForSelector:aSelector];
}
//转发为实现的方法,默认两个参数
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
//打印收集有问题的方法名字
NSLog(@"---%@", NSStringFromSelector(anInvocation.selector));
}
查找所有的成员变量
字典转model 或者 model转字典
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([Person class], &count);
for (int i = 0; i < count; ++i) {
const char *ivarName = ivar_getName(ivars[i]);
const char *ivarType = ivar_getTypeEncoding(ivars);
}
free(ivars);
这里同样可以获取到 未公开的 类的所有成员变量, 比如 UITextField
、UILabel
等的私有成员变量。
然后可以直接KVC
访问或者修改。如:UITextField
的 placehoder
的 _placeholderLabel
的这个私有成员变量的颜色,大小等
交换方法实现 HOOK:
Person *per = [[Person alloc] init];
//Method 取的是 class_rw_ext_t * 里面的指针
Method testMethod = class_getInstanceMethod([Person class], @selector(test));
Method testReplaceMethod = class_getInstanceMethod([Person class], @selector(testReplace));
//这里其实交换的是Method 里面的 imp实现。 也意味着class_rw_ext_t *里面保存的实现被同时交换了,并且缓存也交换了。
//Method.name 不变,Method.imp 交换
method_exchangeImplementations(testMethod, testReplaceMethod);
[per test];
[per testReplace];
交换后的输出结果。
2021-12-09 21:51:19.127764+0800 objc-send[1517:44106] -[Person testReplace]
2021-12-09 21:51:19.128496+0800 objc-send[1517:44106] -[Person test]
Button 点击事件的 hook
@implementation UIControl (MyControl)
+ (void)load {
Method m1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
Method m2 = class_getInstanceMethod(self, @selector(jb_sendAction:to:forEvent:));
method_exchangeImplementations(m1, m2);
}
- (void)jb_sendAction:(SEL)action to:(nullable id)target forEvent:(nullable UIEvent *)event {
if ([self isKindOfClass:[UIButton class]]) {
NSLog(@"%s", __FUNCTION__);
[self jb_sendAction:action to:target forEvent:event];
}
}
备注:部分笔记包含有MJ老师的学习资料。