iOS的内存管理(1) 一些概念点

前言

通过这段时间的学习,对Objective-C的内存管理知识做一个总结。分享给大家,如有理解错误的地方,还望多指正。
总结从以下几个方面来说明:

  1. 引用计数器
  2. ARC(Automatic Reference Counting):自动引用计数
  3. 循环引用问题
  4. 自动释放池autorelease pool

正文

1 引用计数器

1.理解引用计数器
说到引用计数器,有着iOS开发经验的同行一定知道,Objective-C语言内存管理的核心就是引用计数器。
简单来说,每个OC对象都有一个引用计数器,如果想使某个对象继续存在内存中,那就使其引用计数增加1,如果该对象使用结束,我们不希望它继续存在于内存中,那就使其引用计数减少1(,当该对象的引用计数等于0时候,系统收回该对象的内存。

  1. 引用计数器的工作原理
    NSObject协议下声明了一下三个方法用于操作引用计数器:
  • retain 递增引用计数;
  • release 递减引用计数;
  • autorelease 待稍后清理引用释放池时,再递减引用计数。
    查看当前引用计数的方法是retainCount[obj retainCount]

对象创建出来的时候,其引用计数至少为1。若想让它继续存活,则调用retain方法,若该对象不再被使用,则调用release或者autorelease方法。当引用计数为0时候,该对象占用的内存将会被回收。引用计数器的工作原理大概如此。

2 ARC(Automatic Reference Counting):自动引用计数
  1. 自动引用计数(ARC)的理解
    在ARC出现前,开发者使用引用计数需要记住何时使用retainreleaseautorelease,而ARC的诞生就是为了解决这个问题。ARC省去了开发者在代码中调用retainreleaseautorelease精力,取代了开发者内存管理的工作。
  2. ARC的工作原理
    Xcode的Clang编译器带有一个静态分析器,用于检测程序中引用计数有问题的地方。例如:
if (true) {
   id obj = [[SomeClass alloc] init];
   [obj doSometing];
}

这段代码在MRC环境下就会出问题,因为if条件外obj没有被释放,此处会发生内存泄漏。静态分析器做的就是检测这样的错误。
既然静态分析器可以做到这些,那么可以应用这个功能,提前在程序中加入retainrelease等操作。ARC的工作原理,就是使用了这一功能。
在ARC下,代码经过编译后,会自动为源代码添加上相对应的操作。所以在ARC下,retain,release,autorelease都是不允许被使用的,否则会产生编译错误。

3.ARC的一些tips

  • ARC在调用retain,release,autorelease方法的时候,并不走Objctive-C的消息派发机制,而是直接调用底层的C语言方法。这样做提升了性能,也是retain,release,autorelease方法不能被重写的原因。
  • OC语法有非常严格的命名规则,以下列单词开头的方法名:new alloc copy mutableCopy。若方法返回对象,则ARC不会为返回的对象加上autorelease,否则会在返回对象前为其加上autorelease
  • 除了自动的调用retain,release,autorelease之外,ARC还能够把互相抵消的retain,release,autorelease操作简化。若某个对象上重复多次的进行了‘retain’和‘release’操作,那么ARC有时可以成对的抵消这两个操作。
  • ARC也包含运行期组件。当它检测到某方法返回对象前,为其执行了autorelease操作,之后该对象还要执行retain操作,那ARC就会删除这一对操作。具体方式为,在返回对象前,不直接调用autorelease方法,而是调用objc_autoreleaseReturnValue函数,此函数会检测当前方法返回之后即将要执行的代码,若发现那段代码要在返回对象上执行retain操作,则设立一个flag不再执行原有的autorelease操作。同理,若方法返回一个自动释放的对象,而该对象需要被保留,那么不直接执行retain,而是改为执行objc_retainAutoreleasedReturnValue函数,此函数检测刚才设立的那个flag,若已经设置,则不执行retain操作。
    objc_autoreleaseReturnValueobjc_retainAutoreleasedReturnValue两个函数的实现必须通过查看机器码指令才可以判断,所以是由编译器的开发者完成的。
  • 用以下修饰符修饰变量时的一些语义:
    __strong:默认语义,保留此值;
    __unsafe_unretained:不保留此值;
    __weak:不保留此值,但是变量可以安全使用,如果系统回收了该对象,那么这个变量也会被自动清空;
    __autoreleasing:在方法调用时,使用这个修饰参数值,在方法返回后,该值自动释放。
  • ARC环境下,当对象被回收时,实例变量的回收问题:ARC会使用Objctive-C++的一项特性来清理实例变量。回收Objective-C++对象时,待回收对象会调用所有C++对象的析构函数。编译器如果发现某个对象有C++对象,就会生成名为.cxx_destruct的方法。ARC借助此特性,在该方法中生成清理内存所需要的代码。
  • CoreFoundation对象需要手动管理内存,不归ARC管理,开发者必须自己调动CFRetain/CFRelease
3 循环引用问题
  1. 循环引用的理解
    如果A对象强引用了B对象,而B对象也强引用了A对象,这就是最简单的循环引用,两个对象间的互相引用。当系统要回收对象A时,由于A引用了对象B,所以对象B也需要被释放,而此时B又强引用了A,如此一来,两个对象都不能够释放,继续存活于内存中,就会出现内存泄漏。这就是循环引用的问题。
  2. 循环引用问题的解决
    解决循环引用问题的最佳方式就是 弱引用。用unsafe_unretained或者weak修饰属性。在语义上unsafe_unretainedassign等价,区别于assign用于修饰通用类型的属性,比如int,float结构体等,而unsafe_unretained用于修饰对象。
    例如对象A强引用了对象B,那么对象B如果弱引用了对象A,就不会出现以上的问题。当系统回收对象A时,对象B会被回收,而B对A的弱引用不会造成循环引用,所以不会出现内存泄漏的问题。
    weak等价于unsafe_unretained,它们的不同主要表现在被修饰的属性被释放后的行为不同。当用unsafe_unretained修饰的属性被回收后,该属性任然指向那个被回收的属性,而weak则指向nil。使用weak会使程序更加安全一些。
  3. block使用中的循环引用问题
    这个问题在很多的技术文章中被提到过,这里也做个简单的说明。
    例如:
//DemoViewController.m
@interface DemoViewController ()
@property (nonatomic, copy) void (^testBlock) (void);
@end

@implementation DemoViewController
...
- (void)viewDidLoad {
  [super viewDidLoad];
  [self test];
}

- (void)test {
  self.testBlock = ^(){
    [self doSometing];
  }
}
...

@end

以上这段代码,由于testBlock块中捕获了self,所以testBlock强引用了self,而同时self强引用着testBlock,如此就形成了循环引用,有内存泄漏的风险。
打破这种保留的方式很简单,使用__weak定义一个新的weakSelftestBlock捕获。如下:

//DemoViewController.m

@interface DemoViewController ()
@property (nonatomic, copy) void (^testBlock) (void);
@end

@implementation DemoViewController
...
- (void)viewDidLoad {
  [super viewDidLoad];
  [self test];
}

- (void)test {
  __weak typeof(self) weakSelf = self;
  self.testBlock = ^(){
    [weakSelf doSometing];
  }
}
...
@end

关于block的知识点总结,会在后面整理一份。

4 自动释放池autorelease pool
  1. 自动释放池的认识
    在ARC中,自动释放池(autorelease pool)是一项重要的特性。
    当某个对象调用release时,会理解递减引用计数retainCount。如果换做调用autorelease,则对象会被加入autorelease pool中,当清空自动释放池autorelease pool时,会向其中的对象发送release消息。
  2. 自动释放池的使用
    使用自动释放池的语法如下:
//使用语法
@autoreleasepool{
  //...
}

一般情况下,系统创建的主线程或者GCD机制中的线程,都会默认创建自己的自动释放池,每次执行 事件循环 时,就会将其清空。因此,不需要自己来创建 自动释放池块。
应用程序的入口int main()函数处,就为我们手动创建了应用程序的自动释放池。

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

所以通常情况下我们无需自己创建自动释放池。当某些临时产生的对象导致应用程序的内存峰值过高时,我们可以通过创建自动释放池,来解决这个问题。
例如:

NSMutableArray *objsArray = [NSMutableArray array];
for (int i = 0; i < 10000; i ++) {
  id obj = [self createSomeObjcWithi:i];
  [objsArray addObject:obj];
}

以上代码就会造成程序的内存突然暴增,而等所有obj对象都释放以后,又突然下降。
此时,增加一个自动释放池代码块即可解决这个问题:

NSMutableArray *objsArray = [NSMutableArray array];
for (int i = 0; i < 10000; i ++) {
  @atuoreleasepool {
    id obj = [self createSomeObjcWithi:i];
    [objsArray addObject:obj];
  }
}

这样一来,应用程序在执行循环时,就会有效降低内存峰值,不像原来那么高。
创建自动释放池本身也会占用一定的内存,所以是否使用自动释放池完全取决于程序本身。

关于自动释放池Draveness大神的自动释放池的前世今生 ---- 深入解析 Autoreleasepool有详细的解析。

总结

内存管理是应用程序的灵魂,虽然在ARC环境下,我们可以尽量少的投入精力在内存管理上,但是了解其中的原理和机制,会让我们在程序出问题时找到有效的解决途径,更是提高自我的一种方式。
当然,内存管理涉及到的也不止文中提到的内容,还有很多需要挖掘的地方。
文章是本人看书学习中的总结,主要用于知识巩固,顺便和大家分享交流,如果有不妥的地方,欢迎指正。

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

推荐阅读更多精彩内容

  • 内存管理 简述OC中内存管理机制。与retain配对使用的方法是dealloc还是release,为什么?需要与a...
    丶逐渐阅读 1,946评论 1 16
  • 29.理解引用计数 Objective-C语言使用引用计数来管理内存,也就是说,每个对象都有个可以递增或递减的计数...
    Code_Ninja阅读 1,470评论 1 3
  • 内存管理 ARC处理原理 ARC是Objective-C编译器的特性,而不是运行时特性或者垃圾回收机制,ARC所做...
    b485c88ab697阅读 11,180评论 3 47
  • iOS内存管理 概述 什么是内存管理 应用程序内存管理是在程序运行时分配内存(比如创建一个对象,会增加内存占用)与...
    蚊香酱阅读 5,693评论 8 119
  • 内存管理是程序在运行时分配内存、使用内存,并在程序完成时释放内存的过程。在Objective-C中,也被看作是在众...
    蹲瓜阅读 2,992评论 1 8