一、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] -
第一个打印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] -
这次name
偏移了0x10
,这个时候p
偏移16
取到了
也就是self
。那么viewDidLoad
栈中数据结构是怎么样的呢?
5.3.1 结构体压栈
定义一个结构体验证:
//存储低->高
struct hp_struct {
NSNumber *number1;
NSNumber *number2;
};
内存分布如下:
[super viewDidLoad]
在这里相当于有个临时变量objc_super
。
暂不清楚为什么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 :
0x16fb99b90 : viewDidLoad
0x16fb99b88 : ViewController
0x16fb99b80 :
0x16fb99b78 :
六、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
(location, (objc_object*)newObj);
}
非空的情况下调用了storeWeak
。
6.1.2 storeWeak
核心逻辑如下:
template
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(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
@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);
}
}