【iOS开发】——ARC(自动管理内存)的一些补充
上学期我总结过一些关于ARCiOS开发——ARC的知识点,最近在复习ARC,发现当时学习时有一些知识点没有总结好而且有些知识点没有总结到,所以再写一篇博客补充一些知识。
__bridge
我们在使用__bridge修饰符的时候用的最多的就是__bridge转换,那么主要有哪些转换可以用到__bridge呢?
OC指针与void *互相转换
使用 __bridge_retained
- (void)OCToVoid__bridge_retainedInARC {
void *p = 0;
{
id obj = [[NSObject alloc] init];
p = (__bridge_retained void *)obj;
// p = (__bridge void *)obj; 报错,obj出了作用域就会销毁,__bridge不改变持有情况,所以p成为悬垂指针
}
NSLog(@"class===%@",[(__bridge id)p class]);
}
若将上述代码中p = (__bridge_retained void *)obj;改为p = (__bridge void *)obj; 会报错,因为obj出了作用域就会销毁,__bridge不改变持有情况,所以p成为悬垂指针
ARC下使用 __bridge_retained对应在MRC的过程
- (void)OCToVoid__bridge_retainedInMRC {
/* MRC下 */
void *p = 0;
{
id obj = [[NSObject alloc] init];
NSLog(@"obj retainCount===%lu",[obj retainCount]);
/* [obj retainCount] -> 1 */
p = [obj retain];
NSLog(@"obj retainCount===%lu",[obj retainCount]);
/* [obj retainCount] -> 2 */
[obj release];
NSLog(@"obj retainCount===%lu",[obj retainCount]);
/* [obj retainCount] -> 1 */
}
NSLog(@"(id)p retainCount===%lu",[(id)p retainCount]);
/**
* [(id)p retainCount] -> 1
* 即
* [obj retainCount] -> 1
* 对象仍然存在
*/
NSLog(@"class===%@",[(__bridge id)p class]);
}
所以我们就很好理解为什么p = (__bridge_retained void *)obj;改为p = (__bridge void *)obj; 会报错,那是因为:
__bridge_retained
转换可使要转换赋值的变量也持有所赋值的对象。__bridge_retained
转换变为了retain
。- 变量obj和变量p同时持有对象。
- 变量作用域结束时,虽然随着持有强引用的变量obj失效,对象随之释放,但由于 __bridge_retained 转换使变量p看上去处于持有该对象的状态,因此该对象不会被废弃。
void *指针转换为OC指针
使用 __bridge_transfer
- (void)VoidToOC__bridge_transferInARC {
void *p = 0;
{
id tempObj = [[NSObject alloc] init];
p = (__bridge_retained void *)tempObj;
}
id obj = (__bridge_transfer id)p;
}
ARC下使用 __bridge_transfer对应在MRC的过程
- (void)VoidToOC__bridge_transferInMRC {
void *p = 0;
{
id tempObj = [[NSObject alloc] init];
p = [tempObj retain];
[tempObj release];
}
id obj = (id)p;
/**
* 同__bridge_retained转换与retain类似,__bridge_transfer转换与release相似。
* 在给id obj 赋值时retain即相当于__strong修饰符的变量。
*/
[obj retain];
[(id)p release];
}
__bridge_transfer
转换与__bridge_retained
相反,“被转换的变量”所持有的对象在变量赋值给“转换目标变量”后随之释放- 同
__bridge_retained
转换与retain
类似,__bridge_transfer
转换与release
相似。在给 id obj 赋值时 retain 即相当于 __strong 修饰符的变量。
Objective-C指针与CoreFoundation指针之间的转换
ARC
仅管理Objective-C
指针(retain
、release
、autorelease
),不管理CoreFoundation
指针。CF
指针由人工管理,手动的CFRetain
和CFRelease
来管理。
在ARC中,CF和OC之间的转化桥梁是 __bridge,有3种方式:
__bridge
: 只做类型转换,不改变对象所有权,是我们最常用的转换符。__bridge_transfer
:ARC接管 管理内存__bridge_retained
:ARC释放 内存管理
从OC转CF,ARC管理内存:
- (__bridge CFStringRef)
- 需要人工CFRetain,否则,Cocoa指针释放后, 传出去的指针则无效。
例如:
- (void)viewDidLoad {
[super viewDidLoad];
NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];
CFStringRef aCFString = (__bridge CFStringRef)aNSString;
(void)aCFString;
}
上面只是单纯地执行了类型转换
,没有进行所有权的转移,也就是说,当aNSString对象被ARC释放的时候,aCFString也不能被使用了。
从CF转OC,需要开发者手动释放,不归ARC管:
- (__bridge NSString*)
- 需要人工CFRelease,否则,OC对象的指针释放后,对象引用计数仍为1,不会被销毁。
例如:
- (void)viewDidLoad {
[super viewDidLoad];
CFStringRef aCFString = CFStringCreateWithCString(NULL, "test", kCFStringEncodingASCII);
NSString *aNSString = (__bridge NSString *)aCFString;
(void)aNSString;
CFRelease(aCFString);
}
ARC下内存管理发生改变的转换
CF–>OC:__bridge_transfer:
- 非Objective-C 指针到 Objective-C 指针并移交持有权给ARC。ARC负责释放对象
例如:
- (void)viewDidLoad {
[super viewDidLoad];
NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];
CFStringRef aCFString = (__bridge_retained CFStringRef) aNSString;
aNSString = (__bridge_transfer NSString *)aCFString;
}
OC–>CF:__bridge_retained:
- Objective-C 指针到Core Foundation 指针并移交持有权。你要负责调用 CFRelease 或一个相关的函数来释放对象。
例如:
- (void)viewDidLoad {
[super viewDidLoad];
NSString *aNSString = [[NSString alloc]initWithFormat:@"test"];
CFStringRef aCFString = (__bridge_retained CFStringRef) aNSString;
(void)aCFString;
//这时候,即使开启ARC,也需要手动执行CFRelease
CFRelease(aCFString);
}
OC–>CF/CF–>OC进行转换时的细节
- 因为ARC无法管理CF对象的指针,所以,无论是CF转OC还是OC转CF,我们只需关心CF对象的引用需要加1还是减1即可。
CF转OC:CFRef必须减1
这样原来的CF对象就被释放,所以,以后也不用手动释放。
NSString *c = (__bridge_transfer NSString*)my_cfref; // -1 on the CFRef
OC转CF:CFRef 必须加1
这样新的CF对象就不会被释放,所以,以后用完必须手动释放。
CFStringRef d = (__bridge_retained CFStringRef)my_id; // returned CFRef is +1
//这时候,即使开启ARC,CF对象用完后也需要手动执行CFRelease
CFRelease(aCFString);
以前总结过,再总结一下__bridge
关于ARC的一些具体问题
什么是ARC
所谓ARC就是自动管理内存,因为在以前内存管理是由程序员自己手动管理的想想就很头疼,所以就有了自动的引用计数(Automatic Reference Count 简称 ARC)的概念,引用计数是一个简单而有效的管理对象生命周期的方式。当我们创建一个对象时,它的引用计数就为1,当有一个新的指针指向它时,其引用计数就加1,当对象的引用计数变为 0 时,说明这个对象不再被任何指针指向了,这个时候我们就可以将对象销毁,回收内存。引用计数简单有效,帮助我们解决了关于内存管理的问题,所以引用计数也是ARC的基础。
ARC的工作原理
当我们编译源码的时候,编译器会分析源码中每个对象的生命周期
,然后基于这些对象的生命周期
,来添加相应的引用计数操作代码。所以,ARC 是工作在编译期的一种技术方案,这样的好处是:
- 编译之后,ARC 与非 ARC 代码是没有什么差别的,所以二者可以在源码中共存。实际上,你可以通过编译参数 -fno-objc-arc 来关闭部分源代码的 ARC 特性。
- 相对于垃圾回收这类内存管理方案,ARC 不会带来运行时的额外开销,所以对于应用的运行效率不会有影响。相反,由于 ARC 能够深度分析每一个对象的生命周期,它能够做到比人工管理引用计数更加高效。 例如在一个函数中,对一个对象刚开始有一个引用计数 +1的操作,之后又紧接着有一个 -1 的操作,那么编译器就可以把这两个操作都优化掉。
weak修饰的对象被释放时,weak指针自动被置为nil的实现原理
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个哈希表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。
具体步骤:
- 初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
- 添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数,objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
- 释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
当weak引用指向的对象被释放时,如何去处理weak指针?
- 调用objc_release
- 因为对象的引用计数为0,所以执行dealloc
- 在dealloc中,调用了_objc_rootDealloc函数
- 在_objc_rootDealloc中,调用了object_dispose函数
- 调用objc_destructInstance
- 最后调用objc_clear_deallocating
简单来说:
a. 从weak表中获取被释放对象的地址为键值的记录
b. 将包含在记录中的所有附有 weak修饰符变量的地址,赋值为 nil
c. 将weak表中该记录删除
d. 从引用计数表中删除废弃对象的地址为键值的记录
为什么ARC仍然需要@autoreleasepool?
在大多数情况下使用ARC(自动引用计数),我们不需要考虑使用Objective-C对象的内存管理。不允许再创建NSAutoreleasePools,但是有一个新的语法:
@autoreleasepool { …}
相当于:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];…[pool drain];
- ARC使用autorelease以及release。
- 它需要一个自动释放池才能这样做。
- ARC不会为您创建自动释放池。然而:
-
- 每个Cocoa应用程序的主线程都有一个自动释放池。
有两种情况你可能想要使用@autoreleasepool:
- autorelease 机制基于 UI framework。因此写非UI framework的程序时,需要自己管理对象生存周期。
- autorelease 触发时机发生在下一次runloop的时候。因此如何在一个大的循环里不断创建autorelease对象,那么这些对象在下一次runloop回来之前将没有机会被释放,可能会耗尽内存。这种情况下,可以在循环内部显式使用@autoreleasepool {}将autorelease 对象释放。
- 自己创建的线程。Cocoa的应用都会维护自己autoreleasepool。因此,代码里spawn的线程,需要显式添加autoreleasepool。注意:如果是使用POSIX API 创建线程,而不是NSThread,那么不能使用Cocoa,因为Cocoa只能在多线程(multithreading)状态下工作。但可以使用NSThread创建一个马上销毁的线程,使得Cocoa进入multithreading状态。
ARC在编译期和运行期做了什么
- 在编译期,ARC会把互相抵消的retain、release、autorelease操作约简。
- ARC包含有运行期组件,可以在运行期检测到autorelease和retain这一对多余的操作。为了优化代码,在方法中返回自动释放的对象时,要执行一个特殊函数。具体为:为了保证向后兼容性,ARC 在运行时检测到类函数中的 autorelease 后紧跟其后 retain,此时不直接调用对象的 autorelease 方法,而是改为调用 objc_autoreleaseReturnValue。
objc_autoreleaseReturnValue 会检视当前方法返回之后即将要执行的那段代码,若那段代码要在返回对象上执行 retain 操作,则设置全局数据结构中的一个标志位,而不执行 autorelease 操作,与之相似,如果方法返回了一个自动释放的对象,而调用方法的代码要保留此对象,那么此时不直接执行 retain ,而是改为执行 objc_retainAoutoreleasedReturnValue函数。此函数要检测刚才提到的标志位,若已经置位,则不执行 retain 操作,设置并检测标志位,要比调用 autorelease 和retain更快。
总结
目前对ARC的理解与问题的总结主要为这些,肯定还有很多不足,等以后再有理解或者再碰见有关ARC的问题时再进行补充。