iOS 内存管理篇(三)---__strong/__weak/__unsafe_unretain/__autoreleasing 认识和使用

之前学习的文章链接

iOS内存管理篇(一)---alloc/reatain/release/dealloc方法实现

iOS内存管理篇(二)---NSAutoreleasePool/@autoreleasepool/autorelease理解与管理

前言:说到内存管理,避免不了的就是循环应用和某个变量释放的实际,虽然在实际开发种,ARC会为我们自动的加上引用技术和减少引用技术,但是并不是万能的,百密一疏,还是会在实际开发过程中内存管理出现问题,今天我们要来看的就是__strong,__weak,这些关键词的作用和使用方法

__strong关键字与retain关似,用了它,引用计数自动+1

有如下 Demo:

id obj = [[NSObject alloc]init];

/*等价于*/

id __strong obj = [[NSObject alloc]init];

那么在内存里面是怎么执行这段代码的呢

/*编译器模拟的代码*/
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
objc_release(obj);

从上面的代码我们可以看出,系统两次调用objc_msgSend来创建这个对象并为之开辟内存空间,然后在变量作用于失效的时候使用objc_release来释放对象,虽然在 ARC模式下我们不能使用 release,但是系统在运行时为我们自动添加了 release。

那么我们来看看实际的例子吧

有两个变量,是用 strong 修饰的

@property (nonatomic, strong) NSString *str1;
@property (nonatomic, strong) NSString *str2;

做了如下的操作

self.str1 = @"A";
self.str2 = self.str1;
self.str1 = nil;
NSLog(@"self.str2 = %@",self.str2);

大家可以猜猜打印结果是什么

打印结果是 self.str2 = A

由于str2是strong定义的属性,所以引用计数+1,使得它们所指向的值都是@"A", 虽然 self.str = nil 了,但是并没有使得 str2的引用计数减1导致释放,所以打印的结果为 A


__weak修饰符提供的功能如同魔法一般

  • 若附有__weak修饰符的变量所引用的对象被废弃,则将 nil赋值给该变量

  • 使用附有__weak修饰符的变量,即是使用注册到__autoreleasepool 中的对象

有如下代码:

id __strong obj = [[NSObject alloc]init];
id __weak obj1 = obj;

我们看看系统是如何为我们处理的

/*编译器模拟的代码*/
id obj1;
objc_initWeak(&obj1,obj);
objc_destroyWeak(&obj1);

通过objc_initWeak函数初始化附有__weak修饰符的变量,在变量作用域结束的时候用objc_destroyWeak释放该变量

我们来下一下的具体的例子

@property (nonatomic, strong) NSString *str1;
@property (nonatomic, weak)   NSString *str2;

执行如下的代码

self.str1 = @"A";
self.str2 = self.str1;
self.str1 = nil;
NSLog(@"self.str2 = %@",self.str2);

结果是 self.str2 = null 为什么会是这个结果呢 分析一下,由于self.string1与self.str2指向同一地址,且str2没有retain内存地址,而 self.str1=nil释放了内存,所以string1为nil。
所以这是符合第一条规则的,因为str2 指向的地址已经被释放了,那么会将 nil 赋值给 str2

在这个过程中__weak帮我们干了什么呢

(1) 从 weak表中获取废弃对象的地址为键值的记录
  (2) 将包含在记录中的所有附有__weak 修饰变量的地址,赋值为 nil
  (3) 从 weak 表中删除该记录
  (4) 从引用技术表中删除废弃对象的地址为键值的记录

我们来看看 weak 的另外一个特性

id __weak obj1 = obj/*obj 是 strong 的*/
NSLog(%@"obj1 = %@",obj1);

这段代码转换为系统编译器码为

id obj1;
objc_initWeak(&obj1,obj);
id temp = objc_loadWeakRetained(&obj1);
objc_autorelease(temp);
NSLog(%"%@",temp);
objc_destroyWeak(obj1);

我们可以注意到有个objc_autorelease这个函数,所以正说明了第二点:使用附有__weak修饰符的变量,即是使用注册到__autoreleasepool 中的对象,因为附有__weak的变量所使用的对象都被注册到了__autoreleasepool里面,所以在@autoreleasepool块结束之前可以放心使用,所以在实际开发过程中,如果大量使用 weak 来修饰变量的话,是会非常耗费 CPU资源的,所以当只有会可能产生循环引用的地方使用 weak 是合适的。


__unsafe_unretain顾名思义就是不安全的,其功能和 weak 差不多

有如下的代码

id __unsafe_unretain obj = [[NSObject alloc]init];

这时候编译器报出警告,该代码对应的模拟代码为

/*编译器模拟的代码*/
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
objc_release(obj);

objc_release函数立即释放了生成并持有的对象,这样对象的悬垂指针被赋值给了变量obj 当中

我们来看如下的代码:

@property (nonatomic, strong)             NSString *str1;
@property (nonatomic,__unsafe_unretain)   NSString *str2;

执行如下的代码

self.str1 = @"A";
self.str2 = self.str1;
self.str1 = nil;
NSLog(@"self.str2 = %@",self.str2);

这次根本就不用输出,在没有输出前,程序已经崩溃了,其实就是野指针造成的,为何会造成野指针呢?同于用unsafe_unretained声明的指针,由于 self.str1=nil已将内存释放掉了,但是str2并不知道已被释放了,所以是野指针。然后访问野指针的内存就造成crash. 所以尽量少用unsafe_unretained关键字。

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

有如下代码:

@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_autoreleasePoolPop(pool);
- (void) generateErrorInVariable:(__autoreleasing NSError **)paramError{
    NSArray *objects = [[NSArray alloc] initWithObjects:@"A simple error", nil];
    NSArray *keys = [[NSArray alloc] initWithObjects:NSLocalizedDescriptionKey, nil];
    NSDictionary *errorDictionary = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
    *paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];
}
-(void)test
{
    NSError *error = nil;
    [self generateErrorInVariable:&error];
    NSLog(@"Error = %@", error);
}

__autoreleasing则可以使对像延迟释放。比如你想传一个未初始 化地对像引用到一个方法当中,在此方法中实始化此对像,那么这种情况将是__autoreleasing表演的时候。看个示例:

@autoreleasepool{
     id __autoreleasing obj = [[NSObject alloc]init];   
}

对应的编译器码:

- (void) generateErrorInVariable:(__autoreleasing NSError **)paramError{
    NSArray *objects = [[NSArray alloc] initWithObjects:@"A simple error", nil];
    NSArray *keys = [[NSArray alloc] initWithObjects:NSLocalizedDescriptionKey, nil];
    NSDictionary *errorDictionary = [[NSDictionary alloc] initWithObjects:objects forKeys:keys];
    *paramError = [[NSError alloc] initWithDomain:@"MyApp" code:1 userInfo:errorDictionary];
}
-(void)test
{
    NSError *error = nil;
    [self generateErrorInVariable:&error];
    NSLog(@"Error = %@", error);
}

符合内存管理规则:谁分配谁释放。

小结:不管是__strong、__weak、__unsafe_unretain、__autoreleasing 这是为了让我们更好的理解内存管理,而且在必要的时候使用这些修饰符来管理我们的内存,而在实际开发种,一般很少使用到后两者,前两者在循环引用的时候可以用来解循环引用,我们在今后的学习中还会学习和使用到它们,别忘了哟~

此文借鉴的文章链接
iOS中 property中的属性strong 、weak、copy 、assign 、retain 、unsafe_unretained 与autoreleasing区别和作用详解
书籍《Objective-C高级编程 iOS与OS X多线程和内存管理》

你可能感兴趣的:(iOS 内存管理篇(三)---__strong/__weak/__unsafe_unretain/__autoreleasing 认识和使用)