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

这是个人第一篇文章,有什么不对的地方还烦请你们指出啦,总以为作开发颇有必要作笔记,方便记录本身的所得也能与你们探讨,可是以前一直比较懒因此无所做为,那接下来的时间一块儿好好努力吧各位加油加油。编程

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)。atom

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

提醒

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

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

相关文章
相关标签/搜索