本次主要记录几个问题
1、runtime是什么
2、method-swizzling使用及使用过程中的坑点
3、isKinkOfClass和isMemmberOfClass的使用
4、[self class]和[super class]的区别以及原理分析
5、runtime Associate方法的关联对象,及什么时间释放
6、__weak对象的存储及释放
1、runtime是什么
这个就说的比较宽泛一些,runtime是一套由C & C++编写的一套api,给我们OC提供运行时的功能,研究runtime对其他底层的学习会有一个比较有利的帮助
2、method-swizzling使用及使用过程中的坑点
Class class = [self class];
// 原方法名和替换方法名
SEL originalSelector = @selector(isEqualToString:);
SEL swizzledSelector = @selector(swizzle_IsEqualToString:);
// 原方法和替换方法
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// 如果当前类没有原方法的实现IMP,先调用class_addMethod来给原方法添加实现
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {// 添加方法实现IMP成功后,替换方法实现
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else { // 有原方法,交换两个方法的实现
method_exchangeImplementations(originalMethod, swizzledMethod);
}
1、方法交换,一般我们是在+ (void)load方法中进行方法交换,为什么
1)执行比较早,在main函数之前就已经执行
2)自动执行,不需要手动调用,这里的调用 不是通过objc_msgSend调用而是直接通过指针找到相应的imp调用
3)唯一性,调用顺序为 先 父类 -> 子类 -> 分类
》这里有一个优化点,App启动的时候 main函数之前 若是当前类 实现了load方法 就会调用该方法,若是load中执行了耗时操作,就会延长App的启动时间,所以 我们减少App的启动时间的一个优化点就是 不要在load方法中进行耗时操作,也不要随便重写load方法
》还有一个面试题是 load方法是否可以被交换即被hook
答案是可以的,但是成本会比较高
我们看load方法的调用堆栈
动态链接器dyld,完成对二进制文件(动态库、可执行文件)的初始化后通过回调函数_dyld_objc_notify_register,调用load_images和call_load_method实现load方法的调用,
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
load_images通过prepare_load_methods将所有类的load方法加入到list中,包括父类和分类,这里记录的是所有有load方法的类 和 load方法的imp,之后通过call_load_methods调用所有的load方法
有上述问题 若是我们需要hook A的load方法 必须在A的load方法被加载进入load方法列表之前进行hook,即我们把hook A的load方法的代码放在B中,然后保证B动态库在A之前被链接,这样就可以进行hook了
isKinkOfClass和isMemmberOfClass的使用
这里首先我们看一下下面代码的打印,先看实例方法,这个比较简单
- (void)test4 {
NSObject *object = [[NSObject alloc] init];
Person *person = [[Person alloc] init];
bool o1 = [object isKindOfClass:[NSObject class]];
bool o2 = [object isMemberOfClass:[NSObject class]];
bool p1 = [person isKindOfClass:[Person class]];
bool p2 = [person isMemberOfClass:[Person class]];
// 1-1-1-1
NSLog(@"%d-%d-%d-%d",o1,o2,p1,p2);
}
-isKindOfClass
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
我们可以看到,对象方法的 for循环 初始值 变成了 [self class],也就是从当前类开始找superclass继承链。
所以 [(id)[NSObject alloc] isKindOfClass:[NSObject class]] 和 [(id)[DZPerson alloc] isKindOfClass:[DZPerson class]] 都为 YES
-isMemberOfClass
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
- -isMemberOfClass 对象方法更是简单了,直接就是判断当前类和传入类是否相等。
- [(id)[NSObject alloc] isMemberOfClass:[NSObject class]] 和 [(id)[DZPerson alloc] isMemberOfClass:[DZPerson class]] 自然都是 YES。
再看类方法
- (void)test5 {
Class object = [NSObject class];
Class person = [Person class];
bool o1 = [object isKindOfClass:[NSObject class]];
bool o2 = [object isMemberOfClass:[NSObject class]];
bool p1 = [person isKindOfClass:[Person class]];
bool p2 = [person isMemberOfClass:[Person class]];
// 1-0-0-0
NSLog(@"===class=%d-%d-%d-%d",o1,o2,p1,p2);
}
为什么呢?我们看一下isKindOfClass的源码
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
我们可以看出
-
Class tcls = object_getClass((id)self);
从源码可以看到,self 是类本身,object_getClass((id)self) 则是获取 isa,而 isa 是指向元类的,所以 tcls 实际上是当前类的元类。
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass)
- for循环实际上就是从当前类的元类开始,沿着继承链中的 superclass 一直向上循环,在如下 isa指向图 中标注部分,NSObject元类 的父类是 NSObject。所以在第二次循环的时候,NSObject元类 的 superclass 是本身NSObject。
- 但是 DZPerson元类 的继承链是DZPerson元类 -> NSObject元类 -> NSObject,所以在 DZPerson元类 的继承链上永远不会有自身DZPerson。
- 因此 [(id)[NSObject class] isKindOfClass:[NSObject class]] = YES ,而 [(id)[DZPerson class] isKindOfClass:[DZPerson class]] == NO。
再看 isMemberOfClass的源码
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- 从源码中可以看到,代码是直接判断当前类的元类是否等于传入类。
- 所以 [(id)[NSObject class] isMemberOfClass:[NSObject class]] 和 [(id)[DZPerson class] isMemberOfClass:[DZPerson class]]中,NSObject元类 不等于 NSObject,DZPerson元类 也不等于 DZPerson,结果自然都是 NO。
[self class]和[super class]的区别以及原理分析
[self class]
- (Class)class {
return object_getClass(self);
}
///
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
也就是获取当前类的isa指向 实例的isa指向类
[super class]
本质上是调用objc_msgSendSuper
OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
第一个参数为一个objc_super的结构体
struct objc_super {
__unsafe_unretained _Nonnull id receiver;
__unsafe_unretained _Nonnull Class super_class;
};
那么[super class]就等价于
struct objc_super lg_super = {
//我们这里研究对象为当前对象所以消息接收这位self
self,
class_getSuperclass([self class]),
};
objc_msgSendSuper(&lg_super,@selector(class))
消息接受者 还是self 即 还是会打印实例的类
[self class] 就是发送消息objc_msgSend,消息接受者是 self 方法编号:class
[super class] 本质就是objc_msgSendSuper, 消息的接受者还是 self 方法编号:class 只是objc_msgSendSuper 会更快 直接跳过 self 的查找
不能向编译后的类中添加实例变量,但是可以向运行时创建的类中添加实例变量
- 类编译后只读结构体class_ro_t就被确定了,运行时不可修改
- ro结构体中的iver_list也是不可修改的,并且instanceSize决定了创建对象时需要的空间大小
- 运行时添加实例变量 必须在objc_allocateClassPair和objc_registerClassPair之间调用class_addIver
runtime Associate方法的关联对象,及什么时间释放
- 需要调用objc_setAssociatedObject,询问是否存在全局的AssociationsManager有就获取,没有就创建,然后跟拒当前类获取 ObjectAssociationMap,没有就创建一个,存入我们的关联对象
- _object_get_associative_reference,获取也是同样的逻辑 全局AssociationsManager,根据当前类获取ObjectAssociationMap,再根据关联对象的key获取值
- 删除关联对象 ,在主类dealloc时调用析构函数 objc_destructInstance,然后看当前类是否存在析构,存在的话调用_object_remove_assocations,删除析构
__weak对象的存储及释放
- 从全局的SideTables中,利用对象本身的地址取得该对象的弱引用表weak_table
- 如果有分配新值,则检查新值对应的类是否进行过初始化,如果没有则就地初始化weak_register_no_lock
- 若是有旧值,则需要把旧值对应的弱引用表进行注销weak_unregister_no_lock
- 将新值注册到对应的弱引用表中,将isa.weakly_referenced设置为true,表示该类有弱引用变量,释放时需要清空弱引用表
链接弱引用表
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);
}
// 如果正在释放中,则根据 crashIfDeallocating 判断是否触发 crash
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;
// 如果当前表中有该对象的记录则直接加入该 weak 表中对应记录
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
// 没有在 weak 表中找到对应记录,则新建一个记录
weak_entry_t new_entry(referent, referrer);
// 查看是否需要扩容
weak_grow_maybe(weak_table);
// 将记录插入 weak 表中
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
上述代码主要功能如下:
- 判断被指向对象是否可行,也就是判断其是否正在释放,并且会根据crashIfDeallocating判断是否触发crash。
- 在weak_table中检测是否有被指向对象的entry,如果有的话,直接将该弱引用变量指针加入到该entry中
- 如果没有找到对应的entry,新建一个entry,并将弱引用变量指针地址加入entry,同时检查weaktable是否扩容。
移除
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表中查找
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 找到相应记录后,将该引用从记录中移除。
remove_referrer(entry, referrer);
// 移除后检查该记录是否为空
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
// 不为空 将标记记录为false
empty = false;
}
else {
// 对比到记录的每一行
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
// 如果当前记录为空则移除记录
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
这段代码的主要流程
- 从weak_table中根据找到被引用对象对应的entry,然后将弱引用变量指针referrer从entry中移除。
- 移除弱引用变量指针referrer之后,检查entry是否为空,如果为空将其从weak_table中移除
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
// 释放 entry 中的所有弱引用
if (entry->out_of_line()) free(entry->referrers);
// 置空指针
bzero(entry, sizeof(*entry));
// 更新 weak_table 对象数量,并检查是否可以缩减表容量
weak_table->num_entries--;
weak_compact_maybe(weak_table);
}
- 释放entry和其中的弱引用变量。
- 更新 weak_table 对象数量,并检查是否可以缩减表容量
entry 和 referrer
entry以及比较熟悉了,一个对象的弱引用记录,referrer则是代表弱引用变量,每次被弱引用时,都会将弱引用变量指针referrer加入entry中,而当原对象被释放时,会将entry清空并移除
从entry移除referrer的步骤:
out_of_line为false时,从有序数组inline_referrers中查找并移除。
out_of_line为true时,从哈希表中查找并移除
dealloc
当被引用的对象被释放后,会去检查isa.weakly_referenced标志位,每个被弱引用的对象weakly_referenced标志位都为true。
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
// 根据指针获取对应 Sidetable
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();
}
从上面的代码可以看出,在对象释执行dealloc函数时,会检查isa.weakly_referenced标志位,然后判断是否要清理weak_table中的entry。
这最后还是走到了前面的remove。