JHHK

欢迎来到我的个人网站
行者常至 为者常成

自动引用计数(二)

目录

ARC规则

一、概要

二、内存管理的思考方式

引用计数式内存管理的思考方式就是思考ARC所引起的变化。

  • 自己生成的对象,自己所持有
  • 非自己生成的对象,自己也能持有
  • 不再需要自己持有的对象时释放
  • 无法释放非自己持有的对象

三、所有权修饰符

所谓对象类型就是指向NSObject这样的Objective-C类的指针,例如NSObject * ; id类型用于隐藏对象类型的类名部分,相当于C语言中常用的 void * ;
ARC有效时,id类型和对象类型,必须附加所有权修饰符。

  • __strong修饰符

默认所有权修饰符

//默认所有权修饰符为 __strong 相当于下面代码
id objc = [[NSObject alloc] init];

//__strong 修饰符
id __strong obj = [[NSObject alloc] init];

在特定作用域内的__strong

{
    //自己生成并持有对象
    //因为变量为强引用,所以自己持有对象
    id __strong obj = [[NSObject alloc] init];
}
//obj超出作用域,自动释放持有的对象
//对象的持有者不存在,因此废弃该对象

ARC无效时,该源代码可记述如下

{
    //创建并持有对象
    id obj = [[NSObject alloc] init];
    
    //在obj超出作用域被废弃之前释放掉持有对象
    [obj release];
}

赋值操作时的__strong

id __strong obj0 = [[NSObject alloc] init];//obj0持有对象A
id __strong obj1 = [[NSObject alloc] init];//obj1持有对象B

//obj0持有B对象
obj0 = obj1;

//此时obj0不再持有A对象,A对象释放
//B对象被obj0和obj1持有

类成员变量使用 __strong
Test类的声明与实现

@interface Test:NSObject{
  id __strong obj_
}
@end

@implementation Test
-(id)init{
  self = [super init];
  return self;
}

-(void)setObject:(id __strong)obj{
  obj_ = obj;
}

@end

下面试着使用该类

{
  //test持有Test对象的强引用
  id __strong test = [[Test alloc] init];

  //Test对象的obj_成员持有NSObject对象的强引用
  [test setObject:[[NSObject alloc] init]];
}

//test变量超出其作用域,自动调用[test release]
//test指向的对象的引用计数为0,调用[test delloc].
//test指向的对象销毁时,会先释放NSObjec对象,NSObject对象的引用计数也变为0,调用其[object delloc]

重要说明:
__strong __weak __ autoreleasing修饰符,可以保证将附有这些修饰符的自动变量初始化为nil;
id __strong obj0;
id __weak obj1;
id __autoreleasing obj2;

以下源代码与上相同
id __strong obj0 = nil;
id __weak obj1 = nil;
id __autoreleasing obj2 = nil;

自己生成的对象,自己所持有 和 非自己生成的对象,自己也能持有
通过对带__strong修饰符的变量赋值便可达成。

不再需要自己持有的对象时释放
通过废弃带__strong修饰符的变量(变量作用域结束或是成员变量所属对象废弃)或者对变量赋值。

无法释放非自己持有的对象
ARC下由于不必再次键入release,所以原本就不会执行。

  • __weak修饰符

只有__strong会出现循环引用问题

img

场景一:循环引用

{
  id test0 = [[Test alloc] init];//对象A
  id test1 = [[Test alloc] init];//对象B


  //持有对象B的有:test1,A对象->obj_; B的引用计数为2
  [test0 setObject:test1];

  //持有对象A的有:test0,B对象->obj_; A的引用计数为2
  [test1 setObject:test0];
}

//超出test0、test1的作用域范围,test0、test1废弃,分别放弃其持有的对象A、B;
//A与B的引用计数都变为1。对象A没有销毁,B也没有销毁。

img

场景二:对自身的强引用

{
  id test = [[Test alloc] init];//对象A 引用计数为1;

  [test setObject:test];//对象A->obj_ 赋值,对象A的引用计数为2;
}

//离开作用域,变量test被废弃,
//对象A的引用计数变为1,
//对象A,无法释放

img

场景三:__weak 修饰变量

{
  //obj0变量为强引用,所以自己持有对象
  id __strong obj0 = [[NSObject alloc] init];//生成对象A,引用计数为1

  //obj1变量持有生成对象的弱引用,不会导致引用计数改变
  id __weak obj1 = obj0;//对象A引用计数为1
}


//obj0超出作用域,强引用失效,自动释放自己持有的对象 对象A引用计数为0
//因为对象的所有者不存在,所以废弃该对象。

避免循环引用

@interface Test:NSObject{
  id __weak obj_;
}
-(void)setObject:(id __strong)obj;
@end;

img

自动失效且自动赋值nil;

//__weak修饰的变量,在持有对象被废弃,则此弱引用自动失效,且处于nil被赋值的状态。
id __weak obj1 = nil;

{
    id __strong obj0 = [[NSObject alloc] init];//obj0持有对象A
    
    obj1 = obj0;
    
    NSLog(@"obj1=%@",obj1);//obj1=<NSObject: 0x1c0011560>
}

NSLog(@"obj1=%@",obj1);// obj1=(null)

像这样,使用__weak修饰符可避免循环引用。通过检查附有__weak修饰符的变量是否为nil,可以判断被赋值的对象是否已废弃。

  • __unsafe_unretained修饰符

该修饰符与__weak修饰符作用相同,区别是对象释放后不会对变量赋值nil,会导致内存访问出错。

  //__weak修饰的变量,在持有对象被废弃,则此弱引用自动失效,且处于nil被赋值的状态。
  id __unsafe_unretained obj1 = nil;
  
  {
      id __strong obj0 = [[NSObject alloc] init];//obj0持有对象A
      
      obj1 = obj0;
      
      NSLog(@"obj1=%@",obj1);//obj1=<NSObject: 0x1c0011560>
  }
  
  NSLog(@"obj1=%@",obj1);//崩溃 Thread 1: EXC_BAD_ACCESS (code=1, address=0xd2ba7beb8)

  • __autoreleasing修饰符
    在ARC下NSAutoreleasePool类不允许使用,对象的autorelease也不可调用。可通过以下方式来使用:

img

但是,显示的附加__autoreleasing修饰符很罕见,原因如下。

  @autoreleasepool {
      //编译器会判断方法名是否以alloc/new/copy/mutableCopy开始,
      //如果不是,则自动将返回值的对象注册到autoreleasepool
      
      //obj为强引用持有该对象,并且由编译器确定后自动注册到autoreleasepool
      id __strong obj = [NSArray array];
      
  }
  
  //离开作用域,obj废弃,强引用失效
  //@autoreleasepool块结束,注册到autoreleasepool中的对象自动释放

自动放入自动释放池的过程

+(id)array{
    //默认__strong,强引用
    id obj = [[NSArray alloc] init];
    
    //该对象作为返回值自动放入autoreleasepool
    return obj;
}

实际访问附有__weak修饰符的变量时,必定要访问注册到autoreleasepool的对象。

id __weak obj1 = obj0;
NSLog(@"class=%@",[obj1 class]);

以上代码与下面相同
id __weak obj1 = obj0;
id __autoreleasing temp = obj1;
NSLog(@"class=%@",[obj1 class]);

//为什么在访问附有__weak修饰的变量时必须注册到autoreleasepool对象呢?     
//因为__weak修饰符只持有对象的弱引用,访问过程中有可能被废弃。    
//如果把要访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保该对象存在。    

非显示的使用__autoreleasing的另一个例子

id obj;

//与之等价的是
id __strong obj;

//或者
NSObject * __strong obj;


//那么与
id * obj;

//等价的是否是
id __strong * obj;

//答案是否定的,与之等价的是
id __autoreleasing * obj;

//或者
NSObject * __autoreleasing * obj;

//即
NSObject ** obj;
//默认的修饰符是
NSObject * __autoreleasing * obj;
//如下方法error的参数的类型是 NSError * __autoreleasing *;
[NSString stringWithContentsOfURL:(nonnull NSURL *)
                          encoding:(NSStringEncoding)
                            error:(NSError *__autoreleasing  _Nullable * _Nullable)];

      
//这种方法调用时,如下
NSError * error = nil;

[NSString stringWithContentsOfURL:[NSURL URLWithString:@"url"]
                          encoding:NSUTF8StringEncoding
                            error:&error];

//error实例的生成是在方法内部,要想能在方法外部使用,需要在自动释放池注册
NSLog(@"error=%@",error);
对象指针类型赋值时,所有权修饰符必须一致

//编译报错
NSObject * obj = [[NSObject alloc] init];
NSObject ** op = &obj;

//编译通过
NSObject * obj = [[NSObject alloc] init];
NSObject * __strong * op = &obj;

//编译通过
NSObject * __weak obj = [[NSObject alloc] init];
NSObject * __weak * op = &obj;

//编译通过
NSObject * __unsafe_unretained obj = [[NSObject alloc] init];
NSObject * __unsafe_unretained * op = &obj;

//编译通过
NSObject * __autoreleasing obj = [[NSObject alloc] init];
NSObject * __autoreleasing * op = &obj;

详细了解下@autoreleasePool

//autoreleasepool可以嵌套使用
//autoreleasepool代码块同样适用于非ARC模式
@autoreleasepool {
    
    @autoreleasepool {
        
    }
    
}


//调用非公开函数,帮助我们调试注册到autoreleasepool上的对象
_objc_autoreleasePoolPrint();

四、规则

  • 不能使用 retain/release/retainCount/autorelease

  • 不能使用NSAllocateObject 和 NSDeallocateObject

  • 须遵守内存管理的方法命名规则

  • 不要显示调用dealloc

  • 使用@autoreleasepool代替NSAutoreleasePool

  • 不能使用区域NSZone

  • 对象型变量不能作为C语言结构(struct/union)的成员

//编译器报错
struct Datas{
    NSMutableArray*array;
};

//这样可以 __unsafe_unretained 不属于编译器内存管理对象
struct Datas2{
    NSMutableArray* __unsafe_unretained array;
};
  • 显示转换id和void *
    id obj = [[NSObject alloc] init];
    
    void* obj1 = (__bridge void *)obj;
    void * obj2 = (__bridge_retained void*)obj;
    
    id obj3 = (__bridge id)obj1;
    id obj4 = (__bridge_transfer id)obj2;

五、属性

属性声明的属性 所有权修饰符
assign __unsafe_unretained 修饰符
copy __strong 修饰符(但是赋值的是被复制的对象)
retain __strong 修饰符
strong __strong 修饰符
unsafe_unretained __unsafe_unretained 修饰符
weak __weak 修饰符

六、数组

数组释放时要保证数组内的元素释放,避免引起内存泄漏。 静态数组,在释放数组时会自动释放数组内的元素。 动态数组,在释放数组时需要手动将数组内的元素置为nil,然后释放数组。

但在iOS中释放动态数组时也会自动释放数组内的元素。

//retainCount 1
NSObject * obj = [[NSObject alloc] init];
printf("retainCont=%ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj)));

//retainCount 2
NSMutableArray * mArray = [[NSMutableArray alloc] init];
[mArray addObject:obj];
printf("retainCont=%ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj)));

//retainCount 1
mArray = nil;
printf("retainCont=%ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj)));

//retainCount 0
obj = nil;
printf("retainCont=%ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj)));

ARC的实现


行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.