https://www.mikeash.com/pyblog/friday-qa-2010-01-22-toll-free-bridging-internals.html
又过了一周,现在是周五的问答时间。这周的主题是toll-free bridging,由Jonathan Mitchell提问。
What It Is
我希望所有的读者已经知道什么是toll-free bridging,但是如果你不知道,这里有个总结。
Toll-free bridging,简称为TFB,是一种允许objective-c类和corefoundation类互相转换的机制。举个例子,NSString 和 CFString 被绑定,这意味着你可以将任何NSString看做CFString反之亦然。例子:
CFStringRef cfStr = SomeFunctionThatReturnsCFString();
NSUInteger length = [(NSString *)cfStr length];
大多数同时存在Cocoa和CoreFoundation的(但不是全部)类通过这样的方式关联。在这篇文章中将通过一个bridged类来阐述bridging。
Bridging From CF to ObjC
将一个CoreFoundation类关联到Objective-C的方法十分地简单。
每一个关联的类其实一个类簇,这意味这公开的接口类是一个虚类,实际的实用功能由私有的子类实现。CoreFoundation类是这些子类中的一个,与Objective-C对应的CoreFoundation类。其它的Objective-C类也可以独立存在。从外部看,他么都看起来一样,因为他们都有着共同的接口。
来看个具体的例子NSString。NSString是一个虚类。每一次你创建这个类时,你实际上获得的是它的一个子类实例。
这些子类中的一个是NSCFString。它是直接对应CFString的副本。CFString的开头是一个isa指针指向NSCFString类,这让它可以像一个Objective-C对象一样工作。
NSCFString实现NSString的方法来工作。有两种可能的方法。一种方法是通过封装CoreFoundation的方法。另一种方法是自行实现对应的方法。事实上,代码的实现可能是这两种方法的结合。
从CF到ObjC这个方向来看,绑定的机制很简单甚至根本不存在。CFString对象恰巧是NSString类的子类NSCFString的一个实例。许多实现只是碰巧调用CoreFoundation来让他们工作。
Bridging from ObjC to CF另一个方向上的关联稍微复杂。这是因为需要转换的可能是任何一种TFB Objective-C类实例,甚至用户自己创建的类。你只需要写一个NSString的子类,你就有了这样一个类。同时这些自定义的类仍然可以对CoreFoundation函数透明的工作。你可以对你的自定义子类调用callCFStringGetLength,它将调用你的length方法并返回。
正如看到的,并没有什么特殊的魔法来让其正常工作。只是简单地进行了转换。CFStringGetLength看起来像这样
CFIndex CFStringGetLength(CFStringRef str) {
CF_OBJC_FUNCDISPATCH0(__kCFStringTypeID, CFIndex, str, "length");
__CFAssertIsString(str);
return __CFStrLength(str);
}
第一行是一个丑陋的宏转换它隐藏着TFB如何工作。它检查对象的isa是否是一个NSCFString。如果不是,它将不是真正的CFString,而是其它的某个Objective-C类。这种情况时,CoreFoundation并不知道如何查询length,因此它只是将消息发送给对象并将结果返回。如果它是一个“真的”CFString,它只要简单地调用__CFStrLength。
简单来说:每一个TFB的CoreFoundation函数首先检查被传递进来的对象是不是一个“真的”CoreFoundation对象还是一个纯的Objcetive-C对象。如果它是一个纯的Objective-C,它简单的将信号传递给Objcetive-C。另一种情况,它进行正常地处理。这就是为什么我说它进行了简单的转换:每一个函数方法在开头都进行这样的检测来确保TFB正常工作。
这样的实现导致了一个有趣结果。假设你碰巧将一个CFArray传递给了CFStringGetLength。isa检查发现它不是一个NSCFString,所以它像CFArray传递信号,结果是你得到一个这样的错误:
-[NSCFArray length]: unrecognized selector sent to instance 0x100108e50
从CoreFoundation返回了Objcetive的错误。
Bridging Basic Behavior
显示关联通过这种方式工作。但TFB还有一个有趣的部分:所有的对象和类具有一样的基本行为。本质上,NSObject关联到CFType。作为最普遍的例子是,Objective-C对象可以应用CFRetainany,CoreFoundation对象可以应用retain。和其它关联一样,如果在Objective-C子类中你重写了retain,CFRetain将调用被重载的方法。这不仅仅适用于内存管理,同样适用所有的CFType函数,如CFCopyDescription;以及所有的NSObject方法,如performSelector:withObject:afterDelay:
为了关联到Objective-C,任意的CoreFoundation对象的起始部分指向Objective-C类。在Objectove-C中有相关联类的CoreFoundation类指向关联的类,对于没有的则指向一个特殊的__NSCFType类。所有这些类都是NSObject的子类,他们天生就具有CoreFoundation中对应类的行为。对应CoreFoundation中的方法,他们可能重写或者直接调用CoreFoundation的方法进行封装。
为了关联到CoreFoundation,将进行特别的绑定。CFRetain和其它CF类型的函数的首行将检查对象是不是一个“真的”CF对象。如果是,它执行正常的操作,如果不是它将信号分发给对象的Objective-C函数进行工作。
Creating Bridged Classes
我希望这一部分的标题没有给大家带来希望,因为你无法实现这样的工作。现在我们知道了关联是如何工作的,可以很明显的知道原因。你不能关联一个不存在的CF类因为这需要在CF方进行大量的协同工作。每一个方法需要在起始处检查传递进来的对象,你不能在CF方法中添加原本不存在的东西。你不能创建新的CF类,因为apple没有将其开放。
Conclusion
Now you know the basics of how toll-free bridging works. If you're interested in the deeper technical details of just what the dispatching code looks like and how it works, check out ridiculous_fish's article on bridging.
That brings this week's edition to a close, but come back next week for yet another one. Friday Q&A is, of course, driven by user submissions. If you have an idea you would like to see covered in this space, send it in!