iOS开发笔记(一):内存管理

news/2024/7/20 15:31:20 标签: 内存管理, 移动开发, c/c++

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

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/mutablecopy1
持有对象retain+1
释放对象release-1
废弃对象dealloc0

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<MyDelegate> 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.参考

  • 《Objective-C高级编程:iOS与OS X多线程和内存管理》之 自动引用计数
  • 自动引用计数
  • 我的iOS内存管理-MRC-ARC

http://www.niftyadmin.cn/n/992948.html

相关文章

C#生成DLL文件

使用csc命令将.cs文件编译成.dll的过程 很多时候,我们需要将.cs文件单独编译成.dll文件, 操作如下: 打开命令窗口->输入cmd到控制台->cd C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322 转到vs.net安装的该目录下->执行csc命令csc /target:library File.cs->在该目…

Apache启动报错

[rootlocalhost httpd-2.2.21]# service apache start 报错httpd: Could not reliably determine the servers fully qualified domain name, using localhost.localdomain for ServerName 解决方法 [rootlocalhost httpd-2.2.21]# vim /usr/local/apache2/conf/httpd.conf 97行…

netty实现多数据格式,多对象解析处理

多对象解析思路前言具体实现定义实体类定义通用类编解码器发送和接收消息扩展终极解决方案&#xff08;反射&#xff09;前言 接着上一章讲&#xff08;netty实现多协议&#xff0c;多编解码器&#xff09;&#xff0c;上文提到String数据和protostuff序列化对象数据使用不同编…

VS2008(C#)子页嵌套母版页的控件访问方法(一)

VS2008(C#)子页嵌套母版页的控件访问方法&#xff08;一&#xff09;——嵌套一层母版页 嵌套一层母版页后&#xff0c;子页访问母版页的方法 母版页HTML代码&#xff08;后台无需CS代码&#xff09; <% Master Language"C#" AutoEventWireup"true" Cod…

参与思科商务新网络活动,抢iPad大奖

如今&#xff0c;网络作为稳定的可信赖的沟通交流和数据管理商务平台成为商业、教育、政府和家庭通信不可或缺的一部分&#xff0c;全球成千上万的公司、大学、企业和政府部门通过思科提供的技术构架和解决方案建立了基于互联网的交流,协作和语音和数据平台&#xff1b;互联网上…

蓝牙设备探测工具blueranger

蓝牙设备探测工具bluerangerblueranger是Kali Linux预安装的一款蓝牙探测工具。该工具通过向指定设备发送蓝牙L2CAP协议的ping包&#xff0c;创建连接。由于大部分蓝牙设备对ping包不进行认证&#xff0c;所以可以直接连接成功。在连接过程中&#xff0c;通过切换Class 1和Clas…

Gevent 性能和 gevent.loop 的运用和带来的思考

知乎自己在底层造了非常多的轮子&#xff0c;而且也在服务器部署方面和数据获取方面广泛使用 gevent 来提高并发获取数据的能力。现在开始我将结合实际使用与测试慢慢完善自己对 gevent 更全面的使用和扫盲。 在对 gevent loop 的使用上&#xff0c;gevent tutorial 介绍得非常…

netty客户端连接多服务端

前言 最近有个需求&#xff0c;需要客户端分别连接多个服务端来检测状态&#xff0c;于是就大概研究了一下&#xff0c;其实还是挺简单的&#xff0c;在这里记录一下先。 客户端启动类 /*** author: zhouwenjie* description: 客户端* create: 2022-07-20 17:14**/ Slf4j Co…