〇、引言
前面一步步学习了对象、类、方法、类的加载等,这些其实都是Runtime 的基础,而Runtime 又是iOS开发语言 Objective C 的精髓,因此关于Runtime 的面试题举不胜举。
下面就简单的介绍几个,并提供解题思路,希望可以帮读者更清晰的理解Runtime。
一、什么是Runtime
这个问题,问的是对Runtime 的基础理解。
Runtime是由C和C++汇编实现的一套API,为 OC 语言加入了面向对象,运行时的功能。
运行时(Runtime) 是指将数据类型确定推迟到了运行时。
举个例子:
关于类——类的扩展,在编译时作为类的一部分(ro)就已经确定好了,所以可以添加分类;而分类由于是运行时加载,只能添加属性以及对应的setter 和getter 方法,来达到模拟属性的目的。
二、方法的本质是什么?
方法的本质,就是发送消息/消息传递
让一个类(对象/类)执行一个方法的过程,就是向它发送消息,它便开始消息查找的过程。主要包含以下几个过程:
- 快速查找(objc_msgSend),主要向类 cache_t 查找缓存的过的方法。
- 慢速查找:执行 lookUpImpOrForward 方法,递归自己,以及自己的父类,属性
rw
里的methodlist
中查找方法。 - 动态方法加解析(还是查找不到):resolveInstanceMethod 方法,看是否自定义实现过
- 消息转发阶段:
- 快速转发—— forwardingTargetForSelector 寻找特定对象来执行方法
- 慢速转发—— methodSignatureForSelector(获取方法的签名)以及生成相应的invocation,由 forwardInvocation 方法进行消息分发,让有能力执行的类来执行该方法。
三、简述SEL 和IMP和之间关系
定义:SEL是内存中方法编号,IMP 是方法的具体实现
两者个点关系就好比一本书:SEL 是目录页里的 章节标题,而IMP 则是文章的页码,通过页码,可以看到具体内容。
SEL 是由read_images 就已经加载注册到内存表里。
四、能否往已注册的类添加成员变量?
###
Q:已经注册好的类,能否再动态添加成员变量?为什么?
答案是不可以。
我们通过Runtime还原一下场景。
注册好的类,实现的方法是,注册到内存里:
objc_registerClassPair(LGPerson);
而实现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);
即对类更改了状态,更改了什么状态?RW_CONSTRUCTED这个状态,即让类处于内存开辟&注册到内存中——
// class allocated and registered
#define RW_CONSTRUCTED (1<<25)
接下来,根据创建成员变量的函数为addIvar
,创建业务代码如下
class_addIvar(LGPerson, "lgName", sizeof(NSString *), log2(sizeof(NSString *)), "@");
在源码中找到相对应的函数:
走到这一步,就戛然而止了……添加ivars 被拒绝——因为内存已固定,无法再添加新属性了。
原因:因为注册好的类,内存容量已经固定,无法动态添加了。
五、isKindOFClass 和 isMemberOfClass 的区别
5.1 题目
关于这两个函数,我们知道他们各自概念是:
isKindOfClass——某个对象是否是类的成员,或者继承自该类的成员(即父子关系)
isMemberOfClass——某个对象是否当前类的成员,并不考虑回溯的父子类关系。
这里有一道面试题,题目如下,要求回答各打印结果:
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];//1
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];// 0
BOOL re3 = [(id)[Person class] isKindOfClass:[Person class]];//0
BOOL re4 = [(id)[Person class] isMemberOfClass:[Person class]];// 0
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];//1
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];// 1
BOOL re7 = [(id)[Person alloc] isKindOfClass:[Person class]];//1
BOOL re8 = [(id)[Person alloc] isMemberOfClass:[Person class]];// 1
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
答案是什么呢?先别忙,先冷静分析一下
5.2 概念分析
5.2.1 类方法的区别分析
很明显,上半部4个判断,是判断类与类的归属,执行的是类方法判断,先看一下涉及到的两个方法的源码:
类方法的区别
+(void)isKindOfClass** 的实现
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
+(BOOL)isMemberOfClass 的实现
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
从上面源码分析,可以看出,isKindOfClass 多了一步 tcls = tcls->superclass
的循环,即如果当前类不等于目标类,向上查找父类,看看父类与目标是否相等。
5.2.2 实例方法的区别
-(void)isKindOfClass 的实现
- (BOOL)isKindOfClass:(Class)cls { for (Class tcls = [self class]; tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; } return NO; }
步骤为:先判断对象的类,看是否与目标类相同;否则通过
tcls = tcls->superclass
,递归寻找父类及其父类,是否与本类相等-(void)isMemberOfClass 的实现
- (BOOL)isMemberOfClass:(Class)cls { return [self class] == cls; }
步骤为:判断对象指向的类,是否与目标类相同
5.2.3 答题
好的,根据这个,一个一个来进行解答。
返回1:因为根元类的父类=根元类
左边的
NSObject class
为 NSObject 的元类,判断是否与本类NSobject 相等?答案是不一致。
但是此时会进入
tcls = tcls->superclass
这个循环,查找本类的父类,而我们知道 NSObject 的元类的父类就是NSObject ,见下图。所以绕了一圈回来,NSObject = NSObject返回0:因为元类与本类不相等
和上一个问题一样,但是
isMemberOfClass
在第一步就停下了,object_getClass((id)self) == cls
这里问到元类与本类是否相等,当然是否。返回0:因为普通类的元类的父类与本类是不相等的
我们看右边是 Person Class
左边的 Person Class
判断条件是
isKindOfClass
所以,Person 开始找它的元类,看是否等于本类。我们看isa 的指向图可以得出,它的元类递归查找父类,一直都与本类不相等。所以返回为0
返回为0。因为 元类不等于本类
这个和第2条是一样的
*返回为1: * 因为本类等于本类
给出的判断代码为:
[(id)[NSObject alloc] isKindOfClass:[NSObject class]]
其中左边的[NSObject alloc] ,创建了一个 NSObject 的对象,而对他指向 isKindOfClass 即执行以下方法:
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) { if (tcls == cls) return YES; }
创建的
Class tcls = [self class]
,其中tcls 即为 其父类 NSObject,取其与 NSObject 相比,相等,所以返回为1。返回为1: 因为本类等于本类,同第5 题
返回为1:同第5题
返回为1: 同第5题
五、[self class] 与[super class] 区别
5.1 提问:以下打印什么?
#import "Student.h"
#import <objc/message.h>
@implementation Student
- (instancetype)init{
self = [super init];
if (self) {
NSLog(@"%@",NSStringFromClass([self class]));
NSLog(@"%@",NSStringFromClass([super class]));
}
}
5.2 源码分析
因为问题都涉及到了 class
这个方法,在NSObject.mm 这个类里找一下,方法实现如下:
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
可以见到,在object_getClass
方法里,如果给的对象self
——obj
存在,返回的是它的元类,指针指向的是它的类,所以打印应该是 Student
。
而第一行中,向super
发送消息了。
- [self class] 方法,是向 对象(
self
)发送消息(class
),走的流程是 objc_msgSend - [super class],是向 对象(
self
)发送消息(class
),走的流程是 objc_msgSendSuper
那继续探寻objc_msgSendSuper
这个方法,可以看到他的结构如下:
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
可见它执行的是 struct objc_super
类型的super,
原因:在OC中,super只是个符号标识符,objc_msgSendSuper最终接受对象还是
self
5.3 解答
打印都是NSObject
六、weak 是什么及其实现原理
6.1 分析
在OC语言到开发中,我们经常对对象前面添加 weak
来实现弱引用,从而达到避免循环引用造成的内存泄漏。那么这个weak 究竟实现了什么,作为高级开发者,不得不仔细探究一下。
6.1.1 代码创建
为了搞清楚weak 创建对象时内部的实现,创建一个weak 对象,打印试试,这时要把汇编断点打开
NSArray *arr = @[@"jack", @"tiga", @"jade", @"obu"];
id __weak abc = arr;
可以看到运行后,执行了这行代码:
可见创建后执行了关键代码 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(id *location, objc_object *newObj)
这个函数,依稀可以看出来,是一个将newObj
储存到location
的动作。继续往下分析如下:
6.1.2 源码解读 storeWeak
如果有新对象,创建新的散列表
if (haveNew) { newTable = &SideTables()[newObj];
散列表结构如下:
struct SideTable { spinlock_t slock; RefcountMap refcnts; weak_table_t weak_table; /*精简以后的内容*/ };
可见散列表中有重要的3个属性
slock —— 自旋锁
refcnts——引用计数表,这里是App 维护的一张全局表,类型是
weak_table —— 全局的弱引用表,存储所有的弱引用对象,把对象当作
key
来保持,打开看看,它的结构体实现是这样的:/** * The global weak references table. Stores object ids as keys, * and weak_entry_t structs as their values. */ struct weak_table_t { weak_entry_t *weak_entries; size_t num_entries; uintptr_t mask; uintptr_t max_hash_displacement; };
共包含了整个应用里,弱引用实体和数量。
如果当前是创建的新弱引用
haveNew
,添加弱引用。newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating);
记录弱引用实体保存的内存地址:
weak_entry_t *entry; if ((entry = weak_entry_for_referent(weak_table, referent))) { append_referrer(entry, referrer); }
插入对象到弱引用过程如下:
创建数组,插入到weak 表里
weak_entry_t new_entry(referent, referrer); weak_grow_maybe(weak_table); weak_entry_insert(weak_table, &new_entry);
循环弱引用表,将引用实体插入,弱引用的计数增加
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; while (weak_entries[index].referent != nil) { index = (index+1) & weak_table->mask; if (index == begin) bad_weak_table(weak_entries); hash_displacement++; } weak_entries[index] = *new_entry; weak_table->num_entries++; if (hash_displacement > weak_table->max_hash_displacement) { weak_table->max_hash_displacement = hash_displacement; } }
将弱引用的位值,存放在引用计数表里
if (newObj && !newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock(); }
并对isa 的weak引用属性设置为TRUE
newisa.weakly_referenced = true;
添加到计数表的方法如下:
void objc_object::sidetable_setWeaklyReferenced_nolock() { #if SUPPORT_NONPOINTER_ISA assert(!isa.nonpointer); #endif SideTable& table = SideTables()[this]; table.refcnts[this] |= SIDE_TABLE_WEAKLY_REFERENCED; }
最终返回存储的内存地址的指针,指向这个新的对象
*location = (id)newObj;
6.3 weak 的释放
6.1 查看 dealloc
我们知道,对象的销毁,一般是在类的dealloc 后进行,所以目光放在 dealloc 的方法是先上
查看NSObject.mm 中,得知dealloc 如下
- (void)dealloc {
_objc_rootDealloc(self);
}
继续探寻
void
_objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
继续继续,得到一个内联函数如下:
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
目标放在object_dispose((id)this);
这一行,函数的名字——销毁对象,即当前对象的isa还有些弱引用(isa.weakly_referenced
)或者关联对象(isa.has_assoc
)未处理的业务,会比较复杂,需要特别处理。继续探寻如下:
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
继续查看解构过程objc_destructInstance(obj)
这个函数:
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
- if (cxx) object_cxxDestruct(obj); —— 处理析构C++ 的对象
- _object_remove_assocations(obj); ——移除类的关联对象
那么要找寻如何移除弱引用,把目光放在obj->clearDeallocating();
这行代码——
又是一个内联函数,解释了如何dealloc 对象
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
很明显 clearDeallocating_slow()
才是需要找的,因为注释已经解释的很清楚:
Slow path for non-pointer isa with weak refs and/or side table data.
带弱引用或散列表数据的非指针的isa 慢速路径
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
分析关键代码:
如果当前isa 有弱引用,在弱引用表中,弱引用表清除当前的弱引用
if (isa.weakly_referenced) { weak_clear_no_lock(&table.weak_table, (id)this); }
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 *entry = weak_entry_for_referent(weak_table, referent); // zero out references 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) { *referrer = nil; } // } } weak_entry_remove(weak_table, entry); }
这里看上去业务挺多,其实核心也就两个:
移除指针
if (*referrer == referent) { *referrer = nil; }
移除实体:
weak_entry_remove(weak_table, entry); /** * Remove entry from the zone's table of weak references. */ static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) { // remove entry if (entry->out_of_line()) free(entry->referrers); bzero(entry, sizeof(*entry)); weak_table->num_entries--; weak_compact_maybe(weak_table); }
如果当前对象有引用计数存表不为0,引用计数表清楚本对象
if (isa.has_sidetable_rc) { table.refcnts.erase(this); }
6.3 回答
weak 的创建,在内存的散列表中的弱引用表里,存储关于该对象的弱引用,按照key-value 存入。
weak 的创建,正好相反,中dealloc 里,去弱引用表格里,找到引用进行弱引用实体销毁,以及弱引用的引用计数减少。
七、黑魔法·方法交换(Method Swizzling)坑点
7.1 一般使用
如下所示,对数组越界做保护:
#import "NSArray+Empty.h"
#import <objc/runtime.h>
@implementation NSArray (Empty)
+ (void)load{
Method oriMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(objectAtIndex:));
Method swiMethod = class_getInstanceMethod([self class], @selector(lj_objectAtIndex:));
method_exchangeImplementations(oriMethod, swiMethod);
}
- (instancetype)lj_objectAtIndex: (NSUInteger)index{
if (index > self.count -1 ) {
NSLog(@"朋友,数组越界了");
return nil;
}
return [self lj_objectAtIndex:index];
}
试验一下结果如何:
NSArray *arr = @[@"jack", @"tiga", @"jade", @"obu"];
NSString *name = [arr objectAtIndex:4];
打印结果如下:
2020-05-03 23:52:50.759731+0800 SWZ[7438:461811] 朋友,数组越界了
可以看到,经过方法交换,本来使用 objectAtIndex
的方法,它的实现被交换了,被交换到 lj_objectAtIndex
的实现里,执行了越界保护,保证了代码的鲁棒性。
7.2 坑点1 - 重复使用
以上是方法交换的一个简单的例子,对NSArray 添加方法实现方法交换。
7.2.1 症状
那么会不会有什么漏洞呢?留意到方法实现是在 load 方法就实现,假设在实现方法之前,主动加载一次load 会怎么样?
假设是这样:
NSArray *arr = @[@"jack", @"tiga", @"jade", @"obu"];
[NSArray load];
NSString *name = [arr objectAtIndex:4];
结果,出现了越界崩溃
7.2.2 追踪分析
往核心方法load 里添加一段打印标记,看看是否与他有关:
通过追踪,得知load 方法,执行了2次。想一想,执行load 本来是为了交换方法,那执行2次,意思是将原本交换过的方法实现,又交换回去了——白干了,这也是出现越界崩溃的原因。
7.2.3 解决
解决的方法很简单,将该方法设置成单例,通过 onceToken 来保证交换过程只会走一次:
+ (void)load{
NSLog(@"执行load!");
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method oriMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(objectAtIndex:));
Method swiMethod = class_getInstanceMethod([self class], @selector(lj_objectAtIndex:));
method_exchangeImplementations(oriMethod, swiMethod);
});
}
这样,继续执行虽然执行2次load
, 但是内部的交换实现,只会执行一次。
2020-05-04 00:13:53.061523+0800 SWZ[7778:482862] 执行load!
2020-05-04 00:13:56.011281+0800 SWZ[7778:482862] 执行load!
结果如下,虽然load 执行2次,交换方法只执行1次
7.3 坑点2 - 待交换子类未实现
7.3.1 症状
先创建一个案发现场,如下:
- 父类某方法A并实现
- 子类继承父类
- 其他业务向子类交换该A方法
- 执行父类该方法A,查看结果
如下所示:
创建父类
Animal
,以及子类Dog
, 其中父类拥有并实现run
的类方法@interface Animal : NSObject - (void)run; @end @interface Dog : Animal @end
其他业务场景,向子类申请交换了 run 的实现,run 换成了play,在Dog.m 执行:
+ (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method oriMethod = class_getInstanceMethod(self, @selector(run)); Method swiMethod = class_getInstanceMethod(self, @selector(play)); method_exchangeImplementations(oriMethod, swiMethod); }); } - (void)play{ NSLog(@"%s", __func__); }
检查此时子类与父类的的run 方法
生成两个实例,执行
run
方法Animal *a = [[Animal alloc] init]; [a run]; Dog *d = [[Dog alloc ] init]; [d run];
结果如下:
2020-05-04 00:51:33.474658+0800 SWZ[8188:509782] -[Dog(Exchange) play] 2020-05-04 00:51:33.474795+0800 SWZ[8188:509782] -[Dog(Exchange) play]
总结:可见子类方法执行了新方法play
,但是同时父类的run 方法也给换走了,执行了新的play方法
7.3.2. 追踪分析
在分析之前,先明白现在的场景:
- 父类有该方法及其实现
- 子类并无该方法极其实现
可见,业务场景向子类交换了该方法,子类查询方法并未找到,于是递归向父类查找,在父类的方法列表里查找成功,进而完成了交换过程。而父类下次调用该方法,结果使用了交换来的新方法。
好一个坑爹滴子类……
7.3.3 解决
面对如此坑爹的子类,解决方法只有一个,方法没有——自己实现。
当子类需要交换某方法的时候,尝试向自己添加待交换走的方法,以防自己未实现,不得不去找父类交换:
BOOL success = class_addMethod([Dog class], oriSEL, swiIMP, method_getTypeEncoding(oriMethod));
然后,如果添加成功,即自身本来并未实现,借着添加的机会实现了。
接下来做的就是,将新添加的方法与目标方法交换:
if (success)
class_replaceMethod([Dog class],
swiSEL,
oriIMP,
method_getTypeEncoding(oriMethod)
);
当然, 如果添加失败,即原本就有,那么按原来的逻辑,直接交换两种方法即可
贴一下完整的完善的逻辑如下:
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL oriSEL = @selector(run);
SEL swiSEL = @selector(play);
Method oriMethod = class_getInstanceMethod(self, @selector(run));
Method swiMethod = class_getInstanceMethod(self, @selector(play));
IMP oriIMP = method_getImplementation(oriMethod);
IMP swiIMP = method_getImplementation(swiMethod);
BOOL success = class_addMethod([Dog class], oriSEL, swiIMP, method_getTypeEncoding(oriMethod));
if (success) {
class_replaceMethod([Dog class],
swiSEL,
oriIMP,
method_getTypeEncoding(oriMethod)
);
}else{
method_exchangeImplementations(oriMethod, swiMethod);
}
});
}
7.4 坑点2 - 待交换子类父类实现
7.4.1 症状
上面的代码,解决了子类未实现不得不向父类索取,并拿出来交换的弊端。
那么如果该方法,父类都没实现呢?就是将父类的 run
方法实现注释,会如何,再走一遍——
不出意外,瘪犊子了。
7.4.2 追踪分析
这里很明白得出结论是,想要像某个类交换方法,结果该类以及向上的父类都没实现,毫无意外会造成崩溃。该怎么办呢?
先看一下一级一级的关系:
- 交换方法——>找当前类要
- 找不到当前类的方法实现——找父类要
- 找不到父类的方法实现——崩溃
- 找不到当前类的方法实现——找父类要
结合7.4 里的逻辑,追根溯源,是父类没有实现,那么需要做的是,先给父类添加一个空的实现,以避免崩溃。
然后在子类交换方法的过程中,子类会完善自身的添加方法实现,再去交换方法(这部分的逻辑是7.3)
7.4.3 解决
在交换方法时,先判断原方法是否存在,如果不存在,添加一个空方法实现。
添加部分如下:
if (!oriIMP) {
class_addMethod(self, oriSEL, swiIMP, method_getTypeEncoding(swiMethod));
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){
}));
}
全文如下:
+ (void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL oriSEL = @selector(run);
SEL swiSEL = @selector(play);
Method oriMethod = class_getInstanceMethod(self, @selector(run));
Method swiMethod = class_getInstanceMethod(self, @selector(play));
IMP oriIMP = method_getImplementation(oriMethod);
IMP swiIMP = method_getImplementation(swiMethod);
if (!oriIMP) {
class_addMethod(self, oriSEL, swiIMP, method_getTypeEncoding(swiMethod));
method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){
}));
}
BOOL success = class_addMethod(self, oriSEL, swiIMP, method_getTypeEncoding(oriMethod));
if (success) {
class_replaceMethod([Dog class],
swiSEL,
oriIMP,
method_getTypeEncoding(oriMethod)
);
}else{
method_exchangeImplementations(oriMethod, swiMethod);
}
});
}
Z、总结
在这篇文章,初步汇总了一下关于Runtime 几个典型的问题,比如基础的SEL、IMP 的关系,以及self 和super 的区别,以及后面深一点的weak 的底层实现,还有业务上用得最多的黑魔法——方法交换使用过程中的几点坑,如果深刻理解了其内部实现,自然能避开这些坑。
希望在日后更深刻的理会这些原理,欢迎大家有问题留言交流。