iOS 内存管理

周期限定符:

●__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 appDelegate;
@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

你可能感兴趣的:(iOS 内存管理)