内存管理(二) ARC
上篇我们介绍了MRC,本篇我们介绍下ARC
ARC概述
ARC是一种编译器功能,它通过LLVM编译器和Runtime协作来进行自动管理内存。LLVM编译器会在编译时在合适的地方为 OC 对象插入retain、release和autorelease代码来自动管理对象的内存,从而彻底解放程序员。
ARC不能解决的问题
- Block等引发的循环引用问题(更多循环引用看我这篇文章)
- 底层 Core Foundation 对象需要手动管理
所有权修饰符
__strong
__weak
__unsafe_unretained
__autoreleasing
在对象变量的声明中使用所有权修饰符时,正确的格式为:
ClassName * 所有权 varName;
例如:
Person * __weak yang
Person * __unsafe_unretained yang
其它格式在技术上是不正确的,但编译器会 “原谅”。也就是说,以上才是标准写法。
__strong
默认修饰符,_strong 修饰符表示对对象的“强引用”,只要有强指针指向对象,对象就会保持存活。
id strongObj = [[NSObject alloc] init];
id __strong strongObj = [[NSObject alloc] init];
与之相反 如果没有强引用指针指向对象,对象就会死亡。
或者可以这么理解 最少有一个强引用指针指向对象,对象才不会死亡。
__weak
__weak 修饰符表示对对象的“弱引用” 不影响对象的释放
__weak经常会这么用
id __strong strongObj = [[NSObject alloc] init];
id __weak weakObj = strongObj;
在解释这段代码前,我们先看一个极端的特例,能加深我们对ARC 强持有对象规则的理解
__weak极端例子
- (void)viewDidLoad {
[super viewDidLoad];
id __weak weakObj = [[NSObject alloc] init];
NSLog(@"%@", weakObj);
}
编译器警告
Assigning retained object to weak variable; object will be released after assignment
输出
weakObj===(null)
单纯地使用__weak修饰符修饰变量,编译器会给出警告,因为NSObject的实例创建出来没有强引用,就会立即释放,ARC环境中一个对象必须有一个强引用指针指向它来保证自己存活。 这里没有强引用指针所以对象被创建出来之后就销毁了
而_weak修饰的指针 weakObj,会在所指向的NSObject对象被释放后,自动指向nil,所以打印为nil
汇编分析
0x10f5ade30 <+16>: movq 0x75a9(%rip), %rdi ; (void *)0x00007fff80030660: NSObject
// 创建NSObject 对象
0x10f5ade37 <+23>: callq 0x10f5ae402 ; symbol stub for: objc_alloc_init
0x10f5ade3c <+28>: leaq -0x18(%rbp), %rcx
0x10f5ade40 <+32>: movq %rcx, %rdi
0x10f5ade43 <+35>: movq %rax, %rsi
0x10f5ade46 <+38>: movq %rax, -0x30(%rbp)
0x10f5ade4a <+42>: movq %rcx, -0x38(%rbp)
// 创建weak指针变量
0x10f5ade4e <+46>: callq 0x10f5ae420 ; symbol stub for: objc_initWeak
0x10f5ade53 <+51>: movq 0x21b6(%rip), %rcx ; (void *)0x00007fff20191530: objc_release
0x10f5ade5a <+58>: movq -0x30(%rbp), %rdi
0x10f5ade5e <+62>: movq %rax, -0x40(%rbp)
0x10f5ade62 <+66>: callq *%rcx
0x10f5ade64 <+68>: movq -0x38(%rbp), %rdi
-> 0x10f5ade68 <+72>: callq 0x10f5ae426 ; symbol stub for: objc_loadWeakRetained
0x10f5ade6d <+77>: movq %rax, %rcx
0x10f5ade70 <+80>: leaq 0x21a9(%rip), %rdi ; @"weakObj===%@"
0x10f5ade77 <+87>: xorl %edx, %edx
0x10f5ade79 <+89>: movq %rax, %rsi
0x10f5ade7c <+92>: movb %dl, %al
0x10f5ade7e <+94>: movq %rcx, -0x48(%rbp)
0x10f5ade82 <+98>: callq 0x10f5ae3e4 ; symbol stub for: NSLog
0x10f5ade87 <+103>: jmp 0x10f5ade8c ; <+108> at ViewController.m
0x10f5ade8c <+108>: movq -0x48(%rbp), %rdi
// 没有强引用指针能让对象存活 所以对象销毁
0x10f5ade90 <+112>: callq *0x217a(%rip) ; (void *)0x00007fff20191530: objc_release
0x10f5ade96 <+118>: leaq -0x18(%rbp), %rdi
// 对象被销毁之后 objc_destroyWeak被调用 销毁weak指针 weak设置为nil
0x10f5ade9a <+122>: callq 0x10f5ae41a ; symbol stub for: objc_destroyWeak
正常的weak使用
id __strong strongObj = [[NSObject alloc] init];
id __weak weakObj = strongObj;
NSObject的实例已有强引用,再赋值给__weak修饰的变量就不会有警告了
__weak弱引用不影响对象的释放和废弃,若某对象被废弃,则此弱引用将自动失效且处于nil
- (void)viewDidLoad {
[super viewDidLoad];
id obj = [[Person alloc] init];
id __weak obj1 = obj;
NSLog(@"---释放Person实例--");
[obj release];
}
输出
(lldb) p obj1
(Person *) $0 = 0x0000600001ecc320
2021-12-02 22:54:46.242685+0800 05_内存[47385:318004] ---释放Person实例--
2021-12-02 22:54:48.929859+0800 05_内存[47385:318004] -[Person dealloc]
(lldb) po obj1
nil
(lldb)
循环引用
我们经常使用weak解决循环引用,但是在block内部不允许使用弱指针—>访问成员变量
代码如下
@interface Person : NSObject {
@public NSString *_name;
}
@property(nonatomic, copy) void(^block)(void);
@end
@implementation Person
- (void)test {
__weak typeof(self) weakSelf = self;
self.block = ^{
// 报错 error Use of undeclared identifier 'strongSelf'
NSLog(@"-------%@", strongSelf->_name);
};
}
- (void)dealloc {
NSLog(@"%s",__func__);
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *yang = [[Person alloc] init];
[yang test];
}
输出
// 报错
Use of undeclared identifier 'strongSelf'
我们可以加上__strong
@interface Person : NSObject {
@public NSString *_name;
}
@property(nonatomic, copy) void(^block)(void);
@end
@implementation Person
- (void)test {
__weak typeof(self) weakSelf = self;
self.block = ^{
// ARC下不允许使用弱指针—>访问成员变量 需要加上 __strong
__strong typeof(weakSelf) strongSelf = weakSelf;
// 加上之后可以正常访问了 其实就是骗编译器通过
NSLog(@"-------%@", strongSelf->_name);
};
}
- (void)dealloc {
NSLog(@"%s",__func__);
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *yang = [[Person alloc] init];
[yang test];
}
@end
Clang
struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
// 内部还是 __weak
Person *const __weak weakSelf;
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
内部还是 __weak
__unsafe_unretained
不安全且不会持有对象,附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象
对比__weak “不会持有对象” 这一特点使它和__weak的作用相似,可以防止循环引用
“不安全“ 这一特点是它和__weak的区别,那么它不安全在哪呢?
__unsafe
代码
// 注意一下代码崩溃 Thread 1: EXC_BAD_ACCESS
- (void)viewDidLoad {
[super viewDidLoad];
id __weak weakObj = nil;
id __unsafe_unretained unsafeUnretainedObj = nil;
{
id __strong strongObj = [[NSObject alloc] init];
weakObj = strongObj;
unsafeUnretainedObj = strongObj;
NSLog(@"strongObj:%@", strongObj);
NSLog(@"weakObj:%@", weakObj);
NSLog(@"unsafeUnretainedObj:%@", unsafeUnretainedObj);
}
// 出作用域NSObject实例释放 释放时遍历对象weak表 把weak类型指针变量设置为nil
NSLog(@"-----obj dealloc-----");
NSLog(@"weakObj:%@", weakObj); // 访问nil
// Crash
NSLog(@"unsafeUnretainedObj:%@", unsafeUnretainedObj); // 访问不确定空间、释放地址
// 如果你能确定NSObject对象一定存在 那么用unsafeUnretainedObj性能更快。不确定时候 使用weak
}
分析
- 出作用域NSObject实例释放 释放时遍历对象weak表 把weak类型指针变量设置为nil
- 访问不确定空间、释放地址,出现unsafeUnretainedObj野指针,程序Crash
- 如果你能确定NSObject对象一定存在 那么用unsafeUnretainedObj性能更快。不确定时候 使用weak
问题
既然 __weak 更安全,那么为什么已经有了 __weak 还要保留 __unsafe_unretained ?
- __weak仅在ARC中才能使用,而MRC只能使用__unsafe_unretained
- __weak对性能会有一定的消耗,当一个对象dealloc时,需要遍历对象的weak表,把表里的所有weak指针变量值置为nil,指向对象的weak指针越多,性能消耗就越多。所以__unsafe_unretained比__weak快。当明确知道对象的生命周期时,选择__unsafe_unretained会有一些性能提升。
比如,MyViewController 持有 MyView,MyView 需要调用 MyViewController 的接口。MyView 中就可以存储__unsafe_unretained MyViewController *_viewController
__autoreleasing
用附有 _autoreleasing修饰符的变量替代 autorelease 方法
二级指针类型的默认修饰符
__autoreleasing 是二级指针类型的默认修饰符
声明一个参数为NSError **的方法,但不指定其所有权修饰符, 调用该方法,发现智能提示中的参数NSError **附有__autoreleasing修饰符
只能修饰自动变量
__autoreleasing修饰符时,必须注意对象变量要为自动变量(局部变量 函数参数),否则编译不通过
属性修饰符
按照属性特质进行区分
原子性
atomic原子性访问
nonatomic非原子性访问
读/写权限
readwrite读写
readonly只读
内存管理
assign “纯量类型”
retain:“拥有关系”(owning relationship)
strong:“拥有关系”(owning relationship)
weak: “非拥有关系”(nonowning relationship)
copy: “拷贝”
unsafe_unretained:“不安全非拥有”
方法名
getter=XXX:指定“获取方法”的方法名
setter=XXX:指定“设置方法”的方法名
默认值
引用类型:@property (atomic,readWrite,strong) UIView *view;
基本数据类型:@property (atomic,readWrite,assign) int a;
对照表
assign:__unsafe_unretained
- 他不能在对象被释放后自动将引用设置为nil 只能用于基本数据类型
retain:__strong
- retain也会增加引用计数
strong:__strong
- 可以用在ARC上对属性进行修饰,作为强引用
copy:__strong
- copy的所有权也是__strong,所以也会进行强引用
- 区别在于会重新开辟一个空间,指向该引用,新对象引用+1,原来的对象并不会引用+1
weak:__weak
- 在ARC中使用,作为弱引用。
unsafe_unretained
- 该引用不对对象保持强引用,并在对象被释放后不会置为nil
问题
用assign修饰“对象类型”(object type)会如何?
会报warning⚠️,当指向对象被释放掉后,再使用该属性会crash。
用strong/weak/copy 修饰“纯量类型”(scalar type)时会如何?
会报Error❗️,这些修饰符只能用来修饰“对象类型”(object type)。
weak和assign的区别?
assign变量在指向变量释放后不会置为nil,再使用会crash。而weak会置为nil。
weak和strong的区别?
当一个对象还有strong类型的指针指向时,不会被释放。若仅有weak类型的指针指向时,会被释放。
NSString和NSArray和NSDictionary 用copy还是strong修饰?
都属于“容器类型”(collection)的对象,用copy修饰表示不希望值跟随外部改变,用strong修饰会跟随指向内存地址的内存的改变而改变。
copy的所有权也是__strong,所以也会进行强引用
区别在于会重新开辟一个空间,指向该引用,新对象引用+1,原来的对象并不会引用+1
NSMutableArray用copy修饰,会怎如何?
变成不可变数组,进行可变操作时会crash崩溃的原因 看我这篇文章,深浅copy
xib或storyboard拖的控件为什么是weak?
因为xib或storyboard对该控件已经有一个强引用了,而拖出来的属性应该跟这个控件保持相同的生命周期,所以应该用weak修饰。
autoreleasepool
自动释放池
创建
ARC下只能使用@autoreleasepool,用 @autoreleasepool 块替代 NSAutoreleasePool 类
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
MRC下使用autoreleasepool
@autoreleasepool {
id obj = [[NSObject alloc] init];
[obj autorelease];
}
MRC下使用NSAutoreleasePool
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
[pool release] 和 [pool drain]
释放NSAutoreleasePool对象,使用[pool release]与[pool drain]的区别
Objective-C 语言本身是支持 GC 机制的,但有平台局限性,iOS 开发用的是 RC 机制
在 iOS 的 RC 环境下[pool release]和[pool drain]效果一样,但在 GC 环境下drain会触发 GC 而release不做任何操作。使用[pool drain]更佳,一是它的功能对系统兼容性更强,二是这样可以跟普通对象的release区别开。