iOS-底层原理33-内存管理(上)

《iOS底层原理文章汇总》
上一篇文章iOS-底层原理32-启动优化二进制重排介绍了启动优化二进制重排,本文介绍内存管理

1.内存布局

通过汇编去找指针,汇编通过sp的寄存器去定位,栈中内存通过汇编寄存器去定位的,每一个内存都包含地址的内存空间,能直接定位到。0-0x00400000作为预留给系统处理,null以及底层发送的指令


image.png

image.png

image.png

2.内存管理

通过nonpointer_isa(非指针型isa)来优化整个64位地址,为什么self.nameStr为cooci的时候程序不会发生多条线程对同一个对象retain release操作?而self.nameStr为cooci_和谐学习不急不躁时程序在多线程会发生retain release同时对一个对象操作而崩溃?


image.png
  • 查看self.nameStr=@"cooci"时,self.nameStr的isa指向NSTaggedPointerString


    image.png
  • 查看self.nameStr=@"cooci_和谐学习不急不躁"时,self.nameStr的isa指向NSCFString


    image.png

    self.nameStr在进行赋值操作执行setter或getter方法时,会对新值newValue进行retain,对旧值oldValue进行release,查看源码,obj的isa为 isTaggedPointer时,setter和getter方法执行时不会进行新值的retain和旧值的release,故不会发生过度释放而崩溃。
    taggedPointer类型在修饰小对象时有了比较特殊的处理,指针是什么?String有值+指针地址,通过指针去操作引用计数等等其他处理taggerPointer类型修饰的String和对象String是否是相同的处理呢?


    image.png

    image.png

3.taggedPointer

什么是小对象?NSNumber,NSDate,小于11位的小String,一个对象8字节,64位,NSNumber里面存入1,用不了64位,会浪费空间,为了节省空间,可以用小对象去接收

I.在方法查找流程中,第一次接触到taggedpointer指针是在readImages->initializeTaggedPointerObfuscator()

image.png

image.png

通过对指针地址编码进行异或运算混淆,两次异或就能解码计算出原始没有混淆前的地址
要向获取原始的真正属于taggedpointerstring的指针地址呢,对现在的地址进行一次异或运算,得到小对象地址0xa000000000000621,倒数两位62表示98,表示b,@1是NSNumber类型,倒数第二位为1,说明地址里面存储了值,不仅是简单的地址还包含了值,double和float类型底层经过特殊处理


image.png

image.png

image.png

II.小对象地址里面的0xa和0xb是哪里来的?

在判断是否是小对象的方法中指针地址与_OBJC_TAG_MASK进行与运算,相当于保留最高位,_OBJC_TAG_MASK的值为#define _OBJC_TAG_MASK (1UL<<63)


image.png

最高位是否为1代表是否是taggedpointer,2代表NSString,3代表NSNumber,小对象放在常量区,不需要ARC自动管理


image.png

image.png

TP表示是taggedpointer类型
image.png

4.retain

I.判断是否为nonpointer
II.操作引用计数
a.如果不是nonpointer -> 散列表
spinlock_t slock; 开解锁
RefcountMap refcnts; 引用计数表
weak_table_t weak_table; 弱引用表
散列表 在内存里面有多张 + 最多能够多少张 一张 对象开锁解锁会不安全且效率低
b.是否正在释放和析构
c.extra_rc+1 满了-散列表
d.carry 满了标记,extra_rc 满/2 -> extra_rc 满/2 -> 散列表(开锁关锁)


image.png

image.png

image.png

5.散列表

为什么有isa?retain会操作引用计数+1,retainCount在isa的bits中,会在操作引用计数时用到

A.objc_retain->obj->retain()->rootRetain(),判断是否为nonpointerisa,若不是nonpointerisa,无法进行存储。
B.操作引用计数:若不是nonpointerisa,直接操作散列表SideTables不止一张,和关联对象表原理一样
image.png

为什么散列表在内存中有多张,若放在一张表中操作引用计数,会暴露所有对象不安全,锁解锁消耗性能,真机下面是8张表,散列表是哈希表,哈希表结合链表和数组,增删改查都方便,拉链法,通过哈希函数定位相应下标。不用链表和数组,链表不便于查,数组不便于增删。


image.png

image.png

6.dealloc

对象要释放,需要做哪些事情?isa中有个cxx析构代码,关联对象表清空,弱引用表清空,散列表中引用计数表清空,最后free


image.png

7.release:retain的反向操作,将extra_rc中的引用计数减为0后,判断是否有carry,有继续减散列表中的引用计数,extra_rc和散列表中的引用计数都减为0后,开始析构dealloc,自动触发释放函数

alloc -> retain -> release -> dealloc构成一个闭环


image.png

image.png

image.png

8.retainCount

面试题:[NSObject alloc]引用计数,输出为1


image.png

image.png

alloc创建对象引用计数为0,默认+1,为1

9.强持有和循环引用的区别

self.timer添加到runloop中,控制器pop没有进入dealloc方法进行释放?


image.png

I.解决办法:在- (void)didMoveToParentViewController:(UIViewController *)parent方法中对timer进行销毁,之后进入dealloc方法对self进行销毁

image.png

II.强持有和循环引用的区别

以上怎么造成循环引用,self无法释放,无法进入dealloc方法中的?
self -> timer -> self,self很明显持有timer,timer是怎么持有self的呢?查看官方文档,The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated. timer对self进行了强持有

image.png

若用__weak typeof(self) weakSelf = selfweakSelf代替self能解决循环引用嘛,结果是不能。[NSRunLoop currentRunLoop] 强持有 -> timer -> weakSelf -> self,runloop对timer进行了强持有,runloop的生命周期长,runloop对timer没有进行释放,weakSelf和self都释放不掉,和self->block->weakSelf-self的模型完全不一样
weakSelf : 没有对内存加1
image.png

两个内存地址self和weakSelf指向了同一片内存空间,timer根据官方文档捕获的是内存地址<LGTimerViewController: 0x7f91a9006110>,Block捕获的是weakSelf, 和self没有关系,self -> block -> weakSelf (临时变量的指针地址) 地址->内存
block捕获的是地址指针,timer捕获的是对象本身,timer中无法通过weakSelf来打破, NSRunLoop -> timer -> weakSelf (<LGTimerViewController: 0x7fb8c9d11240>),RunLoop包含<LGTimerViewController: 0x7fb8c9d11240>,RunLoop不停,LGTimerViewController无法释放
这就是强持有和循环引用的区别
image.png

image.png

III.解决强持有的方法

解决思路:我们需要打破这一层强持有 - self
A.通过在- (void)didMoveToParentViewController:(UIViewController *)parent方法中对timer进行销毁,之后进入dealloc方法对self进行销毁
B.中介者模式:引入中介者LGTimerWapper,添加timer,给VC中的selector发送消息,当vc pop时释放,判断vc是否为nil,是将timer释放,从而打破runloop强持有LGTimerWapper,皆释放,若timer中的方法过多,要不断添加到LGTimerWapper中

image.png

image.png

C.Proxy:虚基类的方式,强持有引用转移到消息转发,虚基类无法做事进行转发,不断的转发到vc,信息的传递,在vc的dealloc方法中对vc进行释放,proxy进而释放,timer释放,runloop也释放了proxy,皆释放
image.png

image.png

image.png

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

推荐阅读更多精彩内容