前面说了,ObjC的面向对象机制是采用SmallTalk的消息发送的设计,决定具体哪个消息被调用是在运行的时候决定的,而不是像C++那样在编译的时候就确定了。作为和ObjC并驾齐驱的Swift,为了和ObjC能够互联互通,就必须也要能够支持ObjC这种消息发送的设计。实际上在Xcode中,你可以在项目里面混合使用Swift和ObjC来编写程序。
想想早期的mac上的API都是采用ObjC设计的,所以如果Swift不支持ObjC就没办法兼容早期的API了吧。
ObjC和Swift
Objective-C vs Swift messages dispatch这篇文章对二者的消息发送机制有深入介绍
ObjC的Runtime
首先来看看ObjC的消息发送机制。万能指针类型id
其实是这样定义的:
typedef struct objc_object {
Class isa;
} *id;
然后Class
是这样定义的:
typedef struct objc_class *Class;
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
struct objc_method_list {
int method_count
objc_method method_list [1]
objc_method_list * obsolete
}
typedef id (*IMP)(id,SEL, …)
struct objc_method {
IMP method_imp
SEL method_name
char * method_types
}
所以ObjC程序经过编译以后,每个类的信息都用上面的obj_class数据结构来保存在编译生成的目标文件中。ObjC的Runtime会根据所存储的信息来获得每个ObjC类的属性啊方法啊之类的信息。
当往一个类的实例发送消息的时候,编译器会把这个消息发送操作翻译成ObjC Runtime的obc_msgSend
函数调用:
id obc_msgSend(id self, SEL op, …)
举个例子,对于以下ObjC代码:
Person *person = [[Person alloc] init];
[person setName:@“Johnny”];
翻译之后,转换为对ObjC的Runtime的调用:
objc_msgSend(
objc_msgSend(
objc_msgSend(
objc_getClass(@“Person”), NSSelectorFromString(@“alloc”)),
NSSelectorFromString(@“init”)),
NSSelectorFromString(@“setName:”),@“Johnny”);
Swift的Runtime
为了和ObjC在面向对象上能够互相操作,Swift必须兼容上个章节中列举的ObjC的面向对象机制。同时,Swift也必须能够ObjC从SmallTalk继承而来的那套对象消息发送机制。所以Swift中的对象类型,很可能采用的就是ObjC Runtime中所定义的Class
。但是和ObjC不同的是,Swift还支持编译时期的方法绑定,也就是类似C++的vtable机制。所以理论上,Swift的性能可以比ObjC好一点,因为它可以像C++学习很多编译时期的代码优化,而不是把所谓的对象消息处理都放到运行时。
StackOverflow的这篇帖子Why is there no universal base class in Swift?解释得很精彩,直接摘抄过来了:
This is a good question, and you can find out by introspection with the Objective-C runtime. All objects in Swift are, in a sense, also Objective-C objects, in that they can be used with the Objective-C runtime just like objects from Objective-C. Some members of the class (the ones not marked @objc or dynamic) may not be visible to Objective-C, but otherwise all the introspection features of the Objective-C runtime work fully on objects of pure Swift classes. Classes defined in Swift look like any other class to the Objective-C runtime, except the name is mangled.
Using the Objective-C runtime, you can discover that for a class that is a root class in Swift, from the point of view of Objective-C, it actually has a superclass named SwiftObject. And this SwiftObject class implements the methods of the NSObject protocol like retain, release, isEqual:, respondsToSelector:, etc. (though it does not actually conform to the NSObject protocol). This is how you can use pure Swift objects with Cocoa APIs without problem.
From inside Swift itself, however, the compiler does not believe that a Swift root class implements these methods. So if you define a root class Foo, then if you try to call Foo().isKindOfClass(Foo.self), it will not compile it complaining that this method does not exist. But we can still use it with a trick – recall that the compiler will let us call any Objective-C method (which the compiler has heard of) on a variable of type AnyObject, and the method lookup produces an implicitly-unwrapped optional function that succeeds or fails at runtime. So what we can do is cast to AnyObject, make sure to import Foundation or ObjectiveC (so the declaration is visible to the compiler), we can then call it, and it will work at runtime:
(Foo() as AnyObject).isKindOfClass(Foo.self)
So basically, from the Objective-C point of view, a Swift class either has an existing Objective-C class as root class (if it inherited from an Objective-C class), or has SwiftObject as root class.
简单的说,Swift的Class可以被当成一个SwiftObject
,直接被ObjC使用。而ObjC的Class也可以直接在Swift中使用。然而,Swift默认不采用对象消息发送机制,但是可以通过把对象转为AnyObject
来强迫Swift使用这个机制,也就是在运行时决定调用对象上的哪个方法被调用。
ObjC在Xamarin
Xamarin允许开发者使用C#开发mac和ios上的应用。Xamarin的做法是在C#和ObjC上搭建一座桥,参考Xamarin.Mac architecture。
C#和ObjC差异很大,各自管理内存的方式也不尽相同。如果直接调用对方的函数,实现起来会有很多麻烦。最好的方式,就是采用消息机制,通知对方对执行某些操作,然后返回结果。ObjC本来就是基于消息的对象机制,而且又支持反射,所以在C#中向ObjC的对象发消息是一件比较容易的事。Xamarin中使用bmac
和mmac
工具来生成mac API的C#封装层。
反过来,让ObjC能够向C#对象发消息其实也不难。C#也是完全支持反射的语言,它可以做一个薄层,把自己可以被ObjC使用的对象以及方法罗列出来就好了。
看How Xamarin.Mac works中的这个例子:
[Register ("MyClass")]
public class MyClass : NSObject
{
[Export ("init")]
public MyClass ()
{
}
[Export ("run")]
public void Run ()
{
}
}
上面的例子把C#中带有init
和run
方法的MyClass
暴露给ObjC使用。所有这些信息,会保存在编译之后的Xamarin.Mac registrar中。
总的来说,C#能和ObjC互通,得益于ObjC采用的是运行时的对象消息发送机制。如果mac API采用的是C++这种编译时期就绑定对象方法的机制,那么跟C#互通起来难度就增加了学多。到今天为止,C#和C++最靠谱的互通机制还是把C++ API打包成C风格的接口,供C#的P/Invoke调用。
参考资源
苹果文档
- Imported C and Objective-C APIs
- Cocoa Core Competencies: Dynamic typing
- Objective-C Runtime Programming Guide
- Objective-C Interoperability
Xamarin
其他参考
- Why does swift depend on Objective-C so much?
- Advanced ObjC <-> Swift Interoperability
- Setting up Swift and Objective-C Interoperability
- Swift vs Objective-C: Out with the Old, In with the New
- Getting the subclasses of an Objective-C class
- Objective-C method swizzling
- How do you find out the type of an object (in Swift)?
- Kotlin/Native interoperability with Swift/Objective-C