Foundation对象与Core Foundation对象间的转换:Toll-Free Birdge

来源:互联网 发布:淘宝卖家留言在哪里 编辑:程序博客网 时间:2024/06/04 23:35

作者:代培
地址:http://blog.csdn.net/dp948080952/article/details/53366622
转载请注明出处

作为iOS开发人员,却对iOS开发中用到的框架分不清楚,比如说Core Foundation和Foundation,只是知道在这两者之间需要用__bridge进行转换,却没有去具体的研究一下。今天就记录一下这二者之间的区别,和二者之间该如何进行转换。

Foundation

苹果官方对于Foundation的介绍中是这样说的:Foundation框架定义了Objective-C的基础类。除了提供了一系列有用的基础类,它还包含了许多在Objective-C语言中没有包含的如何实现一些功能的范例。
原文如下:

The Foundation framework defines a base layer of Objective-C classes. In addition to providing a set of useful primitive object classes, it introduces several paradigms that define functionality not covered by the Objective-C language.

Foundation框架设计的目标有:

  • 提供一个小的基础实用的类的集合
  • 引出事物的一贯规律例如释放来让软件开发更加容易
  • 支持单一码(Unicode)的字符串,对象的持有和分发
  • 提供系统级的独立以提高移植性

Foundation的类根植于NSObject,所以Objective-C中最基础的类也是NSObject。

详细请看苹果的文档:https://developer.apple.com/reference/foundation

Core Foundation

Core Foundation是由一系列概念上起源于Objective-C为基础的Foundation的接口组成却是用C语言来实现的库。这句话读起来非常拗口,简单来说就是Core Foundation是用C语言实现的库,但他的接口起源于Foundation框架。当我第一次看到这句话时我觉得很惊讶,我一直觉得Core Foundation用C语言实现,应该更基础一些,不曾想到Core Foundation却是起源于Foundation。
Core Foundation使得在OS X不同框架和库之间共享代码成为可能,应用程序、库和框架可以在他们的扩展接口中使用Core Foundation的类型,因此他们可以通过Core Foundation对象互相交换数据。
同时Core Foundation也在特定的服务和Cocos’s Foundation框架之间提供免费桥(toll-free bringing)用以互相转换,免费桥可以让你在函数的参数中使用Cocoa对象去代替Core Foundation对象,当然反之亦然。
一些Core Foundation的类型和函数是那些在不同操作系统上有不同实现的抽象,所以使用Core Foundation的接口更容易在不同平台移植。
Core Foundation还有一个优点是应用开发中的国际化的支持。它的字符串对象在所有的OS X系统都是一致的。实现这个的基础就是一个类型CFString,一个代表一串16位Unicode的字符的实例。一个CFString对象灵活的保留了字符所需信息的同时又在一个足够低的等级保证了在所有编程接口中的通用。

总的来说Core Foundation有如下优点:

  • 在不同框架之间共享代码
  • 实现不同系统之间的可移植性
  • 对国际化的支持

详细请看苹果的文档:https://developer.apple.com/library/content/documentation/CoreFoundation/Conceptual/CFDesignConcepts/CFDesignConcepts.html#//apple_ref/doc/uid/10000122i

我找了一张图能够明更清晰的看出Core Foundation和Foundation之间的关系

Core Foundation是不在苹果的Cocoa中的,是属于Core Services层更加底层的框架,而Foundation是Cocoa中的框架,是用Objective-C写成的框架,也是Objective-C语言的基础框架,Foundation是依赖于Core Foundation的。他们之间有许多数据类型是可以互换的,这类能够被互换的数据类型也被叫做toll-free bridged data types,因为这种转换不会额外消耗CPU资源,所以叫做免费桥。但是不是所有的数据类型都是toll-free bridged,即使他们的名字看起来好像是那样,比如NSRunLoopCFRunLoopNSBundle和CFBundleNSDateFormatterCFDataFormatter
通常Foundation中的对象以NS开头,取自NeXTSTEP的首字母,而Core Foundation中的对象以CF开头,取自Core Foundation首字母。

这里说到了Cocoa,如果让我解释什么是Cocoa,我还真的说不出来,苹果官方文档中是这样定义的:

Cocoa and Cocoa Touch are the application development environments for OS X and iOS, respectively.

Cocoa和Cocoa Touch都包含了Objective-C的运行时和两个核心框架,Cocoa包含的框架是Foundation和AppKit,用于开发运行在OS X(现在应该说是macOS)上的程序;Cocoa Touch包含的框架是Foundation和UIKit,用于开发运行在iOS上的程序,当然这只是核心框架,除了这些,还有其他一系列框架。

如何转换两种对象

Core Foundation中有两个函数可以用来转换:CFBridgingRetainCFBridgingRelease,在Objective-C中有三个关键字:__bridge__bridge_retained__bridge_transfer,下面看一下具体的用法。

  • __bridge可以实现Objective-C对象和Core Foundation对象的相互转换,而且不会进行所有权的转换。
  • __bridge_retainedCFBridgingRetain只能将Objective-C对象转换为Core Foundation对象,同时将所有权交给CF对象
  • __bridge_transferCFBridgingRelease只能将非Objective-C对象转换为Objective-C对象,同时将所有权交给ARC。

下面看一下具体代码(ARC环境),首先看一下__bridge的用法:

- (void)test1 {    CFMutableStringRef cfString;    {        NSMutableString *string = [[NSMutableString alloc] init];        cfString = (__bridge CFMutableStringRef)string;        NSLog(@"%sretain count:%ld", __FUNCTION__, CFGetRetainCount(cfString));    }    NSLog(@"%sretain count:%ld", __FUNCTION__, CFGetRetainCount(cfString));}

这段代码运行的结果是先打印:

-[ViewController test1]retain count:1

然后运行到下面一个NSLog时会报EXC_BAD_ACCESS的错误。
我们来分析一下,string申明并初始化以后引用计数为1,用__bridge转换后cfString也指向该对象,但是__bridge是没有做所有者变化的操作,所以此时引用计数还是1,当到达第二个NSLog时,超出string作用域,因为在ARC环境,string所指向对象引用计数减1,该对象引用计数为0被释放,此时又去访问这块内存,所以会报野指针的错误。

- (void)test2 {    NSMutableString *string;    CFMutableStringRef test;    {        CFMutableStringRef cfString = CFStringCreateMutable(kCFAllocatorDefault, 1);        string = (__bridge NSMutableString *)cfString;        test = cfString;        NSLog(@"%sretain count:%ld", __FUNCTION__, CFGetRetainCount(cfString));    }    NSLog(@"%sretain count:%ld", __FUNCTION__, CFGetRetainCount(test));}

这段代码运行结果是:

-[ViewController test2]retain count:2-[ViewController test2]retain count:2

cfString声明并实例化,cfString所指对象引用计数为1,然后__bridge转换,string指向这个内存,因为在ARC环境下,string的修饰符默认是__strong,所以他们所指向的对象引用计数加1,引用计数为2,当超出作用域后,因为ARC对Core Foundation对象无效,所以引用计数不会变化,所以最后打印仍然为2。(注:因为ARC环境下无法获取OC对象的引用计数,test变量只是为了在cfString作用域外访问这个对象的引用计数,不会对这个对象的引用计数产生影响。)

然后看一下OC对象到CF对象用的__bridge_retainedCFBridgingRetain,其实从名字就能看出一些意思,在转换的过程中,在进行retain操作。下面代码中的转换可以换成cfString = (CFMutableStringRef)CFBridgingRetain(string);

- (void)test3 {    CFMutableStringRef cfString;    {        NSMutableString *string = [[NSMutableString alloc] init];        cfString = (__bridge_retained CFMutableStringRef)string;        NSLog(@"%sretain count:%ld", __FUNCTION__, CFGetRetainCount(cfString));    }    NSLog(@"%sretain count:%ld", __FUNCTION__, CFGetRetainCount(cfString));}

这段代码的运行结果如下:

-[ViewController test3]retain count:2-[ViewController test3]retain count:1

首先申明并实例化一个string对象,引用计数为1,然后__bridge_retained转换为CF对象,引用计数加1变成为2,超出作用域后OC对象被释放,引用计数减1,打印结果为1。
这里和test1做比较,从OC转到CF的时候,很明显__bridge_retained要好一些,可以防止野指针的错误。

最后是__bridge_transferCFBridgingRelease,从CF提供的方法也可以看出在bridge转换同时执行release操作,不知道为何OC中的关键字要命名为__bridge_transfer,从名字很难看出这个转换的用法,与CF的命名一致为__bridge_release或许会更好一些,也正好能与__bridge_retained相对应。

- (void)test4 {    NSMutableString *string;    CFMutableStringRef test;    {        CFMutableStringRef cfString = CFStringCreateMutable(kCFAllocatorDefault, 1);        test = cfString;        string = (__bridge_transfer NSMutableString *)cfString;        NSLog(@"%sretain count:%ld", __FUNCTION__, CFGetRetainCount(cfString));    }    NSLog(@"%sretain count:%ld", __FUNCTION__, CFGetRetainCount(test));}

这段代码的运行结果为:

-[ViewController test4]retain count:1-[ViewController test4]retain count:1

首先声明并实例化一个CF对象,通过__bridge_transfer转换为OC对象,并对其引用计数减1,ARC自动再对其加1,第一次引用计数为1,在花括号外,仍然在OC对象的作用域,引用计数仍然为1。这里的过程就是苹果文档中所说的将所有权交给了ARC,我们看到在这个转换之后,这个对象的内存管理工作完全交给了ARC,不会出现任何错误,而对比test2中的代码,在函数结束前引用计数仍然是2,所以当函数结束后OC对象自动release,引用计数变为1,这就导致了内存泄露,由此能够看出当CF转换为OC对象时使用__bridge_transfer会更好。

Toll-Free Bridged的类型

下面是苹果文档中给出的可以通过免费桥转换的类型

Core Foundation type Foundation class Availability CFArrayRef NSArray OS X v10.0 CFAttributedStringRef NSAttributedString OS X v10.4 CFCalendarRef NSCalendar OS X v10.4 CFCharacterSetRef NSCharacterSet OS X v10.0 CFDataRef NSData OS X v10.0 CFDateRef NSDate OS X v10.0 CFDictionaryRef NSDictionary OS X v10.0 CFErrorRef NSError OS X v10.5 CFLocaleRef NSLocale OS X v10.4 CFMutableArrayRef NSMutableArray OS X v10.0 CFMutableAttributedStringRef NSMutableAttributedString OS X v10.4 CFMutableCharacterSetRef NSMutableCharacterSet OS X v10.0 CFMutableDataRef NSMutableData OS X v10.0 CFMutableDictionaryRef NSMutableDictionary OS X v10.0 CFMutableSetRef NSMutableSet OS X v10.0 CFMutableStringRef NSMutableString OS X v10.0 CFNumberRef NSNumber OS X v10.0 CFReadStreamRef NSInputStream OS X v10.0 CFRunLoopTimerRef NSTimer OS X v10.0 CFSetRef NSSet OS X v10.0 CFStringRef NSString OS X v10.0 CFTimeZoneRef NSTimeZone OS X v10.0 CFURLRef NSURL OS X v10.0 CFWriteStreamRef NSOutputStream OS X v10.0

这里有一个细节值得注意的是对于Core Foundation苹果用的是type这个词,而Foundation用的是class,我们看一下Core Foundation中CFAttributedStringRef的定义:typedef struct CF_BRIDGED_MUTABLE_TYPE(NSMutableString) __CFString * CFMutableStringRef;实际上CFMutableStringRef是一个结构体的指针,在C语言中是没有class这种东西的,而NSAttributedString是OC中的一个class,但实际上他们二者在内存中是没有区别的,不然无法做到互换。

实际上class也是struct objc_class的指针

总结

其实苹果文档中所说的对所有权的操作就是对引用计数的操作,__bridge就是简单的做一个转换,__bridge_retained就是在转换的同时进行了retain操作(将引用计数加1),__bridge_transfer就是在转换的同时进行了release操作(将引用计数减1)。
我当时有一点很疑惑的就是为何__bridge_retained为何只能用于OC到CF,而__bridge_transfer只能用于CF到OC。根本原因是ARC只对OC对象起作用对CF对象不起作用,所以在ARC的环境下尽量使用__bridge_retained__bridge_transfer能更好的避免造成内存泄露或是野指针的问题。

0 0
原创粉丝点击