内存5⼤区
堆:存放通过 alloc , new , malloc 等创建出来的变量。 ⼀般以0x6开头
栈区:存放局部变量,⽅法参数, 对象的指针 等。 栈的内存⼤⼩有限,主线程1MB,其他线程
512KB ,栈是线程独有的,栈在线程开始的时候初始化,每个线程的栈互相独⽴ ⼀般以0x7开头
全局区/静态区:存放全局变量和静态变量。 ⼀般以0x1开头 ,编译时分配的内存区域在程序运
⾏时⼀直存在,直到程序运⾏结束才会被释放
常量区:常量。(const修饰) ⼀般以0x1开头 ,常量区的内存和全局区⼀样也是在编译阶段进⾏
分配,程序运⾏时会⼀直存储在内存中,只有当程序结束后才会由操作系统释放
代码区:存放编译⽣成的⼆进制代码
引⽤计数
引⽤计数(Reference Count)是⼀个简单⽽有效的管理对象⽣命周期的⽅式。
iOS中的内存管理⽅案
NONPOINTER_ISA , Tagged Pointer , SideTable
Tagged Pointer
Tagged Ponter 是苹果在64位操作系统下提出来的概念,也就是从5S开始。 Tagged Ponter 针
对的是⼩对象类型,⽐如NSNumber、NSDate、NSString。
Tagged Pointer 也是指针,是⼀种被打上了tagged标记的指针。
Tagged Pointer 指针,它表示的不再是地址,⽽是真正的值。这样能够⼤幅度的提升它的访问速
度和创建销毁的速度。因为在栈上,不必在堆上为其分配内存,节省了很多内存开销。在性能上,
根据苹果官⽅的说法, Tagged Pointer 有3倍的空间效率的提升,以及106倍的创建和销毁速度的
提升。
retain流程
判断是否是 Tagged Pointer , Tagged Pointer 不需要维护引⽤计数,直接返回如果不是 Tagged Pointer ,获取对象的 isa ,然后判断是否是 NONPOINTER_ISA
如果不是 NONPOINTER_ISA ,交给散列表处理,对其引⽤计数进⾏ ++ 操作,然后返回
判断是否正在析构,如果是直接返回
如果是 NONPOINTER_ISA ,对 isa 的 extra_rc 进⾏ ++ 操作
如果超出了 extra_rc 的最⼤存储范围,就将⼀半的引⽤计数保存在 extra_rc ,并且把 isa
的 has_sidetable_rc 置为1,然后将另⼀半的引⽤计数保存到散列表
当引⽤计数超出isa的extra_rc的最⼤存储范围时,为什么要extra_rc和散列表
中各存⼀半呢,为什么不是把所有的引⽤计数的值都存到散列表⾥⾯?
通过isa可以很容易的拿到extra_rc,通过extra_rc进⾏引⽤计数的存储是很⽅便的。散列表是先拿
到SideTable这张表,再在表中拿到引⽤计数表,才能进⾏操作,表操作还要做加锁和解锁操作,
⾮常浪费性能。所以在SideTable存⼀半,这样的话++,--都能够在extra_rc⾥⾯对引⽤计数进⾏操
作,效率能够更⾼。
release流程
判断是否是 Tagged Pointer , Tagged Pointer 不需要维护引⽤计数,直接返回
如果不是 Tagged Pointer ,获取对象的 isa ,然后判断是否是 NONPOINTER_ISA
如果不是 NONPOINTER_ISA ,交给散列表处理,对其引⽤计数进⾏ -- 操作,如果散列表的引
⽤计数清零,对该对象执⾏ dealloc 操作,然后返回
如果是 NONPOINTER_ISA ,对 isa 的 extra_rc 进⾏ -- 操作,当 extra_rc 计数为0,则需要
向散列表的引⽤计数借位
判断 isa 的 has_sidetable_rc 是否为1,如果不为1,对该对象执⾏ dealloc 操作。
如果 isa 的 has_sidetable_rc 为1,获取散列表的引⽤计数,如果散列表的引⽤计数为0,
对该对象执⾏ dealloc 操作
如果散列表的引⽤计数⼤于0,将引⽤计数-1,然后存⼊ isa 的 extra_rc
dealloc流程
判断是否是 Tagged Pointer , Tagged Pointer 不需要维护引⽤计数,直接返回
如果是 NONPOINTER_ISA ,且没有弱引⽤,没有关联对象,没有c++析构函数,没有散列表引
⽤计数,直接释放,否则进⾏下⼀步
调⽤ objc_dispose() 函数
调⽤ objc_destructInstance() 函数,然后再调⽤ free() 函数释放
objc_destructInstance 函数⾸先判断是否存在c++析构函数和是否存在关联对象,如果有则调⽤c++析构函数、删除关联对象,然后清除弱引⽤表的相关信息和清除散列表的相关信息