OC底层汇总(一)

一、OC方法的调用顺序

1.1 load与c++构造函数调用顺序

  • load是在dyld回调load_images中进行调用的,这个回调是在_objc_init的过程中进行注册的。
  • c++构造函数对于同一个image而言是在load回调后dyld调用的。(并不是绝对的)
  • image内部是先加载所有类的+ load,再加载分类的+ load,最后加载C++全局构造函数。(类load->分类load->C++构造函数)。+loadobjc中调用的,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的时候调用的,loadinitialize调用时机早(initializelookupimporforward慢速消息查找的时候调用)。

整个调用顺序load > c++构造函数 > initialize

1.3同名分类方法的调用顺序

同名分类方法调用顺序分为两种情况:

  1. 分类合并到主类的情况,也就是只有一个/或者没有load方法,这个时候整个方法列表一维数组(不考虑其它动态添加方法的情况)。最后编译的同名分类方法会放在主类与其它分类前面,在进行方法二分查找的时候先从中间开始查找,找到对应方法SEL的时候会继续往前查找,直到找到最前面的同名方法。
  2. 分类没有合并到主类的情况,多个load方法这个时候整个方法列表是一个一个二维数组,后编译加载的分类在数组前面,查找方法的时候从前面开始查找。

也就是同名方法最终会找到后编译加载的分类的同名方法,查找过程不一样而已。

二、runtime是什么?

runtime 是由CC++ 汇编实现的一套API,为OC语言加入了面向对象,运行时的功能。是一种运行机制,不是底层。dyld、汇编、objcmacho才是底层。
运行时(Runtime)是指将数据类型的确定由编译时推迟到了运行时。

平时编写的OC代码,在程序运行过程中,其实最终会转换成RuntimeC语言代 码,RuntimeObjective-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_allocateClassPairobjc_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;
}

classNSObject的方法,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:

image.png

// 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_msgSendSuperobjc_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_supersuperClass为父类,objc_msgSendSuper2传递的objc_supersuperClass为自己,在汇编代码中进行了父类的获取。
那么[super class]receiver也就决定了消息的接收者:

image.png

所以当前的[super class]也打印的是HPObject

llvm中实现如下:

image.png

注释也说明了是参数的不同,superclass of the current classthe current class的区别。

总结

  • [self class]就是发送消息objc_msgSend,消息接受者是self,方法编号是class
  • [super class]本质就是objc_msgSendSuper(其实调用的是objc_msgSendSuper2),消息的接受者还是 self,方法编号classobjc_msgSendSuperobjc_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()
hpcls之间有个关系isa & mask[hp instanceMethod][(__bridge id)p instanceMethod]的本质是发送一个objc_msgSend(receiver以及SEL)消息。hpp都指向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_getPropertycopy/atomic)获取。相当于对于hp的首地址,访问属性是往下平移的:

image.png

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

image.png

这也就是为什么打印了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;
};

image.png

内存分布如下:
image.png

[super viewDidLoad]在这里相当于有个临时变量objc_super

image.png

暂不清楚为什么clshp2之间有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];
}

栈中结构如下:


image.png

修改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中打断点验证:

image.png

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。堆栈中并没有发现objc_initWeak的调用方,直接反编译后发现在编译后就进行了替换:
image.png

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)获取oldTablenewTable,类型是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_tableweak_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);
}
  • 释放entryreferrersnum_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_clearDeallocatingclearDeallocating_slow最终都调用到:

SideTable& table = SideTables()[this];
weak_clear_no_lock(&table.weak_table, (id)this);

获取全局SideTables后获取到SideTable然后获取到weak_tableweak_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_removeweak_table中将entry移除。

SideTables结构如下:

class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif
}
  • SideTablesiOS中是8个,在其它平台64个。

总结:weak的存储与销毁:

  • SideTables()[obj]获取SideTableSideTablesiOS中是8个,在其它平台64个。
  • 通过SideTable获取weak_table
    • 存值:

      • weak_unregister_no_lock清空旧值,从weak_entry_tweak_table移除弱引用指针和对象。
      • weak_register_no_lock添加新值。在weak_table中根据referentweak_entry_t
        • 找到则append_referrer(entry, referrer)将新的弱引用对象加入weak_entry_t
        • 没有找到则通过referentreferrer创建weak_entry_t,对weak_table扩容然后weak_entry_insertweak_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)。

弱引用表结构:


weak表结构分布

弱引用存储与释放流程:


weak的存储与释放

七、 Method Swizzling的问题

正常情况下selimp有如下对应关系:

image.png

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

image.png

在交换后的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);
}
  • 通过clssel获取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_addMethodclass_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。在这里会进行methodimp的判断。

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,找不到返回nilclass_getClassMethod调用的是class_getInstanceMethod
  • method_getImplementationmethod_getTypeEncodingmethod_getName:通过method获取imp、签名以及SEL
  • class_addMethod:添加方法,如果存在不处理,不存在添加方法并加入rwe后返回YES
  • class_replaceMethodimp存在则进行替换,返回旧的imp。不存在添加方法并加入rwe返回nil
  • method_setImplementation:先进行methodimp是否存在,然后替换旧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

HPSubObjectHPObject的子类,实现如下:

@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方法。
报错的点在于:

image.png

  • 对于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获取到的是nilclass_addMethod仍然会成功,调用class_replaceMethod替换失败返回nil

- (void)hp_instanceMethod {
    NSLog(@"%s",__func__);
    [self hp_instanceMethod];
}
  • 由于class_addMethod的处理instanceMethod的实现指向了hp_instanceMethod,而class_replaceMethod却失败了hp_instanceMethod没有指向instanceMethodhp_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_instanceMethodimp指向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);
    }
}

你可能感兴趣的:(OC底层汇总(一))