ios内存管理
主要参考这位大神的笔记,对于里面具体概念不懂又进行了其他特定搜索的总结:https://www.jianshu.com/p/02deeca6958a
基础
强弱指针
(1).强引用
__strong NSObject *obj;//加__strong就为强引用
(2).弱引用
__weak NSObject *obj;//加__weak就为弱引用
(3).区别
强引用持有对象,而弱引用不持有对象
强引用例子
__strong NSObject *obj1=[[NSObject alloc] init]; __strong NSObject *obj2=obj1; NSLog(@"%@,%@",obj1,obj2); obj1=nil; NSLog(@"%@,%@",obj1,obj2); //输出 : //<NSObject: 0x7fef53708b80>,<NSObject: 0x7fef53708b80> //(null),<NSObject: 0x7fef53708b80>
弱引用例子
__strong NSObject *obj1=[[NSObject alloc] init]; __weak NSObject *obj2=obj1; NSLog(@"%@,%@",obj1,obj2); obj1=nil; NSLog(@"%@,%@",obj1,obj2); //输出 : //<NSObject: 0x7fef53708b80>,<NSObject: 0x7fef53708b80> //(null),(null)
可以看出,强引用obj1和obj2都开辟了一个固定的地址空间,都会使得retainCount+1,而弱引用感觉很像obj2指向了obj1强引用的地址,创建弱引用obj2而retainCount不变。第一个例子retainCount为2,obj1改变不会影响obj2,而第二个例子obj1为强指针变为nil时,retainCount-1变为0,内存随之释放,使得obj2也变为nil。
应用
正常情况下都可以使用__strong,但是当两个不同的对象互相指向对方时,弱指针就起到了作用,如果不用弱指针,当两个对象互相指向时,会造成循环引用,其中任意一个当不在主页面显示或者不需要时无法释放,如果多个互相引用,会造成内存极大浪费,此时如果定义为弱引用,则可对其进行释放。
iOS中内存管理的方式主要有三大,1.taggedPointer,2.NONPOINTER_ISA,3.散列表。
1.taggedPointer
这种对象是为了解决这个问题:当在32位系统里一个四个字节指针指向一个四个字节的变量,当换到64位系统,就变成了8字节指针指向八个字节的变量,这对资源是极大地浪费,所以苹果提出了一种taggedpointer的对象。
由于NSNumber、NSDate一类的变量本身的值需要占用的内存大小常常不需要8个字节,拿整数来说,4个字节所能表示的有符号整数就可以达到20多亿。所以我们可以将一个对象的指针拆成两部分,一部分直接保存数据,另一部分作为特殊标记,表示这是一个特别的指针,不指向任何一个地址。
接下来对一个nsnumber对象进行分析。
muStr2 = [NSMutableString stringWithString:@"1"]; for(int i=0; i<20; i+=1){ NSNumber *number = @([muStr2 longLongValue]); NSLog(@"%@, %p", [number class], number); [muStr2 appendString:@"1"]; } // 输出结果 __NSCFNumber, 0xb000000000000013 __NSCFNumber, 0xb0000000000000b3 __NSCFNumber, 0xb0000000000006f3 __NSCFNumber, 0xb000000000004573 __NSCFNumber, 0xb00000000002b673 __NSCFNumber, 0xb0000000001b2073 __NSCFNumber, 0xb0000000010f4473 __NSCFNumber, 0xb00000000a98ac73 __NSCFNumber, 0xb000000069f6bc73 __NSCFNumber, 0xb000000423a35c73 __NSCFNumber, 0xb000002964619c73 __NSCFNumber, 0xb000019debd01c73 __NSCFNumber, 0xb000102b36211c73 __NSCFNumber, 0xb000a1b01d4b1c73 __NSCFNumber, 0xb00650e124ef1c73 __NSCFNumber, 0xb03f28cb71571c73 __NSCFNumber, 0xb27797f26d671c73 __NSCFNumber, 0x60000003d540 __NSCFNumber, 0x61000003cb40 __NSCFNumber, 0x61800003c760
可以看出最后四位都是3,因为这里标记的是Long型(float则为4,Int为2,double为5),而最高4位的“b”表示是NSNumber类型;其余56位则用来存储数值本身内容。当位数超过56位时,才会采用真正的64位内存地址进行存储。
而对于字符串类型,最低位是字符串的长度,最高位是类型,但与前面不同的是:当位数超过56位时,会采用一种算法将位数压缩,如果压缩完依旧超过56位,才会采用真正的64位内存地址进行存储。
2.散列表
一个哈希映射表散列表包含多个散列表,而每个散列表又包含自旋锁、引用技术表和弱引用表,为什么包含多个?因为众多线程访问这个表时为了保证安全,要加自旋锁,因此每个线程的访问是单队列的,速度慢,所以多个表可以多线程同时访问。
接下来,研究一下引用计数表和弱引用表,当引用计数增加或减少时就要访问这两个表了。
当一个对象访问散列表时,通过哈希运算找到其在散列表的位置,如果引用计数为空就新建一个,如果不为空就对其进行操作。这期间是有自旋锁影响的。什么是自旋锁(忙等)呢,当一个线程对该资源加自旋锁,其他线程尝试访问该资源,会不断尝试访问该资源直到自旋锁解除,这和互斥锁不一样,如果进程访问遇到互斥锁就进行休眠,而不是不断尝试访问。
alloc:这个方法实质上是经过了一系列封装调用之后的calloc,需要注意的是调用该方法时,对象的引用计数并没有+1.
retain:这个方法是先在SideTables中通过哈希查找找到对象所在的那张SideTable表,随后在SideTable中的引用计数表中再次通过哈希查找找到对象所对应的size_t,再加上一个系统的(引用计数+1宏)。为什么这里没有+1而是加上一个系统的宏呢,因为在size_t结构中,前两位不是储存引用计数的,第一位存储的是是否有弱引用指针指向,第二位存储的是对象是否在被回收中。所以,在增加其引用计数时需要右移两位再进行增加,所以用到了这个系统的宏SIDE_TABLE_RC_ONE。
release:这个方法跟retain方法原理一样,只不过是减一个系统的宏SIDE_TABLE_RC_ONE
retainCount:这个方法的实现同样是先查找系统的SideTables表,并找到对象对应的SideTable表,但在之前要先申明一个size_t为1的对象,随后在对应的引用计数表中找到了对象对应的引用计数后,通过右移找到的count对象,与之前创建好的1相加,最后返回其结果便是引用计数。所以这就是为什么系统在调用alloc方法后并没有给对象的引用计数+1,但retainCount方法调用后对象的引用计数就是1的原因。
dealloc:对象在被回收时,就会调用dealloc方法,其内部实现流程首先要调用一个_objc_rootDealloc()方法,再方法内部再调用一个rootDealloc()方法,此时在rootDealloc中会判断该对象的isa指针,依次判断指针内的内容:nonpointer_isa,weakly_referenced,has_assoc,has_cxx_dtor,has_sidetable_rc,如果判断结果为:该isa指针不是非指针型的isa指针,没有弱引用的指针指向,没有相应的关联对象,没有c++相关的内容,没有使用ARC模式,没有关联到散列表中,即判断的内容都为否,则可以直接调用c语言中的free()函数进行相应的内存释放,否则就会调用objc_dispose()这个函数。
- iOS面试题(3)----内存管理
- 内存管理——ios工程图片资源
- 理解 iOS 和 macOS 的内存管理
- iOS平台下的内存管理和一些内存检测的实用方法
- IOS 开发内存管理规则摘要
- 一篇比较全面的iOS内存管理知识介绍
- IOS的内存管理
- IOS开发之内存管理--dealloc该写些什么
- iOS中Block介绍(二)内存管理与其他特性
- 理解 iOS 的内存管理
- iOS内功篇:内存管理(整理)
- iOS中引用计数内存管理机制分析
- iOS 内存管理基本原则
- iOS夯实:内存管理
- 【iOS系列】-iOS中内存管理
- iOS ARC内存管理循环引用的问题
- iOS 内存管理
- iOS内存管理之我所见
- IOS 内存管理