Runtime
是什么?Runtime
是一套有C
、C++
和汇编混合编写的API
,为OC
加入了面向对象以及运行时的功能。
运行时是指将数据类型的确定有编译时,推迟到了运行时。
比如:在编译时,只读取macho
中的数据到ro
,而真正方法的读取是在rw
中体现的,编译好的ro
是无法修改的,可以通过运行时API
给编译好的类可以通过运行时添加方法
和属性
。
可以通过Runtime API
,可以动态的添加属性,交换方法,调用底层发送消息
方法的本质是发送消息,发送消息会有一下几个流程
(objc_msgSend)
,从cache_t中查找是否有缓存的IMP
。lookUpImpOrForward
resolveInstanceMethod
forwardingTargetForSelector
methodSignatureForSelector & forwardInvocation
SEL
是方法编号,在read_iamges
期间,就被编译进了内存中的相关表中
IMP
就是我们函数实现的指针,找IMP
,就是找函数实现的过程。
就比如:sel
相当于书本的目录的标题,IMP
就相当于书本的页码,我们首先知道我们想看什么,即SEL
,然后根据目录找到对应的页码IMP
,然后翻到具体内容的一个过程。
ro
中,一旦编译完成,就无法修改。setter
和getter
,要手动添加。类拓展
添加实例变量关联对象,类拓展在编译的时候做为类的一部分直接编译到ro
中的,分类
添加实例变量,需要用关联对象,本质是实例变量的值在关联哈希表中的存储和读取。下面代码怎么打印:
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; // BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; // BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; // BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; // 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]]; // BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; // BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; // BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; // NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8); 打印结果:1 0 0 0 1 1 1 1 复制代码
经典isa
走位图:
isKindOfClass
有一个继承递归父类
的过程,有更多的容错
isMemberOfClass
直接对比元类
和类
[self class]
本质就是发送消息objc_msgSend
,消息接受者是 self
, 方法编号:class
。
[super class]
本质是objc_msgSendSuper
,消息的接收者还是self
,
方法编号:class
。只是objc_msgSendSuper
会更快 直接跳过 self
的查找
通过汇编
查看,当用__weak
去修饰一个对象的时候,底层会调用下面的方法:
id objc_initWeak(id *location, id newObj) { if (!newObj) { *location = nil; return nil; } return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating> (location, (objc_object*)newObj); } 复制代码
查看
storeWeak
方法
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; // Acquire locks for old and new values. // Order by lock address to prevent lock ordering problems. // Retry if the old value changes underneath us. retry: if (haveOld) { oldObj = *location; oldTable = &SideTables()[oldObj]; } else { oldTable = nil; } if (haveNew) { newTable = &SideTables()[newObj]; } else { newTable = nil; } SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable); if (haveOld && *location != oldObj) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); goto retry; } // Prevent a deadlock between the weak reference machinery // and the +initialize machinery by ensuring that no // weakly-referenced object has an un-+initialized isa. 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); // If this class is finished with +initialize then we're good. // If this class is still running +initialize on this thread // (i.e. +initialize called storeWeak on an instance of itself) // then we may proceed but it will appear initializing and // not yet initialized to the check above. // Instead set previouslyInitializedClass to recognize it on retry. previouslyInitializedClass = cls; goto retry; } } // ✅Clean up old value, if any. if (haveOld) { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } // ✅Assign new value, if any. if (haveNew) { newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating); // weak_register_no_lock returns nil if weak store should be rejected // Set is-weakly-referenced bit in refcount table. if (newObj && !newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock(); } // Do not set *location anywhere else. That would introduce a race. *location = (id)newObj; } else { // No new value. The storage is not changed. } SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); return (id)newObj; } 复制代码
在这个方法中,看到底层维护了一张散列表SideTable
,从SideTable
中找到维系的一张weak_table
,然后判断新值
和旧值
。
weak_unregister_no_lock
,最终在weak_resize
方法中static void weak_resize(weak_table_t *weak_table, size_t new_size) { size_t old_size = TABLE_SIZE(weak_table); weak_entry_t *old_entries = weak_table->weak_entries; weak_entry_t *new_entries = (weak_entry_t *) calloc(new_size, sizeof(weak_entry_t)); weak_table->mask = new_size - 1; weak_table->weak_entries = new_entries; weak_table->max_hash_displacement = 0; weak_table->num_entries = 0; // restored by weak_entry_insert below if (old_entries) { weak_entry_t *entry; weak_entry_t *end = old_entries + old_size; for (entry = old_entries; entry < end; entry++) { if (entry->referent) { //✅ entry加入到我们的weak_table weak_entry_insert(weak_table, entry); } } free(old_entries); } } 复制代码
通过
weak_entry_insert(weak_table, entry)
将修饰的对象,插入到eak_table
中。
weak_register_no_lock
,/** * Registers a new (object, weak pointer) pair. Creates a new weak * object entry if it does not exist. * * @param weak_table The global weak table. * @param referent The object pointed to by the weak reference. * @param referrer The weak pointer address. */ id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, bool crashIfDeallocating) { objc_object *referent = (objc_object *)referent_id; objc_object **referrer = (objc_object **)referrer_id; if (!referent || referent->isTaggedPointer()) return referent_id; // ensure that the referenced object is viable bool deallocating; if (!referent->ISA()->hasCustomRR()) { deallocating = referent->rootIsDeallocating(); } else { BOOL (*allowsWeakReference)(objc_object *, SEL) = (BOOL(*)(objc_object *, SEL)) object_getMethodImplementation((id)referent, SEL_allowsWeakReference); if ((IMP)allowsWeakReference == _objc_msgForward) { return nil; } deallocating = ! (*allowsWeakReference)(referent, SEL_allowsWeakReference); } if (deallocating) { if (crashIfDeallocating) { _objc_fatal("Cannot form weak reference to instance (%p) of " "class %s. It is possible that this object was " "over-released, or is in the process of deallocation.", (void*)referent, object_getClassName((id)referent)); } else { return nil; } } // now remember it and where it is being stored weak_entry_t *entry; // ✅根据referent 找到 entyry if ((entry = weak_entry_for_referent(weak_table, referent))) { append_referrer(entry, referrer); } else { // ✅创建了这个数组 - 插入weak_table weak_entry_t new_entry(referent, referrer); weak_grow_maybe(weak_table); weak_entry_insert(weak_table, &new_entry); } // Do not set *referrer. objc_storeWeak() requires that the // value not change. return referent_id; } 复制代码
通过referent
从 weak_table
中,找到entry
,判断entry
是否存在(entry = weak_entry_for_referent(weak_table, referent))
),存在append_referrer
,将其添加到entry
中,不存在,则创建一个weak_entry_t
,然后四分之三扩容,然后插入到new_entry
中(weak_entry_insert(weak_table, &new_entry)
)。
总结:
__weak
底层维系一张散列表SideTable
,SideTable
中维系一张弱引用表weak_table
,在这张弱引用表中,有很多弱引用对象的实体weak_entry_t *entry
。
__weak
就是根据传进来的弱引用对象,去weak_table
中找到对应的实体weak_entry_t *entry
,然后检查是否需要扩容(3/4扩容
),然后拼接进行内部持有(new_referrers[i] = entry->inline_referrers[i]
)的过程,如果这个entry
不存在,则创建一下新的实体entry
,然后扩容,再插入weak_entry_insert
那么,
__weak
为什么可以自动置为nil
?
查看dealloc
源码,最终在objc_destructInstance
方法中,如下源码:
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; } 复制代码
进入obj->clearDeallocating()
方法
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
经过判断,最终都会进入到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); if (entry == nil) { /// XXX shouldn't happen, but does with mismatched CF/objc //printf("XXX no entry for clear deallocating %p\n", referent); return; } // 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; } else if (*referrer) { _objc_inform("__weak variable at %p holds %p instead of %p. " "This is probably incorrect use of " "objc_storeWeak() and objc_loadWeak(). " "Break on objc_weak_error to debug.\n", referrer, (void*)*referrer, (void*)referent); objc_weak_error(); } } } weak_entry_remove(weak_table, entry); } 复制代码
在weak_clear_no_lock
中直接将关联对象的指针referrer
置为nil
,所以当其释放时,会自动设置为空。
跟__weak
一样,先通过汇编分析,找到底层调用的objc_storeStrong
方法,如下:
void objc_storeStrong(id *location, id obj) { id prev = *location; if (obj == prev) { return; } objc_retain(obj); *location = obj; objc_release(prev); } 复制代码
先objc_retain(obj)
,在objc_release(prev)
,而objc_retain
方法和objc_release
方法底层都是发送retain
和release
消息。
在处理数组越界的时候,我们很容易想到的就是通过Runtime
进行方法交换。
我们通常会创建一个分类,如下:
#import "NSArray+LG.h" #import "LGRuntimeTool.h" #import <objc/runtime.h> @implementation NSArray (LG) + (void)load{ [LGRuntimeTool lg_methodSwizzlingWithClass:objc_getClass("__NSArrayI") oriSEL:@selector(objectAtIndex:) swizzledSEL:@selector(lg_objectAtIndex:)]; [LGRuntimeTool lg_methodSwizzlingWithClass:objc_getClass("__NSArrayI") oriSEL:@selector(objectAtIndexedSubscript:) swizzledSEL:@selector(lg_objectAtIndexedSubscript:)]; } // 交换的方法 - (id)lg_objectAtIndex:(NSUInteger)index{ if (index > self.count-1) { NSLog(@"数组越界 -- "); NSLog(@"取值越界了,记录:%lu > %lu", (unsigned long)index, self.count - 1); return nil; } return [self lg_objectAtIndex:index]; } - (id)lg_objectAtIndexedSubscript:(NSUInteger)index { if (index > self.count-1) { NSLog(@"数组越界 -- "); NSLog(@"取值越界了,记录:%lu > %lu", (unsigned long)index, self.count - 1); return nil; } return [self lg_objectAtIndexedSubscript:index]; } #import "LGRuntimeTool.h" #import <objc/runtime.h> @implementation LGRuntimeTool + (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{ if (!cls) NSLog(@"传入的交换类不能为空"); Method oriMethod = class_getInstanceMethod(cls, oriSEL); Method swiMethod = class_getInstanceMethod(cls, swizzledSEL); method_exchangeImplementations(oriMethod, swiMethod); } @end 复制代码
对objectAtIndex
和lg_objectAtIndex
进行交换,当调用系统objectAtIndex
方法时,调用lg_objectAtIndex
方法,方便我们在数组越界时的一些处理(比如防崩溃)
那么假如我们在添加了NSArray
的分类之后,在某个调用的地方,不小心调用了,NSArray
的load
方法,比如下面的代码,会发生什么呢?
self.dataArray = @[@"Hank",@"Cooci",@"Kody",@"CC"]; [NSArray load]; NSLog(@"%@",[self.dataArray objectAtIndex:4]); 复制代码
通过验证,上面的代码,会崩溃,当再次调用load
方法时,会再次交换方法,调用系统的objectAtIndex
方法,而导致崩溃。
所以,我们要在添加分类的load
方法中,使用单例,防止方法多次重复交换
+ (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [LGRuntimeTool lg_methodSwizzlingWithClass:objc_getClass("__NSArrayI") oriSEL:@selector(objectAtIndex:) swizzledSEL:@selector(lg_objectAtIndex:)]; [LGRuntimeTool lg_methodSwizzlingWithClass:objc_getClass("__NSArrayI") oriSEL:@selector(objectAtIndexedSubscript:) swizzledSEL:@selector(lg_objectAtIndexedSubscript:)]; }); } 复制代码
那么在日常的开发中,经常出现继承关系,而在这个继承关系中,经常出现子类继承父类的方法,那么在子类中交换继承自父类的方法会出现什么情况呢?如下代码:
// 父类 @interface LGPerson : NSObject - (void)personInstanceMethod; + (void)personClassMethod; @end #import "LGPerson.h" @implementation LGPerson - (void)personInstanceMethod{ NSLog(@"person对象方法:%s",__func__); } + (void)personClassMethod{ NSLog(@"person类方法:%s",__func__); } @end // 子类 @interface LGStudent : LGPerson @end #import "LGStudent.h" @implementation LGStudent @end 复制代码
上面代码中LGStudent
继承自LGPerson
,而LGPerson
中实现了personInstanceMethod
和personClassMethod
两个方法,
在子类LGStudent
中,对personInstanceMethod
方法进行方法交换,如下:
@implementation LGStudent (LG) + (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [LGRuntimeTool lg_methodSwizzlingWithClass:self oriSEL:@selector(personInstanceMethod) swizzledSEL:@selector(lg_studentInstanceMethod)]; }); } - (void)lg_studentInstanceMethod{ [self lg_studentInstanceMethod]; NSLog(@"LGStudent分类添加的lg对象方法:%s",__func__); } @end + (void)lg_methodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{ if (!cls) NSLog(@"传入的交换类不能为空"); Method oriMethod = class_getInstanceMethod(cls, oriSEL); Method swiMethod = class_getInstanceMethod(cls, swizzledSEL); method_exchangeImplementations(oriMethod, swiMethod); } 复制代码
这样的方法交换,在我们调用LGStudent
的时候,完美的实现了方法互换,那么在我们调用父类LGPerson
时,会有什么问题呢?调用如下:
LGStudent *s = [[LGStudent alloc] init]; [s personInstanceMethod]; LGPerson *p = [[LGPerson alloc] init]; [p personInstanceMethod]; 复制代码
结果如下:
在运行结果中看到,在父类调用personInstanceMethod
时,出现了父类调用lg_studentInstanceMethod
,而父类本身没有这个方法,所以就崩溃了。
那么,子类在交换继承自父类而自己本身没有重写的方法时,应该怎么做呢 ?
其实,我们可以在方法交换的时候做一个判断,先尝试添加一个方法,
+ (void)lg_betterMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{ if (!cls) NSLog(@"传入的交换类不能为空"); // oriSEL personInstanceMethod // swizzledSEL lg_studentInstanceMethod Method oriMethod = class_getInstanceMethod(cls, oriSEL); Method swiMethod = class_getInstanceMethod(cls, swizzledSEL); // 尝试添加 // ✅ 首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP) BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod)); /** personInstanceMethod(sel) - lg_studentInstanceMethod(imp) lg_studentInstanceMethod (swizzledSEL) - personInstanceMethod(imp) */ //oriSEL:personInstanceMethod if (success) {// 自己没有 - 交换 - 没有父类进行处理 (重写一个) // ✅ 然后再将父类的IMP给swizzle personInstanceMethod(imp) -> swizzledSEL class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); }else{ // 自己有 method_exchangeImplementations(oriMethod, swiMethod); } } 复制代码
这样就可以完美的对子类的方法进行交换了,调用结果如下,
那么,还有一个问题,假如某人交换了只有声明并没有实现的方法,上面的方式就会出现死循环,因为根本没有父类方法的IMP
,所以,还要对其进行改造。
代码如下:
+ (void)lg_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{ if (!cls) NSLog(@"传入的交换类不能为空"); Method oriMethod = class_getInstanceMethod(cls, oriSEL); Method swiMethod = class_getInstanceMethod(cls, swizzledSEL); if (!oriMethod) { // 在oriMethod为nil时,替换后将swizzledSEL复制一个不做任何事的空实现,代码如下: class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod)); method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){ })); } // 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败 // 交换自己没有实现的方法: // 首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP) // 然后再将父类的IMP给swizzle personInstanceMethod(imp) -> swizzledSEL //oriSEL:personInstanceMethod BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod)); if (didAddMethod) { class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); }else{ method_exchangeImplementations(oriMethod, swiMethod); } } 复制代码
在方法交换的时候,先对被交换方法进行判断,判断这个方法是否实现,当方法未实现时,我们手动添加一个空的方法实现,在这个空的方法实现中,我们可以做一些上传等操作,来记录收集crash
。
@interface LGPerson : NSObject @property (nonatomic, copy)NSString *name; @property (nonatomic, copy)NSString *subject; @property (nonatomic)int age; - (void)saySomething; @end @implementation LGPerson - (void)saySomething{ NSLog(@"NB %s ",__func__); } @end #import "ViewController.h" #import <objc/runtime.h> #import "LGPerson.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // NSString *tem = @"KC"; id pcls = [LGPerson class]; void *pp= &pcls; [(__bridge id)pp saySomething]; // p -> LGPerson 实例对象 LGPerson *p = [LGPerson alloc]; [p saySomething]; } 复制代码
运行打印结果如下,那么为什么会这样呢?
首先,指针p
指向的是LGPerson
的实例对象的存储空间,而指针pcls
指向的是LGPerson
类对象的存储空间。pp
指向的是指针pcls
的空间,而实例对象中的isa
也指向指针pcls
,所以pp
能够正常调用saySomething
方法
saySomething
方法进行修改,如下,并打印self.name
。- (void)saySomething{ NSLog(@"NB %s - %@",__func__,self.name); } 复制代码
通过调试,打印结果如下:
那么为什么会打印<ViewController: 0x105105e10>
呢,接下来我们把上面的// NSString *tem = @"KC"
注释打开,再来看一下,这一次打印的是KC
。
那么为什么会这样呢?
因为栈的内存是连续的,而我们的属性的读取是通过指针偏移来读取的,而压栈的示意如下:
上图中pp
指向的是isa
,在通过指针偏移读取self.name
时,刚好读取到了tem
,所以打印了KC
,所以当没有打开注释时,打印的是ViewController
接着在saySomething
中打印subject
,打印出的是ViewController
那么我们接着再添加属性
@property (nonatomic)NSString *age; 复制代码
打印的话,就会出现野指针报错
或者将age
的类型改成int
,也会因为读取不到内存,而报错。