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<UIApplicationDelegate> 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 <Foundation/Foundation.h>

@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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容