iOS - 内存管理(ARC)

历史

苹果在 2011 年的时候,在 WWDC 大会上提出了自动的引用计数(ARC)。ARC 背后的原理是依赖编译器的静态分析能力,通过在编译时找出合理的插入引用计数管理代码.
2014 年的 WWDC 大会上,苹果推出了 Swift 语言,而该语言仍然使用 ARC 技术,作为其内存管理方式


1.引用计数

1.1什么是引用计数

引用计数(Reference Count)是一个简单而有效的管理对象生命周期的方式。当我们创建一个新对象的时候,它的引用计数为 1,当有一个新的指针指向这个对象时,我们将其引用计数加 1,当某个指针不再指向这个对象是,我们将其引用计数减 1,当对象的引用计数变为 0 时,说明这个对象不再被任何指针指向了,这个时候我们就可以将对象销毁,回收内存。


引用计数

2.ARC的内存管理

ARC 能够解决 iOS 开发中 90% 的内存管理问题,但是另外还有 10% 内存管理,是需要开发者自己处理的,这主要就是与底层 Core Foundation 对象交互的那部分,底层的 Core Foundation 对象由于不在 ARC 的管理下,所以需要自己维护这些对象的引用计数。

内存管理问题
1.循环引用(block,代理)
2.Core Foundation 对象需要手动管理计数器

2.1 循环引用

引用计数这种管理内存的方式虽然使开发变得简便,但是也有瑕疵,那就是不能很好解决循环引用.


循环引用

上图所示:对象A和对象B 相互引用成为成员变量,,只有当对象A销毁时,才会对对象A中所以成员变量引用计数-1.因为对象A的销毁依赖于对象B的销毁,对象B的销毁依赖于对象A的销毁.那么就造成了循环引用。即使这两个对象在在其他地方已经没有任何指针调用它们,它们依旧不会释放.
上述问题不单只是在两个对象之间出现,只有是多个对象中出现了相互持有引用的情况下,都会出现循环引用

解决方法:

  1. 主动断开循环引用
    因为是相互引用造成的循环引用,那么只要在 不再需要使用这个对象时,将其主动断开引用


    主动断开

    代码示例

@property (nonatomic,copy) void(^testBlock)(void);

- (void)viewDidLoad {
    [super viewDidLoad];
    self.testBlock = ^{
        //当你在快中主动调用self时 Xcode会提示你 出现循环引用 这里只是为了测试
        NSLog(@"%@",self);
    };
    self.testBlock();
}
- (void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    self.testBlock = nil;
    NSLog(@"viewWillDisappear");
}
- (void)dealloc {
    NSLog(@"TestViewController -- dealloc");
}

上述代码中 在 viewDidLoad 中 self引用 testBlock 作为成员变量 , testBlock 值中调用self, 那么就造成了循环引用. 所以我们假设在 viewWillDisappear 中不再需要使用 testBlock 所以将值设为nil, 那么testBlock 中就不再调用self了, 所以最后self走了dealloc方法

image.png

主动断开循环引用这种操作依赖开发中手动操作,这样感觉像回到了 MRC年代 谁创建谁释放 的年代 ,依赖于开发者自己知道什么时候不再需要,主动断开. 所以这种方法不常用.

  1. 使用弱引用
    弱引用虽然持有对象,但是并不增加引用计数,这样就避免了循环引用的产生。在 iOS 开发中,弱引用通常在 delegate 模式中使用。


    弱引用

声明属性时将其修饰为弱引用

    @property (nonatomic,weak) id<TestDelegate> delegate;

如果block中调用到相互持有的对象 那么将其弱引用 就不会造成循环引用

    __weak typeof(self) weakSelf = self;
    self.testBlock = ^{
        NSLog(@"%@",weakSelf);
    };
    self.testBlock();

弱引用的实现原理

弱引用的实现原理是这样,系统对于每一个有弱引用的对象,都维护一个表来记录它所有的弱引用的指针地址。这样,当一个对象的引用计数为 0 时,系统就通过这张表,找到所有的弱引用指针,继而把它们都置成 nil。

从这个原理中,我们可以看出,弱引用的使用是有额外的开销的。虽然这个开销很小,但是如果一个地方我们肯定它不需要弱引用的特性,就不应该盲目使用弱引用。举个例子,有人喜欢在手写界面的时候,将所有界面元素都设置成 weak 的,这某种程度上与 Xcode 通过 Storyboard 拖拽生成的新变量是一致的。但是我个人认为这样做并不太合适。因为:

  1. 我们在创建这个对象时,需要注意临时使用一个强引用持有它,否则因为 weak 变量并不持有对象,就会造成一个对象刚被创建就销毁掉。
  2. 大部分 ViewController 的视图对象的生命周期与 ViewController 本身是一致的,没有必要额外做这个事情。
  3. 早先苹果这么设计,是有历史原因的。在早年,当时系统收到 Memory Warning 的时候,ViewController 的 View 会被 unLoad 掉。这个时候,使用 weak 的视图变量是有用的,可以保持这些内存被回收。但是这个设计已经被废弃了,替代方案是将相关视图的 CALayer 对应的 CABackingStore 类型的内存区会被标记成 volatile 类型,详见《再见,viewDidUnload方法》
2.2 Core Foundation 对象需要手动管理计数器

一般来说,以CF开头的系统API都是 CoreFoundation框架的
手动管理,说白了,就是谁创建,谁销毁
创建两个CF对象

// 创建一个 CFStringRef 对象
CFStringRef strRef= CFStringCreateWithCString(kCFAllocatorDefault, “hello world", kCFStringEncodingUTF8);
// 创建一个 CTFontRef 对象
CTFontRef fontRef = CTFontCreateWithName((CFStringRef)@"ArialMT", fontSize, NULL);

创建完后 两个对象引用计数器 = 1
当这两个对象不需要再使用时,那么就需要调用 CFRelease 来使计数器-1

CFRelease(strRef);
CFRelease(fontRef);
手动管理计数器

所以对于底层 Core Foundation 对象,我们只需要延续以前手工管理引用计数的办法即可。
其实Core 开头的框架 很多有会 retain 和 release 方法, 所以我们一般在使用结束后,将创建出来的core对象release一次.

除此之外,还有另外一个问题需要解决。在 ARC 下,我们有时需要将一个 Core Foundation 对象转换成一个 Objective-C 对象,这个时候我们需要告诉编译器,转换过程中的引用计数需要做如何的调整。这就引入了bridge相关的关键字,以下是这些关键字的说明:

  • __bridge: 只做类型转换,不修改相关对象的引用计数,原来的 Core Foundation 对象在不用-时,需要调用 CFRelease 方法。
  • __bridge_retained:类型转换后,将相关对象的引用计数加 1,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。
  • __bridge_transfer:类型转换后,将该对象的引用计数交给 ARC 管理,Core Foundation 对象在不用时,不再需要调用 CFRelease 方法。
    我们根据具体的业务逻辑,合理使用上面的 3 种转换关键字,就可以解决 Core Foundation 对象与 Objective-C 对象相对转换的问题了。

使用 Xcode 检测循环引用
在Xcode的的菜单栏中选择 :Product -> Profile,然后选择 “Leaks”,再点击右下角的”Choose” 按钮开始检测。如下图


Leaks

这个时候 iOS 模拟器会运行起来,我们在模拟器里进行一些界面的切换操作。稍等几秒钟,就可以看到 Instruments 检测到了我们的这次循环引用。Instruments 中会用一条红色的条来表示一次内存泄漏的产生。


切换到 Leaks 这栏,点击”Cycles & Roots”,就可以看到以图形方式显示出来的循环引用。这样我们就可以非常方便地找到循环引用的对象了

代码截图

如果出现下图的情况 那么说明Profile中的Build Configuration 选的不是Debug


Profile错误
修改 Profile中的Build Configuration

然后重启Xcode,重新 Product -> Profile 就可以了,还不行就重启电脑.

总结

在ARC的环境下,iOS开发在内存管理方面的工作大部分都不用手动来实现了,但是我认为,我们还是需要去理解引用计数这种内存管理方式,注意循环引用的问题.
学会使用 Instruments 工具来调试项目

最后,愿大家共同进步.

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

推荐阅读更多精彩内容