本篇主作总结笔记,参阅和摘抄了许多优质博客,在最底部。

内存布局

iOS内存办理(内存布局/nonpointer/sidetable/alloc/init/retain/release/weak/dealloc/主动开释池)

内存分五大区,App 发动时,体系会把程序拷贝到内存,在内存中履行代码。

首要说一下排在内存五大区之外的内核区和保存区:

  1. 内核区:首要处理内核模块,比方咱们的体系内存为 4GB,那么咱们实际上能运用 3GB,剩余的 1GB 便是给了内核区,指针地址 0xc0000000(3x1024x1024x1024)
  2. 保存区:用来给体系供给一些必要空间

当一个 app 发动后,代码区、常量区、大局区巨细就现已固定,因而指向这些区的指针不会产生溃散性的过错。而堆区和栈区是时时刻刻改变的(堆的创立毁掉,栈的弹入弹出),所以当运用一个指针指向这个区里边的内存时,必定要注意内存是否现已被开释,否则会产生程序溃散(也便是野指针报错)

1. 栈区(Stack)

栈区里边会放一些函数参数以及一些部分变量,逐渐增多并在内存地址中由高向低延伸,因为栈的数据结构的原因,它的内存地址是连续的,栈区的内存巨细在App发动时就确认下来的,若在压入时请求的内存空间大于栈的剩余空间,就会呈现栈溢出(内存泄露),打印的地址为0x7在栈区

  • 栈区是一块连续的内存空间,内存巨细在 App 发动时就确认下来,若在压入时请求的内存空间大于栈的剩余空间,就会呈现栈溢出,即内存泄漏
  • 存储结构从高地址往低地址延伸
  • 栈区存储的是部分变量函数办法参数指针
  • 栈区的地址空间一般是以0x7最初

举例:

- (void)testStack{
    // 栈区
    int a = 10;
    int b = 20;
    NSObject *object = [NSObject new];
    NSLog(@"a == %p",&a);
    NSLog(@"b == %p",&b);
    NSLog(@"object == %p",&object);
    NSLog(@"%lu",sizeof(&object));
    NSLog(@"%lu",sizeof(a));
}

打印成果如下:

iOS内存办理(内存布局/nonpointer/sidetable/alloc/init/retain/release/weak/dealloc/主动开释池)

举个栈溢出的比方:

while10000) {
  int a = 2;
}

上面这段代码会形成内存暴涨,因为栈区只要1M巨细,一个int类型是4字节,创立一个部分变量就会压入一个4字节到栈区,当超越栈区的上限时就会形成栈溢出。

解决办法能够给代码加一个主动开释池

栈区在内存中是由高向低存储,堆区是由低向高存储,当两者相遇时,就呈现了仓库溢出

2. 堆区(heap)

  • 堆内存巨细是动态改变的,取决于体系的虚拟内存
  • 存储结构是从低地址向高地址扩展
  • 体系是用链表来办理堆的内存的,所以它的内存地址是不连续
  • 堆区存储的是方针allocnew出来的变量。
  • 堆区地址空间一般以0x6最初

举例:

- (void)testHeap{
    // 堆区
    NSObject *object1 = [NSObject new];
    NSObject *object2 = [NSObject new];
    NSObject *object3 = [NSObject new];
    NSObject *object4 = [NSObject new];
    NSObject *object5 = [NSObject new];
    NSObject *object6 = [NSObject new];
    NSObject *object7 = [NSObject new];
    NSObject *object8 = [NSObject new];
    NSObject *object9 = [NSObject new];
    NSLog(@"object1 = %@",object1);
    NSLog(@"object2 = %@",object2);
    NSLog(@"object3 = %@",object3);
    NSLog(@"object4 = %@",object4);
    NSLog(@"object5 = %@",object5);
    NSLog(@"object6 = %@",object6);
    NSLog(@"object7 = %@",object7);
    NSLog(@"object8 = %@",object8);
    NSLog(@"object9 = %@",object9);
}

成果如下:

iOS内存办理(内存布局/nonpointer/sidetable/alloc/init/retain/release/weak/dealloc/主动开释池)

在 OC,体系是经过引证计数判别是否开释方针,当引证计数为 0 就阐明没有任何变量运用该空间,体系将开释方针。

3. 大局区/静态区

大局变量和静态变量的存储是放在一块的,分为.bss段.data段,内存地址一般由0x1最初:

  • Bss段: 未初始化的大局变量和静态变量在一块区域
  • Data段: 已初始化的大局变量和静态变量在相邻的另一块区域

举例:

static int bss;
static int bssStr;
static int data = 10;
static NSString *dataStr = @"nihao";
- (void)globalTest {
    // 大局区
    NSLog(@"****bss****");
  NSLog(@"bss == %p",&bss);
  NSLog(@"bssStr == %p",&bssStr);
  NSLog(@"****data****");
  NSLog(@"data == %p",&data);
  NSLog(@"dataStr == %p",&dataStr);
}

成果如下:

iOS内存办理(内存布局/nonpointer/sidetable/alloc/init/retain/release/weak/dealloc/主动开释池)

若在.h中创立一个静态变量ws_number,则运用这个静态变量的每个文件都会生成一个ws_number的静态变量,且初始值都相同。也便是说在多个文件运用同一个静态变量,体系会各自生成一个相同初始值地址不同的静态变量,这样在各自文件内运用就不会相互搅扰,数据比较安全。这也是为什么静态区也称作静态安全区

4. 常量区

  • 常量字符串便是放在这儿的
  • 程序完毕后由体系开释

5. 代码段(.text)

  • 存储程序代码,在编译时加载到内存中,代码会被编译成二进制的形式进行存储

举个比方来理解一下:

1、方针查找进程: 在程序查找一个方针时,首要到栈区找到方针的指针,再经过这个方针指针到堆区找到这个方针。

内存办理

NONPOINTER_ISA

正如上面所说,防止地址空间糟蹋,isa指针规划成了联合体,在isa地址中存储了许多信息。

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
      uintptr_t nonpointer        : 1;                                       
      uintptr_t has_assoc         : 1;                                       
      uintptr_t has_cxx_dtor      : 1;                                       
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ 
      uintptr_t magic             : 6;                                       
      uintptr_t weakly_referenced : 1;                                       
      uintptr_t deallocating      : 1;                                       
      uintptr_t has_sidetable_rc  : 1;                                       
      uintptr_t extra_rc          : 19
    };
#endif
};

OC方针的实质,每个OC方针都含有一个isa指针,__arm64__之前,isa仅仅是一个指针,保存着方针或类方针内存地址,在__arm64__架构之后,apple对isa进行了优化,变成了一个联合体union结构,一起运用位域来存储更多的信息。 它是经过isabits进行位运算,取出呼应位置的值,runtime中的isa是被联合体位域优化过的,它不单单是指向类方针了,而是把64位中的每一位都运用了起来,其间的shiftcls为33位,代表了类方针的地址,其他的位都有各自的用处。

  • nonpointer:表明是否对isa指针敞开指针优化 0:不敞开,表明纯isa指针。 1:敞开,不单单是类方针的地址,isa中包含了类信息和方针的引证计数等。
  • has_assoc:相关方针标识位,0没有,1有,没有相关方针会开释的更快。
  • has_cxx_dtor:该方针是否有 C++ 或许 Objc 的析构器,假如有析构函数,则需求做析构逻辑, 假如没有,则能够更快的开释方针。
  • shiftcls:存储类指针class的值。敞开指针优化的状况下,在 arm64 架构中有 33 位⽤用来存储类指针。
  • magic:固定值为0xd2,用于在调试时分辨方针是否完结初始化。
  • weakly_referenced:表明方针是否被指向或许曾经指向一个 ARC 的弱引证变量, 没有弱引⽤的方针能够更快开释。
  • deallocating:标志方针是否正在开释内存。
  • has_sidetable_rc:当方针的引证计数大于10,以至于无法存储在isa指针中时,用散列表sidetable去计数。
  • extra_rc:表明该方针的引证计数,实际上是引证计数值减 1, 例如,假如方针的引证计数为 10,那么 extra_rc 为 9。假如引证计数⼤于 10, 则需求使⽤到has_sidetable_rc。

TaggedPointer

为了节约内存和提高履行功率,苹果提出了Tagged Pointer的概念。关于 64 位程序,引进Tagged Pointer后,相关逻辑能削减一半的内存占用,以及 3 倍的拜访速度提高,100 倍的创立、毁掉速度提高。

NSTaggedPointer类型的方针选用和isa相同的联合体位域的办法,可直接从地址中读取出想要的值,一般当数据类型的”value”满足小时,体系会主动转换为NSTaggedPointer类型,比方NSString转换为NSTaggedPointerString

方针的值直接存储在了指针中,不用在堆上为其分配内存,节约了许多内存开支。

更具体的 TaggedPointer 解析能够看iOS – 陈词滥调内存办理(五):Tagged Pointer

引证计数

首要摘自iOS办理方针内存的数据结构以及操作算法–SideTables、RefcountMap、weak_table_t-一

引证计数(Reference Count)是一个简单而有用的办理方针生命周期的办法。当咱们创立一个新方针的时分,它的引证计数为 1,当有一个新的指针指向这个方针时,咱们将其引证计数加 1,当某个指针不再指向这个方针时,咱们将其引证计数减 1,当方针的引证计数变为 0 时,阐明这个方针不再被任何指针指向了,这个时分咱们就能够将方针毁掉,收回内存。

SideTables

引证计数要么存放在 isaextra_rc 中,要么存放在引证计数表中,而引证计数表包含在一个叫 SideTable 的结构中,它是一个散列表,也便是哈希表。而 SideTable 又包含在一个大局的 StripeMap 的哈希映射表中,这个表的名字叫 SideTables

NSObject.mmSideTables对应的源码如下

// SideTables
static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
// SideTable
struct SideTable {
    spinlock_t slock;           // 自旋锁
    RefcountMap refcnts;        // 引证计数表
    weak_table_t weak_table;    // 弱引证表
    // other code ...
};

它们的联系如下图:

iOS内存办理(内存布局/nonpointer/sidetable/alloc/init/retain/release/weak/dealloc/主动开释池)

自旋锁spinlock_t

自旋锁适用于锁运用者坚持锁时刻比较短的状况。正是因为自旋锁运用者一般坚持锁时刻十分短,因而选择自旋而不是睡眠是十分必要的,自旋锁的功率远高于互斥锁。

spinlock_t slock用于对sidetable加锁,确保数据安全,运用自旋锁作为安全锁其实是因为引证计数的操作十分快且频繁。

引证计数器RefcountMap refcnts

具体的引证计数数量是记载在这儿的,refcntsC++Map,在SideTable中需求再次调用table.refcnts.find(0x0000)或许table.refcnts.find(0x000f)找到真实的引证计数器。

引证计数器的存储结构如下图所示

iOS内存办理(内存布局/nonpointer/sidetable/alloc/init/retain/release/weak/dealloc/主动开释池)

具体的,引证计数器RefcountMap refcnts经过find查找到的value其实是个位域,类似NONPOINTER_ISA指针:

  • 1UL<<0:WEAKLY_REFERENCED
    表明是否有弱引证指向这个方针,假如有的话(值为1)在方针开释的时分需求把一切指向它的弱引证都变成nil(相当于其他言语的NULL),防止野指针过错。
  • 1UL<<1:DEALLOCATING
    表明方针是否正在被开释。1正在开释,0没有。
  • REAL COUNT
    图中REAL COUNT的部分才是方针真实的引证计数存储区。所以咱们说的引证计数加一或许减一,实际上是对整个unsigned long加四或许减四,因为真实的计数是从2^2位开端的。
  • 1UL<<(WORD_BITS-1):SIDE_TABLE_RC_PINNED
    其间WORD_BITS在32位和64位体系的时分分别等于32和64。其实这一位没啥具体含义,便是随着方针的引证计数不断变大。假如这一位都变成1了,就表明引证计数现已最大了不能再增加了。
  • (1UL<<0)的意思是将一个”1″放到最右侧的盒子里,然后将这个”1″向左移动0位(便是原地不动):0b0000 0000 0000 0000 0000 0000 0000 0001
  • (1UL<<1)的意思是将一个”1″放到最右侧的盒子里,然后将这个”1″向左移动1位:0b0000 0000 0000 0000 0000 0000 0000 0010

弱引证列表weak_table_t weak_table

weak_table_t结构如图所示

iOS内存办理(内存布局/nonpointer/sidetable/alloc/init/retain/release/weak/dealloc/主动开释池)

  • weak_entry_t *weak_entries:是一个数组,上面的RefcountMap是要经过find(key)来找到准确的元素的。weak_entries则是经过循环遍历来找到对应的entry
    • referent:被指方针的地址。前面循环遍历查找的时分便是判别方针地址是否和他持平。
    • referrers:可变数组,里边保存着一切指向这个方针的弱引证的地址。当这个方针被开释的时分,referrers里的一切指针都会被设置成nil。
    • inline_referrers:只要4个元素的数组,默许状况下用它来存储弱引证的指针。当大于4个的时分运用referrers来存储指针。
  • num_entries:用来保护确保数组一直有一个适宜的size。比方数组中元素的数量超越3/4的时分将数组的巨细乘以2

Q:既然SideTables是一个哈希映射的表,为什么不用 SideTables 直接包含自旋锁,引证计数表和弱引证表呢?

这是因为在众多线程一起拜访这个 SideTable 表的时分,为了确保数据安全,需求给其加上自旋锁,假如只要一张 SideTable 的表,那么一切数据拜访都会出一个进一个,单线程进行,十分影响功率,虽然自旋锁现已是功率十分高的锁,这会带来十分不好的用户体验。针对这种状况,将一张 SideTable 分为多张表的 SideTables,再各自加锁确保数据的安全,这样就增加了并发量,提高了数据拜访的功率,这便是为什么一个 SideTables 下包括众多 SideTable 表的原因。

Q:为什么SideTables现现已过Hash映射了,还需求RefcountMap再映射一次

其实苹果选用的是分块化思维,内存中方针的数量实在是太巨大了咱们经过第一个Hash表仅仅过滤了第一次,然后咱们还需求再经过这个Map才能准确的定位到咱们要找的方针的引证计数器。

假定现在内存中有16个方针,0x0000、0x0001、...... 0x000e、0x000f,咱们创立一个SideTables[8]来存放这 16 个方针,那么查找的时分产生Hash抵触的概率便是八分之一。假定SideTables[0x0000]SideTables[0x0x000f]抵触,映射到相同的成果。

SideTables[0x0000] == SideTables[0x0x000f]  ==> 都指向同一个SideTable

苹果把两个方针的内存办理都放到同一个SideTable中。你在这个SideTable中需求再次调用table.refcnts.find(0x0000)或许table.refcnts.find(0x000f)来找到他们真实的引证计数器。

内存办理相关操作

alloc

从一个面试题开端探究alloc流程:
问:alloc 之后,引证计数如何改变?
答:在初始化isa的时分,并没有对extra_rc进行操作。也便是说alloc办法实际上并没有设置方针的引证计数值为 1。

验证如下:创立一个LGPerson类,在main办法中创立一个实例方针

#import <Foundation/Foundation.h>
#import "LGPerson.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        LGPerson *objc2 = [LGPerson alloc];
        NSLog(@"Hello, World!  %@",objc2);
    }
    return 0;
}

NSObject.mm类的+ (id)alloc办法开端探究,办法(函数)从上往下顺次履行,省掉了部分影响解读的代码

+ (id)alloc {
    return _objc_rootAlloc(self);
}
id _objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    // some code ...
    id obj = class_createInstance(cls, 0);
    return obj;
}
id class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}
// 内部调用 calloc 办法分配内存。
static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, 
                              bool cxxConstruct = true, 
                              size_t *outAllocatedSize = nil)
{
    // some code ...
    id obj;
    obj = (id)calloc(1, size);  // 此时分配内存
    obj->initInstanceIsa(cls, hasCxxDtor);
    return obj;
}

上面的代码首要目的是分配内存,真实的初始化在initInstanceIsa,内部会初始化isa指针的内容

inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    initIsa(cls, true, hasCxxDtor);
}
inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{ 
    if (!nonpointer) {
        isa.cls = cls;
    } else {
        isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        // 将cls右移3位赋值给shiftcls
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        isa = newisa;
    }
}

initIsa里边,将bits赋值了ISA_MAGIC_VALUEISA_MAGIC_VALUE为宏界说0x001f800000000001ULL,断点后能够看到nonpointer1magic59,如下图

iOS内存办理(内存布局/nonpointer/sidetable/alloc/init/retain/release/weak/dealloc/主动开释池)
履行shiftcls赋值办法后,cls值现已变成了LGPerson(自界说的类名),即现已赋值成功,如下图
iOS内存办理(内存布局/nonpointer/sidetable/alloc/init/retain/release/weak/dealloc/主动开释池)
此时能够注意到,extra_rc的值是0,表明alloc这一步实际上分配了内存,初始化了方针,但引证计数实际上是 0(调用init之后就变成 1 了)。

值得一提的是callAlloc函数中的slowpathfastpathcallAlloc完好实现如下:

static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
    if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
    if (fastpath(!cls->ISA()->hasCustomAWZ())) {
        if (fastpath(cls->canAllocFast())) {
            // No ctors, raw isa, etc. Go straight to the metal.
            bool dtor = cls->hasCxxDtor();
            id obj = (id)calloc(1, cls->bits.fastInstanceSize());
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            obj->initInstanceIsa(cls, dtor);
            return obj;
        }
        else {
            // Has ctor or raw isa or something. Use the slower path.
            id obj = class_createInstance(cls, 0);
            if (slowpath(!obj)) return callBadAllocHandler(cls);
            return obj;
        }
    }
#endif
    if (allocWithZone) return [cls allocWithZone:nil];
    return [cls alloc];
}

办法中运用到的 slowpathfastpath,其实这两个都是宏界说,与代码逻辑自身无关,界说如下:

// x 很可能为 true,期望编译器进行优化
#define fastpath(x) (__builtin_expect(bool(x), 1))
// x 很可能为 false,期望编译器进行优化
#define slowpath(x) (__builtin_expect(bool(x), 0))

其实它们是所谓的快途径和慢途径,为了解释这个,咱们来看一段代码:

if (x)
    return 1;
else 
    return 39;

因为计算机并非一次只读取一条指令,而是读取多条指令,所以在读到 if 语句时也会把 return 1 读取进来。假如 x 为 0,那么会从头读取 return 39,重读指令相对来说比较耗时。

假如 x 有十分大的概率是 0,那么 return 1 这条指令每次不可防止的会被读取,而且实际上几乎没有机会履行,形成了不用要的指令重读。

因而,在苹果界说的两个宏中,fastpath(x) 依然回来 x,仅仅告知编译器 x 的值一般不为 0,从而编译能够进行优化。同理,slowpath(x) 表明 x 的值很可能为 0,期望编译器进行优化。

// 以下代码表明,
// 很可能 checkNil && !cls 的成果是 false,
// 编译器能够不用每次都读取 return nil 指令
 if (slowpath(checkNil && !cls)) return nil;

当然,当checkNil && !cls判别建立的时分,return nil指令还是会被读取,然后履行的。
fastpath(expression)也是相同的机制,表明很可能 expression 成果是 true

init

// NSObject.mm
// Calls [[cls alloc] init].
id
objc_alloc_init(Class cls)
{
    return [callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/) init];
}
- (id)init {
    return _objc_rootInit(self);
}
id
_objc_rootInit(id obj)
{
    // In practice, it will be hard to rely on this function.
    // Many classes do not properly chain -init calls.
    return obj;
}

基类的init办法啥都没干,仅仅将alloc创立的方针回来。咱们能够重写init办法来对alloc创立的实例做一些初始化操作。

retainCount

retainCount办法是取出方针的引证计数值。怎样取值的呢?信任你们现已想到了,isaSidetable,下面咱们进入源码看看它的取值进程。

retainCount办法的函数调用栈如下

- (NSUInteger)retainCount {
    return _objc_rootRetainCount(self);
}
uintptr_t _objc_rootRetainCount(id obj)
{
    return obj->rootRetainCount();
}
inline uintptr_t 
objc_object::rootRetainCount()
{
    // 假如是 tagged pointer,直接回来 this
    if (isTaggedPointer()) return (uintptr_t)this; 
    sidetable_lock();
    // 获取 isa 
    isa_t bits = LoadExclusive(&isa.bits); 
    ClearExclusive(&isa.bits);
    // 假如 isa 是 nonpointer
    if (bits.nonpointer) { 
        // isa指针里的引证计数字段extra_rc,再+1,为引证计数的值
    // alloc没有让引证计数+1,而获取retainCount却是1原因就在这
        uintptr_t rc = 1 + bits.extra_rc; 
        // 假如还额定运用 sidetable 存储引证计数
        if (bits.has_sidetable_rc) { 
            rc += sidetable_getExtraRC_nolock(); // 加上 sidetable 中引证计数的值
        }
        sidetable_unlock();
        return rc;
    }
    sidetable_unlock();
    // 假如 isa 不是 nonpointer,回来 sidetable_retainCount() 的值
    return sidetable_retainCount(); 
}
size_t 
objc_object::sidetable_getExtraRC_nolock()
{
    ASSERT(isa.nonpointer);
    // 取得 SideTable
    SideTable& table = SideTables()[this]; 
    // 取得 refcnts
    RefcountMap::iterator it = table.refcnts.find(this);
    // 假如没找到,回来 0
    if (it == table.refcnts.end()) return 0;  
    // 假如找到了,经过 SIDE_TABLE_RC_SHIFT 位掩码获取对应的引证计数
    else return it->second >> SIDE_TABLE_RC_SHIFT; 
}
#define SIDE_TABLE_RC_SHIFT 2

小结

  • arm64之前,isa不是nonpointer。方针的引证计数全都存储在SideTable中,retainCount 办法回来的是方针自身的引证计数值 1,加上SideTable中存储的值;
  • arm64开端,isanonpointer。方针的引证计数先存储到它的isa中的extra_rc中,假如 19 位的extra_rc不行存储,那么溢出的部分再存储到SideTable中,retainCount 办法回来的是方针自身的引证计数值 1,加上isa中的extra_rc存储的值,加上SideTable中存储的值。
  • 所以,其实咱们经过retainCount办法打印alloc创立的方针的引证计数为 1,这是retainCount办法的劳绩,alloc办法并没有设置方针的引证计数。

alloc办法没有设置方针的引证计数为 1,它内部也没有调用retainCount办法,init时也仅仅回来alloc创立的方针,按照引证计数的界说,方针不会直接dealloc吗?

dealloc办法是在release办法内部调用的。只要你直接调用了dealloc,或许调用了release且在release办法中判别方针的引证计数为 0 的时分,才会调用dealloc。概况请参阅release源码剖析。

retain&release

  • retain
    • isa中的extra_rc +1,假如溢出,就将extra_rcRC_HALF搬运到sidetable中存储,extra_rc19位,而RC_HALF宏是(1ULL<<18),实际上持平于进行了 +1 操作
    • 关于NSTaggedPointer类型,直接回来,不参加引证计数计算,因为NSTaggedPointer方针的值直接存储在了指针中,不用在堆上为其分配内存,这点在objc_msgSend也有体现,首要会判别LNilOrTagged
  • release
    • isa中的extra_rc -1,假如下溢,则判别has_sidetable_rc是否为true,便是否运用了sidetable,假如有的话就从sidetable中搬运RC_HALF个引证计数给extra_rc,若不行RC_HALF个,就有多少搬运多少,假如extra_rc中引证计数为 0 且has_sidetable_rcfalse或许Sidetable中的引证计数也为 0 了,那就dealloc方针。

概况参阅 [iOS – 陈词滥调内存办理 – retain&release]

weak

假如用__weak 修饰一个变量,底层履行了什么呢?以下面代码为例

id obj = [[NSObject alloc] init];
id __weak obj1 = obj;

底层的操作其实是

objc_initWeak(&obj1, obj);
// NSObject.mm 
① objc_initWeak 
② storeWeak 
// objc-weak.mm 
③ weak_unregister_no_lock
④ weak_register_no_lock 

接下来查看源码

objc_initWeak

// *location 为 __weak 指针地址(即obj1),newObj(即obj)为被弱引证方针地址
id objc_initWeak(id *location, id newObj) 
{
    // 假如方针为 nil,那就将 weak 指针置为 nil
    if (!newObj) {
        *location = nil;
        return nil;
    }
    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

storeWeak

// 更新 weak 变量
// 假如 HaveOld == true,表明方针有旧值,即旧地址,它需求被清理掉,这个旧值可能为 nil
// 假如 HaveNew == true,表明一个新值需求赋值给变量,这个新值可能为 nil
// 假如 CrashIfDeallocating == true,则假如方针正在毁掉或许方针不支持弱引证,则中止更新
// 假如 CrashIfDeallocating == false,则存储 nil
enum CrashIfDeallocating {
    DontCrashIfDeallocating = false, DoCrashIfDeallocating = true
};
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;
    // Acquire locks for old and new values.
    // Order by lock address to prevent lock ordering problems. 
    // Retry if the old value changes underneath us.
 retry: // 分别获取新旧值相相关的弱引证表
    // 假如变量有旧值,获取已有方针(该旧值方针)和旧表
    if (haveOld) {
        oldObj = *location;
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    // 假如有新值要赋值给变量,则创立新表
    if (haveNew) {
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    // 分别给 oldTable 和 newTable 加锁
    SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
    // 判别 oldObj 和 location 是否是同一方针,假如不是就从头获取旧值相相关的表
    if (haveOld  &&  *location != oldObj) {
        // 解锁
        SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
        goto retry;
    }
    // Prevent a deadlock between the weak reference machinery
    // and the +initialize machinery by ensuring that no 
    // weakly-referenced object has an un-+initialized isa.
    // 假如有新值,则判别新值所属的类是否现已初始化,没初始化的话在此初始化
    // 这一步是防止 +initialize 内部调用 storeWeak 产生死锁
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
            _class_initialize(_class_getNonMetaClass(cls, (id)newObj));
            // If this class is finished with +initialize then we're good.
            // If this class is still running +initialize on this thread 
            // (i.e. +initialize called storeWeak on an instance of itself)
            // then we may proceed but it will appear initializing and 
            // not yet initialized to the check above.
            // Instead set previouslyInitializedClass to recognize it on retry.
            previouslyInitializedClass = cls;
            goto retry;
        }
    }
    // Clean up old value, if any.
    // 假如有旧值,则调用weak_unregister_no_lock履行清空操作
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
    // Assign new value, if any.
    // 假如有新值,则调用weak_register_no_lock把一切 weak 指针从头指向新的方针
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected
        // Set is-weakly-referenced bit in refcount table.
        // 设置 weakly-referenced 标志位
        // 假如方针是 Tagged Pointer,则不做操作
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }
        // Do not set *location anywhere else. That would introduce a race.
        // 将 location 指向新的方针
        *location = (id)newObj;
    }
    else {
        // No new value. The storage is not changed.
    }
    // 解锁
    SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
    return (id)newObj;
}

store_weak函数的履行进程如下:

  1. 分别获取新旧值相相关的弱引证表
  2. 假如有旧值,调用weak_unregister_no_lock函数铲除旧值,移除一切指向旧值的weak引证,而不是赋值为nil
  3. 假如有新值,调用weak_register_no_lock函数分配新值,将一切weak指针从头指向新的方针
  4. 设置isaweakly_referenced弱引证标志位

weak_unregister_no_lock

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 结构
     * - referent 被弱引证的方针
     * - referrers 当有超越4个弱引证方针时,则存储到 referrers 中
     * - inline_referrers 存储小于4个的弱引证方针
     */
    weak_entry_t *entry;
    if (!referent) return;
    // 调用 weak_entry_for_referent 找到 entry 弱引证指针 item
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        // 从内层 inline_referrers 中移除 entry
        // inline_referrers 中只能存储 4 个弱引证指针,
        // 多了就要存储到 referrers 中,所以要多一步 empty 判空操作
        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;
                }
            }
        }
        //从 weak_table 中移除 entry 弱引证条目
        if (empty) {
            weak_entry_remove(weak_table, entry);
        }
    }
    // Do not set *referrer = nil. objc_storeWeak() requires that the 
    // value not change.
}

weak_unregister_no_lock用来移除现已存在的弱引证表,一般用于弱引证方针现已不再引证,但被弱引证方针还没有死亡的状况,内部履行步骤为:

  1. 查询weak_table,假如有弱引证信息,则得到entry
  2. remove_referrer(entry, referrer)移除相相关的弱引证信息。

weak_register_no_lock

weak_register_no_lock用来保存弱引证方针

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;
    //假如该弱引证方针是taggedPointer方针,则不做处理直接回来该方针
    //taggedPointer方针是为了苹果为了性能最大化做的处理,
    //针对不需求到堆中寻觅的方针,能够直接从地址中经过必定的算法得到他们的值。
    if (!referent  ||  referent->isTaggedPointer()) return referent_id;
    // ensure that the referenced object is viable
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
        deallocating = referent->rootIsDeallocating();
    }
    else {
        BOOL (*allowsWeakReference)(objc_object *, SEL) = 
            (BOOL(*)(objc_object *, SEL))
            object_getMethodImplementation((id)referent, 
                                           SEL_allowsWeakReference);
        if ((IMP)allowsWeakReference == _objc_msgForward) {
            return nil;
        }
        deallocating =
            ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
    }
    if (deallocating) {
        if (crashIfDeallocating) {
            _objc_fatal("Cannot form weak reference to instance (%p) of "
                        "class %s. It is possible that this object was "
                        "over-released, or is in the process of deallocation.",
                        (void*)referent, object_getClassName((id)referent));
        } else {
            return nil;
        }
    }
    // now remember it and where it is being stored
    weak_entry_t *entry;//弱引证指针的条目
    //判别weak_table中是否有该条目
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
        //假如有,则把弱引证方针追加进去
        append_referrer(entry, referrer);
    } 
    else {
        //假如没有,则创立一个
        weak_entry_t new_entry(referent, referrer);
        //假如索引现已超越本来的3/4,则给weak_table扩容
        weak_grow_maybe(weak_table);
        //将新的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_register_no_lock保存弱引证方针具体流程如下:

  • 判别TaggedPointer类型则直接回来,不需求保存弱引证信息(TaggedPointer不需求在堆中分配内存)
  • 假如正在开释且方针不支持弱引证,则中止更新(crashIfDeallocating==true && deallocating == true)
  • 判别weak_table中是否有该方针的弱引证表,
    • 有就追加上append_referrer
    • 没有则创立个weak_table在加入

小结

weak方针在底层的存储流程如下图所示

iOS内存办理(内存布局/nonpointer/sidetable/alloc/init/retain/release/weak/dealloc/主动开释池)

dealloc

dealloc办法的函数调用栈为:

// NSObject.mm
① dealloc
② _objc_rootDealloc
// objc-object.h
③ rootDealloc
// objc-runtime-new.mm
④ object_dispose
⑤ objc_destructInstance
// objc-object.h
⑥ clearDeallocating
// NSObject.mm
⑦ sidetable_clearDeallocating
   clearDeallocating_slow

objc_rootDealloc&rootDealloc

- (void)dealloc {
  _objc_rootDealloc(self);
}
void _objc_rootDealloc(id obj)
{
    assert(obj);
    obj->rootDealloc();
}
inline void objc_object::rootDealloc()
{
    // 判别是否为 TaggerPointer 内存办理计划,是的话直接 return
    if (isTaggedPointer()) return;  // fixme necessary? * 
    if (fastpath(isa.nonpointer  &&          // 假如 isa 为 nonpointer
                 !isa.weakly_referenced  &&  // 没有弱引证
                 !isa.has_assoc  &&          // 没有相关方针
                 !isa.has_cxx_dtor  &&       // 没有 C++ 的析构函数
                 !isa.has_sidetable_rc))     // 没有额定选用 SideTabel 进行引证计数存储
    {
        assert(!sidetable_present());
        free(this);               // 假如以上条件建立,直接调用 free 函数毁掉方针
    } 
    else {
        object_dispose((id)this); // 假如以上条件不建立,调用 object_dispose 函数
    }
}

fastpath(x)表明内部的x值很可能为true,期望编译器进行优化,假如符合5个条件,则直接free

object_dispose&objc_destructInstance

id
object_dispose(id obj)
{
    if (!obj) return nil;
    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.
        // 假如有 C++ 的析构函数,调用 object_cxxDestruct 函数
        if (cxx) object_cxxDestruct(obj);
        // 假如有相关方针,调用 _object_remove_assocations 函数,移除相关方针
        if (assoc) _object_remove_assocations(obj);
        // 调用 clearDeallocating 函数
        obj->clearDeallocating();
    }
    return obj;
}

clearDeallocating

inline void
objc_object::clearDeallocating()
{
    // 假如 isa 不是 nonpointer
    if (slowpath(!isa.nonpointer)) {     
        // Slow path for raw pointer isa.
        // 调用 sidetable_clearDeallocating 函数
        sidetable_clearDeallocating();   
    }
    // 假如 isa 是 nonpointer,且有弱引证或许有额定运用 SideTable 存储引证计数
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) { 
        // Slow path for non-pointer isa with weak refs and/or side table data.
        // 调用 clearDeallocating_slow 函数
        clearDeallocating_slow();        
    }
    assert(!sidetable_present());
}

slowpath(x) 表明 x 的值很可能为false,期望编译器进行优化。先不考虑isa不是nonpointer的状况,咱们持续看clearDeallocating_slow铲除弱引证及引证计数的操作。

NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));
    // 获取 SideTable
    SideTable& table = SideTables()[this]; 
    table.lock();
    // 假如有弱引证
    if (isa.weakly_referenced) { 
        // 调用 weak_clear_no_lock:将指向该方针的弱引证指针置为 nil
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    // 假如有运用 SideTable 存储引证计数
    if (isa.has_sidetable_rc) {  
        // 调用 table.refcnts.erase:从引证计数表中擦除该方针的引证计数
        table.refcnts.erase(this);
    }
    table.unlock();
}

weak_clear_no_lock

铲除弱引证的核心办法,在方针dealloc的时分,会调用weak_clear_no_lock函数将指向该方针的弱引证指针置为nil,具体实现如下

void weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
    //referent为被毁掉方针的指针
    objc_object *referent = (objc_object *)referent_id;
    //经过被毁掉方针的指针取得entry,这个entry里存着这个方针的弱引证指针数组
    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        //printf("XXX no entry for clear deallocating %pn", referent);
        return;
    }
    //弱引证指针数组
    weak_referrer_t *referrers;
    size_t count;
    //判别弱引证指针数量,小于4个存在entry的inline_referrers中,大于4个存在entry的referrers中
    if (entry->out_of_line()) {
        //假如大于4个,则到referrers中取弱引证指针的数组
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    //循环把一切弱引证指针置为nil
    for (size_t i = 0; i < count; ++i) {
        objc_object **referrer = referrers[i];
        if (referrer) {
            if (*referrer == referent) {
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    // 一些 free和num_entries--操作
    weak_entry_remove(weak_table, entry);
}

小结

dealloc的调用流程如下:

  1. dealloc时首要履行dealloc->_objc_rootDealloc(),直接来到rootDealloc()函数
  2. rootDealloc()中判别5个条件决议是否能够直接开释free()
    • NONPointer_ISA // 是否是非指针类型 isa
    • weakly_reference // 是否有若引证
    • has_assoc // 是否有相关方针
    • has_cxx_dtor // 是否有 c++ 相关内容
    • has_sidetable_rc // 是否运用到 sidetable
  3. 不符合5个条件则调用objc_dispose(),进而履行objc_destructInstance()函数逐渐开释
    • 先判别hasCxxDtor,毁掉 c++ 相关内容
    • 再判别hasAssociatedObjects,毁掉相关方针
    • 履行clearDeallocating(),用以毁掉弱引证和引证计数
  4. clearDeallocating()毁掉弱引证和引证计数
    • 履行waek_clear_no_lock毁掉弱引证
    • 获取SideTable履行table.refcnts.eraser()擦除该方针的引证计数

主动开释池AutoReleasePool

这儿首要作总结笔记,更具体的内容参阅iOS – 聊聊 autorelease 和 @autoreleasepool

AutoreleasePool原理

iOS内存办理(内存布局/nonpointer/sidetable/alloc/init/retain/release/weak/dealloc/主动开释池)

  • 主动开释池(即一切的AutoreleasePoolPage方针)是以为结点经过双向链表的形式组合而成,每当Page满了的时分,就会创立一个新的Page,并设置它为hotPage,而首个PagecoldPage
  • 有个属性叫POOL_BOUNDARY,称为岗兵方针,用来解决主动开释池嵌套的问题
    • 每当创立一个主动开释池,就会调用push()办法将一个POOL_BOUNDARY入栈
    • 当毁掉一个主动开释池时,会调用pop()办法并传入一个POOL_BOUNDARY,会从主动开释池中最终一个方针开端,顺次给它们发送release音讯,直到遇到这个POOL_BOUNDARY
  • 主动开释池与线程一一对应,每个线程都会保护一个主动开释池仓库结构,新的pool在创立时会被压栈到栈顶,pool毁掉时,会被出栈,关于当前线程来说,开释方针会被压栈到栈顶,线程中止时,会主动开释与之相关的主动开释池
  • 每个AutoreleasePoolPage方针占用4096字节内存,其间56个字节用来存放它内部的成员变量,剩余的空间(4040个字节)用来存放autorelease方针的地址。

嵌套@autoreleasepool

嵌套的@autoreleasepool其实便是不断的push岗兵方针(POOL_BOUNDARY),在pop时,会先开释里边的,在开释外面的。

举例如下

int main(int argc, const char * argv[]) {
    _objc_autoreleasePoolPrint();             // print1
    @autoreleasepool { //r1 = push()
        _objc_autoreleasePoolPrint();         // print2
        HTPerson *p1 = [[[HTPerson alloc] init] autorelease];
        HTPerson *p2 = [[[HTPerson alloc] init] autorelease];
        _objc_autoreleasePoolPrint();         // print3
        @autoreleasepool { //r2 = push()
            HTPerson *p3 = [[[HTPerson alloc] init] autorelease];
            _objc_autoreleasePoolPrint();     // print4
            @autoreleasepool { //r3 = push()
                HTPerson *p4 = [[[HTPerson alloc] init] autorelease];
                _objc_autoreleasePoolPrint(); // print5
            } //pop(r3)
            _objc_autoreleasePoolPrint();     // print6
        } //pop(r2)
        _objc_autoreleasePoolPrint();         // print7
    } //pop(r1)
    _objc_autoreleasePoolPrint();             // print8
    return 0;
}

autoreleasePool结构如图所示

iOS内存办理(内存布局/nonpointer/sidetable/alloc/init/retain/release/weak/dealloc/主动开释池)

Q:Runloop和@autoreleasePool联系

iOSautorelease方针的开释机遇是由RunLoop控制的,会在RunLoop每次循环完毕时开释。

阅读Runloop源码其实没发现和autoreleasePool有什么联系,但在 App 发动后,苹果在主线程RunLoop里注册了两个Observer,回调都是_wrapRunLoopWithAutoreleasePoolHandler()

  • 第一个Observer监督的事情
    • Entry(行将进入 Loop),其回调内会调用 _objc_autoreleasePoolPush()创立主动开释池。其order-2147483647,优先级最高,确保创立开释池产生在其他一切回调之前。
  • 第二个Observer监督了两个事情
    • BeforeWaiting(准备进入休眠)时调用 _objc_autoreleasePoolPop()_objc_autoreleasePoolPush() 开释旧的池并创立新池;
    • Exit(即 将退出 Loop)时调用 _objc_autoreleasePoolPop()来开释主动开释池。
    • 这个Observerorder2147483647,优先级最低,确保其开释池子产生在其他一切回调之后。

举例验证

__weak id reference = nil;
- (void)viewDidLoad {
    [super viewDidLoad];    
    // str是一个autorelease方针,设置一个weak的引证来调查它
    NSString *str = [NSString stringWithFormat:@"sunnyxx"];    
    reference = str;
}
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];    
    NSLog(@"%@", reference); // Console: sunnyxx
}
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];    
    NSLog(@"%@", reference); // Console: (null)
}

viewDidLoadviewWillAppear是在同一个runloop循环下调用的,因而在viewWillAppear中,这个autorelease的变量依然有值。

当然,咱们也能够手动干预autorelease方针的开释机遇:

- (void)viewDidLoad
{
    [super viewDidLoad];
    @autoreleasepool {        
        NSString *str = [NSString stringWithFormat:@"sunnyxx"];
    }    
    NSLog(@"%@", str); // Console: (null)

这样出了@autoreleasepool效果域,内部的autorelease方针就被开释了。

iOS内存办理(内存布局/nonpointer/sidetable/alloc/init/retain/release/weak/dealloc/主动开释池)

Q:main函数的@autoreleasepool和主线程runloop的@autoreleasepool联系?

Xcode新旧版别main函数里边的@autoreleasepool办理的效果域?
Xcode 新版别

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

Xcode 旧版别

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

假如你的程序运用了AppKitUIKit框架,那么主线程的RunLoop就会在每次事情循环迭代中创立并处理@autoreleasepool。也便是说,应用程序一切autorelease方针的都是由RunLoop创立的@autoreleasepool来办理。而main()函数中的@autoreleasepool仅仅负责办理它的效果域中的autorelease方针。

旧版别 Xcode 中 main 函数的@autoreleasepool
Xcode 旧版别的main函数中是将整个应用程序运转(UIApplicationMain)放在@autoreleasepool内,而主线程的RunLoop便是在UIApplicationMain中创立,所以RunLoop创立的@autoreleasepool是嵌套在main函数的@autoreleasepool内的。RunLoop会在每次事情循环中对主动开释池进行poppush,可是它的pop只会开释掉它的POOL_BOUNDARY之后的方针,它并不会影响到外层main函数中@autoreleasepool

新版别 Xcode11 今后的 main 函数产生了哪些改变?

  • 旧版别是将整个应用程序运转放在@autoreleasepool内,因为RunLoop的存在,要return即程序完毕后@autoreleasepool效果域才会完毕,这意味着程序完毕后main函数中的@autoreleasepool中的autorelease方针才会开释。
  • 而在 Xcode 11 中,触发主线程RunLoopUIApplicationMain函数放在了@autoreleasepool外面,这能够确保@autoreleasepool中的autorelease方针在程序发动后立即开释。正如新版别的@autoreleasepool中的注释所写 “Setup code that might create autoreleased objects goes here.”(如上代码),能够将autorelease方针放在此处。

Q:什么时分需求手动增加@autoreleasepool?

AppKit 和 UIKit 框架会在RunLoop每次事情循环迭代中创立并处理@autoreleasepool,因而,你通常不用自己创立@autoreleasepool,甚至不需求知道创立@autoreleasepool的代码怎样写。可是,有些状况需求自己创立@autoreleasepool

例如,假如咱们需求在循环中创立了许多暂时的autorelease方针,则手动增加@autoreleasepool来办理这些方针能够很大程度地削减内存峰值。比方在for循环中alloc图片数据等内存消耗较大的场景,需求手动增加@autoreleasepool

苹果给出了三种需求手动增加@autoreleasepool的状况:

  • ① 假如你编写的程序不是根据 UI 框架的,比方说命令行东西;
  • ② 假如你编写的循环中创立了大量的暂时方针;
    你能够在循环内运用@autoreleasepool鄙人一次迭代之前处理这些方针。在循环中运用@autoreleasepool有助于削减应用程序的最大内存占用。
  • ③ 假如你创立了辅佐线程。
    一旦线程开端履行,就必须创立自己的@autoreleasepool;否则,你的应用程序将存在内存泄漏。

参阅博客

iOS底层-内存分区与布局
iOS概念攻坚之路(三):内存办理
iOS办理方针内存的数据结构以及操作算法–SideTables、RefcountMap、weak_table_t-一
iOS – 陈词滥调内存办理(四):内存办理办法源码剖析
iOS – 陈词滥调内存办理(三):ARC 面世
iOS – 聊聊 autorelease 和 @autoreleasepool