周期限定符:
●__strong
这是默认的限定符,无需显示引入。只要有强引用指向,对象就会长时间驻留在内存中。可以将 __strong 理解为 retain 调用的 ARC 版本。
●__weak
这表明引用不会保持被引用对象的存活。当没有强引用指向对象时,弱引用会被置为 nil。可将 __weak 看作是 assign 操作符的 ARC 版本,只是对象被回收时,__weak 具有安全性——指针将自动被设置为 nil。
●__unsafe_unretained
与 __weak 类似,只是当没有强引用指向对象时,__unsafe_unretained不会被置为 nil。可将其看作 assign 操作符的 ARC 版本。
●__autoreleasing
__autoreleasing 用于由引用使用 id * 传递的消息参数。它预期了 autorelease 方法会在传递参数的方法中被调用。
Person * __strong p1= [[Person alloc] init]; ❶
Person * __weak p2= [[Person alloc] init]; ❷
Person * __unsafe_unretained p3= [[Person alloc] init]; ❸
Person * __autoreleasing p4= [[Person alloc] init]; ❹
❶ 创建对象后引用计数为1,并且对象在 p1引用期间不会被回收。
❷ 创建对象后引用计数为0,对象会被立即释放,且 p2将被设置为 nil。
❸ 创建对象后引用计数为1,对象会被立即释放,但 p3不会被设置为 nil。
❹ 创建对象后引用计数为1,当方法返回时对象会被立即释放。
属性限定符:
●strong
默认符,指定了 __strong 关系。
●weak
指定了 __weak 关系。
●assign
这不是新的限定符,但其含义发生了改变。在 ARC 之前,assign 是默认的持有关系限定符。在启用 ARC 之后,assign 表示了 __unsafe_unretained 关系。
●copy
暗指了 __strong 关系。此外,它还暗示了 setter 中的复制语义的常规行为。
●retain
指定了 __strong 关系。
●unsafe_unretained
指定了 __unsafe_unretained 关系。
由于assign和unsafe_unretained只进行了值复制而没有任何实质性的检查,所以它们只应该用于值类型(BOOL,NSInteger,NSUInteger,等等)。应避免将他们用于引用类型,尤其是指针类型。
@property (nonatomic, strong) IBOutlet UILabel *titleView;
@property (nonatomic, weak)id
@property (nonatomic, assign) UIView *danglingReference; ❶
@property (nonatomic, assign) BOOL selected; ❷
@property (nonatomic, copy) NSString *name;
@property (nonatomic, retain) HPPhoto *photo; ❸
@property (nonatomic, unsafe_unretained) UIView *danglingReference;
❶ 错误地将 assign 用于指针。
❷ 对值类型正确地使用了 assign 限定符。
❸ retain 是 ARC 纪元之前的老古董,现代的代码已经鲜有使用。在这里添加它只是为了完整性。
- (void)createStrongPhoto{
//如果不写__strong 默认也是__strong
HPPhoto * __strong photo = [[HPPhoto alloc] init];
photo.title = @"strong photo";
NSMutableString *ms = [[NSMutableString alloc] init];
[ms appendString:photo == nil ? @"photo is nil" : @"photo is not nil"];
[ms appendString:@"\n"];
if (photo != nil) {
[ms appendString:photo.title];
}
NSLog(@"ms==%@",ms);
}
- (void)createWeakPhoto{
//创建后就被释放掉了
HPPhoto * __weak photo = [[HPPhoto alloc] init];
photo.title = @"weak photo";
NSMutableString *ms = [[NSMutableString alloc] init];
[ms appendString:photo == nil ? @"photo is nil" : @"photo is not nil"];
[ms appendString:@"\n"];
if (photo != nil) {
[ms appendString:photo.title];
}
NSLog(@"ms==%@",ms);
}
- (void)creatensafeUnretainedPhoto{
//创建后就被释放掉了
HPPhoto * __unsafe_unretained photo = [[HPPhoto alloc] init];
//会崩溃
photo.title = @"weak photo";
NSMutableString *ms = [[NSMutableString alloc] init];
[ms appendString:photo == nil ? @"photo is nil" : @"photo is not nil"];
[ms appendString:@"\n"];
if (photo != nil) {
[ms appendString:photo.title];
}
NSLog(@"ms==%@",ms);
}
(1) __strong 引用(createStrongPhoto:方法)确保了对象在其作用域内不会被销毁。对象只会在方法完成之后被回收。
(2) __weak 引用(createWeakPhoto:方法)对引用计数没有贡献。因为内存被分配在方法内且一个 __weak 引用指向这段内存,所以引用计数为0,对象被立即回收,甚至在其被用于紧邻的下一个语句前。
(3) 在 createStrongPhoto:方法中,虽然 __weak 引用不会增加引用计数,但之前创建的 __strong 引用确保了对象不会在方法结束前释放。
(4) creatensafeUnretainedPhoto:方法的结果更加有趣。注意,对象会立即被释放,但由于内存还没有被回收,这个引用可以使用,且不会导致错误。
(5) 但是,当再次调用该方法时,我们不仅看到对象已经析构,而且内存也被重新分配和再使用了。于是,使用该引用导致了非法访问,应用出现了以 SIGABRT 为信号的崩溃。这是由内存在后续(对象析构之后,访问内存之前)被回收使用造成的。
你会发现内存刚好在设置 title 属性前被回收了,从而导致了 unrecognized selector sent to instance 错误。这是因为内存已经被回收,并且现在可能已经用于存储其他对象。
循环引用的场景:
1.delegate,这个大家应该比较熟悉,记得weak。
2.block块的循环引用:
错误的写法:
GCDController *controller = [[GCDController alloc] init];
[self presentViewController:controller animated:YES completion:^{
self.dataArray = controller.dataArray;
[self dismissViewControllerAnimated:controller completion:nil];
}
}];
因为:在controller显示的时候,不会被销毁。其父视图控制器也不会被回收,因为被completion块捕获了,在GCDController执行耗时很长的任务时候,如图像处理或复杂的视图渲染,其父视图控制器的内存不会被清空,应用存在内存不足的风险。
正确的写法:
- (void)testBlock{
GCDController *controller = [[GCDController alloc] init];
__weak typeof (self) weakSelf = self;//获取一个弱引用
[self presentViewController:controller animated:YES completion:^{
typeof(self) strongSelf = weakSelf;//通过弱引用获得强引用,__strong是隐式的,可以增加引用计数。
if (strongSelf != nil) {
strongSelf.dataArray = controller.dataArray;
[strongSelf dismissViewControllerAnimated:controller completion:nil];
}
}];
}
3.线程与计时器:
不正确的使用NSTimer 和 NSThread 对象也可能导致循环引用。示例:
- (void)dealloc{
//亲测dealloc 是不会执行的,不信自己去敲代码试试。
[self.timer invalidate];
self.timer = nil;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self timerTest];
}
- (void)timerTest{
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
}
- (void)timerAction{
NSLog(@"timer====");
}
原因分析:
上述循环引用很明显,vc持有了计时器,计时器也持有了vc(target:self ),其中循环也持有计时器。必要在调用invalidate才会被释放。如果vc在很多地方创建使用的话,内存泄露就很严重了。
如果代码修改如下,即在一个合适的时机去主动invalidate就可以了,这里放在了viewWillDisappear,不是很合适,只是为了演示要在某个时机主动去调用invalidate。然后你就会发现dealloc执行了,vc被释放掉了。
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[self.timer invalidate];
self.timer = nil;
}
完整的代码示例:
DataTask.h
#import
@interface DataTask : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
- (instancetype)initWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector;
- (void)shutDown;
@end
DataTask.m
#import "DataTask.h"
@interface DataTask()
@property (nonatomic, strong) NSTimer *timer;
@end
@implementation DataTask
- (void)dealloc{
NSLog(@"task-dealloc");
}
- (instancetype)initWithTimeInterval:(NSTimeInterval)interval target:(id)target selector:(SEL)selector{
if (self = [super init]) {
self.target = target;
self.selector = selector;
self.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(fetchAndUpdate:) userInfo:nil repeats:YES];
}
return self;
}
- (void)fetchAndUpdate:(NSTimer *)timer{
//相关的代码
NSString *str = @"123";
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
typeof(self) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
if (strongSelf.target == nil) {
return;
}
//本地变量进行保存,是为了防止资源的竞争,如果target和selector发生了改变,也不会造成影响。
id target = sself.target;
SEL selector = sself.selector;
if ([target respondsToSelector:selector]) {
[target performSelector:selector withObject:str];
}
});
}
- (void)shutDown{
[self.timer invalidate];
self.timer = nil;
}
NextVC.m
#import "NextVC.h"
#import "DataTask.h"
@interface NextVC ()
@property (nonatomic, strong)DataTask *task;
@end
@implementation NextVC
- (void)dealloc{
NSLog(@"dealloc===");
[self.task shutDown];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
self.task = [[DataTask alloc] initWithTimeInterval:1.0 target:self selector:@selector(update:)];
}
- (void)update:(NSString *)str{
NSLog(@"task====%@",str);
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end