OC基础-内存管理-ARC(六)

ARC全称Automatic Reference Counting,自动引用计数内存管理,是苹果在 iOS 5、OS X Lion 引入的新的内存管理技术。ARC是一种编译器功能,它通过LLVM编译器和Runtime协作来进行自动管理内存。
LLVM编译器会在编译时在合适的地方为 OC 对象插入retain、release和autorelease代码来自动管理对象的内存,省去了在MRC手动引用计数下手动插入这些代码的工作,减轻了开发者的工作量,让开发者可以专注于应用程序的代码、对象图以及对象间的关系上。

概述

ARC会分析对象的生存期需求,并在编译时自动插入适当的内存管理方法调用的代码,而不需要你记住何时使用retain、release、autorelease方法。编译器还会为你生成合适的dealloc方法。一般来说,如果你使用ARC,那么只有在需要与使用MRC的代码进行交互操作时,传统的 Cocoa 命名约定才显得重要。
默认情况下,对象属性是strong。

1. 新规则

ARC引入了一些在使用其他编译器模式时不存在的新规则。这些规则旨在提供完全可靠的内存管理模型。有时候,它们直接地带来了最好的实践体验,也有时候它们简化了代码,甚至在你丝毫没有关注内存管理问题的时候帮你解决了问题。在ARC下必须遵守以下规则,如果违反这些规则,就会编译错误。

  • 不能使用 retain / release / retainCount / autorelease
  • 不能使用 NSAllocateObject / NSDeallocateObject
  • 须遵守内存管理的方法命名规则
  • 不能显式调用 dealloc
  • 使用 @autoreleasepool 块替代 NSAutoreleasePool
  • 不能使用区域(NSZone)
  • 对象型变量不能作为 C 语言结构体(struct / union)的成员
  • 显式转换 “id” 和 “void *” —— 桥接
不能使用 retain / release / retainCount / autorelease

在ARC下,禁止开发者手动调用这些方法,也禁止使用@selector(retain),@selector(release) 等,否则编译不通过。但你仍然可以对 Core Foundation 对象使用CFRetain、CFRelease等相关函数(请参阅《Managing Toll-Free Bridging》章节)。

不能使用 NSAllocateObject / NSDeallocateObject

在ARC下,禁止开发者手动调用这些函数,否则编译不通过。 你可以使用alloc创建对象,而Runtime会负责dealloc对象。

须遵守内存管理的方法命名规则

在MRC下,通过 alloc / new / copy / mutableCopy 方法创建对象会直接持有对象,我们定义一个 “创建并持有对象” 的方法也必须以 alloc / new / copy / mutableCopy 开头命名,并且必须返回给调用方所应当持有的对象。如果在ARC下需要与使用MRC的代码进行交互,则也应该遵守这些规则。

为了允许与MRC代码进行交互操作,ARC对方法命名施加了约束: 访问器方法的方法名不能以new开头。这意味着你不能声明一个名称以new开头的属性,除非你指定一个不同的getterName

// Won't work:
@property NSString *newTitle;
 
// Works:
@property (getter = theNewTitle) NSString *newTitle;
不能显式调用 dealloc

无论在MRC还是ARC下,当对象引用计数为 0,系统就会自动调用dealloc方法。大多数情况下,我们会在dealloc方法中移除通知或观察者对象等。
在MRC下,我们可以手动调用dealloc。但在ARC下,这是禁止的,否则编译不通过。
在MRC下,我们实现dealloc,必须在实现末尾调用[super dealloc]。

使用 @autoreleasepool 块替代 NSAutoreleasePool

在ARC下,自动释放池应使用@autoreleasepool,禁止使用NSAutoreleasePool,否则编译错误。
关于@autoreleasepool的原理,可以参阅《iOS - 聊聊 autorelease 和 @autoreleasepool》

不能使用区域(NSZone)

对于现在的运行时系统(编译器宏 __ OBJC2 __ 被设定的环境),不管是MRC还是ARC下,区域(NSZone)都已单纯地被忽略。

NSZone 是为防止内存碎片化而引入的结构。...

摘自《Objective-C 高级编程:iOS 与 OS X 多线程和内存管理》

对象型变量不能作为 C 语言结构体(struct / union)的成员

C 语言的结构体(struct / union)成员中,如果存在 Objective-C 对象型变量,便会引起编译错误。
备注: Xcode10 开始支持在 ARC 模式下在 C Struct 里面引用 Objective-C 对象。之前可以用 Objective-C++。

struct Data {
    NSMutableArray *mArray;
};
// error:ARC forbids Objective-C objs in struct or unions NSMutableArray *mArray;

虽然是 LLVM 编译器 3.0,但不论怎样,C 语言的规约上没有方法来管理结构体成员的生存周期。因为ARC把内存管理的工作分配给编译器,所以编译器必须能够知道并管理对象的生存周期。例如 C 语言的自动变量(局部变量)可使用该变量的作用域管理对象。但是对于 C 语言的结构体成员来说,这在标准上就是不可实现的。因此,必须要在结构体释放之前将结构体中的对象类型的成员释放掉,但是编译器并不能可靠地做到这一点,所以对象型变量不能作为 C 语言结构体的成员。
这个问题有以下三种解决方案:

① 使用 Objective-C 对象替代结构体。这是最好的解决方案。如果你还是坚持使用结构体,并把对象型变量加入到结构体成员中,可以使用以下两种方案:
② 将 Objective-C 对象通过Toll-Free Bridging强制转换为void *类型,请参阅《Managing Toll-Free Bridging》章节。
③ 对 Objective-C 对象附加__unsafe_unretained修饰符。

struct Data {
    NSMutableArray __unsafe_unretained *mArray;
};

附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。如果管理时不注意赋值对象的所有者,便有可能遭遇内存泄漏或者程序崩溃。这点在使用时应多加注意。

struct x { NSString * __unsafe_unretained S; int X; }

__unsafe_unretained指针在对象被销毁后是不安全的,但它对诸如字符串常量之类的从一开始就确定永久存活的对象非常有用。

显式转换 “id” 和 “void *” —— 桥接

在MRC下,我们可以直接在 id 和 void * 变量之间进行强制转换。

//MRC下
    id obj = [[NSObject alloc] init];
    void *p = obj;
    id o = p;
    [o release];

但在ARC下,这样会引起编译报错:在Objective-C指针类型idC指针类型void *之间进行转换需要使用Toll-Free Bridging,请参阅 Managing Toll-Free Bridging 章节。

//ARC下
    id obj = [[NSObject alloc] init];
    void *p = obj; // error:Implicit conversion of Objective-C pointer type 'id' to C pointer type 'void *' requires a bridged cast
    id o = p;      // error:Implicit conversion of C pointer type 'void *' to Objective-C pointer type 'id' requires a bridged cast
    [o release];   // error:'release' is unavailable: not available in automatic reference counting mode

2. 所有权修饰符

ARC为对象引入了几个新的生命周期修饰符(我们称为 “所有权修饰符”)以及弱引用功能。弱引用weak不会延长它指向的对象的生命周期,并且该对象没有强引用(即dealloc)时自动置为nil。
你应该利用这些修饰符来管理程序中的对象图。特别是,ARC不能防止强引用循环(以前称为Retain Cycles,请参阅《从 MRC 说起 —— 使用弱引用来避免 Retain Cycles》章节)。明智地使用弱引用weak将有助于确保你不会创建循环引用。

属性关键字

ARC中引入了新的属性关键字strong和weak

// 以下声明同:@property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;
 
// 以下声明类似于:@property(assign)MyClass *myObject;
// 不同的是,如果 MyClass 实例被释放,属性值赋值为 nil,而不像 assign 一样产生悬垂指针。
@property(weak) MyClass *myObject;

strong和weak属性关键字分别对应__strong和__weak所有权修饰符。在ARC下,strong是对象类型的属性的默认关键字。
在ARC中,对象类型的变量都附有所有权修饰符,总共有以下 4 种。

__strong
__weak
__unsafe_unretained
__autoreleasing
  • __strong是默认修饰符。只要有强指针指向对象,对象就会保持存活。

__strong修饰符为强引用,会持有对象,使其引用计数 +1。该修饰符是对象类型变量的默认修饰符。如果我们没有明确指定对象类型变量的所有权修饰符,其默认就为__strong修饰符。

  • __weak指定一个不使引用对象保持存活的引用。当一个对象没有强引用时,弱引用weak会自动置为nil。

如果单单靠__strong完成内存管理,那必然会发生循环引用的情况造成内存泄漏,这时候__weak就出来解决问题了。 __weak修饰符为弱引用,不会持有对象,对象的引用计数不会增加。__weak可以用来防止循环引用。

  • __unsafe_unretained指定一个不使引用对象保持存活的引用,当一个对象没有强引用时,它不会置为nil。如果它引用的对象被销毁,就会产生悬垂指针。

__unsafe_unretained修饰符的特点正如其名所示,不安全且不会持有对象。

  • __autoreleasing用于表示通过引用(id *)传入,并在返回时(autorelease)自动释放的参数。
避免循环引用
  • __weak和__unsafe_unretained 修饰符解决循环引用
    解决 “循环引用” 问题就是采用 “断环” 的方式,让其中一方持有另一方的弱引用。同MRC,父对象对它的子对象持有强引用,而子对象对父对象持有弱引用。
  • delegate 避免循环引用
    delegate避免循环引用,就是在委托方声明delegate属性时,使用weak关键字
@property (nonatomic, weak) id<protocolName> delegate;
为什么 block 会产生循环引用?

① 相互循环引用: 如果当前block对当前对象的某一成员变量进行捕获的话,可能会对它产生强引用。根据block的变量捕获机制,如果block被拷贝到堆上,且捕获的是对象类型的auto变量,则会连同其所有权修饰符一起捕获,所以如果对象是__strong修饰,则block会对它产生强引用(如果block在栈上就不会强引用)。而当前block可能又由于当前对象对其有一个强引用,就产生了相互循环引用的问题;

② 大环引用: 我们如果使用__block的话,在ARC下可能会产生循环引用(MRC则不会)。由于__block修饰符会将变量包装成一个对象,如果block被拷贝到堆上,则会直接对__block变量产生强引用,而__block如果修饰的是对象的话,会根据对象的所有权修饰符做出相应的操作,形成强引用或者弱引用。如果对象是__strong修饰(如__block id x),则__block变量对它产生强引用(在MRC下则不会),如果这时候该对象是对block持有强引用的话,就产生了大环引用的问题。在ARC下可以通过断环的方式去解除循环引用,可以在block中将指针置为nil(MRC不会循环引用,则不用解决)。但是有一个弊端,如果该block一直得不到调用,循环引用就一直存在。

ARC下解决方式
  • 用__weak或者__unsafe_unretained解决:
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        NSLog(@"%p",weakSelf);
    };

注意:__unsafe_unretained会产生悬垂指针,建议使用weak。

    __unsafe_unretained id uuSelf = self;
    self.block = ^{
        NSLog(@"%p",uuSelf);
    };

对于 non-trivial cycles,我们需要这样做:

    __weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if(!strongSelf) return;
        NSLog(@"%p",weakSelf);
    };
  • 用__block解决(必须要调用block)
    缺点:必须要调用block,而且block里要将指针置为nil。如果一直不调用block,对象就会一直保存在内存中,造成内存泄漏。
    __block id blockSelf = self;
    self.block = ^{
        NSLog(@"%p",blockSelf);
        blockSelf = nil;
    };
    self.block();

3.

在ARC下,我们可以使用_objc_rootRetainCount函数查看对象的引用计数。

uintptr_t _objc_rootRetainCount(id obj);

https://juejin.cn/post/6844904130431942670

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

推荐阅读更多精彩内容