iOS底层探索--内存管理

1. 五大分区

在一个4G内存的移动设备中,内核区约占1GB。 内存分区:代码段、数据段、BSS段,栈区,堆区。栈区地址通常为0x7开头,堆区地址通常为0x6开头。数据段通常0x1开头。c++

0x70000000对其进行转换,恰好为3GB面试

  • 栈区:存储函数,方法,快速高效,
  • 堆区:经过alloc分配的对象,block copy,灵活方便,数据适应面普遍,
  • BSS段:未初始化的全局变量,静态变量,程序结束后有系统释放。
  • 数据段:初始化的全局变量,静态变量,程序结束后有系统释放。
  • 代码段:程序代码,加载到内存中

栈的内存是访问寄存器直接访问其内存空间,堆里的对象的访问是经过存在栈区的指针存储的地址,再找到堆区对应的地址。安全

全局变量和局部变量在内存中是否有区别?有什么区别?

  • 存储位置不一样全局变量存在相应的全局存储区域。局部变量定义在局部的空间,存储在栈中

Block中是否能够直接修改全局变量

Block中能够修改全局变量。bash

全局静态变量的修改

LGPerson中,定义一个全局变量personNum,并定义两个方法,对全局变量++,而后在ViewController调用打印,结果是什么样的?多线程

static int personNum = 100;

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson : NSObject

- (void)run;
+ (void)eat;
@end

#import "LGPerson.h"

@implementation LGPerson
- (void)run{
    personNum ++;
    NSLog(@"LGPerson内部:%@-%p--%d",self,&personNum,personNum);
}

+ (void)eat{
    personNum ++;
    NSLog(@"LGPerson内部:%@-%p--%d",self,&personNum,personNum);
}

- (NSString *)description{
    return @"";
}

@end

NSLog(@"vc:%p--%d",&personNum,personNum); // 100
personNum = 10000;
NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
[[LGPerson new] run]; // 100 + 1 = 101
NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
[LGPerson eat]; // 102
NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
[[LGPerson alloc] cate_method];

复制代码

打印结果以下: 架构

static修饰的静态变量,只针对文件有效。在vc中和LGPerson中的两个全局变量的地址不相同。

2. TaggedPointer

使用TaggedPointer存储小对象NSNumber、NSDate,优化内存管理。app

首先,看下面的代码,能正常执行么?点击屏幕时会有什么问题?less

- (void)taggedPointerDemo {
  
    self.queue = dispatch_queue_create("com.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"cooci"];
             NSLog(@"%@",self.nameStr);
        });
    }
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    

    
    NSLog(@"来了");
    for (int i = 0; i<10000; i++) {
        dispatch_async(self.queue, ^{
            self.nameStr = [NSString stringWithFormat:@"和谐学习不急不躁"];
            NSLog(@"%@",self.nameStr);
        });
    }
}
复制代码

经过测试,上述代码,能正常执行,当点击屏幕时,发生崩溃。那么为何在点击屏幕是,会发生崩溃呢?dom

其实在多线程代码块中赋值,打印,是调用的settergetter,当settergetter加入多线程时,就会不安全。async

setter方法底层是retian newvalue,而后realase oldvalue。多加入多线程时,就会出现屡次释放,形成野指针。

那么,为何第一段可以正常执行呢?

经过上面的断点调试,发现第一段代码中_nameStr的类型并非NSString而是taggedPointer类型,而第二段中是NSString类型。

接下来看一下objc_releaseobjc_retain的源码:

void 
objc_release(id obj)
{
    if (!obj) return;
    if (obj->isTaggedPointer()) return;
    return obj->release();
}

objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}
复制代码

release时,先判断是不是isTaggedPointer,是,则直接返回,并无真的进行release操做,而在retain时也是一样的操做,先判断isTaggedPointer,并无进行retain,这也就解释了为何第一段代码能正常执行,由于其底层并无retainrelease,即便搭配多线程,也不会出现屡次释放的问题,也就不会出现野指针,也不会崩溃。

其实在read_images中的的initializeTaggedPointerObfuscator()中,会初始化一个objc_debug_taggedpointer_obfuscator,在构造TaggedPointer时,经过对这个值的^操做,进行编码和解码。

static void
initializeTaggedPointerObfuscator(void)
{
    if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) ||
        // Set the obfuscator to zero for apps linked against older SDKs,
        // in case they're relying on the tagged pointer representation. DisableTaggedPointerObfuscation) { objc_debug_taggedpointer_obfuscator = 0; } else { // Pull random data into the variable, then shift away all non-payload bits. arc4random_buf(&objc_debug_taggedpointer_obfuscator, sizeof(objc_debug_taggedpointer_obfuscator)); objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK; } } 复制代码

initializeTaggedPointerObfuscator中,在iOS以前低版本时,objc_debug_taggedpointer_obfuscator = 0,以后的版本objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK

在构建TaggedPointer时,会进行编码,在获取TaggedPointer时,解码。

_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value)
{
    // PAYLOAD_LSHIFT and PAYLOAD_RSHIFT are the payload extraction shifts.
    // They are reversed here for payload insertion.

    // ASSERT(_objc_taggedPointersEnabled());
    if (tag <= OBJC_TAG_Last60BitPayload) {
        // ASSERT(((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT) == value);
        uintptr_t result =
            (_OBJC_TAG_MASK | 
             ((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) | 
             ((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
        // ✅ 返回一个编码的值
        return _objc_encodeTaggedPointer(result);
    } else {
        // ASSERT(tag >= OBJC_TAG_First52BitPayload);
        // ASSERT(tag <= OBJC_TAG_Last52BitPayload);
        // ASSERT(((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT) == value);
        uintptr_t result =
            (_OBJC_TAG_EXT_MASK |
             ((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
             ((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
        return _objc_encodeTaggedPointer(result);
    }
}

_objc_getTaggedPointerTag(const void * _Nullable ptr) 
{
    // ASSERT(_objc_isTaggedPointer(ptr));
    // ✅ 解码,而后进行一系列偏移运算,返回
    uintptr_t value = _objc_decodeTaggedPointer(ptr);
    uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
    uintptr_t extTag =   (value >> _OBJC_TAG_EXT_INDEX_SHIFT) & _OBJC_TAG_EXT_INDEX_MASK;
    if (basicTag == _OBJC_TAG_INDEX_MASK) {
        return (objc_tag_index_t)(extTag + OBJC_TAG_First52BitPayload);
    } else {
        return (objc_tag_index_t)basicTag;
    }
}

复制代码

其实编码和解码的操做就是与上objc_debug_taggedpointer_obfuscator

_objc_encodeTaggedPointer(uintptr_t ptr)
{
    return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}

/**
 1000 0001
^0001 1000
 
 1001 1001
^0001 1000
 1000 0001
 */

static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr)
{
    return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
复制代码

咱们能够调用解码方法,来打印一下具体的TaggedPointer

NSString *str1 = [NSString stringWithFormat:@"a"];
    NSString *str2 = [NSString stringWithFormat:@"b"];

    NSLog(@"%p-%@",str1,str1);
    NSLog(@"%p-%@",str2,str2);
    NSLog(@"0x%lx",_objc_decodeTaggedPointer_(str2));

    NSNumber *number1 = @1;
    NSNumber *number2 = @1;
    NSNumber *number3 = @2.0;
    NSNumber *number4 = @3.2;
    
    NSLog(@"%@-%p-%@ - 0x%lx",object_getClass(number1),number1,number1,_objc_decodeTaggedPointer_(number1));
    NSLog(@"0x%lx",_objc_decodeTaggedPointer_(number2));
    NSLog(@"0x%lx",_objc_decodeTaggedPointer_(number3));
    NSLog(@"0x%lx",_objc_decodeTaggedPointer_(number4));

复制代码

打印结果:

上图打印结果中,0xb000000000000012b表示数字,1就是变量的值。

不一样类型的标记:

{
    // 60-bit payloads
    OBJC_TAG_NSAtom            = 0, 
    OBJC_TAG_1                 = 1, 
    OBJC_TAG_NSString          = 2, 
    OBJC_TAG_NSNumber          = 3, 
    OBJC_TAG_NSIndexPath       = 4, 
    OBJC_TAG_NSManagedObjectID = 5, 
    OBJC_TAG_NSDate            = 6,

    // 60-bit reserved
    OBJC_TAG_RESERVED_7        = 7, 

    // 52-bit payloads
    OBJC_TAG_Photos_1          = 8,
    OBJC_TAG_Photos_2          = 9,
    OBJC_TAG_Photos_3          = 10,
    OBJC_TAG_Photos_4          = 11,
    OBJC_TAG_XPC_1             = 12,
    OBJC_TAG_XPC_2             = 13,
    OBJC_TAG_XPC_3             = 14,
    OBJC_TAG_XPC_4             = 15,
    OBJC_TAG_NSColor           = 16,
    OBJC_TAG_UIColor           = 17,
    OBJC_TAG_CGColor           = 18,
    OBJC_TAG_NSIndexSet        = 19,

    OBJC_TAG_First60BitPayload = 0, 
    OBJC_TAG_Last60BitPayload  = 6, 
    OBJC_TAG_First52BitPayload = 8, 
    OBJC_TAG_Last52BitPayload  = 263, 

    OBJC_TAG_RESERVED_264      = 264
};
复制代码

Tagged Pointer指针的值再也不是地址了,而是真正的值。因此,实际上它再也不 是一个对象了,它只是一个披着对象皮的普通变量而已。因此,它的内存并不存储 在堆中,也不须要mallocfree,也不用retainrelease

在内存读取上有着3倍的效率,建立时比之前快106倍,通常一个变量的位数在8-10位时,系统默认会使用Tagged Pointer

3.NONPOINTER_ISA的优化

经过对NONPOINTER_ISA64个字节位置的存储,来内存管理。 isa结构:

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
复制代码

  • nonpointer:表示是否对 isa 指针开启指针优化

    0:纯isa指针,1:不止是类对象地址,isa 中包含了类信息、对象的引用计数当对象引用技术大于 10 时,则须要借用该变量存储进位等

  • has_assoc:关联对象标志位,0没有,1存在

  • has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器,若是有析构函数,则须要作析构逻辑, 若是没有,则能够更快的释放对象

  • shiftcls:存储类指针的值。开启指针优化的状况下,在 arm64 架构中有 33 位用来存储类指针。

  • magic :用于调试器判断当前对象是真的对象仍是没有初始化的空间

  • weakly_referenced:标志对象是否被指向或者曾经指向一个 ARC 的弱变量, 没有弱引用的对象能够更快释放

  • deallocating:标志对象是否正在释放

  • has_sidetable_rc:当对象引用技术大于 10 时,则须要借用该变量存储进位

  • extra_rc:当表示该对象的引用计数值,其实是引用计数值减 1, 例如,若是对象的引用计数为 10,那么 extra_rc 为 9。若是引用计数大于 10, 则须要使用到下面的 has_sidetable_rc

3. retain & release & retainCount & dealloc分析

retain 和 release 分析

首先咱们看一下retain的源码:

objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;
    return obj->retain();
}
复制代码

从源码中能够看出,在retain时,先判断是不是isTaggedPointer,是则直接返回,不是,则开始retain

最终进入到rootRetain方法中。

objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    // retain 引用计数处理
    // 
    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        // ✅ 判断不是nonpointer,散列表的引用计数表 进行处理 ++
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return (id)this;
            // 在散列表中存储引用计数++
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        // don't check newisa.fast_rr; we already called any RR overrides // ✅ 判断是否正在析构 if (slowpath(tryRetain && newisa.deallocating)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); return nil; } // ✅ isa 中extra_rc++ uintptr_t carry; newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++ if (slowpath(carry)) { // newisa.extra_rc++ overflowed if (!handleOverflow) { ClearExclusive(&isa.bits); return rootRetain_overflow(tryRetain); } // Leave half of the retain counts inline and // prepare to copy the other half to the side table. if (!tryRetain && !sideTableLocked) sidetable_lock(); sideTableLocked = true; transcribeToSideTable = true; newisa.extra_rc = RC_HALF; newisa.has_sidetable_rc = true; } } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); if (slowpath(transcribeToSideTable)) { // Copy the other half of the retain counts to the side table. sidetable_addExtraRC_nolock(RC_HALF); } if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock(); return (id)this; } 复制代码

rootRetain中,对引用计数进行处理,先获取isa.bits

  • 判断是否是nonpointer isa,若是不是nonpointer,在散列表中对引用计数进行++(先sidetable_unlock,在sidetable_retain())。

在对散列边解锁时(sidetable_unlock),

先从 SideTables()中(从 安全和性能的角度考虑, sidetable有多张表),找到对应的散列表(底层是哈希结构,经过下标快速查找到对应 table),进行开锁。而后 sidetable_retain()

sidetable_retain时,

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;
}

复制代码

先加锁,而后存储引用计数++(refcntStorage += 往左偏移两位),而后再解锁。

为何是偏移两位呢?

由于 前两位都不是引用计数的标识位,第一位是 弱引用weak标识位,第二位 析构标识位

  • 判断是否是正在析构

  • 判断是nonpointer isa,则addc(newisa.bits, RC_ONE, 0, &carry)。对isa中的extra_rc++

    其实就是平移RC_ONE(在真机上是56位)位,找到extra_rc(nonpointer isa的引用计数存在这个地方),对其进行++

  • 当时nonpointer isa时,会判断是不是slowpath(carry),即:extra_rc的引用计数是否存满,

    • 当存满时,会isa中的引用计数砍去一半,而后修改isa中引进计数借位标识,而后将另外一半的引用计数存储到散列表

将通常的引用计数存储到散列表中,以下:

sidetable_addExtraRC_nolock源码以下:

objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
    ASSERT(isa.nonpointer);
    SideTable& table = SideTables()[this];

    size_t& refcntStorage = table.refcnts[this];
    size_t oldRefcnt = refcntStorage;
    // isa-side bits should not be set here
    ASSERT((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
    ASSERT((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);

    if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;

    uintptr_t carry;
    size_t newRefcnt = 
        addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
    if (carry) {
        refcntStorage =
            SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
        return true;
    }
    else {
        refcntStorage = newRefcnt;
        return false;
    }
}
复制代码

分析完了retain,那么release就相对的比较简单了,最终也会进入到rootRelease方法,先查看源码:

objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
    // ✅1. 判断TaggedPointer
    if (isTaggedPointer()) return false;

    bool sideTableLocked = false;

    isa_t oldisa;
    isa_t newisa;

 retry:
    do {
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        // ✅2. 判断是nonpointer,当不是nonpointer时,获取对应散列表,对引用计数表--
        if (slowpath(!newisa.nonpointer)) {
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return false;
            if (sideTableLocked) sidetable_unlock();
            return sidetable_release(performDealloc);
        }
        // don't check newisa.fast_rr; we already called any RR overrides // ✅3.是nonpointer,对extra_rc-- uintptr_t carry; newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc-- // ✅4.当减到必定程度时,直接underflow if (slowpath(carry)) { // don't ClearExclusive()
            goto underflow;
        }
    } while (slowpath(!StoreReleaseExclusive(&isa.bits, 
                                             oldisa.bits, newisa.bits)));

    if (slowpath(sideTableLocked)) sidetable_unlock();
    return false;

 underflow:
    // newisa.extra_rc-- underflowed: borrow from side table or deallocate

    // abandon newisa to undo the decrement
    newisa = oldisa;
    // ✅判断是否借用引用计数标志,
    if (slowpath(newisa.has_sidetable_rc)) {
        if (!handleUnderflow) {
            ClearExclusive(&isa.bits);
            return rootRelease_underflow(performDealloc);
        }

        // Transfer retain count from side table to inline storage.
        // 操做散列表
        if (!sideTableLocked) {
            ClearExclusive(&isa.bits);
            sidetable_lock();
            sideTableLocked = true;
            // Need to start over to avoid a race against 
            // the nonpointer -> raw pointer transition.
            goto retry;
        }

        // Try to remove some retain counts from the side table.
        // ✅ 将引用计数表中的引用计数,移到extra_rc中
        size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

        // To avoid races, has_sidetable_rc must remain set 
        // even if the side table count is now zero.
        // ✅对移出来的引用计数大于0时
        if (borrowed > 0) {
            // Side table retain count decreased.
            // Try to add them to the inline count.
            newisa.extra_rc = borrowed - 1;  // redo the original decrement too
            bool stored = StoreReleaseExclusive(&isa.bits, 
                                                oldisa.bits, newisa.bits);
            // ✅将移出来的引用计数加到extra_rc中。
            if (!stored) {
                // Inline update failed. 
                // Try it again right now. This prevents livelock on LL/SC 
                // architectures where the side table access itself may have 
                // dropped the reservation.
                isa_t oldisa2 = LoadExclusive(&isa.bits);
                isa_t newisa2 = oldisa2;
                if (newisa2.nonpointer) {
                    uintptr_t overflow;
                    newisa2.bits = 
                        addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
                    if (!overflow) {
                        stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, 
                                                       newisa2.bits);
                    }
                }
            }

            if (!stored) {
                // Inline update failed.
                // Put the retains back in the side table.
                sidetable_addExtraRC_nolock(borrowed);
                goto retry;
            }

            // Decrement successful after borrowing from side table.
            // This decrement cannot be the deallocating decrement - the side 
            // table lock and has_sidetable_rc bit ensure that if everyone 
            // else tried to -release while we worked, the last one would block.
            sidetable_unlock();
            return false;
        }
        else {
            // Side table is empty after all. Fall-through to the dealloc path.
        }
    }

    // Really deallocate.

    if (slowpath(newisa.deallocating)) {
        ClearExclusive(&isa.bits);
        if (sideTableLocked) sidetable_unlock();
        return overrelease_error();
        // does not actually return
    }
    newisa.deallocating = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;

    if (slowpath(sideTableLocked)) sidetable_unlock();

    __c11_atomic_thread_fence(__ATOMIC_ACQUIRE);

    if (performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
    }
    return true;
}

复制代码

rootRelease中,先判断TaggedPointer,而后判断是否nonpointer isa,当不是nonpointer时,获取对应散列表,对引用计数表--,当时nonpointer isa时,对extra_rc--,当减到必定程度时,直接调用underflow,判断引用计数借用标识,将暂存到引用计数表中的引用计数,存到extra_rc中。

小结:

retain
1. 判断是不是nonpointer isa,不是,则对散列表处理,对引用计数表处理
2. 是nonpointer isa,对extra_rc++
3. 超出时,将extra_rc中的一半存在储到引用计数表中
为何超出时不所有存在引用计数表中?
散列表要开锁解锁,优先选择extra_rc

release
1. 先判断TaggedPointer,
2. 判断是否nonpointer isa,当不是nonpointer时,获取对应散列表,对引用计数表--,
3. 当时 nonpointer isa 时,对 extra_rc--
4. 当引用计数减到必定程度时,直接调用 underflow
5. underflow中,判断引用计数借用标识,将暂存到引用计数表中的引用计数,存到 extra_rc 中。
复制代码

RetainCount 分析

首先咱们看下面的一个问题:

NSObject *objc = [NSObject alloc];
    NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)objc));
复制代码

问:上面的代码,引用计数打印是多少?alloc出来的对象,引用计数是多少?

这个打印结果,咱们都知道是1,那么alloc出来的对象引用计数真的是1么?

接下来,咱们深刻看一下RetainCount的源码,以下:

inline uintptr_t 
objc_object::rootRetainCount() // 1
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {
        // bits.extra_rc = 0;
        // ✅ 对isa 的 bits.extra_rc + 1,即对引用计数+1
        uintptr_t rc = 1 + bits.extra_rc; // isa
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock(); // 散列表
        }
        sidetable_unlock();
        return rc; // 1
    }
    sidetable_unlock();
    return sidetable_retainCount();
}
复制代码

经过源码的断点调试,发现alloc出来的对象的引用计数extra_rc0,而经过retainCount打印出来的引用计数为1,是经过uintptr_t rc = 1 + bits.extra_rc得来的,而单纯alloc出来的对象的引用计数为0,默认给1,防止被错误释放

dealloc分析

dealloc底层,必然会调用rootDealloc()方法,源码以下:

inline void
objc_object::rootDealloc()
{
    // ✅ 判断是TaggedPointer,直接返回,不须要dealloc
    if (isTaggedPointer()) return;  // fixme necessary?
    // ✅ 判断是否是nonponiter isa,不是则直接free
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        // ✅ 是nonponinter isa
        object_dispose((id)this);
    }
}
复制代码

rootDealloc中,会讲过上面的几个判断,当不是nonponinter isa时,调用object_dispose((id)this)方法。

object_dispose中,经过objc_destructInstance(obj)cxx关联对象进行释放,而后经过obj->clearDeallocating()weak表引用计数表

object_dispose(id obj)
{
    if (!obj) return nil;
    // weak
    // cxx
    // 关联对象
    // ISA 64
    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

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;
}

复制代码

清除weak表引用计数表

objc_object::clearDeallocating_slow()
{
    ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this];
    table.lock();
    // 清除weak表
    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 底层调用 rootDealloc,
rootDealloc 判断 aggedPointer 直接返回,而后判断不是nonponiter isa 则直接释放
判断是nonponiter isa则调用 object_dispose 开始释放
在 object_dispose 中,对cxx、关联对象表、weak表和引用计数表进行释放。
复制代码

4. 循环引用

在开发中,咱们常常会使用NSTimer定时器,以下:

// 定义属性
@property (nonatomic, strong) NSTimer       *timer;

// 初始化
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];

- (void)fireHome{
    num++;
    NSLog(@"hello word - %d",num);
}


// 释放
- (void)dealloc{
    [self.timer invalidate];
    self.timer = nil;
    NSLog(@"%s",__func__);
}
复制代码

首先在VC中定义timer属性,在dealloc中,调用[self.timer invalidate] self.timer = nil,对timer进行析构和置空。

当咱们把dealloc中的代码去掉,当频繁的pushpop页面时,就会出现问题,而形成问题的缘由是循环引用

首先VCtimer是强持有,timertarget属性强持有,这样就形成了循环引用。

那么怎么解决这个循环引用呢?

按照一般的处理方法,就是使用weakSelf,即下面的方式:

self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];
复制代码

经过测试,发现并无打破self -> block -> self的循环引用。那么为何呢?

由于,NSTimer 依赖于 NSRunLooptimer须要加到NSRunLoop才能运行,NSRunLoopNSTimer强持有,timerself或者weakself,强持有,timer没法释放,致使weakself没法释放,self没法释放。

那么为何block 的循环引用中,使用weakself能够解决,为何这个不能释放呢?

__weak typeof(self) weakSelf = self;
复制代码

经过打印weakSelfself的地址以下:

经过上面打印发现weakSelfself是两个不一样的地址。通过weakSelf 弱引用后,并无对引用计数处理。

NSRunLoop -> timer -> weakSelf -> selfweakSelf间接操做self,间接强持有了self,因此没法释放

block解决循环引用时,使用weakSelf,实际上是一个临时变量的指针地址,block强持有的是一个新的指针地址。因此打破了循环引用的问题。 self -> block -> weakSelf (临时变量的指针地址)

block的循环引用操做的是对象地址,timer循环引用操做的是对象。

5. Timer循环引用的解决

  • pop的时候就销毁timer
- (void)didMoveToParentViewController:(UIViewController *)parent{
    // 不管push 进来 仍是 pop 出去 正常跑
    // 就算继续push 到下一层 pop 回去仍是继续
    if (parent == nil) {
       [self.timer invalidate];
        self.timer = nil;
        NSLog(@"timer 走了");
    }
}
复制代码
  • 中介者模式

定义一个self.target = [[NSObject alloc] init],让其做为target来响应。

self.target = [[NSObject alloc] init];
class_addMethod([NSObject class], @selector(fireHome), (IMP)fireHomeObjc, "v@:");
self.timer = [NSTimer scheduledTimerWvoid fireHomeObjc(id obj){
    NSLog(@"%s -- %@",__func__,obj);
}

- (void)fireHome{
    num++;
    NSLog(@"hello word - %d",num);
}
ithTimeInterval:1 target:self.target selector:@selector(fireHome) userInfo:nil repeats:YES];


复制代码
  • 封装一个中间类
@interface LGTimerWapper : NSObject

- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
- (void)lg_invalidate;

@end

#import "LGTimerWapper.h"
#import <objc/message.h>

@interface LGTimerWapper()
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL aSelector;
@property (nonatomic, strong) NSTimer *timer;

@end

@implementation LGTimerWapper

- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
    if (self == [super init]) {
        self.target     = aTarget; // vc
        self.aSelector  = aSelector; // 方法 -- vc 释放
        
        if ([self.target respondsToSelector:self.aSelector]) {
            Method method    = class_getInstanceMethod([self.target class], aSelector);
            const char *type = method_getTypeEncoding(method);
            class_addMethod([self class], aSelector, (IMP)fireHomeWapper, type);
            
            // 难点: lgtimer
            // 没法响应
            // 时间点: timer invalid
            // vc -> lgtimerwarpper
            // runloop -> timer -> lgtimerwarpper
            self.timer      = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo];
        }
    }
    return self;
}

// 一直跑 runloop
void fireHomeWapper(LGTimerWapper *warpper){
    
    if (warpper.target) {
        void (*lg_msgSend)(void *,SEL, id) = (void *)objc_msgSend;
         lg_msgSend((__bridge void *)(warpper.target), warpper.aSelector,warpper.timer);
    }else{ // warpper.target
        [warpper.timer invalidate];
        warpper.timer = nil;
    }
}


- (void)lg_invalidate{
    [self.timer invalidate];
    self.timer = nil;
}

- (void)dealloc{
    NSLog(@"%s",__func__);
}

@end


self.timerWapper = [[LGTimerWapper alloc] lg_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];

复制代码
  • 使用proxy 虚基类的方式
@interface LGProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end


#import "LGProxy.h"

@interface LGProxy()
@property (nonatomic, weak) id object;
@end

@implementation LGProxy
+ (instancetype)proxyWithTransformObject:(id)object{
    LGProxy *proxy = [LGProxy alloc];
    proxy.object = object;
    return proxy;
}

// 仅仅添加了weak类型的属性还不够,为了保证中间件可以响应外部self的事件,须要经过消息转发机制,让实际的响应target仍是外部self,这一步相当重要,主要涉及到runtime的消息机制。
// 转移
-(id)forwardingTargetForSelector:(SEL)aSelector {
    return self.object;
}
@end

self.proxy = [LGProxy proxyWithTransformObject:self];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];


复制代码

6. 自动释放池 AutoreleasePool

首先,咱们来看几道面试题目:

  • 题目1:临时变量何时释放
  • 题目2:自动释放池原理
  • 题目3:自动释放池可否嵌套使用

接下来,咱们来探索一下自动释放池AutoreleasePool的底层,首先在main.m文件中写一行代码,以下:

经过clang -rewrite-objc main.m -o main.cpp将其编译成cpp文件,查看AutoreleasePool的底层结构。

编译后查看main.cpp

自动释放池 AutoreleasePool,在底层是一个 __AtAutoreleasePool类型的结构体,以下:

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};
复制代码

__AtAutoreleasePool结构体里,有构造函数析构函数

当咱们建立__AtAutoreleasePool这样一个结构体时,就会调用构造函数析构函数

AutoreleasePoolPage分析

在调用构造函数时,会调用objc_autoreleasePoolPush(),由函数名可直接定位到objc源码中,接着在objc源码中查看objc_autoreleasePoolPush()函数的具体实现,

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}
复制代码

其中会经过一个AutoreleasePoolPage来调用push(),那么AutoreleasePoolPage是什么东西呢?咱们查看一下源码。

发现AutoreleasePoolPage继承自私有类AutoreleasePoolPageData

/***********************************************************************
   Autorelease pool implementation
    // 先进后出
   A thread's autorelease pool is a stack of pointers. Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary. A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released. The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary. Thread-local storage points to the hot page, where newly autoreleased objects are stored. **********************************************************************/ BREAKPOINT_FUNCTION(void objc_autoreleaseNoPool(id obj)); BREAKPOINT_FUNCTION(void objc_autoreleasePoolInvalid(const void *token)); class AutoreleasePoolPage : private AutoreleasePoolPageData { friend struct thread_data_t; ... } 复制代码

从上面的注释能够看出Autorelease池是实现:

  1. 线程的自动释放池是指针的堆栈。每一个指针要么是要释放的对象,要么是要释放的POOL_BOUNDARY(自动释放池边界能够理解为哨兵,释放到这个位置,就到边界了,释放完了)。

  2. 池标记是指向该池的POOL_BOUNDARY的指针。当池被弹出,每个比哨兵更热的物体被释放。

  3. 该堆栈被划分为一个页面的双连接列表。页面在必要时进行添加和删除。

  4. 新的自动释放的对象,存储在聚焦页(hot Page

AutoreleasePoolPageData的具体源码实现以下:

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
	magic_t const magic; // 16
	__unsafe_unretained id *next; //8
	pthread_t const thread; // 8
	AutoreleasePoolPage * const parent; //8
	AutoreleasePoolPage *child; //8
	uint32_t const depth; // 4
	uint32_t hiwat; // 4

	AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
		: magic(), next(_next), thread(_thread),
		  parent(_parent), child(nil),
		  depth(_depth), hiwat(_hiwat)
	{
	}
};
复制代码

上面的代码中能够看出,在AutoreleasePoolPageData中有一系列的属性。那么全部AutoreleasePool的对象都有上面的属性。

每一个属性的含义:

  • magic:用来校验AutoreleasePoolPage的结构是否完成
  • next :指向新添加的autoreleased对象的下一个位置,初始化时指向begin()
  • thread:指向当前线程
  • parent:指向父结点,第一个结点的parent值为nil
  • child :指向子结点,最后一个结点的子结点值为nil
  • depth :表明深度,从0开始,日后递增1
  • hiwat :表明 high water mark 最大入栈数量标记

而属性parentchild证实了上面注释所解释的自动释放池是双向链表结构

在构建AutoreleasePoolPage时,会调用AutoreleasePoolPageData的构建函数,传入参数,以下:

传入的第一个参数begin(),实现以下:

那么为何要this+sizeof(*this)呢?经过断点调试,并打印sizeof(*this)的值,如上图,为56

那么为何要 +56呢?

实际上是恰好往下偏移了AutoreleasePoolPageData 属性所占的空间(AutoreleasePoolPageData的属性中next、thread、parent、child都是指针类型,占8字节,depth、hiwat各占4字节,magic是一个结构体,所占内存是由结构体内部属性决定,因此占4*4个字节,属性共占56字节),开始存储autoreleased对象

咱们能够经过打印自动释放池,来验证一下,在main()中写一下代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        // 1 + 504 + 505 + 505
        for (int i = 0; i < 5; i++) {
            NSObject *objc = [[NSObject alloc] autorelease];
            NSLog(@"objc = %@",objc);
        }
        _objc_autoreleasePoolPrint();
    }
    return 0;
}
复制代码

打印结果以下:

从打印结果看出,有 6个释放对象,包括循环建立加入的 5个和一个 边界哨兵对象,而从 0x1018030000x101803038恰好是 56字节,正是 AutoreleasePoolPageData的属性,进而验证了上面的推测。

自动释放池添加对象的数量

自动释放池是否是能无限添加对象呢?

咱们对上面的循环进行修改,循环505次

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        // 1 + 504 + 505 + 505
        for (int i = 0; i < 505; i++) {
            NSObject *objc = [[NSObject alloc] autorelease];
//            NSLog(@"objc = %@",objc);
        }
        _objc_autoreleasePoolPrint();
    }
    return 0;
}
复制代码

打印结果:

从打印结果看出,有506个对象,其进行了分页,第一个page(full),已存储满,而最后一个对象存储在page(hot)

因而可知,AutoreleasePool每一页恰好存储505个8字节的对象,而第一页存储的是1(边界对象)+504(添加到释放池的对象)

经过查看AutoreleasePoolPage源码,

最终获得size = 4096,减去属性所占的56个字节,恰好是505个8字节对象

因此,自动释放池AutoreleasePool第一页存储504个8字节对象,其余页505个8字节对象

objc_autoreleasePoolPush()分析

当自动释放池建立进行析构时,会调用push()

push()函数源码

static inline void *push() 
    {
        id *dest;
        if (slowpath(DebugPoolAllocation)) {
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }
复制代码

push()中,进入autoreleaseFast(POOL_BOUNDARY)函数,源码以下:

static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        // ✅ 1. 判断page存在&非满的状态
        if (page && !page->full()) {
            return page->add(obj);
        } else if (page) { // ✅ 判断page满了
            return autoreleaseFullPage(obj, page);
        } else { // ✅ 没有page
            return autoreleaseNoPage(obj);
        }
    }
复制代码
  • 首先,判断page存在而且非满的状态的状况下,将对象添加的page
id *add(id obj)
    {
        ASSERT(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        protect();
        return ret;
    }
复制代码

获取next指针,将要添加的obj存储到next指针指向的位置,而后next指针往下偏移8字节,准备下一次存储。

  • 而后,判断page已经满的状况,调用autoreleaseFullPage()
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
    {
        // The hot page is full. 
        // Step to the next non-full page, adding a new page if necessary.
        // Then add the object to that page.
        ASSERT(page == hotPage());
        ASSERT(page->full()  ||  DebugPoolAllocation);
        // ✅循环判断page的child是否full,最后一页的child为nil,而后建立新的page
        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());
        // ✅将新的page设置为聚焦页,(打印时,显示page(hot))
        setHotPage(page);
        return page->add(obj);
    }
复制代码

autoreleaseFullPage()函数中,递归,而后建立一个新的page,并设置为HotPage

  • 最后,判断没有page的状况,通过一系列判断,而后建立一个新的page,并设置为 HotPage

注意

而在MRC状况下,autorelease也会一步一步最终调用autoreleaseFast()函数,进入上面的判断流程。

autorelease底层调用顺序:

- (id)autorelease {
    return _objc_rootAutorelease(self);
}

_objc_rootAutorelease(id obj)
{
    ASSERT(obj);
    return obj->rootAutorelease();
}


inline id 
objc_object::rootAutorelease()
{
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

id 
objc_object::rootAutorelease2()
{
    ASSERT(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}

    static inline id autorelease(id obj)
    {
        ASSERT(obj);
        ASSERT(!obj->isTaggedPointer());
        id *dest __unused = autoreleaseFast(obj);
        ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
    }

复制代码

objc_autoreleasePoolPop()分析

objc_autoreleasePoolPop底层调用以下:

void
_objc_autoreleasePoolPop(void *ctxt)
{
    objc_autoreleasePoolPop(ctxt);
}

objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

复制代码

其中ctxt上下文参数,传入的关联pushpop的对象,即:atautoreleasepoolobj对象(经过上面的cpp文件得知),atautoreleasepoolobj对象是进行push时返回的对象,最终传到pop中。

pop()实现以下:

static inline void
    pop(void *token)
    {
        AutoreleasePoolPage *page;
        id *stop;
        // ✅ 判断标识是否为空,
        // 为空:没有压栈对象,为空直接将标识设置为begin,即占位的地方。
        // 不为空:返回当前page
        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            page = hotPage();
            if (!page) {
                // Pool was never used. Clear the placeholder.
                return setHotPage(nil);
            }
            // Pool was used. Pop its contents normally.
            // Pool pages remain allocated for re-use as usual.
            page = coldPage();
            token = page->begin();
        } else {
            page = pageForPointer(token);
        }

        stop = (id *)token;
        // ✅ 越界判断
        if (*stop != POOL_BOUNDARY) {
            
            // 第一个节点 - 没有父节点
            if (stop == page->begin()  &&  !page->parent) {
                // Start of coldest page may correctly not be POOL_BOUNDARY:
                // 1. top-level pool is popped, leaving the cold page in place
                // 2. an object is autoreleased with no pool
            } else {
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token);
            }
        }

        if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
            return popPageDebug(token, page, stop);
        }
        // ✅ 开始释放
        return popPage<false>(token, page, stop);
    }
复制代码

pop()中,先判断token标识是否为空,而后进行越界判断,最终执行popPage()开始释放,

popPage()源码以下:

static void
    popPage(void *token, AutoreleasePoolPage *page, id *stop)
    {
        if (allowDebug && PrintPoolHiwat) printHiwat();
        // ✅ release 对象
        page->releaseUntil(stop);

        // memory: delete empty children
        // ✅ 杀表
        if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (allowDebug && DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top)
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } else if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }

复制代码

在释放时,先释放对象,而后对建立的page进行释放(杀表)。而具体的对象释放,则以下:

void releaseUntil(id *stop) 
    {
        // Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage // ✅ 从next位置开始,一直释放,next--,知道stop位置 while (this->next != stop) { // Restart from hotPage() every time, in case -release // autoreleased more objects AutoreleasePoolPage *page = hotPage(); // fixme I think this `while` can be `if`, but I can't prove it
            // ✅ empty (next == begin())当next == begin()时,page释放完了
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }

            page->unprotect();
            id obj = *--page->next;
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            page->protect();
            // ✅ 释放对象
            if (obj != POOL_BOUNDARY) {
                objc_release(obj);
            }
        }

        setHotPage(this);

#if DEBUG
        // we expect any children to be completely empty
        for (AutoreleasePoolPage *page = child; page; page = page->child) {
            ASSERT(page->empty());
        }
#endif
    }
复制代码

解读:从next位置开始,一直释放对象,next--,直到stop位置,每个page释放到next == begin()时,该page释放完。

自动释放池嵌套

main中,写一下代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        // 1 + 504 + 505 + 505
        NSObject *objc = [[NSObject alloc] autorelease];
            NSLog(@"objc = %@",objc);
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                @autoreleasepool {
                    NSObject *obj = [[NSObject alloc] autorelease];
                    NSLog(@"obj = %@",obj);
                    _objc_autoreleasePoolPrint();
                }
            });
            _objc_autoreleasePoolPrint();

    }

    sleep(2);
    return 0;
}
复制代码

打印结果以下:

当两个autoreleasePool进行嵌套时,只会建立一个page,可是有两个哨兵

小结

1. autoreleasePool和线程关联,不一样的线程autoreleasePool地址不一样
2. 在建立autoreleasePool时,先构造,调用objc_autoreleasePoolPush,再析构,调用objc_autoreleasePoolPop
3. 在压栈对象时,判断page存在且不满,而后添加,不存在或者已满的时候,建立新的page
4. 自动释放池,第一页存储504个8字节对象,其余页505个8字节对象
5. 对象出栈时,先对对象release,而后释放page
6. 在MRC状况下,autorelease也会一步一步最终调用autoreleaseFast()函数,而后进行判断,而后将对象入栈
复制代码

6. RunLoop

RunLoop是一个运行循环,底层是一个do...while循环。 做用:

  • 保持程序持续运行,不会挂掉。
  • 处理APP中的各类事件(触摸,定时器,performSelector
  • 节省CPU资源,提供程序的性能,该作事作啥,该休息休息

RunLoop的六大事件:

RunLoop 和 线程的关系

咱们一般经过下面的方式,获取主运行循环和当前运行循环。

// 主运行循环
CFRunLoopRef mainRunloop = CFRunLoopGetMain();
// 当前运行循环
CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();
复制代码

CFRunLoop源码中,CFRunLoopGetMain调用_CFRunLoopGet0方法。而CFRunLoopGetCurrent获取当前线程的底层也是调用_CFRunLoopGet0方法。

CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}
复制代码

_CFRunLoopGet0函数实现:

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
    }
    __CFSpinLock(&loopsLock);
    // 判断runloop
    if (!__CFRunLoops) {
        __CFSpinUnlock(&loopsLock);
        // ✅建立一个dict
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        // ✅建立一个mainLoop
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        // ✅经过key-value的形式,将线程和mainloop存在dict中
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        CFRelease(mainLoop);
        __CFSpinLock(&loopsLock);
    }
    
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFSpinUnlock(&loopsLock);
    if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it __CFSpinUnlock(&loopsLock); CFRelease(newLoop); } if (pthread_equal(t, pthread_self())) { _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) { _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop); } } return loop; } 复制代码

底层线程runLoopkey-value的形式一一对应的。

RunLoop也是一个对象,里面有一系列的属性,好比(线程、commonModescommonModeItemscurrentMode)。

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;            /* locked for accessing mode list */
    __CFPort _wakeUpPort;            // used for CFRunLoopWakeUp
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};
复制代码

runLoop和线程一对一绑定,runLoop底层依赖一个CFRunLoopMode,是一对多的关系,而commonModes中的items(好比:sourcetimerobserve)也是一对多的关系。

子线程runloop默认不开启,须要run一下。

接下来,咱们以NSTimer为例,分析一下是如何回调的,首先以下断点,打印调用堆栈,

发现,先调用__CFRunLoopRun,而后调用__CFRunLoopDoTimers

__CFRunLoopDoTimers中:

static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {    /* DOES CALLOUT */
    Boolean timerHandled = false;
    CFMutableArrayRef timers = NULL;
    // ✅ 准备times
    for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);
        
        if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
            if (rlt->_fireTSR <= limitTSR) {
                if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
                CFArrayAppendValue(timers, rlt);
            }
        }
    }
    
    // ✅ 取出全部的timer ,开始__CFRunLoopDoTimer
    for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
        // ✅ 调用 __CFRunLoopDoTimer
        Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
        timerHandled = timerHandled || did;
    }
    if (timers) CFRelease(timers);
    return timerHandled;
}

复制代码

先准备timer,而后取出全部的timer开始__CFRunLoopDoTimer,而后释放timers

__CFRunLoopDoTimer中,经过下面的代码进行回调,

__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
复制代码

也验证了调用堆栈的方法调用

同理,mode中的其余事件(sources0source1observeblockGCD)也是经过这样的方式回调的。

runloop中会添加不少itemsitems的运行依赖于mode(UITrackingRunLoopMode, GSEventReceiveRunLoopMode, kCFRunLoopDefaultMode)。

RunLoop原理

void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
复制代码

do...while循环,若是返回的结果不是stop或者finished,一直执行CFRunLoopRunSpecific,一直循环,是则,跳出循环。

CFRunLoopRunSpecific中,

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    //根据modeName找到本次运行的mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    //若是没找到 || mode中没有注册任何事件,则就此中止,不进入循环
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
        Boolean did = false;
        if (currentMode) __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopUnlock(rl);
        return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    //取上一次运行的mode
    CFRunLoopModeRef previousMode = rl->_currentMode;
    //若是本次mode和上次的mode一致
    rl->_currentMode = currentMode;
    //初始化一个result为kCFRunLoopRunFinished
    int32_t result = kCFRunLoopRunFinished;
    
    if (currentMode->_observerMask & kCFRunLoopEntry )
        /// ✅ 1. 通知 Observers: RunLoop 即将进入 loop。
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    if (currentMode->_observerMask & kCFRunLoopExit )
        /// ✅ 10. 通知 Observers: RunLoop 即将退出。
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
    __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopPopPerRunData(rl, previousPerRun);
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

复制代码
  1. 先根据modeName获取mode,若是没有找到mode或者mode中没有注册事件,则直接中止,不进入循环,
  2. 而后取上一次运行的mode对当前mode进行对比
  3. 判断是不是kCFRunLoopEntry,是则__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry),通知 ObserverRunLoop即将进入loop
  4. 判断是不是kCFRunLoopExit,是则__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit),通知 ObserverRunLoop即将退出。

__CFRunLoopRun中,先进行超时判断和runloop状态的判断,而后开始一个do...while循环,

在这个do...while循环中,循环执行下面的判断

  • 依次判断

    rlm->_observerMask & kCFRunLoopBeforeTimers,是则,通知ObserversRunLoop即将出发Timer回调;

    即:__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers)

  • 判断rlm->_observerMask & kCFRunLoopBeforeSources,是则,通知ObserversRunLoop即将出发Sources0(非port)回调;

    即:__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)

    而后执行被加入的block__CFRunLoopDoBlocks(rl, rlm)

  • RunLoop 触发 Source0 (非port) 回调,即处理Source0

  • 若是有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 而后跳转去处理消息。

  • 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。而后设置RunLoop为休眠状态

而后进入一个do...while内循环,用于接收等待端口的消息,进入这个循环后,线程进入休眠,直达收到下面的消息才被唤醒,跳出循环,执行runloop

  1. 一个基于 port 的Source 的事件。
  2. 一个 Timer 到时间了
  3. RunLoop 自身的超时时间到了
  4. 被其余什么调用者手动唤醒

而后通知 Observers: RunLoop 的线程刚刚被唤醒;

而后处理被唤醒时的消息:

  1. 若是一个Timer 到时间了,触发这个Timer的回调,__CFRunLoopDoTimers,而后准备times,循环执行__CFRunLoopDoTimer,最后调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info)回调方法。
  2. 若是有dispatchmain_queueblock,执行block__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg)
  3. 判断是不是一个 Source1 (基于port) 事件

相关文章
相关标签/搜索