前言
虽然之前看了小白书Effective Objective-C 2.0关于内存管理,里面讲了关于ARC的相关内容,但最近发现好像寒假只是简单的过了一遍书,没有具体的学到什么,很多知识点还是没有理解到位导致忘的的快。所以这里重新学习一遍ARC的相关知识,会持续更新。
基础知识
强引用和弱引用
在iOS中,不可避免的会用的强引用和弱引用,特别是在block块中使用,关键词也很直白就是weak和strong。因为OC中采用的引用计数的方式,当引用计数为0时该对象会被释放,不为0时会一直存在不被释放。使用strong就会将引用计数加1,当持有的对象被释放的时候,就会对应的减掉该引用数,而当使用weak时,不会修改引用计数。
强引用
在ARC中修饰符是__strong
,比如
__strong NSObject *obj;
- 不加修饰符的话默认都是
__strong
。
弱引用
在ARC中修饰符是__weak
,比如
__weak NSObject *obj;
- 弱引用表示并不持有对象,当所引用的对象销毁了,这个变量就自动设为nil。
两者区别
简单点讲的话,强引用持有对象,而弱引用不持有对象。
下面看代码详解:
__strong NSObject *obj1=[[NSObject alloc] init];
__strong NSObject *obj2=obj1;
NSLog(@"%@,%@",obj1,obj2);
obj1 = nil;
NSLog(@"%@,%@",obj1,obj2);
将obj2
声明改为__weak
__strong NSObject *obj1 = [[NSObject alloc] init];
__weak NSObject *obj2 = obj1;
NSLog(@"%@,%@",obj1, obj2);
obj1 = nil;
NSLog(@"%@,%@",obj1, obj2);
从上面可以看出使用__strong
和__weak
的区别,因为strong
的对象会使retainCount+1,而weak
的并不会。
所以第一个例子的retainCount
为2,obj1 = nil
之后retainCount为1,并不会对obj2
造成影响,而第二个例子obj1 = nil
之后retainCount 为0了,内存也跟着释放了,所以obj2
也为nil
。
注意
:这里提下weak
和MRC时代的assign
的区别,两者都可以用于弱引用,但是内存释放后使用weak
会将对象置nil
,而assign
不会,会造成野指针
,现在assign
一般只用在基础类型。
应用场景
至于什么时候用strong 和weak?
- 平时一般都是用
strong
,也就是默认不添加。 __weak
只有在避免循环引用的时候才会使用,至于循环引用就是两个对象互相持有,或者或互相强引用对方,所以两者永远不能被销毁。必须将其中一方的引用改成__weak
的才能打破这种死循环,最常见的就是delegate
模式和block
情况下使用。
1.当两个不同的对象各有一个强引用指向对方时就造成循环引用,会导致对象无法释放,例如我们常用的delegate
, 见图:
这就是我们在申明delegate
时都是用weak
的原因(MRC的话是用assign)
@property (nonatomic, weak) id<Delegate> delegate
2.block
的使用也会照成循环引用,比如当一个对象持有block
,而该block
又持有该对象时,类似下面的伪代码会照成循环引用:
[self block:^{
self.number = 1;
}];
应该改为
__weak typeof(self) weakself = self;
[self block:^{
weakself.number = 1;
}];
注意
:只有该对象持有block
,而block
里的代码块又持有该对象时才需要用到weak
。
3.NSTimer
也会照成循环引用,所以使用了NSTimer
后,释放资源前要先调用invalidate
方法
[Timer invalidate];
Timer = nil;
strong和copy的区别
看完了上述关于强引用和弱引用的相关描述,大家可能会感觉这跟前面学的 对象的深浅拷贝(Objective-C——浅复制和深复制)有些类似。
这里讲述一下强引用关键字strong
和copy
的区别
- strong是将对象的引用计数加一,当对象空间的值发生改变时,指向这个空间的对象都会改变。此特质标明该属性定义了一种拥有关系。为这种属性设定新值时,设置方法会先保留新值再释放旧值,然后再讲新值设置上去。
- copy是将对象值拷贝一份,然后自己创建一个空间放进去。此特质所表达的所属关系与strong类似。然而设置方法并不保留新值而是将其拷贝。
内存管理四大原则
- 自己生成的对象自己持有
- 非自己生成的对象自己也能持有
- 不再需要自己持有的对象时释放
- 非自己持有的对象无法释放
ARC
ARC(Automatic Reference Counting)是在 Xcode 4.2的时候推出的编译器的一个功能,编译器简单的说就是把你写的代码翻译成机器能识别的代码,通常还会在这个过程中给你的代码添砖加瓦,ARC就是这样的一个功能,编译器在编译过程中自动地在合适的位置加入内存管理的代码,
- 基于ARC管理内存,就一条口诀:只要还有强引用(strong reference)引用着一个对象,这个对象就不会被销毁,反之则对象会被销毁,内存会被释放。
ARC特性
- ARC 是编译器特性,而不是 iOS 运行时特性,它也不是类似于其它语言中的垃圾收集器。因此 ARC 和手动内存管理性能是一样的,有时还能更加快速,因为编译器还可以执行某些优化
- “在LLVM编译器中设置ARC为有效状态,就无需再次键入retian或者release代码。”
所有权修饰符
ARC有效时,id类型和对象类型必须附加所有权修饰符,所有权修饰符一共有4种
- __strong
- __weak
- __unsafe_unretained
- __autoreleasing
__strong
表示引用为强引用。在ARC模式下,只要一个变量有__strong
标识了,就标识拥有了赋值的对象,不管赋值的对象是怎么来的。也可理解为只要见到__strong
标识,编译器就会给那些不是你持有的对象自动加上retain
,并且在变量超出作用域后自动调用了一次release
。
- 例如,控制器中有个文本输入框属性
@property (nonatomic,strong) UITextField *textField;//
- 如果用户在文本框中输入
first
这个字符串
那么可以这样说,textField
的text
属性是NSString
对象的指针,也就是拥有者,该对象保存了文本输入框的内容。
2. 如果执行了如下的代码
NSString *name = self.textField.text;
一个对象可以有多个拥有者,在上面的代码中,name
变量同样也是这个NSString
对象的拥有者,也就是有两个指针指向同一个对象
- 随后用户改变了输入框的内容
此时textFeild
的text
属性就指向了新的NSString
对象。但原来的NSString
对象仍然还有一个所有者(name
变量),因此会继续保留在内存中
- 当
name
变量获得新值,或者不再存在时(如局部变量方法返回时、实例变量对象释放时),原先的NSString
对象就不再拥有任何所有者,retain计数降为0,这时对象会被释放
name = @"third";
我们称name
和textField.text
指针为 “Strong指针” ,因为它们能够保持对象的生命。默认所有实例变量和局部变量都是Strong指针
__weak
表示引用为弱引用。对应在定义property
时用的"weak"。弱引用不会影响对象的释放,即只要对象没有任何强引用指向,即使有100个弱引用对象指向也没用,该对象依然会被释放。 不过好在,对象在被释放的同时,指向它的弱引用会自动被置nil,这个技术叫zeroing weak pointer。这样有效得防止无效指针、野指针的产生。
如下所示:
1. 执行下面的代码 (接着刚才strong的例子)
__weak NSString *name = self.textField.text;
name
变量和nameField.text
属性都指向同一个NSString
对象,但name
不是拥有者
2. 如果文本框的内容发生变化
则原先的NSString
对象就没有拥有者, 会被释放, 此时name
变量会自动变成nil
, 称为空指针
注意:
即使是使用alloc/new/copy/mutableCopy
创建的对象,也不持有,结果就是这个对象没人要,所以一出来就销毁了,这里需要注意的是通过非这4个方法创建的对象,并不会因为__weak
标识已创建就销毁,而是要等到超出autoreleasepool
的时候才会销毁。
__unsafe_unretained
__unsafe_unretained
和__weak
很像,唯一区别就是,__unsafe_unretained
变量引用的对象再被销毁以后,不会被自动设置为nil
,仍然指向对象销毁前的内存地址。所以它的名字叫做unsafe
,此时你再尝试通过变量访问这个对象的属性或方法就会crash。
不安全的修饰符
- 人如其名,
__unsafe__unretained
是不安全的所有权修饰符,附有__unsafe__unretained
修饰符的变量不属于编译器的内存管理类对象。
__autoreleasing
- 表示在autoreleasepool中自动释放对象的引用,和MRC时代
autorelease
的用法相同。定义property
时不能使用这个修饰符,任何一个对象的property
都不应该是autorelease
型的。 __autoreleasing
可以说跟__strong
有点类似,你可以放心地在autoreleasing
块结束此前都可以正常地使用这个对象,而不用担心它被销毁。- 这个对象也会被注册到
autoreleasepool
里,将autoreleasepool
结束时候才会被销毁,而在变量的作用域结束后不会像__strong
那样马上被release
。
__autoreleasing的应用场景
__autoreleasing在我们实际真实的iOS应用开发的时候,基本上是用不到的,我说的用不到只是针对业务程序员来说。实际上编译器是会用到它的,另外在一些方法声明的时候,你也会看到自动地给你加上的__autoreleasing,即时你没有显式地手敲出来。
隐式使用的情况
隐式使用__autoreleasing
的情况就是你的代码上看不到它,但是它编译后确实会存在,比如返回值:
//这个是我们的TestObj的创建对象的一个自定义类方法
+ (TestObj *)obj
{
/*
本来这个对象创建出来默认是__strong的,
但是因为编译器发现这个对象是要被当做返回值返回出去的,
所以不能在这个方法结束时候就release这个对象,
而应该放长远的眼光,使用autorelease,这个其实也是在MRC里
*/
TestObj * obj = [[TestObj alloc] init];
return obj;
}
再结合__weak
的例子来说说隐式的__autoreleasing
TestObj *__weak obj = [[TestObj alloc] init];
这句TestObj
对象已创建就被销毁了,因为它没有被强引用,那么假如用__weak
修饰非new/alloc/copy/mutableCopy
方法获得的对象会怎样?
@autoreleasepool {
//通过类方法obj获取一个对象,注意不是使用alloc/new/copy/mutablCopy获取的对象
//所以这个+obj内部返回对象之前其实已经给对象调用了autorelease。
TestObj * __weak obj1 = [TestObj obj];
//TestObj不会马上销毁,XCode编译器也不会提醒你警告。
.
.
.
//在即将超出autoreleasepool作用域的时候,obj1指向那个对象才会因为注册到了autoreleasepool而调用release方法进行释放
}
可以看到区别,虽然obj1
是弱引用,但是通过类方法obj创建获得的对象不会马上被销毁,而是要等到超过autoreleasepool的作用域才会真实执行release
。这就是因为obj方法里的编译器帮你返回了一个autoreleasing
的对象的效果。
显式使用的情况
显式使用是你在代码上能看到它,虽然可能不是你手动敲出来的,但是你如果手动去修改它是会影响到对象的销毁的。
- 最常见的显示使用的情况就是发生在指向对象的指针的指针时候。
什么叫做指向对象的指针的指针,有点拗口,我们知道对象内存是在heap(堆)里面,我们声明一个引用对象变量,实际上这个变量就是一个指针,它里面存放的就是这个对象再内存中的地址。
NSError**与autoreleasing的关系
- (BOOL)doSomethingWithError:(NSError **)error;
看到这个假方法是不是很眼熟?如果执行成功返回YES,失败返回NO,但是失败的时候还能通过读取error来知道具体错误原因,也就是说将是否创建error
对象的权利交给这个方法。
接下来来说明它跟__autoreleasing
之间的关系之前,先给我们的测试对象加上一些方法,变成以下的样子
//TestObj.m
@interface TestObj : NSObject
+ (TestObj *)obj;
//增加一个doSomethingWithError方法
- (BOOL)doSomethingWithError:(NSError **)error;
@end
//TestObj.h
@implementation TestObj
+ (TestObj *)obj
{
return [[TestObj alloc] init];
}
//重写dealloc方法,未了稍后测试时打印对象的销毁
- (void)dealloc
{
NSLog(@"dealloc:%@", [self className]);
}
/*
注意注意,当我们先在.h头文件里写了方法的定义后,再来.m里来敲这个方法的时候,XCode会自动地补全这个方法
可以看到error的类型多了修饰符变成NSError *__autoreleasing _Nullable *
_Nullable不重要,只是说明此方法这个参数不能传空
__autoreleasing才是我们要关注的重点
*/
- (BOOL)doSomethingWithError:(NSError *__autoreleasing _Nullable *)error {
//做某事
NSLog(@"do something - start");
//做某事出错,设置错误
*error = [[NSError alloc] initWithDomain:@"test error" code:1234 userInfo:@{}];
/*
由于*error类型是NSError *__autoreleasing,所以编译器自动在这里加上了
[*error autoreleasing];
*/
//返回失败
return NO;
}
@end
为什么我们在.h头文件里写的NSError **
到.m文件里会自动变成NSError* __autoreleasing*
类型?
- 那是因为编译器看到
NSError
这种指向指针的指针作为参数的时候就默认给它加上__autoreleasing
的标识,为了延迟销毁在指针方法里创建的对象。
而在调用方,并没有显示地声明一个__autoreleasing
修饰的对象,这样传递的话就会类型不匹配(修饰符也属于类型定义),那为什么没有报错呢?
- 因为编译器会改写我们的改码:
NSError * __strong error;
NSError *__autoreleasing tmp = error;
[obj doSomethingWithError:&tmp];
error = tmp;
它会创建一个_autoreleasing
的临时变量,传给方法的实际上是这个临时变量,所以不会出现类型不匹配的警告。
鉴于以上编译器的行为,有的文章问告诉你,这个可以进行一定的优化:当你想要传递指针的指针的时候,就自己显式地申明一个__autoreleasing
,比如NSError * __autoreleasing error
。这样编译器就不用再隐式地帮你创建一个临时变量来做类型转换,可以提高一定的效率。
可以选择这么做,但是不是必要的,因为针对传递error这个情况不会持续频繁的动作,对app的实际影响很小。而ARC的出现的目的就是为了让程序员在写业务代码的时候更加专注入业务本身,所以就将手动内存管理简化为强引用和弱引用两个概念,并且编译器做了很多工作为了让程序员不要手写
__autoreleasing
。
以MRC的思想分析ARC模式下对象的内存释放
注释代表在MRC中的模拟运行过程
@autoreleasepool {
TestObj *__autoreleasing atObj;
TestObj *__weak wObj;
{
//创建一个强引用的obj1变量,这个TestObj对象的retainCount = 1
TestObj * obj1 = [[TestObj alloc] init];
//obj1赋值给atObj
atObj = obj1;
/*
由于atObj是__autoreleasing修饰的,相当于编译器会在此加上
[atObj retain];
[atObj autorelease];
由于只是autorelease,不是马上release,此时obj1引用的TestObj对象的retainCount = 2,
*/
//创建一个强引用的obj2变量,这个TestObj对象的retainCount = 1
TestObj * obj2 = [[TestObj alloc] init];
//obj2赋值给wObj
wObj = obj2;
/*
由于wObj是__weak修饰的,编译器不会在此加上retain和autorelease
此时obj2引用的TestObj对象的retainCount = 1,
*/
/*
编译器会对超出作用的变量所持有的对象调用release
obj1和obj2即将超出其作用域,所以再这里会自动调用release
[obj1 release];
[obj2 release];
执行完后,obj1引用的对象retainCount = 1,所以还未销毁
obj2引用的对象retainCount = 0,所以被销毁了
*/
}
//此时,超出了内部大括号的作用域,obj2引用的对象已经被销毁了,wObj也重新指向了nil
//obj1引用的对象还存活着,虽然obj1这个变量已经没有了
}
//执行到最后这个位置,超出了autoreleasepool,atObj1引用的对象的autorelease才被触发,才真正地调用了release,才被销毁。
ARC中不存在autorelease吗?
一个常见的误解是,在ARC中没有autorelease
,因为这样一个“自动释放”看起来好像有点多余。这个误解可能源自于将ARC的“自动”和autorelease
“自动”的混淆。其实你只要看一下每个iOS App的main.m
文件就能知道,autorelease
不仅好好的存在着,并且变得更fashion了:不需要再手工被创建,也不需要再显式得调用[drain]方法释放内存池。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
以下两行代码的意义是相同的。
NSString *str = [[[NSString alloc] initWithFormat:@"boom"] autorelease]; // MRC
NSString *__autoreleasing str = [[NSString alloc] initWithFormat:@"boom"]; // ARC
ARC有效时属性与修饰符的对照关系
所有权修饰符的使用注意
- 下面代码是有问题的:
__weak NSString *str = [[NSString alloc] initWithFormat:@""];
NSLog(@"%@", str);
虽然能编译,运行结果为null
,但是会发出警告
str
是个weak
指针,所以NSString
对象没有拥有者,在创建之后就会被立即释放。
- 使用修饰符的正确方式
这可能是很多人都不知道的一个问题,但却是一个特别要注意的问题。
苹果的文档中明确地写道:
You should decorate variables correctly. When using qualifiers in an object variable declaration,the correct format is:
ClassName * qualifier variableName;
也就是像下面这两种写法
NSString * __weak str = @"haha"; // 正确!
__weak NSString *str = @"haha"; // 错误!
但是错误写法在编译时并不会报错而且是正常运行的,这是因为苹果考虑到很多人会用错,所以在编译器这边贴心地帮我们忽略并处理掉了这个错误。虽然不报错,但是我们还是应该按照正确的方式去使用这些修饰符。
Other variants are technically incorrect but are “forgiven” by the compiler. To understand the issue, seehttp://cdecl.org/.
- 以下代码在ARC之前是可能会行不通的,因为在手动内存管理中,从
NSArray
中移除一个对象时,这个对象会发送一条release
消息,可能会被立即释放。随后NSLog()
打印该对象就会导致应用崩溃
id obj = [array objectAtIndex:];
[array removeObjectAtIndex:];
NSLog(@"%@", obj);
在ARC中这段代码是完全合法的,因为obj
变量是一个strong
指针,它成为了对象的拥有者,从NSArray
中移除该对象也不会导致对象被释放
@property
: 想长期拥有某个对象,应该用strong
,其他对象用weak
,其他基本数据类型依然用assign
- 两端互相引用时,一端用
strong
、一端用weak
栈中指针默认值为nil
无论是被strong
,weak
还是autoreleasing
修饰,声明在栈中的指针默认值都会是nil
。所有这类型的指针不用再初始化的时候置nil
了。虽然好习惯是最重要的,但是这个特性更加降低了 “野指针” 出现的可能性。
在ARC中,以下代码会输出null
而不是crash
- (void)myMethod {
NSString *name;
NSLog(@"name: %@", name);
}
ARC规则
- 不能使用
retain/release/retainCount/autorelease
- 不能使用
NSAllocateObject/NSDeallocateObject
- 必须遵守内存管理的方法命名规则
- 不能显示的调用
dealloc
,可以重写dealloc
,但是不能调用[super dealloc]
- 使用
@autoreleasepool
块来替代NSAutoreleasePool
- 不能使用区域(
NSZone
) - 对象型变量不能作为C语言结构体(
struct/union
)的成员 - 显示的转化“
id
”和“void*
”
ARC与__bridge
在Cocoa应用程序中,我们常常会使用到Core Foundation对象,例如CFArrayRef 或者
CFMutableDictionaryRef等等,ARC不会自动管理Core Foundation对象的生命周期,你必须根据Core
Foundation的内存管理规则来手动管理。
同时,我们经常又面临着CoreFoundation对象与Objective-C对象的转换,在转换中用到的关键字就是bridge,如果使用不好会造成内存泄露,或者crash,设置直接编译不通过。
Objective-C 与 C语言之间的转换
显式转换 id 和 void *
在MRC下,像以下代码这样将 id
变量强制转换成 void *
变量并不会出问题。
// MRC下
id obj = [[NSObject alloc] init];
void *p = obj;
更进一步,将改 void *
变量赋值给 id
变量中,调用其实例方法,运行时也不会有问题。
// MRC下
id 0 = p;
[o release];
但是以上代码在ARC下便会引起编译错误
id
型或对象型变量赋值给void *
或者逆向赋值时都需要进行特定的转换。如果只想单纯地赋值,则可以使用 “__bridge转换”。
OC指针与void *互相转换
使用 __bridge
- (void)OCAndVoidUse__bridgeInARC {
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)(p);
}
像这样,通过“__bridge转换
”,id
和 void *
就能够相互转换。但是转换为 void *
的 __bridge
转换,其 安全性与赋值给 __unsafe_unretained修饰符相近,甚至会更低 。如果管理时不注意赋值对象的所有者,就会 因悬垂指针而导致程序崩溃 。
注意:
此时指针变量p
并没有持有对象,因为__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]);
}
__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
修饰符的变量。
Foundation与Core Foundation对象之间的转换
- “Toll-free bridging 是ARC下Foundation和Core Foundation对象之间的桥梁”
- “CF和OC对象转化时只涉及对象类型不涉及对象所有权的转化”
两个框架的基本知识
Foundation
框架名是Foundation.framework,在Xcode新建工程时可以选择导入(其实会默认自动依赖好)。Foundation框架允许使用一些基本对象,如数字和字符串,以及一些对象集合,如数组,字典和集合,其他功能包括处理日期和时间、内存管理、处理文件系统、存储(或归档)对象、处理几何数据结构(如点和长方形)。这个框架中的类都是一些最基础的类。来自于这个框架的类名以NS开头。
Foundation框架提供了非常多好用的类, 比如:
NSString : 字符串
NSArray : 数组
NSDictionary : 字典
NSDate : 日期
NSData : 数据
NSNumber : 数字
Core Foundation
Core Foundation 对象主要使用在用C语言编写的Core Foundation 框架中,并使用引用计数的对象。在ARC无效时,Core Foundation 框架中的retain/release 分别是 CFRetain /CFRelease。
框架CoreFoundation.framework是一组C语言接口,它们为iOS应用程序提供基本数据管理和服务功能。
两者关系
Core Foundation 框架和 Foundation 框架紧密相关,它们为相同功能提供接口,但 Foundation
框架提供Objective-C接口。Foundation对象 和 Core Foundation对象间的转换,俗称为桥接。如果您将Foundation 对象和 Core Foundation 类型掺杂使用,则可利用两个框架之间的 “ Toll Free Bridging”。所谓的Toll-free bridging是说您可以在某个框架的方法或函数同时使用 Core Foundation 和 Foundation 框架中的某些类型。
很多数据类型支持这一特性,其中包括群体和字符串数据类型。每个框架的类和类型描述都会对某个对象是否为 Toll-free bridged,应和什么对象桥接进行说明。
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释放 内存管理
简单互相转换:__bridge
从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
可以实现Objective-C与C语言变量 和 Objective-C与Core Foundation对象之间的互相转换__bridge
不会改变对象的持有状况,既不会retain
,也不会release
__bridge
转换需要慎重分析对象的持有情况,稍不注意就会内存泄漏__bridge_retained
用于将OC变量转换为C语言变量 或 将OC对象转换为Core Foundation对象__bridge_retained
类似于retain
,“被转换的变量”所持有的对象在变量赋值给“转换目标变量”后持有该对象__bridge_transfer
用于将C语言变量转换为OC变量 或 将Core Foundation对象转换为OC对象__bridge_transfer
类似于release
,“被转换的变量”所持有的对象在变量赋值给“转换目标变量”后随之释放
有一个问题没解决
在CF对象转换为OC对象使用__bridge_transfer
修饰时,上面已经明确说了ARC来负责释放对象,已经把CF的控制权交给OC了。就说明编译器内部自动进行release操作,那么我再手动对aCFString
进行release操作时,表明我对对象进行了两次释放,这时候超出界限,应该会报crush。但是程序没有进行警告,也没有crush。为什么?????????
如下代码:
CFStringRef aCFString = CFStringCreateWithCString(NULL, "test", kCFStringEncodingASCII);
NSString *aNSString = (__bridge_transfer NSString *)aCFString;
CFRelease(aCFString);