一、OC方法的调用顺序
1.1 load与c++构造函数调用顺序
-
load是在dyld回调load_images中进行调用的,这个回调是在_objc_init的过程中进行注册的。 -
c++构造函数对于同一个image而言是在load回调后dyld调用的。(并不是绝对的) -
image内部是先加载所有类的+ load,再加载分类的+ load,最后加载C++全局构造函数。(类load->分类load->C++构造函数)。+load是objc中调用的,C++全局构造函数是在dyld中调用的。(image内部的顺序默认是按Compile Sources中顺序进行加载的,当然对于有依赖库的image,依赖库+load先被调用)。Dyld初始化image是按Link Binary With Libraries顺序逐个初始化的,从下标1开始,最后再初始化主程序(下标0)。 - 当然对于同一个
image而言c++构造函数在load之后调用并不是绝对的。比如objc系统库,在进行dyld注册回调之前调用了自身库的c++构造函数(自启)。
具体可以查看这个案例:load加载案例
1.2 initialize调用顺序
initialize是在第一次发送消息的时候进行的调用。load是在load_images的时候调用的,load比initialize调用时机早(initialize在lookupimporforward慢速消息查找的时候调用)。
整个调用顺序load > c++构造函数 > initialize。
1.3同名分类方法的调用顺序
同名分类方法调用顺序分为两种情况:
- 分类合并到主类的情况,也就是只有一个/或者没有
load方法,这个时候整个方法列表一维数组(不考虑其它动态添加方法的情况)。最后编译的同名分类方法会放在主类与其它分类前面,在进行方法二分查找的时候先从中间开始查找,找到对应方法SEL的时候会继续往前查找,直到找到最前面的同名方法。 - 分类没有合并到主类的情况,多个
load方法这个时候整个方法列表是一个一个二维数组,后编译加载的分类在数组前面,查找方法的时候从前面开始查找。
也就是同名方法最终会找到后编译加载的分类的同名方法,查找过程不一样而已。
二、runtime是什么?
runtime 是由C、C++ 汇编实现的一套API,为OC语言加入了面向对象,运行时的功能。是一种运行机制,不是底层。dyld、汇编、objc、macho才是底层。
运行时(Runtime)是指将数据类型的确定由编译时推迟到了运行时。
平时编写的OC代码,在程序运行过程中,其实最终会转换成Runtime的C语言代 码,Runtime 是 Objective-C 的幕后工作者。
2.1 extension 与 category 的区别
category(类别、分类)
- 专门用来给类添加方法。
- 不能给类添加成员属性,添加了成员变量,也无法取到。
- 可以通过
runtime给分类添加属性。 - 分类中用
@property定义变量,只会生成变量的geter & setter方法的声明,不能生成方法的实现和带下划线的成员变量。
extension(扩展)
- 可以称为特殊分类(匿名分类)。
- 可以给类添加成员属性,但是是私有变量。
- 可以给类添加方法,也是私有方法。
category会影响到类的编译和加载,extension不会影响类的编译和加载。category也有可能被合并进类中。
三、方法的本质
方法的本质:发送消息流程
- 1.快速消息查找 (
objc_msgSend),cache_t缓存查找消息。 - 2.慢速消息查找(
lookUpImpOrForward)递归自己以及父类,自己找不到去父类缓存中找,依然找不到会进行父类慢速查找,直到找到nil。 - 3.查找不到消息进行动态方法解析(
resolveInstanceMethod/resolveClassMethod)。resolveClassMethod的过程中如果没有找到方法,会调用resolveInstanceMethod。 - 4.消息快速转发(
forwardingTargetForSelector),相当于找消息备用接收者。 - 5.消息慢速转发(
methodSignatureForSelector & forwardInvocation),在仍然没有解决问题后在methodSignatureForSelector的时候会再进行一次慢速消息查找(这次不进行消息转发)。 - 6.最后仍然没有解决问题会进入
doesNotRecognizeSelector报错。
3.1sel是什么? IMP是什么?两者之间的关系又是什么?
sel是方法编号,在read_images 期间就编译进入了内存。
imp 就是函数实现指针 ,找imp就是找函数的过程。
可以将sel-imp理解为书本的目录,sel书本目录的名称,imp 就是书本的⻚码。查找具体的函数就是想看这本书里面具体篇章的内容。
3.2 能否向编译后的得到的类中增加实例变量? 能否向运行时创建的类中添加实例变量?
- 不能向编译后的得到的类中增加实例变量,编译好的实例变量存储的位置在
ro,一旦编译完成,内存结构就完全确定就无法修改。 - 运行时创建的类只要类没有注册到内存可以添加,注册后无法添加。(
objc_allocateClassPair、objc_registerClassPair)。
在class_addIvar中,类注册后直接返回NO:
#define RW_CONSTRUCTING (1<<26)
// Can only add ivars to in-construction classes.
//类注册后直接返回NO
if (!(cls->data()->flags & RW_CONSTRUCTING)) {
return NO;
}
在objc_registerClassPair中进行了赋值:
// Clear "under construction" bit, set "done constructing" bit
cls->ISA()->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
- 可以添加属性和方法。
四、 [self class]和[super class]的区别以及原理分析
4.1 代码验证
- (instancetype)init {
self = [super init];
if (self) {
NSLog(@"%@ --- %@",[self class],[super class]);
}
return self;
}
对于上面的代码输出:
HPObject --- HPObject
class的实现:
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
class是NSObject的方法,class的隐藏参数是id self, SEL _cmd。
所以[self class] 就是发送消息objc_msgSend,消息接受者是 self 和方法编号class。所以返回HPObject。
对于super隐藏参数是没有这个参数的。它不是参数名,是一个编译器关键字。在clang中编译后调用的是objc_msgSendSuper:
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
有两个参数objc_super以及SEL。
4.2 源码探索分析
objc_super定义如下:
/// Specifies the superclass of an instance.
struct objc_super {
__unsafe_unretained _Nonnull id receiver;
#if !defined(__cplusplus) && !__OBJC2__
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
};
#endif
也就是传递了receiver是消息接收者,super_class为第一个被查找的类。但是是实际的调试中调用的却是objc_msgSendSuper2:

// objc_msgSendSuper2() takes the current search class, not its superclass.
OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
根据注释可以看到super_class应该是当前类。
在源码中objc_msgSendSuper与objc_msgSendSuper2实现如下:
ENTRY _objc_msgSendSuper
UNWIND _objc_msgSendSuper, NoFrame
//p0存储receiver,p16存储class
ldp p0, p16, [x0] // p0 = real receiver, p16 = class
//跳转到 L_objc_msgSendSuper2_body 的实现
b L_objc_msgSendSuper2_body
END_ENTRY _objc_msgSendSuper
// no _objc_msgLookupSuper
ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
#if __has_feature(ptrauth_calls)
ldp x0, x17, [x0] // x0 = real receiver, x17 = class
//读取
add x17, x17, #SUPERCLASS // x17 = &class->superclass
ldr x16, [x17] // x16 = class->superclass
AuthISASuper x16, x17, ISA_SIGNING_DISCRIMINATOR_CLASS_SUPERCLASS
LMsgSendSuperResume:
#else
//ldp读取两个寄存器,将objc_super解析成receiver和class
ldp p0, p16, [x0] // p0 = real receiver, p16 = class
//通过class找到superclass
ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
#endif
L_objc_msgSendSuper2_body:
//查找缓存
CacheLookup NORMAL, _objc_msgSendSuper2, __objc_msgSend_uncached
END_ENTRY _objc_msgSendSuper2
可以看到objc_msgSendSuper是对objc_msgSendSuper2实现的调用。区别是_objc_msgSendSuper直接调用,objc_msgSendSuper2通过cls获取了superClass。也就是说objc_msgSendSuper传递的objc_super中superClass为父类,objc_msgSendSuper2传递的objc_super中superClass为自己,在汇编代码中进行了父类的获取。
那么[super class]中receiver也就决定了消息的接收者:

所以当前的
[super class]也打印的是HPObject。
在llvm中实现如下:

注释也说明了是参数的不同,
superclass of the current class与the current class的区别。
总结
-
[self class]就是发送消息objc_msgSend,消息接受者是self,方法编号是class。 -
[super class]本质就是objc_msgSendSuper(其实调用的是objc_msgSendSuper2),消息的接受者还是self,方法编号class。objc_msgSendSuper的objc_super构造是receiver:self,superClass:superClass,而objc_msgSendSuper2的的objc_super构造是receiver:self,superClass:currentClass。 - 在这里
objc_msgSendSuper查找会更快(对于class),直接跳过self查找。 -
self本质上是形参名 ,super是编译器关键字。
五、内存偏移
5.1 代码案例
有如下代码:
@interface HPObject : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *age;
- (void)instanceMethod;
@end
@implementation HPObject
- (void)instanceMethod {
NSLog(@"%s ",__func__);
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
HPObject *hp = [HPObject alloc];
hp.name = @"hotpot";
[hp instanceMethod];
Class cls = [HPObject class];
void *p = &cls;
//类调用 instanceMethod
[(__bridge id)p instanceMethod];
}
输出:
-[HPObject instanceMethod]
-[HPObject instanceMethod]
对于[hp instanceMethod]的输出没有什么疑问,问题是类[(__bridge id)p instanceMethod]为什么也能调用instanceMethod?
instanceMethod在类的data()中。在查找方法的时候isa偏移0x20找到bits从而找到methods()。
hp与cls之间有个关系isa & mask。[hp instanceMethod]与[(__bridge id)p instanceMethod]的本质是发送一个objc_msgSend(receiver以及SEL)消息。hp与p都指向cls,在这里p是一个伪装的实例对象,所以都能发送instanceMethod消息。也就是说能不能发送消息与hp没有任何关系,方法是在类中的。
5.2 增加成员变量的访问
修改instanceMethod如下:
- (void)instanceMethod {
NSLog(@"%s - %@",__func__,self.name);
}
输出如下:
-[HPObject instanceMethod] - (null)
-[HPObject instanceMethod] - <HPObject: 0x6000030239c0>
第一个打印null没问题,因为name没有赋值,但是第二个的打印是hp为什么?
由于p不是一个完整的实例对象,只是一个地址没有内存空间。hp是一个完整的实例对象,开辟了内存空间(存放isa,成员变量)。
hp访问name通过地址平移或者objc_getProperty(copy/atomic)获取。相当于对于hp的首地址,访问属性是往下平移的:

而p也通过地址进行平移获取name。 而p指向cls是函数栈帧中的地址,那就要找它的偏移地址。

这也就是为什么打印了
hp的原因。
5.3 栈中结构分析
对于上面的代码修改HPObject.h如下:
@interface HPObject : NSObject
@property (nonatomic, strong) NSString *hobby;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *age;
- (void)instanceMethod;
@end
输出:
-[HPObject instanceMethod] - hotpot
-[HPObject instanceMethod] - <ViewController: 0x100608730>
这次name偏移了0x10,这个时候p偏移16取到了<ViewController: 0x100608730>也就是self。那么viewDidLoad栈中数据结构是怎么样的呢?
5.3.1 结构体压栈
定义一个结构体验证:
//存储低->高
struct hp_struct {
NSNumber *number1;
NSNumber *number2;
};

内存分布如下:

[super viewDidLoad]在这里相当于有个临时变量objc_super。
image.png
暂不清楚为什么cls与hp2之间有0x10的空间。
5.3.2 参数压栈分布
定义如下代码:
//高->低
void hpFunction(id p1, id p2) {
NSLog(@"p1 = %p",&p1);
NSLog(@"p2 = %p",&p2);
}
//调用
hpFunction(hp,hp2);
调用后输出:
AppTest[4829:2176873] p1 = 0x16eec9b38
AppTest[4829:2176873] p2 = 0x16eec9b30
可以看到参数的压栈顺序正好与结构体相反。
参数从前往后压栈,结构体从后往前压栈。其实结构体是开辟结构体空间大小栈后,从低地址往高地址存。
那么对于:
- (void)viewDidLoad {//参数 id self, SEL _cmd
//结构体 objc_super{receiver,super_class}
[super viewDidLoad];
//变量 hp
HPObject *hp = [HPObject alloc];
hp.name = @"hotpot";
[hp instanceMethod];
Class cls = [HPObject class];
void *p = &cls;
//类调用 instanceMethod
[(__bridge id)p instanceMethod];
}
栈中结构如下:

修改HPObject如下:
@interface HPObject : NSObject
@property (nonatomic, copy) NSString *name1;
@property (nonatomic, copy) NSString *hobby;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *age;
@end
这个时候访问name就需要偏移0x20也就是访问到super_class:
-[HPObject instanceMethod] - hotpot
-[HPObject instanceMethod] - ViewController
输出了ViewController是子类,而不是父类(UIViewController)。因为根据上面的分析super其实调用的是objc_msgSendSuper2,它的super_class参数是类自己。
可以通过在objc_msgSendSuper2中打断点验证:

5.3.3 代码验证栈中数据分布
可以在hp后增加以下代码打印栈中所有数据:
void *sp = (void *)&self;
void *end = (void *)&hp;
long count = (sp - end) / 0x8;
for (long i = 0; i <= count; i++) {
void *address = sp - 0x8 * i;
if ( i == 1) {
NSLog(@"%p : %s",address, *(char **)address);
} else {
NSLog(@"%p : %@",address, *(void **)address);
}
}
输出:
0x16fb99b98 : <ViewController: 0x100807740>
0x16fb99b90 : viewDidLoad
0x16fb99b88 : ViewController
0x16fb99b80 : <ViewController: 0x100807740>
0x16fb99b78 : <HPObject: 0x2807aad90>
六、Runtime是如何实现weak的,为什么可以自动置nil
在上篇文章关联属性的探索中,简单探索了下weak对象释放的逻辑。那么有释放就有存储,在以下代码中weakObj前打一个断点:
HPObject *obj = [HPObject alloc];
id __weak weakObj = obj;
看对应的汇编调用:

可以看到是直接调用的
objc_initWeak。堆栈中并没有发现objc_initWeak的调用方,直接反编译后发现在编译后就进行了替换:
6.1 weak存值分析
6.1.1 objc_initWeak
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
非空的情况下调用了storeWeak。
6.1.2 storeWeak
核心逻辑如下:
template <HaveOld haveOld, HaveNew haveNew,
enum CrashIfDeallocating crashIfDeallocating>
static id
storeWeak(id *location, objc_object *newObj)
{
ASSERT(haveOld || haveNew);
if (!haveNew) ASSERT(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
//旧表
SideTable *oldTable;
//新表
SideTable *newTable;
……
//整体是SideTablesMap
if (haveOld) {
oldObj = *location;
//取旧表
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (haveNew) {
//取新表
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}
……
if (haveNew && newObj) {
Class cls = newObj->getIsa();
//类没有初始化
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized())
{
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
//类初始化
class_initialize(cls, (id)newObj);
previouslyInitializedClass = cls;
goto retry;
}
}
if (haveOld) {
//清空旧值
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
if (haveNew) {
newObj = (objc_object *)
//存储新值
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
if (!newObj->isTaggedPointerOrNil()) {
newObj->setWeaklyReferenced_nolock();
}
*location = (id)newObj;
}
else {
}
……
return (id)newObj;
}
- 从
SideTables()(SideTablesMap)获取oldTable与newTable,类型是SideTable。 -
class_initialize进行类的初始化。 -
weak_unregister_no_lock清空旧值,从weak_table中移除弱引用对象。 -
weak_register_no_lock存储新值,将弱引用对象存入weak_table中。
SideTable结构如下:
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;//引用计数表
weak_table_t weak_table;//弱引用表
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
};
-
refcnts引用计数表,weak_table弱引用表。
6.1.3 weak_unregister_no_lock
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
if (!referent) return;
//找到对象的 weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//将弱引用指针从weak_entry_t中移除。
remove_referrer(entry, referrer);
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
if (empty) {
//如果entry为空了,则将entry从整个weak_table中移除
weak_entry_remove(weak_table, entry);
}
}
}
- 根据对象获取得到
weak_entry_t,调用的是weak_entry_for_referent。 -
remove_referrer将弱引用指针从weak_entry_t中移除。 - 如果
weak_entry_t为空了,则从weak_table中移除(weak_entry_remove)。
6.1.3.1 weak_entry_for_referent
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
ASSERT(referent);
//从weak表中获取weak_entries首地址
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
//遍历找到referent对应的weak_entry_t
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
//返回weak_entry_t
return &weak_table->weak_entries[index];
}
- 遍历
weak_table的weak_entries找到referent对应的weak_entry_t返回。
6.1.3.2 remove_referrer
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
……
size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (entry->referrers[index] != old_referrer) {
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
hash_displacement++;
……
}
//遍历找到置old_referrer对应的referrers置为nil
entry->referrers[index] = nil;
entry->num_refs--;
}
遍历找到old_referrer对应的referrer置为nil。
6.1.3.3 weak_entry_remove
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
// remove entry
if (entry->out_of_line()) free(entry->referrers);
//清空entry
bzero(entry, sizeof(*entry));
weak_table->num_entries--;
//重新计算空间
weak_compact_maybe(weak_table);
}
- 释放
entry的referrers,num_entries计数--,重新计算空间。
6.1.4 weak_register_no_lock
//全局弱引用表,对象指针,弱引用指针
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, WeakRegisterDeallocatingOptions deallocatingOptions)
{
//对象
objc_object *referent = (objc_object *)referent_id;
//弱引用指针
objc_object **referrer = (objc_object **)referrer_id;
……
// now remember it and where it is being stored
//被弱引用对象的指针以及weak指针的地址。
weak_entry_t *entry;
//根据弱引用对象从weak_table中找出weak_entry_t
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//将弱引用指针加入entry
append_referrer(entry, referrer);
}
else {
//通过弱引用指针与对象创建new_entry
weak_entry_t new_entry(referent, referrer);
//weak_table扩容
weak_grow_maybe(weak_table);
//将new_entry插入weak_table
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
//返回对象
return referent_id;
}
-
weak_entry_for_referent查找对象对应的weak_entry_t。 - 如果存在对象的
weak_entry_t,则将弱引用指针加入该weak_entry_t(append_referrer)。 - 不存在则根据对象和弱引用指针创建
weak_entry_t,并对weak_table进行扩容(weak_grow_maybe)然后将weak_entry_t插入weak_table中(weak_entry_insert)。
6.1.4.1 weak_entry_insert
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
weak_entry_t *weak_entries = weak_table->weak_entries;
ASSERT(weak_entries != nil);
size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
size_t index = begin;
size_t hash_displacement = 0;
//找到对象对应的weak_entry_t
while (weak_entries[index].referent != nil) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_entries);
hash_displacement++;
}
//设置new_entry
weak_entries[index] = *new_entry;
//计数+1
weak_table->num_entries++;
if (hash_displacement > weak_table->max_hash_displacement) {
weak_table->max_hash_displacement = hash_displacement;
}
}
- 根据
weak_table找到对象对应的weak_entry_t。 - 将
new_entry存入对应的weak_entries中。 - 计数
++。
6.2 weak置为nil分析
之前在关联对象篇章分析的时候在dealloc释放时调用c++构造函数、释放关联对象、清除弱引用表、清除引用计数表。根据是不是纯指针分别调用sidetable_clearDeallocating与clearDeallocating_slow最终都调用到:
SideTable& table = SideTables()[this];
weak_clear_no_lock(&table.weak_table, (id)this);
获取全局SideTables后获取到SideTable然后获取到weak_table。weak_clear_no_lock的实现如下:
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
//要清除的对象
objc_object *referent = (objc_object *)referent_id;
//weak_entry_t
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
……
// zero out references weak对象引用表
weak_referrer_t *referrers;
size_t count;
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
//weak引用置为nil
*referrer = nil;
}
else if (*referrer) {
…… objc_weak_error();
}
}
}
//移除弱引用表中的entry
weak_entry_remove(weak_table, entry);
}
- 根据
weak_table获取到weak_entry_t,循环遍历weak_entry_t将弱引用指针置为nil。 -
weak_entry_remove从weak_table中将entry移除。
SideTables结构如下:
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
}
-
SideTables在iOS中是8个,在其它平台64个。
总结:weak的存储与销毁:
- 从
SideTables()[obj]获取SideTable,SideTables在iOS中是8个,在其它平台64个。 - 通过
SideTable获取weak_table。-
存值:
-
weak_unregister_no_lock清空旧值,从weak_entry_t与weak_table移除弱引用指针和对象。 -
weak_register_no_lock添加新值。在weak_table中根据referent找weak_entry_t。- 找到则
append_referrer(entry, referrer)将新的弱引用对象加入weak_entry_t。 - 没有找到则通过
referent与referrer创建weak_entry_t,对weak_table扩容然后weak_entry_insert将weak_entry_t插入weak_table。
- 找到则
-
-
销毁:(
weak_clear_no_lock)- 根据
referent获取到weak_entry_t。 - 遍历将
weak_referrer_t中的referrer置为nil。 - 从
weak_table中移除weak_entry_t。(weak_entry_remove)。
- 根据
-
弱引用表结构:

弱引用存储与释放流程:

七、 Method Swizzling的问题
正常情况下sel和imp有如下对应关系:

在进行swizz后一般有如下调用关系:

在交换后的
imp实现中一般会调用原来的实现。
- 需要保证只交换一次,交换两次会交换回原来的实现。
- 一般放在
+ load方法中配合dispatch_once保证只被调用一次。(+ load也可以被主动调用,所以需要配合dispatch_once)。
7.1 API源码分析
7.1.1 class_getInstanceMethod
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// Search method lists, try method resolver, etc.
//慢速消息查找,主要是为了进行动态方法决议
lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
//会进行二分查找
return _class_getMethod(cls, sel);
}
static Method _class_getMethod(Class cls, SEL sel)
{
mutex_locker_t lock(runtimeLock);
return getMethod_nolock(cls, sel);
}
- 通过
cls与sel获取method,任意一个不存在返回nil。 -
lookUpImpOrForward慢速消息查找主要是为了动态方法决议。 -
_class_getMethod进行了二分查找方法。
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
class_getClassMethod内部调用了class_getInstanceMethod。
7.1.2 method_getImplementation、method_getTypeEncoding、method_getName
IMP
method_getImplementation(Method m)
{
return m ? m->imp(true) : nil;
}
- 通过
Method获取imp。
const char *
method_getTypeEncoding(Method m)
{
if (!m) return nil;
return m->types();
}
- 通过
Method获取types签名。
SEL
method_getName(Method m)
{
if (!m) return nil;
ASSERT(m->name() == sel_registerName(sel_getName(m->name())));
return m->name();
}
- 通过
method获取SEL。
7.1.3 class_addMethod、class_replaceMethod
BOOL
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return NO;
mutex_locker_t lock(runtimeLock);
return ! addMethod(cls, name, imp, types ?: "", NO);
}
IMP
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return nil;
mutex_locker_t lock(runtimeLock);
return addMethod(cls, name, imp, types ?: "", YES);
}
-
class_addMethod与class_replaceMethod中直接调用了addMethod。区别是返回值和第四个参数replace。 -
class_addMethod没有添加成功的情况下返回NO,成功的情况下返回YES。通过有没有返回imp进行判断。当有方法的时候返回imp,没有方法的时候返回nil。 -
class_replaceMethod在有方法的时候会进行替换,没有方法的时候方法添加进list,返回nil。
7.1.3.1addMethod
static IMP
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
IMP result = nil;
runtimeLock.assertLocked();
//判断 allocatedClasses表中是否有类
checkIsKnownClass(cls);
method_t *m;
//二分查找到方法
if ((m = getMethodNoSuper_nolock(cls, name))) {
// already exists
//不替换
if (!replace) {
//返回imp
result = m->imp(false);
} else {
//替换,返回旧imp
result = _method_setImplementation(cls, m, imp);
}
} else {
// fixme optimize
method_list_t *newlist;
newlist = (method_list_t *)calloc(method_list_t::byteSize(method_t::bigSize, 1), 1);
newlist->entsizeAndFlags =
(uint32_t)sizeof(struct method_t::big) | fixed_up_method_list;
newlist->count = 1;
auto &first = newlist->begin()->big();
first.name = name;
first.types = strdupIfMutable(types);
first.imp = imp;
//添加newlist 到cls中
addMethods_finish(cls, newlist);
//返回nil
result = nil;
}
return result;
}
- 二分查找方法
- 找到,是否需要替换?根据第四个参数
replace确定。class_addMethod不替换,class_replaceMethod进行替换。 - 没有找到创建
newlist,构建方法。addMethods_finish将方法类表关联到类中。result置为nil。
- 找到,是否需要替换?根据第四个参数
7.1.3.2 _method_setImplementation
static IMP
_method_setImplementation(Class cls, method_t *m, IMP imp)
{
runtimeLock.assertLocked();
if (!m) return nil;
if (!imp) return nil;
IMP old = m->imp(false);
SEL sel = m->name();
//替换imp
m->setImp(imp);
//刷新缓存
flushCaches(cls, __func__, [sel, old](Class c){
return c->cache.shouldFlush(sel, old);
});
//
adjustCustomFlagsForMethodChange(cls, m);
//返回旧imp
return old;
}
替换imp,返回旧imp。
7.1.3.3 addMethods_finish
static void
addMethods_finish(Class cls, method_list_t *newlist)
{
auto rwe = cls->data()->extAllocIfNeeded();
if (newlist->count > 1) {
//排序
method_t::SortBySELAddress sorter;
std::stable_sort(&newlist->begin()->big(), &newlist->end()->big(), sorter);
}
//修正sel和排序
prepareMethodLists(cls, &newlist, 1, NO, NO, __func__);
//将方法添加进rwe的方法列表
rwe->methods.attachLists(&newlist, 1);
// If the class being modified has a constant cache,
// then all children classes are flattened constant caches
// and need to be flushed as well.
//刷新缓存
flushCaches(cls, __func__, [](Class c){
// constant caches have been dealt with in prepareMethodLists
// if the class still is constant here, it's fine to keep
return !c->cache.isConstantOptimizedCache();
});
}
- 对方法进行排序并且修正
SEL。 - 通过
attachLists将新的方法列表添加进rwe中。 - 刷新缓存。
7.1.4 method_setImplementation
IMP
method_setImplementation(Method m, IMP imp)
{
mutex_locker_t lock(runtimeLock);
return _method_setImplementation(Nil, m, imp);
}
直接调用的_method_setImplementation替换旧的imp,返回旧的imp。在这里会进行method与imp的判断。
7.1.5 method_exchangeImplementations
void method_exchangeImplementations(Method m1, Method m2)
{
//判断method是否存在
if (!m1 || !m2) return;
mutex_locker_t lock(runtimeLock);
//获取imp和sel
IMP imp1 = m1->imp(false);
IMP imp2 = m2->imp(false);
SEL sel1 = m1->name();
SEL sel2 = m2->name();
//交换
m1->setImp(imp2);
m2->setImp(imp1);
//刷新缓存
flushCaches(nil, __func__, [sel1, sel2, imp1, imp2](Class c){
return c->cache.shouldFlush(sel1, imp1) || c->cache.shouldFlush(sel2, imp2);
});
adjustCustomFlagsForMethodChange(nil, m1);
adjustCustomFlagsForMethodChange(nil, m2);
}
- 先判断两个
method是否存在,然后进行交换。
小结
-
class_getInstanceMethod:二分查找找method,找到返回method,找不到返回nil。class_getClassMethod调用的是class_getInstanceMethod。 -
method_getImplementation、method_getTypeEncoding、method_getName:通过method获取imp、签名以及SEL。 -
class_addMethod:添加方法,如果存在不处理,不存在添加方法并加入rwe后返回YES。 -
class_replaceMethod:imp存在则进行替换,返回旧的imp。不存在添加方法并加入rwe返回nil。 -
method_setImplementation:先进行method与imp是否存在,然后替换旧imp,返回旧imp。 -
method_exchangeImplementations:判断方法是否存在,存在则交换imp。
7.2 Method Swizzling 实现与分析
7.2.1 子类交换父类方法
HPObject实现如下:
@interface HPObject : NSObject
- (void)instanceMethod;
@end
@implementation HPObject
- (void)instanceMethod {
NSLog(@"%s",__func__);
}
@end
HPSubObject是HPObject的子类,实现如下:
@interface HPSubObject : HPObject
@end
#import "HPSubObject.h"
#import <objc/runtime.h>
@implementation HPSubObject
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method oriMethod = class_getInstanceMethod(self, @selector(instanceMethod));
Method swiMethod = class_getInstanceMethod(self, @selector(hp_instanceMethod));
method_exchangeImplementations(oriMethod, swiMethod);
});
}
- (void)hp_instanceMethod {
[self hp_instanceMethod];
NSLog(@"%s",__func__);
}
@end
这个时候调用:
HPSubObject *subObj = [HPSubObject alloc];
[subObj instanceMethod];
HPObject *obj = [HPObject alloc];
[obj instanceMethod];
直接报错:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[HPObject hp_instanceMethod]: unrecognized selector sent to instance 0x280798ce0'
自己调用没有问题,父类调用报错。因为父类没有实现hp_instanceMethod方法。
报错的点在于:

- 对于
HPSubObject而言调用instanceMethod就是调用HPSubObject:hp_instanceMethod-> HPObject:instanceMethod。 - 对于
HPObject调用instanceMethod是调用HPSubObject:hp_instanceMethod -> HPObject:hp_instanceMethod。而HPObject没有实现hp_instanceMethod,所以报错。
所以交换方法一定是去交换自己的方法。
7.2.2 添加方法防治类自身没有实现方法
在进行交换前先进行添加方法就能避免上面的报错了:
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method oriMethod = class_getInstanceMethod(self, @selector(instanceMethod));
Method swiMethod = class_getInstanceMethod(self, @selector(hp_instanceMethod));
//自己没有则会添加成功,自己有什么都不做。添加的实现指向了`swiMethod`,相当于已经完成了`instanceMethod`指向hp_instanceMethod
BOOL success = class_addMethod(self, @selector(instanceMethod), method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
if (success) {
//进入这里证明类本身没有instanceMethod方法,class_addMethod添加成功了。instanceMethod.imp -> hp_instanceMethod.imp
//替换hp_instanceMethod.imp -> instanceMethod.imp,也就是指向了父类的实现。
class_replaceMethod(self, @selector(hp_instanceMethod), method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
} else { //自己有直接进行交换
method_exchangeImplementations(oriMethod, swiMethod);
}
});
}
输出:
-[HPSubObject hp_instanceMethod]
-[HPObject instanceMethod]
-[HPObject instanceMethod]
- 对于自己不存在
instanceMethod方法,相当于HPSubObject增加了一个instanceMethod方法,实现是hp_instanceMethod。在进行class_replaceMethod的时候,将自身的hp_instanceMethod指向了父类的instanceMethod的实现。这样就只交换了自己的方法,没有交换父类的方法(但是指针指向了父类的实现)。这也就是为什么[self hp_instanceMethod]输出的是[HPObject instanceMethod]。 - 自己存在
instanceMethod方法则直接进行了交换。
7.2.3 父类没有实现instanceMethod方法
对于父类也没有instanceMethod方法,这个时候oriMethod获取到的是nil。class_addMethod仍然会成功,调用class_replaceMethod替换失败返回nil。
- (void)hp_instanceMethod {
NSLog(@"%s",__func__);
[self hp_instanceMethod];
}
- 由于
class_addMethod的处理instanceMethod的实现指向了hp_instanceMethod,而class_replaceMethod却失败了hp_instanceMethod没有指向instanceMethod。hp_instanceMethod就成了死循环了。
7.2.4 处理子类与父类都没有实现的方法
当instanceMethod子类和父类都没有实现的时候,交换后造成了死循环,可以在交换前进行判断:
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method oriMethod = class_getInstanceMethod(self, @selector(instanceMethod));
Method swiMethod = class_getInstanceMethod(self, @selector(hp_instanceMethod));
if (!oriMethod) {//原始方法没有实现
// instanceMethod.imp -> hp_instanceMethod.imp
class_addMethod(self, @selector(instanceMethod), method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
//添加一个空的实现 hp_instanceMethod.imp ->block(null)
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){
NSLog(@"imp default null implementation");
}));
}
//自己没有则会添加成功,自己有添加失败 instanceMethod.imp -> hp_instanceMethod.imp(null)
BOOL success = class_addMethod(self, @selector(instanceMethod), method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
if (success) {//自己没有方法添加一个,添加成功则证明自己没有。
IMP result = class_replaceMethod(self, @selector(hp_instanceMethod), method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
} else { //自己有直接进行交换
method_exchangeImplementations(oriMethod, swiMethod);
}
});
}
- 如果
oriMethod不存在,则先将instanceMethod添加进类,imp指向hp_instanceMethod。 - 将
hp_instanceMethod的imp指向block空实现。 - 这个时候就已经完成了交换,由于
oriMethod为空,所以最后的method_exchangeImplementations不会执行,不会交换回去。
7.2.5 简单封装
当然更好的方案是将方法交换实现在公共的工具类中:
+ (void)hp_methodSwizzleWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL isClassMethod:(BOOL)isClassMethod {
if (!cls) {
NSLog(@"class is nil");
return;
}
if (!swizzledSEL) {
NSLog(@"swizzledSEL is nil");
return;
}
//类/元类
Class swizzleClass = isClassMethod ? object_getClass(cls) : cls;
Method oriMethod = class_getInstanceMethod(swizzleClass, oriSEL);
Method swiMethod = class_getInstanceMethod(swizzleClass, swizzledSEL);
if (!oriMethod) {//原始方法没有实现
// 在oriMethod为nil时,替换后将swizzledSEL复制一个空实现
class_addMethod(swizzleClass, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
//添加一个空的实现
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){
NSLog(@"imp default null implementation");
}));
}
//自己没有则会添加成功,自己有添加失败
BOOL success = class_addMethod(swizzleClass, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
if (success) {//自己没有方法添加一个,添加成功则证明自己没有。
class_replaceMethod(swizzleClass, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
} else { //自己有直接进行交换
method_exchangeImplementations(oriMethod, swiMethod);
}
}

