博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS开发笔记(一):内存管理
阅读量:6413 次
发布时间:2019-06-23

本文共 7281 字,大约阅读时间需要 24 分钟。

这是我的第一篇文章,有什么不对的地方还烦请大家指出啦,总觉得做开发很有必要做笔记,方便记录自己的所得也能与大家探讨,但是之前一直比较懒所以无所作为,那接下来的时间一起好好努力吧各位加油加油。

1.总起

1.1 了解内存管理

了解内存管理的最普通的方式是考虑所有权。如果Manny对Jack曾说过alloc,retain或者copy,则表示Manny已经宣称对Jack的所有权。多个对象可以同时拥有Jack,但每个对象只负责正确地管理自己对Jack的所有权,最终释放Jack是每个Jack所有者的责任,而Jack的非所有者永远不需要释放Jack。只要所有拥有Jack所有权的对象都以这种方式执行,Jack就不会泄漏也不会有任何指向Jack的指针留下来摇晃。

1.2 相关知识点

  • 野指针:指针变量没有进行初始化或指向的空间已经释放。

    • 使用野指针调用对象方法,会报异常,程序崩溃。
    • 通常在调用完release方法后,把保存对象指针的地址清空(不是销毁对象,引用计数为0时才会调用dealloc销毁对象),赋值为nil,但OC中没有空指针异常,所以[nil retain]调用方法不会有异常。
  • 内存泄漏:

    • 在ARC自动引用计数模式下,造成内存泄漏的情况:

      • 如Person *person = [Person new];(对象指针提前赋值nil或者清空,⚠️提前)在栈区中的person已经释放,而堆区new产生的对象还没有释放,就会造成内存泄漏。
    • 在MRC手动引用计数模式下,造成内存泄漏的情况:

      • 没有配对释放,不符合内存管理原则。
      • 对象指针提前赋值nil或者清空,导致release不起作用。
  • 僵尸对象:堆中已经被释放的对象(retainCount=0)。

  • 空指针:指针赋值为空(nil)。

提醒

一个变量的名称,包括实例变量,只是一个指针。当你向该指针发送消息时,你实际上就是通过该指针将消息发送到它指向的对象。内存管理的规则是关于对象的原则,而不是关于名称、引用或指针的规则。你不能递增或递减一个指针的保留计数,因为没有这个东西。指针所占用的内存是自动管理的(而且很小)。内存管理所关注的是指针所指向的对象。

2.MRC

2.1 实现原理

Objective-C对象中保存着引用计数这一整数值。调用alloc或者retain方法后,引用计数+1。调用release后,引用计数-1。引用计数为0时,调用dealloc方法废弃对象。

2.2 相关操作

对象操作 Objective-C方法 引用计数
生成并持有对象 alloc/new/copy/mutablecopy 1
持有对象 retain +1
释放对象 release -1
废弃对象 dealloc 0

2.3 内存管理的思考方式

  • 自己生成的对象,自己持有

    NSObject *obj = [[NSObject alloc] init];  [obj retain];  NSLog(@"obj - %lu",[obj retainCount]);复制代码
  • 非自己生成的对象,自己也能持有

    id obj = [NSMutableArray array];  [obj retain];  NSLog(@"obj - %lu",[obj retainCount]);复制代码
  • 不再需要自己持有的对象时释放

    NSObject *obj = [[NSObject alloc] init];//自己生成自己持有  [obj retain];  NSObject *obj2 = [obj retain];//非自己生成,自己持有  [obj release];  [obj2 release];  NSLog(@"obj - %lu",[obj retainCount]);复制代码
  • 非自己持有的对象自己无法释放

    id obj = [NSMutableArray array];  [obj release];复制代码

提醒

MRC下对象的引用计数可以通过[object retainCount]方法获得。

2.4 NSAutoreleasePool

autorelease故名思议就是自动释放,看上去很像ARC,但其实更类似于C语言中的局部变量,也就是说,超出变量作用域的时候将自动被废弃,但这里与C语言不同的是,编程人员可以手动设置其作用域。 autorelease的具体使用方法如下:

  • 生成并持有NSAutoreleasePool对象
  • 调用已分配的autorelease方法
  • 废弃NSAutoreleasePool对象

NSAutoreleasePool对象的生命周期相当于C语言变量的作用域,对于所有调用过autorelease方法的对象,在废弃NSAutoreleasePool对象时,都将对对象统一调用release方法,代码如下所示:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init] ;id obj = [ [NSObject alloc]init];[obj autorelease] ;[pool drain];复制代码

提醒

在Cocoa框架中,如果不是使用alloc/new/copy/mutablecopy这几个方法返回的对象,其余方法返回的对象都将自动注册到NSAutoreleasePool中,id array = [NSMutableArray arrayWithCapacity:10];其实也就等同于:id array = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];

3.ARC

ARC是Objective-C编译器的特性,而不是运行时特性或者垃圾回收机制,ARC所做的只不过是在代码编译时为你自动在何时的位置插入release或者autorelease,减少了开发的工作量。但我们有时仍需要四种所有权修饰符来配合ARC来进行内存管理。

3.1 四种所有权修饰符

  • __strong:强引用,持有所指向对象的所有权,无修饰符情况下的默认值。如需强制释放,可置nil(这里先创建一个指针,将新的值retain一次,将指针动态指向新的值,并将旧的值release一次)。

    NSObject *obj = [[NSObject alloc]init];  //他们是等价的  NSObject __strong *obj = [[NSObject alloc]init];复制代码
  • __weak:弱引用,不持有所指向对象的所有权,引用指向的对象内存被回收之后,引用本身会置nil,避免野指针.避免循环引用,会将对象注册到autoreleasepool(既不保留新值,也不释放旧值,动态地将指针指向新的值,如果这个值刚被dealloc,就会将指针更新为一个nil指针)。

  • unsafe_unretained:相当于assign。直接赋值。引用计数不变。他会发生野指针现象。所以不安全。不像weak,当指向的对象为空的时候,将指针置为nil。

  • _autoreleasing:将对象赋值给附有 _ autoreleasing 修饰符的变量等同于ARC 无效时调用对象的autorelease方法。

    @autoreleasepool {  	id __autoreleasing obj = [[NSObject alloc] init];  }复制代码

3.2 底层实现

  • __strong底层实现

    • alloc/new/copy/mutablecopy情况下

      //alloc为例的模拟底层代码为:  id __Strong obj = [[NSObject alloc] init];  id obj = objc_msgSend(NSobject, @selector(alloc));  objc_msgSend(obj, @selector(init));  objc_release(obj);  //编译器自动帮我们加入了release,来释放对象复制代码
    • 非alloc/new/copy/mutablecopy情况下

      //array为例的模拟底层代码为:  id obj = objc_msgSend(NSMutableArray, @selector(array));  objc_retainAutoreleasedReturnValue(obj);  objc_release(obj);    //objc_retainAutoreleasedReturnValue(obj)是主要用于最优化程序运行    //array方法的底层模拟为:  id obj = objc_msgSend(NSmutableArray,@selector(alloc));  objc_msgSend(obj,@selector(init));  return objc_autorealeaseReturnValue(obj);复制代码

      objc_autorealeaseReturnValue(obj)与objc_retainAutoreleasedReturnValue(obj)是成对的,objc_autorealeaseReturnValue(obj)会把对象注册到autorealeasepool中,但是如果它检测到方法执行列表中出现objc_retainAutoreleasedReturnValue(obj)方法,那么就不会将返回的对象注册到autorealeasepool,而是直接传递到方法和函数的调用方。这样直接传递可以达到最优化。

  • __weak底层实现

    • __weak源码

      id __weak obj1 = obj;    //模拟代码  id obj1;  obj1 = 0;  objc_storeWeak(&obj1,obj);  objc_destoryWeak(&obj1);  等同于objc_storeWeak(&obj1,0);复制代码

      objc_storeWeak(&obj1,obj)函数将第二个参数的赋值对象的地址作为键值,将第一个参数的附有__weak修饰符的变量的地址注册到weak表中,如果第二个参数为0,则把变量的地址从weak中删除。一个键值可以注册多个变量的地址由此可见,如果大量的weak变量,则会消耗CPU资源,所以weak 只用来避免循环引用。

    • __weak与@autoreleasepool

      id __weak obj1 = obj;  NSLog(@"%@",obj1);    //模拟代码  id obj1;  objc_initWeak(&obj1,obj);  id temp = objc_loadWeakRetained(&obj1);  objc_autorelease(temp);  NSLog(@"%@",temp);  objc_destoryWeak(&obj1);复制代码

      所以在@autorealeasepool块结束前可以放心使用weak修饰变量

    • __autoreleasing底层实现

      将对象赋值给附有__autoreleasing修饰符的变量等同于ARC无效时,调用对象的autorelease方法。

      • 使用alloc/new/copy/mutableCopy时

        @autoreleasepool {  	id __autoreleasing obj = [[NSObject alloc] init];  }    //模拟代码  id pool = objc_autoreleasePoolPush();  id obj = objc_msgSend(NSObject, @selector(alloc));  objc_msgSend(obj, @selector(init));  objc_autorelease(obj);  objc_autoreleasPoolPop(pool);复制代码
      • 使用alloc/new/copy/mutableCopy以外的方法时

        @autoreleasepool {    	id __autoreleasing obj = [NSMutableArray array];  }    // 模拟代码  id pool = objc_autoreleasePoolPush();  id obj = objc_msgSend(NSMutableArray,@selector(array));  objc_retainAutoreleasedReturnValue(obj);  objc_autorelease(obj);  objc_autoreleasPoolPop(pool);  // 虽然 obj 持有对象的方法变为 objc_retainAutoreleasedReturnValue, 但是将 obj 所引用的对象注册到 autoreleasepool 中的方法并没有改变复制代码

      关于@autoreleasepool,在ARC下应该使用@autoreleasepool而不是NSAutoreleasePool,@autoreleasepool的具体实现将在之后的文章中继续探讨。

3.3 使用规则

  • 不能使用retain/release/retainCount/autorelease
  • 不重载dealloc(如果是释放对象内存以外的处理,是可以重载该函数的,但是不能调用[super dealloc])
  • 不能使用NSAllocateObject, NSDeallocateObject
  • 不能在C结构体中使用对象指针
  • id与void *间的如果cast时需要用特定的方法(__bridge关键字)
  • 不能使用NSAutoReleasePool,而需要使用@autoreleasepool块
  • 不能使用区域(NSZone)

4.属性的内存管理

ObjC2.0引入了@property,提供成员变量访问方法、权限、环境、内存管理类型的声明,下面主要说明ARC中属性的内存管理。属性的参数分为三类,基本数据类型默认为(atomic,readwrite,assign),对象类型默认为(atomic,readwrite,strong),其中第三个参数就是该属性的内存管理方式修饰,修饰词可以是以下之一:

  • assign

    直接赋值,一般用来修饰基本数据类型。当然也可以修饰ObjC对象,但是不推荐,因为被assign修饰的对象释放后,指针还是指向释放前的内存,在后续操作中可能会导致内存问题引发崩溃。

    @property (nonatomic, assign) NSInteger count;复制代码
  • retain

    retain和strong一样,都用来修饰ObjC对象,使用set方法赋值时,实质上是会先保留新值,再释放旧值,再设置新值,避免新旧值一样时导致对象被释放的的问题。

    //MRC写法如下  - (void)setCount:(NSObject *)count {  		[count retain];   		[_count release];   		_count = count;   	}    	//ARC对应写法 	- (void)setCount:(NSObject *)count {  		_count = count;  	}复制代码
  • copy

    一般用来修饰String、Dict、Array等需要保护其封装性的对象,尤其是在其内容可变的情况下,因此会拷贝(深拷贝)一份内容給属性使用,避免可能造成的对源内容进行改动。使用set方法赋值时,实质上是会先拷贝新值,再释放旧值,再设置新值。实际上,遵守NSCopying的对象都可以使用copy,当然,如果你确定是要共用同一份可变内容,你也可以使用strong或retain。

  • weak

    ARC新引入修饰词,可代替assign,比assign多增加一个特性(置nil)。weak和strong一样用来修饰ObjC对象。使用set方法赋值时,实质上不保留新值,也不释放旧值,只设置新值。

    @property (weak) id
    delegate;复制代码
  • strong

    ARC新引入修饰词,可代替retain,ARC一般都写strong。

    Person *per = [[Person alloc] init];

    self.person = per;

    如果是strong,对象的retainCount为2,如果为weak,对象的retainCount为1。

  • unsafe_unretained

    等价于assign,可以用来修饰数据类型和OC对象,但是不会使计数器加1,且对象销毁时也不会将对象指向nil,容易造成野指针错误。

5.block的内存管理

OC中使用block必须自己管理内存,错误的内存管理将导致循环引用等内存泄漏问题,这里主要说明在ARC下block声明和使用的时候需要注意的两点:

  • 如果你使用@property去声明一个block的时候,一般使用copy来进行修饰(当然也可以不写,编译器自动进行copy操作),尽量不要使用retain。

    @property (nonatomic, copy) void(^block)(NSData * data);复制代码
  • block会对内部使用的对象进行强引用,因此在使用的时候应该确定不会引起循环引用,当然保险的做法就是添加弱引用标记。

    __weak typeof(self) weakSelf = self;复制代码

6.参考

转载地址:http://xrdra.baihongyu.com/

你可能感兴趣的文章
C指针练习
查看>>
web项目buildPath与lib的区别
查看>>
php对redis的set(集合)操作
查看>>
我的友情链接
查看>>
ifconfig:command not found的解决方法
查看>>
js使用正则表达式判断手机和固话格式
查看>>
计算机是怎么存储数字的
查看>>
mysql简单的单表查询详解
查看>>
mysql性能优化之Tuning-primer.sh的使用
查看>>
Unix的发展史
查看>>
CentOS改变docker默认存储池大小
查看>>
Docker存储驱动devicemapper介绍和配置
查看>>
win2008作为个人电脑用需要优化的部分
查看>>
vi教程
查看>>
yum 本地源配置问题
查看>>
从Vue.js窥探前端行业
查看>>
Linux chown改变文件所属关系命令
查看>>
android开发——获取手机SD卡的容量
查看>>
django ajax提交 Forbidden CSRF token missing
查看>>
maven常见异常
查看>>