iOS runtime相关01

下面看到头大,有空再看看

https://xietao3.com/2019/05/ARC/

arc都做了啥

  1. 主要体现在__weak/__strong object_retain、release和autorelease(包括优化) 这块

objc_storeStrong objc_storeweak objc_release等方法 所有的方法都会转成objc_msgsend,然后生成下面的代码 例如[NSArray array], 插入_objc_autoreleaseReturnValue方法, alloc 会调用 objc_release

void objc_storeStrong(id *location, id obj) {
    id prev = *location;   
    if (obj == prev) {
        return;    
    }    
    objc_retain(obj);    
    *location = obj;    
    objc_release(prev);
}

storeStrong 是将新的objretain、老的指针对象release

objc_autoreleaseReturnValue: 这个函数的作用相当于代替我们手动调用 autorelease, 创建了一个autorelease对象。编译器会检测之后的代码, 根据返回的对象是否执行 retain操作, 来设置全局数据结构中的一个标志位, 来决定是否会执行 autorelease操作。该标记有两个状态, ReturnAtPlus0代表执行 autorelease, 以及ReturnAtPlus1代表不执行 autorelease。

// Zoo
+ (instancetype)createZoo {
    id temp = [self new];  
    return objc_autoreleaseReturnValue(temp); 
} 
// VC
- (void)testForARC { 
    objc_unsafeClaimAutoreleasedReturnValue([Zoo createZoo]); 
}

objc_unsafeClaimAutoreleasedReturnValue: 这个函数的作用是对autorelease对象不做处理仅仅返回,对非autorelease对象调用objc_release函数并返回。所以本情景中它创建时执行了 autorelease操作了,就不会对其进行 release操作了。只是返回了对象,在合适的实际autoreleasepool会对其进行释放的。

// Prepare a value at +0 for return through a +0 autoreleasing convention.
id objc_retainAutoreleaseReturnValue(id obj)
{
    if (prepareOptimizedReturn(ReturnAtPlus0)) return obj;

    // not objc_autoreleaseReturnValue(objc_retain(obj)) 
    // because we don't need another optimization attempt
    return objc_retainAutoreleaseAndReturn(obj);
}
// 俩方法名不一样
// Accept a value returned through a +0 autoreleasing convention for use at +1.
id objc_retainAutoreleasedReturnValue(id obj)
{
    if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;

    return objc_retain(obj);
}

  1. 为了节省了一个将对象注册到autoreleasePool的操作,在执行objc_autoreleaseReturnValue时,根据查看后续调用的方法列表是否包含objc_retainAutoreleasedReturnValue方法,以此判断是否走优化流程。
  2. 在执行objc_autoreleaseReturnValue时,优化流程将一个标志位存储在 TLS (Thread Local Storage) 中后直接返回对象。
  3. 执行后续方法objc_retainAutoreleasedReturnValue时检查 TLS 的标志位判断是否处于优化流程,如果处于优化流程中则直接返回对象,并且将 TLS 的状态还原。 f9bfc39b6511a616c6dd4b6bb6373912
id objc_autoreleaseReturnValue(id obj) {
	// 如果走优化程序则直接返回对象
    if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;
	// 否则还是走自动释放池
    return objc_autorelease(obj);
}

static ALWAYS_INLINE bool prepareOptimizedReturn(ReturnDisposition disposition) {
    assert(getReturnDisposition() == ReturnAtPlus0);
	// 检查使用该函数的方法或调用方的的调用列表,如果紧接着执行 objc_retainAutoreleasedReturnValue ,将不注册到 autoreleasePool 中
    if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
    	// 设置标记 ReturnAtPlus1
        if (disposition) setReturnDisposition(disposition);
        return true;
    }

    return false;
}

// 将 ReturnAtPlus1 或 ReturnAtPlus0 存入 TLS
static ALWAYS_INLINE void setReturnDisposition(ReturnDisposition disposition) {
    tls_set_direct(RETURN_DISPOSITION_KEY, (void*)(uintptr_t)disposition);
}

// 取出标记
static ALWAYS_INLINE ReturnDisposition getReturnDisposition() {
    return (ReturnDisposition)(uintptr_t)tls_get_direct(RETURN_DISPOSITION_KEY);
}

/// -------------
id objc_retainAutoreleasedReturnValue(id obj) {
	// 如果 TLS 中标记表示使用了优化程序,则直接返回
    if (acceptOptimizedReturn() == ReturnAtPlus1) return obj;

    return objc_retain(obj);
}

static ALWAYS_INLINE ReturnDisposition acceptOptimizedReturn() {
	// 取出标记后返回
    ReturnDisposition disposition = getReturnDisposition();
    // 还原至未优化状态
    setReturnDisposition(ReturnAtPlus0);  // reset to the unoptimized state
    return disposition;
}

objc_autoreleaseReturnValue代码逻辑大概分为:

  1. 检查调用者方法里后面是否紧跟着调用了objc_retainAutoreleasedReturnValue。
  2. 保存 ReturnAtPlus1 至 TLS 中。
  3. 使用了优化处理则返回对象,否则加入自动释放池。

objc_retainAutoreleasedReturnValue代码逻辑大概分为:

  1. 取出 TLS 中标记。
  2. 重置 TLS 中标记至 ReturnAtPlus0 。
  3. 判断使用了优化处理则返回对象,否则引用计数 + 1。

方法调剂 连续对同一个方法hook两次

a-aa b-bb c-cc 交换a,b后 a-bb b-aa c-cc 交换a-c后 a-cc c-bb b-aa 所以调用a()后,执行了cc,cc里面执行了c,所以打印的是bb, 即 cc、bb 所以很可能会出现死循环

load initialize调用顺序

前置条件:父类、子类、子类category 都写了 load 是父-子-category initialize 是父-category, 如果没有category则是父-子、如果没有category,子类也没有实现,会调用两次父类initialize的方法

子类的category里有+load 父类的category也有+load,则这两个的调用顺序跟build Phases 的 compile Source 顺序有关

Methods中 包含哪几个部分?对方法的绝对寻址和相对寻址有什么区别

  1. selecter(选择器或方法名称)、方法类型编码、IMP(函数指针)区别:使用32位偏移代替绝对64位地址,优点:1> 无论将库加载到内存的任何位置,偏移量都是相同的。不需要修正指针地址。 2> 可以保存在只读寄存器中。(clean meonry).3> 所需内存减半
  2. 相对寻址后,method Swizzling ,官方使用全局映射表来解决这个问题,映射表维护了调剂方法对应的实现函数指针地址。

tagged Pointer是什么?为甚可以加速访问和操作速度, 为什么在Intel 和ARM64架构下对Tagged Pointer 区别对待

  1. 特殊标记指针(特殊标记对象)。通过在最后一个bit位设置为特殊标记,并将数据保存在指针自身之中。在最低位设置为标记位,倒数3位(可以表示7中类型,如 string,number、 NSIndexPath、date)赋予类型意义,其中OBJC_TAG_7类型特殊, 它可以将后8位作为扩展地址,支持256中类型的tagger pointer。 比如UIColor之类的。
  2. ARM64架构的标记位置正好相反,在第一位。原因是优化objc_msgSend检索的速度。对比标记指针和nil,objc_msgSend的检索速度时间复杂度O(1)问题
  3. [NSString stringWithFormat:] [NSString alloc] initWithFormat:] 根据传入的字符串,长度小的是标记指针,大的就是普通指针,同理 number、nsindexpath等 但是直接 name = @”abc” 这种不是标记指针,存在全局数据里
  4. 如果禁用Tagged Pointer,只需设置环境变量 OBJC_DISABLE_TAGGED_POINTERS为YES 即可 (现在会crash,看下面的_objc_registerTaggedPointerClass方法)
  5. Tagged Pointer指针上存储的数据我们完全能够计算出来,此时数据暴露在外,及其危险!苹果为了数据安全问题,设计了数据混淆, 通过设置环境变量OBJC_DISABLE_TAG_OBFUSCATION为 YES,可以关闭Tagged Pointer的数据混淆
    /// objc_debug_taggedpointer_obfuscator是一个全局变量,该变量就是一个随机生成的数字,通过编码函数、或者解码函数,用来与 value做位运算
    /// 
    static void initializeTaggedPointerObfuscator(void){
     if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) || DisableTaggedPointerObfuscation) {
         // 对于链接到旧sdk的应用程序,如果它们依赖于tagged pointer表示,将混淆器设置为0,
         objc_debug_taggedpointer_obfuscator = 0;
     } else {
         // 将随机数据放入变量中,然后移走所有非净负荷位。
         arc4random_buf(&objc_debug_taggedpointer_obfuscator,
                        sizeof(objc_debug_taggedpointer_obfuscator));
         objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
     }
    }
    
// 加载程序时,从 dyld 库 的_dyld_start()函数开始,经历了多般步骤,开始调用_objc_registerTaggedPointerClass()
void _objc_registerTaggedPointerClass(objc_tag_index_t tag, Class cls){
    if (objc_debug_taggedpointer_mask == 0) {
        _objc_fatal("tagged pointers are disabled");
    }
    
    Class *slot = classSlotForTagIndex(tag);//根据索引获取指定的类指针
    if (!slot) {
        _objc_fatal("tag index %u is invalid", (unsigned int)tag);
    }
    
    Class oldCls = *slot;//取出指针指向的类
    if (cls  &&  oldCls  &&  cls != oldCls) {
        //指定的索引被用于两个不同的类,终止程序
        _objc_fatal("tag index %u used for two different classes (was %p %s, now %p %s)", tag,oldCls, oldCls->nameForLogging(),cls, cls->nameForLogging());
    }
    *slot = cls;//将入参 cls 赋值给该类指针指向的地址
    
    if (tag < OBJC_TAG_First60BitPayload || tag > OBJC_TAG_Last60BitPayload) {
        Class *extSlot = classSlotForBasicTagIndex(OBJC_TAG_RESERVED_7);
        if (*extSlot == nil) {
            extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
            *extSlot = (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer;//表示 TaggedPointer 类
        }
    }
}

1、首先判断 objc_debug_taggedpointer_mask是否为 0 ,也就是判断开发者是否把 OBJC_DISABLE_TAGGED_POINTERS 设置为 YES;如果禁用了 Tagged Pointer,那么不好意思,直接调用 objc_fatal()函数终止该程序,不让该程序启动! 只有启用 Tagged Pointer,程序才有执行下去的意义! 2、根据索引 tag去取出数组objc_tag_classes或数组objc_tag_ext_classes中指定的类指针classSlotForTagIndex(tag): 如果传递无效的索引 tag,获取一个 nil,还是要调用_objc_fatal()终止该程序; 3、尝试着去获取该指针指向的类Class oldCls = *slot:如果要注册的类和该处的类不是同一个?不好意思,_objc_fatal()终止程序! 只有类指针 slot指向的位置为 NULL,或者类指针 slot指向的位置就是存储着我们要注册的类,系统才能安稳的运行下去; 4、将入参cls赋值给类指针 slot指向的位置*slot = cls;到此,该函数的功能经过重重考验就已经实现了! 5、假如注册的不是基础类,而是第一次注册扩展类,该函数还有个额外功能:在OBJC_TAG_RESERVED_7出存储占位类 OBJC_CLASS$___NSUnrecognizedTaggedPointer

static Class *  
classSlotForTagIndex(objc_tag_index_t tag)
{
    if (tag >= OBJC_TAG_First60BitPayload && tag <= OBJC_TAG_Last60BitPayload) {
        return classSlotForBasicTagIndex(tag);
    }

    if (tag >= OBJC_TAG_First52BitPayload && tag <= OBJC_TAG_Last52BitPayload) {
        int index = tag - OBJC_TAG_First52BitPayload;
        uintptr_t tagObfuscator = ((objc_debug_taggedpointer_obfuscator
                                    >> _OBJC_TAG_EXT_INDEX_SHIFT)
                                   & _OBJC_TAG_EXT_INDEX_MASK);
        return &objc_tag_ext_classes[index ^ tagObfuscator];
    }

    return nil;
}

1. 根据负载数据容量是60bits还是52bits,区分为类标识是基础类标识还是扩展类标识。也可以说根据tag类标识区间判断。
2. tag是基础类标识,返回classSlotForBasicTagIndex(tag)的结果;
3. tag是扩展类标识,对tag进行位操作,然后取出存在objc_tag_ext_classes数组里的结果返回。
4. 数组 objc_tag_classes:存储苹果定义的几个基础类;
5. 数组 objc_tag_ext_classes:存储苹果预留的扩展类

什么时候创建的,这个标记指针存在哪里,这两个问题还未搞明白

kvo实现原理,如何创建一个新类

KVO 的本质其实就是基于被观察的实例的 isa 生成一个新的类并在这个类的 extra 空间中存放各种和 KVO 操作相关的关键数据,然后这个新的类以一个中间人的角色借助 extra 空间中存放各种数据完成复杂的方法调度。extra结构如下:0919b1ae47e00fd3c2a2391b9d688310 可以调用 runtime 的 objc_allocateClassPair、objc_registerClassPair 函数动态地生成新的类,然后调用 object_setClass 函数去将某个对象的 isa 替换为我们自建的临时类。(给新类命名时,需要加前后缀+随机数+类对象的地址),使用objc_disposeClassPair(cls)释放临时类。6e83b9f2b3ff8f2c2b4bfe17b0c0bd91 ///链接1 链接2

kvo监听一个int类型后,调用set方法会走到这里。

/// C函数,然后进行分发
void _NSSetIntValueAndNotify(id obj, SEL sel, int number),

native-KVO 会持有一个全局的字典:_NSKeyValueContainerClassForIsa.NSKeyValueContainerClassPerOriginalClass 以 KVO 操作的原类为 key 和 NSKeyValueContainerClass 实例为 value 存储 KVO 类信息。

KVO类的生成函数 _NSKVONotifyingCreateInfoWithOriginalClass(Class originalClass)

/// objc_allocateClassPair 在 runtime.h 中的声明为 Class _Nullable objc_allocateClassPair(Class _Nullable superclass, const char * _Nonnull name, size_t extraBytes) ,
/// 苹果对 extraBytes 参数的解释为“The number of bytes to allocate for indexed ivars at the end of the class and metaclass objects.”,
/// 这就是说当我们在通过 objc_allocateClassPair 来生成一个新的类时可以通过指定 extraBytes 来为此类开辟额外的空间用于存储一些数据。系统在生成 KVO 类时会额外分配 0x68 字节的空间

typedef struct {
    Class originalClass;                // offset 0x0
    Class KVOClass;                     // offset 0x8
    CFMutableSetRef mset;               // offset 0x10
    CFMutableDictionaryRef mdict;       // offset 0x18
    pthread_mutex_t *lock;              // offset 0x20
    void *sth1;                         // offset 0x28
    void *sth2;                         // offset 0x30
    void *sth3;                         // offset 0x38
    void *sth4;                         // offset 0x40
    void *sth5;                         // offset 0x48
    void *sth6;                         // offset 0x50
    void *sth7;                         // offset 0x58
    bool flag;                          // offset 0x60
} SDTestKVOClassIndexedIvars;

oc ro rw 结构

关联对象

weak的实现

类方法存放在哪里

meta-class里,父类优先子类,

load的调用顺序

父类优先子类, 子类优先category

isa和super_clss指向

instance的isa指向class class的isa指向meta-class meta-class的isa指向基类的meta-class class的superclass指向父类的class,如果没有父类,superclass指针为nil meta-class的superclass指向父类的meta-class;基类的meta-class的superclass指向基类的class

91ff21a6ea5af175a9f5d5bb68d51173

实例对象没有super_class 属性

[super class] 和[self class]的区别

在调用[super class]的时候,runtime会去调用objc_msgSendSuper方法,而不是objc_msgSend

在objc_msgSendSuper方法中,第一个参数是一个objc_super的结构体,这个结构体里面有两个变量,一个是接收消息的receiver (这个receiver是当前这个类实例),一个是当前类的父类super_class。

OBJC_EXPORT void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )


/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained Class class;
#else
    __unsafe_unretained Class super_class;
#endif
    /* super_class is the first class to search */
};

// 由于是实例调用,所以是减号方法

  • (Class)class { return object_getClass(self); }

所以上述方法里的self是自己,虽然这个方法是在父类的方法里找到的

检测 NSObject 对象持有的强指针

  1. 调用 runtime 中的 class_copyIvarList 得到类的所有 ivar:ivar包括属性的名称、类型、偏移量以及索引,
  2. 类型是通过类型编码来获取的,在 FBIvarReference 的实例初始化时,会通过私有方法 - _convertEncodingToType: 将类型编码转换为枚举类型:, 用于区分是object类型、block类型,还是基础类型
    (FBType)_convertEncodingToType:(const char *)typeEncoding {
     if (typeEncoding[0] == '{') return FBStructType;
    
     if (typeEncoding[0] == '@') {
         if (strncmp(typeEncoding, "@?", 2) == 0) return FBBlockType;
         return FBObjectType;
     }
    
     return FBUnknownType;
    }
    
  3. 在 ObjC 运行时中的 class_getIvarLayout 可以获取某一个类的 Ivar Layout,而 XXObject 的 Ivar Layout 是什么样的呢? (lldb) po fullLayout “\x01\x12\x11” Ivar Layout 就是一系列的字符,每两个一组,比如 \xmn,每一组 Ivar Layout 中第一位表示有 m 个非强属性,第二位表示接下来有 n 个强属性
  4. 然后过滤出强引用类型对象

具体看 链接

alloc 流程

a851ce9b372df18c9f453ee3971d0541 当我们写了一个[Class alloc]命令,会相应的调用objc_alloc(Class cls)(objc4-706,NSObject.mm文件,1781行)方法,继而执行callAlloc方法,如果是__OBJC2__进入上图流程,否则执行allocWithZone方法,而这两个流程最终会执行_class_createInstanceFromZone方法,如下:

id  _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone) {
    void *bytes;
    size_t size;

    // Can't create something for nothing
    if (!cls) return nil;

    // Allocate and initialize
    size = cls->alignedInstanceSize() + extraBytes;

    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;

    if (zone) {
        bytes = malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
    } else {
        bytes = calloc(1, size);
    }

    return objc_constructInstance(cls, bytes);
}

该方法在通过malloc_zone_calloc或calloc计算所需空间后,进入objc_constructInstance构造方法中,首先会初始化isa指针,然后如果当前类有构造方法,也就是读取我们前文提到的hasCxxCtor标识,会去执行object_cxxConstructFromClass方法,依次执行父类和当前类的.cxx_construct方法,返回生成对象,否则直接返回生成的对象.

总结起来,殊途同归,alloc方法最终通过calloc方法分配空间,初始化isa指针,如果存在构造函数.cxx_construct,则一并执行.

calloc方法最终会走到libsystem_malloc.dylib的malloc方法来分配内存.(可以查看内存相关的malloc)

__strong

clang 生成中间代码,如下

id dic = objc_msgSend(NSDictionary, @selector(alloc));
objc_msgSend(obj,selector(init));
objc_storeStrong(dic);


void objc_storeStrong(id *location, id obj) {
    id prev = *location;
    if (obj == prev) {
        return;
    }
///    也就是在对象指针指向发生变化时,retain新值,并release旧值.
    objc_retain(obj);
    *location = obj;
    objc_release(prev);
}

NSDictionary *dic = [NSDictionary dictionary];

生成如下:
id dic = objc_msgSend(NSDictionary, @selector(dictionary));

/// objc_objc_retainAutoreleasedReturnValue是autoreleasepool的一个内存优化命令
objc_objc_retainAutoreleasedReturnValue(dic);
objc_storeStrong(dic);

__weak

NSObject* sp = [NSObject new]; NSObject* __weak wp = sp; NSLog(@”%@”, wp);

在 ARC 模式下,获取 weak 变量时,会调用 objc_loadWeakRetained 然后在要出当前作用域时调用了一次 objc_release,之所以这样,是因为在 objc_loadWeakRetained 中会对 weak 指针指向的对象调用 objc_object::rootRetain 函数,使该对象的引用计数加 1,为了抵消这一次加 1,会在即将出作用域之前调用 objc_release 函数 若不能理解,参考这个即可 id temp = objc_loadWeakRetained(obj1);

  1. 通过弱引用指向的对象,获取弱引用表,并且将其上锁,防止在此期间被清除。
  2. 判断是否包含自定义retain方法,如果没有,则使用默认rootTryRetain方法,使引用计数 + 1 。
  3. 如果使用了自定义retain方法,则调用自定义方法,在调用之前会先判断该对象所属类是否已经初始化过,如果没有初始化会先进行初始化然后再调用。

d3f1de1932c7d9b91ef7665f39f10791

id obj ;
objc_initWeak(&obj1,obj);
objc_destoryWeak(&obj1);

initWeak是封装的storeWeak,storeWeak 里做了清除旧值,并设置新值

template <HaveOld haveOld, HaveNew haveNew,
          CrashIfDeallocating crashIfDeallocating>
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;

 retry:
    // 从 SideTables 中取出存储弱引用表的 SideTable(为弱引用表 weak_table_t 的一层封装)
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }

    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    // location 指向的值发生改变,则重新执行获取 oldObj
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }

    // 如果有新值
    if (haveNew  &&  newObj) {
        // 如果该对象类还未初始化则进行初始化
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            // 创建一个非元类,并且初始化,会调用 +initialize 函数
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(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);

        // 设置 isa 标志位 weakly_referenced 为 true
        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;
}

这段代码大概做了这几件事:

  1. 从全局的哈希表SideTables中,利用对象本身地址进行位运算后得到对应下标,取得该对象的弱引用表。SideTables是一个 64 个元素长度的散列表,发生碰撞时,可能一个SideTable中存在多个对象共享一个弱引用表。
  2. 如果有分配新值,则检查新值对应的类是否初始化过,如果没有,则就地初始化。
  3. 如果 location 有指向其他旧值,则将旧值对应的弱引用表进行注销。
  4. 如果分配了新值,将新值注册到对应的弱引用表中。将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;
    
    // 检查当前对象没有在释放中
    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;
        }
    }
    
    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_table 表是否要扩容
        weak_grow_maybe(weak_table);
        // 将记录插入 weak 表中
        weak_entry_insert(weak_table, &new_entry);
    }
        
    return referent_id;
}

weak_register_no_lock 检查是否正在被释放中,如果是则根据crashIfDeallocating判断是否触发 crash 。 检查weak_table中是否有被引用对象对应的entry,如果有则直接将弱引用变量指针地址加入该entry中。 如果weak_table没有找到对应的entry,则新建一个entry,并将弱引用变量指针地址加入entry中。同时检查weak_table是否需要扩容

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;

    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        // 找到 weak 表中对应记录后,将引用从记录中移除
        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) {
            weak_entry_remove(weak_table, entry);
        }
    }
}

weak_unregister_no_lock

  1. 从weak_table中根据找到被引用对象对应的entry,然后将弱引用变量指针referrer从entry中移除。
  2. 移除弱引用变量指针referrer之后,检查entry是否为空,如果为空将其从weak_table中移除。

其他函数

static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
    assert(referent);


    weak_entry_t *weak_entries = weak_table->weak_entries;

    if (!weak_entries) return nil;

    size_t index = hash_pointer(referent) & weak_table->mask;
    size_t hash_displacement = 0;
    while (weak_table->weak_entries[index].referent != referent) {
        index = (index+1) & weak_table->mask;
        hash_displacement++;
        if (hash_displacement > weak_table->max_hash_displacement) {
            return nil;
        }
    }

    return &weak_table->weak_entries[index];
}

static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)


weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent) weak_table查找entry的过程,也是哈希表寻址过程,使用线性探测的方法解决哈希冲突的问题: 1。 通过被引用对象地址计算获得哈希表下标。

  1. 检查对应下标存储的是不是我们要找到地址,如果是则返回该地址。
  2. 如果不是则继续往下找,直至找到。在下移的过程中,下标不能超过weak_table最大长度,同时hash_displacement不能超过记录的max_hash_displacement最大哈希位移。max_hash_displacement是所有插入操作时记录的最大哈希位移,如果超过了,那肯定是出错了。

static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry) weak_table插入entry的的步骤:

  1. 通过被引用对象地址计算获得哈希表下标。
  2. 检查对应下标是否为空,如果不为空继续往下查找,直至找到空位。
  3. 将弱引用变量指针存入空位,同时更新weak_table的当前成员数量num_entries和最大哈希位移max_hash_displacement。

static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry) 从weak_table移除entry的的步骤:

释放entry和其中的弱引用变量。 更新 weak_table 对象数量,并检查是否可以缩减表容量

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)

往entry中添加referrer

entry的结构和weak_table相似,都使用了哈希表,并且使用线性探测法寻找对应位置。在此基础上有一点不同的地方:

  1. entry有一个标志位out_of_line,最初时该标志位为false,entry使用的是一个有序数组inline_referrers的存储结构。
  2. 当inline_referrers的成员数量超过了WEAK_INLINE_COUNT,out_of_line标志位变成true,开始使用哈希表存储结构。每当哈希表负载超过 3/4 时会进行扩容。

static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)

从entry移除referrer的步骤: out_of_line为false时,从有序数组inline_referrers中查找并移除。 out_of_line为true时,从哈希表中查找并移除。

ac3ec202f71251d57381dc113dcd46ab 673837906848b2ec7b302ce8d6026944

struct weak_table_t {
    weak_entry_t *weak_entries;       // 存储对象地址 和 __weak 修饰变量的 hash table
    size_t    num_entries;            // hash table 大小
    uintptr_t mask;                   // 辅助计算 hash 索引的位遮罩
    uintptr_t max_hash_displacement;  // hash 索引最大偏移量 (下文会说明用处)
};

struct weak_entry_t {
    DisguisedPtr<objc_object> referent;//被引用的对象
    union {
        struct {
            weak_referrer_t *referrers;//The address of a __weak variable.
            uintptr_t        out_of_line_ness : 2;
            uintptr_t        num_refs : PTR_MINUS_2;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line_ness field is low bits of inline_referrers[1]
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];//4
        };
    };

    bool out_of_line() {
        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
    }

    weak_entry_t& operator=(const weak_entry_t& other) {
        memcpy(this, &other, sizeof(other));
        return *this;
    }
    weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
        : referent(newReferent)
    {
        inline_referrers[0] = newReferrer;
        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
            inline_referrers[i] = nil;
        }
    }
};

其中referent是被引用对象(key),也就是示例代码中的obj,下面的union即存储了所有指向该对象的弱引用,其中referrers存储所有的弱引用对象的地址(value),且当引用少于4时,hash表被一个数组所代替。

具体来说,初始化时,objc_initWeak(&obj1,obj)将执行objc_storeWeak(&obj1, obj);,将被引用对象obj地址作为key值,将当前引用对象obj1作为value存储,而当obj引用计数为0时,objc_destoryWeak(&obj1)函数会执行objc_storeWeak(&obj1,0),把变量obj1的地址从 weak 表中删除.

调用栈 设置weak时 objc_initWeak / objc_storeWeak storeWeak weak_register_no_lock weak_entry_insert

移除weak时 objc_destroyWeak storeWeak weak_unregister_no_lock remove_referrer

retain release

id objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];

    table.lock();
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}

可以看到,retain操作是读取当前对象的SideTable中refcnts属性,如果没有越界,会将其增加SIDE_TABLE_RC_ONE即(1UL«2),而不仅仅是我们熟知的1,这由于引用计数的第一位用来表示计数是否越界,后两位分别被弱引用以及析构状态两个标识位占领.

objc_retain objc_object::rootRetain

id objc_retain(id obj) {
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}
/// root_retain源码参考这个[链接](http://xietao3.com/2019/05/ARC/)
  1. TaggedPointer:值存在指针内,直接返回。
  2. !newisa.nonpointer:未优化的 isa ,使用sidetable_retain()。
  3. newisa.nonpointer:已优化的 isa , 这其中又分 extra_rc 溢出和未溢出的两种情况。
    • 未溢出时,isa.extra_rc + 1 完事。
    • 溢出时,将 isa.extra_rc 中一半值转移至sidetable中,然后将isa.has_sidetable_rc设置为true,表示使用了sidetable来计算引用次数。

release操作,也会首先读取当前对象SideTable中refcnts属性,然后加以判断. 如果引用计数为计数表中的最后一个,标记对象为正在析构SIDE_TABLE_DEALLOCATING状态,然后执行完成后发送 SEL_dealloc消息释放对象

objc_release objc_object::rootRelease (源码参考如上链接)

  1. TaggedPointer: 直接返回 false。
  2. !nonpointer: 未优化的 isa 执行 sidetable_release。
  3. nonpointer:已优化的 isa ,分下溢和未下溢两种情况。
    • 未下溢: extra_rc–。
    • 下溢:从 sidetable 中借位给 extra_rc 达到半满,如果无法借位则说明引用计数归零需要进行释放。其中借位时可能保存失败会不断重试。

objc_object::rootRetainCount方法是用来计算引用计数的。通过前面rootRetain和rootRelease的源码分析可以看出引用计数会分别存在isa.extra_rc和sidetable。中,这一点在rootRetainCount方法中也得到了体现。

dealloc

dealloc方法在最后一次release后被调用,但此时实例变量(Ivars)并未释放,父类的dealloc的方法将在子类dealloc方法返回后自动调用.析构对象,并释放空间..具体流程如图: 3ad982aa4d4cc488361aa9c5a0fb3d64

objc_clear_deallocating方法,除了清除SideTable中的引用计数外,也会对弱引用表进行清除.

NEVER_INLINE void objc_object::clearDeallocating_slow()
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));
	// 根据指针获取对应 weak_table
    SideTable& table = SideTables()[this];
    table.lock();
    // 判断如果有被弱引用则清空该对象对应的 entry
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    // 清空该对象存储在 sidetable 中的引用计数
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

sideTable

static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}


struct SideTable {
    spinlock_t slock;           // 自旋锁,防止多线程访问冲突
    RefcountMap refcnts;        // 对象引用计数map
    weak_table_t weak_table;    // 对象弱引用map

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

    // 锁操作 符合StripedMap对T的定义
    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }
    void forceReset() { slock.forceReset(); }

    // Address-ordered lock discipline for a pair of side tables.

    template<HaveOld, HaveNew>
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template<HaveOld, HaveNew>
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

什么对象自动加入到 autoreleasepool中

虽然在程序入口,已经帮我们加上了 autoreleasepool,但是并不是说大括号内的所有 对象都会交给autoreleasepool来处理

如下会加入:id obj = [NSMutableArray array];

  1. 使用__autoreleasing 标记的
  2. 系统的比如NSString stringWithFormat:@”xxx“, 其他比如[NSArray array]等

注:alloc/new/copy/mutableCopy不会加入

autorelease 和 weak指针的关系

/// id __weak obj1 = obj0;
/// id __autorealeasing tmp = obj1;

以上代码, weak指针的对象会做引用计数增加吗,

答案:其实autorelease和weak没一点关系, tmp = obj1的时候,tmp指向了obj1,obj1并未销毁, 此时加入autorelease的是这个对象实例,或者叫做这个对象地址。而不是指向这个对象的指针(比如tmp或obj1), 所以引用计数会加1