内存管理(二) ARC
内存管理(二) 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_init0x10f5ade3c <+28>: leaq -0x18(%rbp), %rcx0x10f5ade40 <+32>: movq %rcx, %rdi0x10f5ade43 <+35>: movq %rax, %rsi0x10f5ade46 <+38>: movq %rax, -0x30(%rbp)0x10f5ade4a <+42>: movq %rcx, -0x38(%rbp)// 创建weak指针变量0x10f5ade4e <+46>: callq 0x10f5ae420 ; symbol stub for: objc_initWeak0x10f5ade53 <+51>: movq 0x21b6(%rip), %rcx ; (void *)0x00007fff20191530: objc_release0x10f5ade5a <+58>: movq -0x30(%rbp), %rdi0x10f5ade5e <+62>: movq %rax, -0x40(%rbp)0x10f5ade62 <+66>: callq *%rcx0x10f5ade64 <+68>: movq -0x38(%rbp), %rdi
-> 0x10f5ade68 <+72>: callq 0x10f5ae426 ; symbol stub for: objc_loadWeakRetained0x10f5ade6d <+77>: movq %rax, %rcx0x10f5ade70 <+80>: leaq 0x21a9(%rip), %rdi ; @"weakObj===%@"0x10f5ade77 <+87>: xorl %edx, %edx0x10f5ade79 <+89>: movq %rax, %rsi0x10f5ade7c <+92>: movb %dl, %al0x10f5ade7e <+94>: movq %rcx, -0x48(%rbp)0x10f5ade82 <+98>: callq 0x10f5ae3e4 ; symbol stub for: NSLog0x10f5ade87 <+103>: jmp 0x10f5ade8c ; <+108> at ViewController.m0x10f5ade8c <+108>: movq -0x48(%rbp), %rdi// 没有强引用指针能让对象存活 所以对象销毁0x10f5ade90 <+112>: callq *0x217a(%rip) ; (void *)0x00007fff20191530: objc_release0x10f5ade96 <+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类型指针变量设置为nilNSLog(@"-----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区别开。
内存管理(二) ARC相关推荐
- iOS内存管理(ARC,MRC)
iOS内存管理方式: ARC Automatic Reference Counting 自动引用计数 MRC Manual Reference Counting 手动引用计数 更改管理方式: 内存管理 ...
- iOS 与OS X多线程和内存管理 笔记 ARC与所有权修饰符
注:本文为笔记形式,所以很多都是摘抄的.<<iOS 与OS X多线程和内存管理>>书中写的很棒,简单易懂,建议各位看官自己去看看. ####ARC和MRC 前一篇主要是MRC环 ...
- Objective-C 内存管理之ARC规则
基本概念 ARC为自动引用计数,引用计数式内存管理的本质并没有改变,ARC只是自动处理"引用计数"的相关部分. 在编译上,可以设置ARC有效或无效.默认工程中ARC有效,若设置无效 ...
- 操作系统概念学习笔记 16 内存管理(二) 段页
操作系统概念学习笔记 16 内存管理 (二) 分页(paging) 分页(paging)内存管理方案允许进程的物理地址空间可以使非连续的.分页避免了将不同大小的内存块匹配到交换空间上(前面叙述的内存管 ...
- Objective-C(9)内存管理之ARC
ARC机制及判断准则 ARC:Auto Reference Counting 自动引用计数 是一种编译器机制,在编译过程中,为我们的代码添加retain.release.autorelease等方法 ...
- Linux任督二脉之内存管理(二) PPT
五节课的第二节课-内存的动态申请和释放 * slab.kmalloc/kfree./proc/slabinfo和slabtop * 用户空间malloc/free与内核之间的关系 * mallopt ...
- Linux内存管理二(页表)
1.综述 用来将虚拟地址空间映射到物理地址空间的数据结构称为页表,即页表用于建立用户进程的虚拟地址空间和系统物理内存(内存.页帧)之间的关联 实现两个地址空间的关联最容易的方法是使用数组,对虚拟地址空 ...
- 高端内存映射之kmap持久内核映射--Linux内存管理(二十)
日期 内核版本 架构 作者 GitHub CSDN 2016-09-29 Linux-4.7 X86 & arm gatieme LinuxDeviceDrivers Linux内存管理 在内 ...
- 内存管理(二) - MRC关键字解读
本篇主要学习以下几个知识点 alloc/reatin/release/dealloc 理解 autorelease 理解 autorelease GUN 实现 autorelease 苹果 实现 原文 ...
- BOOST内存管理(二) --- boost::pool
Boost库的pool提供了一个内存池分配器,用于管理在一个独立的.大的分配空间里的动态内存分配.Boost库的pool主要适用于快速分配同样大小的内存块,尤其是反复分配和释放同样大小的内存块的情况. ...
最新文章
- Linux环境变量设置中配置文件分析(/etc/profile,~/.bashrc等)(转)
- 双任务延时原理与空闲任务
- linux CMA使用机制分析--基于SigmaStar SSD202
- 在Eclipse中使用Java 12
- 为什么要给计算机配置IP地址,更改ip地址 为何要重启电脑
- 40名大学生被退学,教育部表态:学生对自己不负责,就要付出代价
- LAMP或LNMP一键安装包
- 软件毕业设计文档流程与UML图之间的关系
- 自己实现memcpy/strcpy/strcmp/strcat/strlen/strstr
- 支持所有浏览器的右键菜单
- 心理学实验必备 | 脑电实验流程及注意事项
- 什么是同城商超配送系统
- 大学计算机第四讲答案,大学职业生涯规划课第四讲答案
- 阿里研究院花几年心得终成趣谈网络协议,附技术官讲解
- 终于有人把智慧城市和边缘计算说清楚了
- 【转】抽象语法树简介(AST)
- python day9
- 最全最通俗易懂的设计模式全集
- covmatrix matlab,matlab cov函数
- 移除Linux体系下不需求的效劳