内存管理、自动释放池与循环引用

引用自: 内存管理、自动释放池与循环引用

一、内存布局

1. 堆栈解释

  • 栈(stack): 方法调用, 局部变量等, 是连续的, 高地址(0xc0000000)往低地址(0xc8048000)扩展
  • 堆(heap): 通过alloc等分配的对象, 是离散的, 低地址往高地址扩展, 需要手动控制
  • 未初始化数据(bss): 未初始化的全局变量等
  • 已初始化数据(data): 已初始化的全局变量等
  • 代码段(text): 程序代码

2. 64bit和32bit下long 和 char所占字节是不同的

char: 1字节(ASCII 2⁸ = 256个字符)
char*(即指针变量): 4个字节(32位的寻址控件是2³², 即32个bit,也就是4个字节. 同理64位编译器为8个字节)
short int: 2个字节范围 -2¹⁶~>2¹⁶即 -32768~>32767
int: 4个字节 范围 -2147483648 ~> 2147483647
unsigned int: :4个字节
long: 4个字节 范围和int 一样64位下8个字节, 范围-9223372036854775808 ~ 9223372036854775807
long long: 8个字节 范围 -9223372036854775808 ~ 9223372036854775807
unsigned long long: 8个字节 最大值: 184467440737095516
float: 4个字节
double: 8个字节

二、内存管理方案

  • taggedPointer: 储存小对象如NSNumber.
  • NONPOINTER_ISA(非指针型的isa): 在64位结构下, isa指针是占64比特位的, 实际上只有30多位就已经够用了, 为了提高利用率, 剩余的比特位储存了内存管理的相关数据内容.
  • 散列表: 复杂的数据结构, 包括了引用计数表和弱引用表
    通过SideTables()结构来实现的, SideTables()结构下, 有很多SideTable的数据结构.
    而sideTable当中包含了自旋锁, 引用计数表, 弱引用表.
    SideTables()实际上市一个哈希表, 通过对象的地址来计算该对象的引用计数在哪个sideTable中

自旋锁:

  • 自旋锁是"忙等"的锁
  • 适用于轻量访问

引用计数表和弱引用表实际是一个哈希表,来提高查找效率

三、MRC(手动引用计数) 和ARC(自动引用计数)

1. MRC: alloc, retain, release, retainCount, autorelease, dealloc
2. ARC:

  • ARC是LLVM和Runtime协作的结果
  • ARC进制手动调用retain, release, retainCount, autorelease关键字
  • ARC新增weak, strong关键字

3. 引用计数管理:

  • alloc: 经过一系列函数调用, 最终调用了calloc函数, 这里并没有设置引用计数为1
  • retain: 经过两次哈希查找, 找到其对应引用计数值, 然后将引用计数加1(实际是加偏移量)
  • release: 和retain相反, 经过两次哈希查找, 找到其对应引用计数值, 然后将引用计数减1
  • dealloc:

4.弱引用管理

  • 添加weak变量:通过哈希算法位置查找添加. 如果查找对应位置中已经有了当前对象所对应的弱引用数组, 就把新的弱引用变量添加到数组当中; 如果没有, 就创建一个弱引用数组, 并将该弱引用变量添加到该数组中.
  • 当一个被weak修饰的对象被释放后, weak对象怎么处理?
    清楚weak变量,同时设置指向为nil. 当对象被dealloc释放后, 在dealloc的内部实现中, 会电泳弱引用清除的相关函数, 会根据当前对象指针查找弱引用表, 找到当前对象所对应的弱引用数组,将数组中的所有弱引用的指针都设置为nil.

5.自动释放池
在档次runloop将要结束的时候调用objc_autoreleasePoolPop, 并push进来一个新的AutoreleasePool

AutoreleasePoolPage是以栈为节点通过双向链表的形式组合而成, 是和线程一一对应的. 内部属性有parent,child对应前后两个节点,thread对应线程, next指针指向栈中下一个可填充的位置.

  • AutoreleasePool实现原理?
    编译器会将@autoreleasepool{}改写为:
void * ctx = objc_autoreleasePoolRush;
    {}
objc_autoreleasePoolPop(ctx);
  • objc_autoreleasePoolPop:
    根据传入的哨兵对象找到对应位置.
    给上次push操作之后添加的对象一次发送release消息.
    回退next指针到正确位的位置.

四、循环引用

循环引用的实质: 多个对象相互之间有强引用,不能释放让系统回收.
如何解决循环引用?

  1. 避免产生循环引用, 通常是将strong引用改为weak引用.
    比如在修饰属性时用weak
    在block内调用对象方法时, 使用其弱引用, 这里可以使用两个宏
#define WS(weakSelf)                        __weak __typeof(&*self)weakSelf = self;
#define ST(strongSelf)                      __strong __typeof(&*self)strongSelf = weakSelf;

还可以使用__block来修饰变量
在MRC下,__block不会增加引用计数, 避免了循环引用
在ARC下,__block修饰对象会被强引用,无法避免循环引用, 需要手动解除

2.在适合时机去手动断开循环引用.
通常我们使用第一种.
循环引用场景:

  • 自循环引用
    对象强持有的属性同时持有该对象
  • 相互循环引用
  • 多循环引用
    1. 代理(delegate)循环引用属于相互循环引用
    delegate是iOS开发中比较常遇到的循环引用, 一般在声明delegate的时候都要使用弱引用weak,或者assign,当然怎么选择使用assign还是weak, MRC的话只能用assign, 在ARC的情况下最好使用weak, 因为weak修饰的变量在释放后自动执行nil,防止野指针存在
    2. NSTimer循环引用属于相互循环使用
    在控制器内, 创建NSTimer作为其属性, 由于定时器创建后也会强引用改控制器对象, 那么该对象和定时器就相互循环引用了.
    3. block循环引用
    一个简单的例子:
@property(copy, nonatomic) dispatch_block_t myBlock;
@property(copy, nonatomic) NSString *blockString;

- (void)testBlock
{
    self.myBloc = ^()
    {
        NSLog(@"%@",self.blockString);
    };
}

由于block会对block中的对象进行持有操作,就相当于持有了其中的对象,而如果此时block中的对象又持有了该block,则会造成循环引用.
解决方案就是使用__weak修饰self即可

__weak typeof(self) weakSelf = self;

self.myBlock = ^()
{
        NSLog(@"%@",weakSelf.blockString);
}
  • 并不是所有block都会造成循环引用.
    只有被强引用了的block才会产生循环引用
    而比如下边这些系统方法等
dispatch_async(dispatch_get_main_queue(),^{
      [UIView animateWithDuration:1 animations:^{
      
      }]
}),

或者block并不是其属性而是临时变量,即栈block

[self testWithBlock:^{
        NSLog(@"%@",self);
}];
- (void)testWithBlock:(dispatch_block_t)block
{
    block();
}

还有一种场景, 在block执行开始时self对象还未被脂肪, 而执行过程中,self被释放了, 由于是用weak修饰的,那么weakSelf也被释放了,此时在block里访问weakSelf时,就可能会发生错误(向nil对象发消息并不会崩溃,但也没任何效果).
对于这种场景,应该在block中对 对象使用__strong修饰,使得在block期间对 对象持有, block执行结束后, 解除其持有.

__weak typeof(self) weakSelf = self;
self.myBlock = ^()
{
        __strong __typeof(self) strongSelf = weakSelf;
        [strongSelf test];
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342