您的位置:首页 > 移动开发 > IOS开发

ios内存管理

2019-03-19 16:25 113 查看

主要参考这位大神的笔记,对于里面具体概念不懂又进行了其他特定搜索的总结: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()这个函数。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: