NSString NSCFString isMemberOfClass 遇到的相关的问题

来源:互联网 发布:战无不胜天罡进阶数据 编辑:程序博客网 时间:2024/05/01 22:27

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;    }#else    if (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#endif    if (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: sheetWindow       modalForWindow: mainWindow        modalDelegate: self       didEndSelector: @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/

原创粉丝点击