Toll Free Bridging

一.Toll-free bridging介绍

Toll-free bridging,简称为TFB,是一种允许某些ObjC类与其对应的CoreFoundation类之间可以互换使用的机制。比如 NSString与CFString是桥接(bridged)的, 这意味着可以将任意NSString当做CFString使用,也可以将任意的CFString当做NSString使用。如下:

NSString ocString = [[[NSString]] stringWithString:@"pants"];
CFIndex length = CFStringGetLength((CFStringRef)ocString);CFStringRef cfString = CFStringCreateWithCString(NULL,"pants",kCFStringEncodingASCII);
NSUInteger length = [(NSString *)cfString length];

大部分(但不是全部)同时在Cocoa和CoreFoundation中存在的类是以这种方式桥接的,除NSString之外,还有NSArray与CFArray,NSDictionary与CFDictionary等。
TollFreeBridged中列出了可以toll-free bridged的类和non toll-free bridged的类。

二.从CoreFoundation类桥接到ObjC类

从CoreFoundation类桥接(bridge)到Objective-C类(即CFString可以当做NSString处理)的方法很简单。每个桥接类(bridged class)实际上是一个类簇(class cluster),这意味着公共类(public class)是抽象的,核心功能由私有子类(private subclasses)实现。CoreFoundation类的内存结构(memory layout)与其中一个私有子类相同,而这个子类仅仅作为CoreFoundation类的Objective-C对应类存在,其他Objective-C私有子类可能以其他方式独立存在,但因为所有私有子类都有相同的公共接口,所以从外界看来,它们看起来一样而且以相同的方式工作。

具体来说,NSString是一个抽象类,每当你创建一个NSString实例,实际上是创建的NSString的一个私有子类实例。其中一个私有子类就是NSCFString,其是CFString类的在ObjC中的对应类。CFString中的第一个元素就是isa指针,指向NSCFString类,所以其可以作为一个Objective-C对象工作。

NSCFString实现了作为NSString需要的所有方法,实现这些方法有两种方式,一种是作为桩函数(stub)实现,仅仅通过它调用CoreFoundation中的对应函数;另一种方法是实现与CoreFoundation中函数相同的方法。在实际代码中,这两种方法共同存在。

从这个方向看,桥接机制很简单,就像不存在一样。CFString只是NSCFString类的一个实例,而NSCFString是NSString的子类,实现了作为NSString的所有方法,很多的实现只是直接调用CoreFoundation的实现。

三.从ObjC类桥接到CoreFoundation类

这个方向的桥接就会很复杂,这是因为一个TFB Objective-C类的实例可能是其任意一个子类的实例,还可能是应用程序自定义的子类,但这些可能为任意子类的实例需要在CoreFoundation函数中透明使用,即你可以在任意一个NSString实例(很可能不是NSCFString)上使用CFStringGetLength获得字符串的长度。实际上如果这是NSString实例恰好是NSCFString,则直接调用CoreFoundation的函数,如果不是则直接向其发送-length消息,以Objective-C的方式处理。
这是怎么实现的呢,现在来看下CFStringGetLength函数的实现

   CFIndex CFStringGetLength(CFStringRef str) {CF_OBJC_FUNCDISPATCH0(__kCFStringTypeID, CFIndex, str, "length");__CFAssertIsString(str);return __CFStrLength(str);}

第一行的CF_OBJC_FUNCDISPATCH0宏可以处理Objective-C对象,其实现如下:

   // Invoke an ObjC method, return the result#define CF_OBJC_FUNCDISPATCH0(typeID, rettype, obj, sel) \if (__builtin_expect(CF_IS_OBJC(typeID, obj), 0)) \{rettype (*func)(const void *, SEL) = (void *)__CFSendObjCMsg; \static SEL s = NULL; if (!s) s = sel_registerName(sel); \return func((const void *)obj, s);}

首先看第一行if (__builtin_expect(CF_IS_OBJC(typeID, obj), 0)),__builtin_expect()只是gcc的分支预测标识,表示我们希望CF_IS_OBJC(typeID, obj)返回false。而CF_IS_OBJC做了什么呢?

CF_INLINE int CF_IS_OBJC(CFTypeID typeID, const void *obj) {return (((CFRuntimeBase *)obj)->_isa != __CFISAForTypeID(typeID) && ((CFRuntimeBase *)obj)->_isa > (void *)0xFFF);}

这部分代码检查obj是否为Objective-C对象,首先检查其isa指针是否指向了特定的CF类型,如果不是则检查isa指针是否大于0xFFF,如果大于,则说明此对象为Objective-C对象。
而如何根据typeID(__kCFStringTypeID)找到其对应的类(NSCFString)呢,来看__CFISAForTypeID的实现。

extern struct objc_class *__CFRuntimeObjCClassTable[];
CF_INLINE void *__CFISAForTypeID(CFTypeID typeID) {return (void *)(__CFRuntimeObjCClassTable[typeID]);
}

__CFISAForTypeID只是以typeID为索引在__CFRuntimeObjCClassTable中查找。__CFRuntimeObjCClassTable是一个CFType与ObjC类的匹配表,在CoreFoundation初始化时,所有桥接类在此表中赋值,而其他项赋值为哑值(NSCFType)。所以以__kCFStringTypeID为索引在此表中可找到NSCFString。如果对象的isa不是指向NSCFString,则说明其不是CFString。

如果经CF_IS_OBJC判断对象不是CF类型,则其为其他Objective-C对象,这时会走Objecitive-C的消息机制,通过__CFSendObjCMsg函数向对象发送-length消息,__CFSendObjCMsg实际上是objc_msgSend_rtp()。

简单来将,处理TFB类的每个CoreFoundation函数首先检查传递的对象是否真正的CoreFoundation类型(特定的CoreFoundation类型,如CFStringGetLength则需要对象是CFString,而不能是其它CoreFoundation对象),如果是纯Objective-C类型,则简单的调用Objective-C类的消息处理。如果是CoreFoundation类型,则以CoreFoundation通常的方式处理。

这种实现有个有趣的后果,如果我们对一个CFArray使用CFStringGetLength函数会有什么效果呢?isa检查为检测到CFArray不是一个真正的CFString,所以会走向Objective-C分支,最终我们得到一个错误

 -[NSCFArray length]: unrecognized selector sent to instance 0x100108e50

以上的代码是CF-368.27中的,在最近的CF-635中实现有变化,不过原理相同。CF635中的CF_IS_OBJC实现

#define __CFRuntimeClassTableSize 1024extern uintptr_t __CFRuntimeObjCClassTable[];
CF_INLINE uintptr_t __CFISAForTypeID(CFTypeID typeID) {return (typeID < __CFRuntimeClassTableSize) ? __CFRuntimeObjCClassTable[typeID] : 0;
}CF_INLINE Boolean CF_IS_OBJC(CFTypeID typeID, const void *obj) {if (CF_IS_TAGGED_OBJ(obj)) return true;uintptr_t cfisa = ((CFRuntimeBase *)obj)->_cfisa;if (cfisa == 0) return false;
#if 0// Temporarily disabled
#if __LP64__if (cfisa < 0x10000000UL) {CFLog(kCFLogLevelWarning, CFSTR("*** Warning: CF tested pointer %p for objectness and found its isa pointer to be bogus (%p)"), obj, cfisa);return false;}
#elseif (cfisa < 0x1000UL) {CFLog(kCFLogLevelWarning, CFSTR("*** Warning: CF tested pointer %p for objectness and found its isa pointer to be bogus (%p)"), obj, cfisa);return false;}
#endif
#endifif (cfisa == (uintptr_t)__CFConstantStringClassReferencePtr) return false;uintptr_t type_isa = (uintptr_t)(typeID < __CFRuntimeClassTableSize ? __CFRuntimeObjCClassTable[typeID] : 0);if (cfisa == type_isa) return false;return true;
}

四.基本行为的桥接

以上描述的是显式的桥接的工作方式,对于TFB还有更有趣的一方面,所有对象都共享的基本行为实际上是对所有类桥接的,实质上NSObject可以桥接到CFType。一个最普遍的例子,可以对任意Objective-C对象使用CFRetain,也可以对任意CoreFoundation对象使用retain方法,与其他桥接相同,如果你在Objective-C代码中重写了retain方法,则CFRetain会调用你重写的retain方法。这种桥接行为不仅适合于内存管理方法,对任意的CFType函数(如CFCopyDescription),任意的NSObject方法(如performSelector:withObject:afterDelay:)都适用。

CoreFoundation对象桥接到ObjC对象时,因为任何CoreFoundation对象的首字段(isa)都指向一个ObjC类,对于桥接类isa指向其在ObjC中的对应类,对于非桥接类isa指向一个特殊的__NSCFType(__CFRuntimeObjCClassTable表中的默认值),所有这些类都是NSObject的子类,所以它们继承了NSObject类的所有行为。对于映射到对应CoreFoundation类的方法,则是直接调用CoreFoundation侧的实现。

当ObjC类桥接到CoreFoundation时,和上面描述的机制相同,CFRetain以及其他CFType函数首先检查此对象是否真正的CoreFundation对象(应该是仅检测是否CoreFundation对象,而不是桥接类中检测是否特定的CoreFundation对象)或者是其他Objecitive-C类。如果是CF对象,则调用CoreFoundation的实现,如果不是,则使用消息模式让Objective-C侧处理。

五.ARC与Toll-free bridging

ARC只能管理Objective-C类型,对于Objective-C对象我们不用手动进行retain/release/autorelease的操作,ARC会自动为我们完成。但对于CoreFoundation对象则仍然需要我们手动管理。因为存在所有权(ownership)不明确的问题,所以ARC禁止Objecitive-C对象指针与其它指针类型进行标准cast转换,对CoreFoundation对象指针也是如此。下面一行代码,则手动内存管理时是很典型的转换,但在ARC下却不能编译。

id obj = (id)CFDictionaryGetValue(cfDict, key);

为了使其在ARC环境下可编译,需要使用特殊的casting标注告诉ARC关于所有权(ownership)的语义。这些标注为__bridge,__bridge_transfer和__bridge_retained。
最简单的是__bridge,这是没有所有权影响的直接转换。ARC接收此值,像通常那样处理。

id obj = (__bridge id)CFDictionaryGetValue(cfDict, key);

另外两个casting标注分别向ARC系统转移所有权和从ARC系统获取所有权,在以下情况下使用。以下面代码为例

    NSString *value = (NSString *)CFPreferencesCopyAppValue(CFSTR("someKey"), CFSTR("com.company.someapp"));[self useValue: value];[value release];

在使用ARC时,如果使用__bridge,则代码需修改为

    NSString *value = (__bridge NSString *)CFPreferencesCopyAppValue(CFSTR("someKey"), CFSTR("com.company.someapp"));[self useValue: value];

CFPreferencesCopyAppValue操作需要一个与之匹配的release操作,在初始化value时,ARC系统使用了retain,当value不再使用时,ARC系统会自动对value release,以与之前的retain操作配对。但并没有与CFPreferencesCopyAppValue操作配对的release,所以此对象泄漏。
__bridge_transfer可以用来解决这个问题,__bridge_transfer在将指针值传递给ARC时,同时传递了所有权(transfers ownership)。当__bridge_transfer用来标注cast时,它告诉此对象已经被retain过了,不需要再次retain。既然ARC获取到了所有权,那么它仍然会在使用完毕后release。代码如下:

NSString *value = (__bridge_transfer NSString *)CFPreferencesCopyAppValue(CFSTR("someKey"), CFSTR("com.company.someapp"));[self useValue: value];

Toll-free bridging可以在两个方向进行,当从ObjC对象向CoreFundation桥接时代码如下:

   CFStringRef value = (__bridge CFStringRef)[self someString];UseCFStringValue(value);

这种情况下,ARC不会管理value的声明周期,所以在赋值操作完成之后,UseCFStringValue使用value之前,此对象就会被立即释放,这样在UseCFStringValue使用该对象时就会因访问已释放的对象而crash。通过使用__bridge_retained可以让ARC将所有权转出,既然ARC已将所有权转出,所以其不会再释放此对象,这时UseCFStringValue就可以安全使用了,使用完毕后由CoreFundation负责释放,如下

    CFStringRef value = (__bridge_retained CFStringRef)[self someString];UseCFStringValue(value);CFRelease(value);

这些cast标注不仅可用于toll-free bridging,当需要将Objecitve-C对象指针不当做ObjC对象存储时都可以使用,可以使用此方式将ObjC对象指针与void *或其他指针任意转换。如下:

    NSDictionary *contextDict = [NSDictionary dictionary...];[NSApp beginSheet: sheetWindowmodalForWindow: mainWindowmodalDelegate: selfdidEndSelector: @selector(sheetDidEnd:returnCode:contextInfo:)contextInfo: (__bridge_retained void *)contextDict];- (void)sheetDidEnd: (NSWindow *)sheet returnCode: (NSInteger)code contextInfo: (void *)contextInfo{NSDictionary *contextDict = (__bridge_transfer NSDictionary *)contextInfo;if(code == NSRunStoppedResponse)...}

总结如下:

  • __bridge仅仅在ARC和non-ARC之间转移指针,并不转移所有权。
  • __bridge_transfer将一个non-Objective-C指针移动到Objective-C指针,并转移所有权,这样ARC会代替你release它.
  • __bridge_retained将一个Objective-C指针指针移动到一个non-Objective-C指针,同样转移所有权,这样ARC将不会release它,编程者负责后续使用CFRelease或其他函数释放。

六.创建桥接类

不是所有的CoreFoundation类是可以桥接的,从上述讨论的TFB的实现方式可见,做到双向桥接需要CoreFoundation类的每个函数都要首先判断该对象是否真正的CF对象,若不是CF对象,则以Objective-C对象处理。因为CoreFoundation类由苹果控制,所以若其本身未实现此功能,我们没有办法手动添加,所以不能创建双向桥接的TFB类。
但由于任何CoreFoundation的首字段都是isa,我们可以把其当做objective-C对象处理,所以单向的桥接时可以实现的。我们可以在Objective-C侧创建新的类以匹配CoreFoundation中的类。在HowToCreateTollFreeBridgedClass中讨论了如何创建新的TollFreeBridged类,在http://code.google.com/p/corefoundation-bridging中收集了开发者创建的TollFreeBridged类。

参考:
Friday Q&A 2010-01-22: Toll Free Bridging Internals
Friday Q&A 2011-09-30: Automatic Reference Counting
Bridge
HowToCreateTollFreeBridgedClass
TollFreeBridging
TollFreeBridged
corefoundation-bridging
Cocoa Core Competencies - Class cluster
Core Foundation Design Concepts - Toll-Free Bridged Types
CFType Reference
CF-368.27 CFInternal.h
CF-635 CFInternal.h
CF-368.27 CFString.c

本文出自 清风徐来,水波不兴 的博客,转载时请注明出处及相应链接。

本文永久链接: http://www.winddisk.com/2012/08/16/toll-free-bridging/

NSString NSCFString isMemberOfClass 遇到的相关的问题相关推荐

  1. NSString使用stringWithFormat拼接的相关知识

    保留2位小数点: //.2代表小数点后面保留2位(2代表保留的数量) NSString *string = [NSString stringWithFormat:@"%.2f",M ...

  2. IOS开发-表视图LV3导航控制器

    学到这里感觉有点难了,其实这篇文章再草稿箱里放了好久了~ 最近对于学习的热情下降了.这不行-抓紧学习走起! 在这一章节的学习中主要针对导航控制器及表视图来建立多视图的应用, 首先要了解一些概念-- 1 ...

  3. iOS 关于手机权限的检查与获取

    手机通讯录权限: /**  *  检测权限并作响应的操作  */ - (void)checkAuthorizationStatus:(UISwitch *)sender {          swit ...

  4. iOS地图定位(Map)

    1.地图的简介 在移动互联网时代,移动app能解决用户的很多生活琐事,比如     导航:去任意陌生的地方     周边:找餐馆.找酒店.找银行.找电影院     手机软件:微信摇一摇.QQ附近的人. ...

  5. 转-iOS开发系列--地图与定位

    来自: http://www.cnblogs.com/kenshincui/p/4125570.html#autoid-3-4-0 概览 现在很多社交.电商.团购应用都引入了地图和定位功能,似乎地图功 ...

  6. iOS学习笔记19 地图(一)定位CoreLocation

    ###一.定位介绍 现在很多社交.电商.团购应用都引入了地图和定位功能,似乎地图功能不再是地图应用和导航应用所特有的.的确,有了地图和定位功能确实让我们的生活更加丰富多彩,极大的改变了我们的生活方式. ...

  7. iOS开发--地图与定位

    iOS开发--地图与定位 概览 现在很多社交.电商.团购应用都引入了地图和定位功能,似乎地图功能不再是地图应用和导航应用所特有的.的确,有了地图和定位功能确实让我们的生活更加丰富多彩,极大的改变了我们 ...

  8. iOS 动画绘制线条颜色渐变的折线图

    效果图 .................... 概述 现状 折线图的应用比较广泛,为了增强用户体验,很多应用中都嵌入了折线图.折线图可以更加直观的表示数据的变化.网络上有很多绘制折线图的demo,有 ...

  9. iOS-Senior20-Map定位

    定位功能: 定位是一个很常用的功能,如一些地图软件打开之后如果用户允许软件定位的话,那么打开软件后就会自动锁定到当前位置,如果用户手机移动,那么当前位置也会随着变化.要实现功能需要使用CoreLoca ...

  10. Ios开发之定位CLLocationManager

    Ios中的定位功能是通过 Core Location框架实现的.它和地图开发框架是相互独立的.在Core Location中主要实现了定位和地理编码的功能! 下面我们就来介绍一下它的属性,方法和代理方 ...

最新文章

  1. jira以及jira API简单介绍
  2. memcpy函数的实现
  3. 河南大学计算机组成原理,河南大学计算机组成原理考点
  4. codeforces 58A-C语言解题报告
  5. 小米2019开发者大会:核心技术集体亮相,推动下一代超级互联网
  6. Linux高可用负载均衡 集群理解
  7. [Google] 再见 SharedPreferences 拥抱 Jetpack DataStore
  8. 搭建机器人电控系统——通信协议——IIC通信原理及其实例(库函数+模拟IO口)
  9. 如何免费在本地播放flv格式的视频
  10. LCA 最近公共祖先 (倍增算法)
  11. idea一直indexing JDK卡死解决方案
  12. Win11-GTX3060-配置Pytorch GPU
  13. QQ、微信可以正常访问通讯,浏览器无法打开网页
  14. [剑指Offer]斐波那契数列、跳台阶、兔子数量问题(递归、非递归)(Java)
  15. 【练习】面向对象系列(002)——双色球
  16. 思维导图-------java-IO流知识结构梳理
  17. python怎么查看安装了哪些库_怎么查看python安装的第三方库
  18. 00——Phsical Design初稿2021-09-06上午
  19. 考勤助手——时序图设计
  20. 2020-07-28 httprunner+locusts+python接口测试框架

热门文章

  1. 通俗易懂的UART协议帧格式
  2. python连接Oracle数据库报错Cannot locate a 64-bit Oracle Client library问题
  3. 九种机器学习模型的简单介绍
  4. php ctype xdigit,php ctype_digit() 函数介绍
  5. 基于SAML2.0单点登录的实现(JAVA)
  6. PCB走线电感、导线电感、过孔电感 计算公式
  7. 串口、Modbus通信协议
  8. QtQuick 技巧 2
  9. edge便捷截取长图
  10. PS改变证件照的背景颜色