前面说了,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

Xamarin Mac Arch

C#和ObjC差异很大,各自管理内存的方式也不尽相同。如果直接调用对方的函数,实现起来会有很多麻烦。最好的方式,就是采用消息机制,通知对方对执行某些操作,然后返回结果。ObjC本来就是基于消息的对象机制,而且又支持反射,所以在C#中向ObjC的对象发消息是一件比较容易的事。Xamarin中使用bmacmmac工具来生成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#中带有initrun方法的MyClass暴露给ObjC使用。所有这些信息,会保存在编译之后的Xamarin.Mac registrar中。

总的来说,C#能和ObjC互通,得益于ObjC采用的是运行时的对象消息发送机制。如果mac API采用的是C++这种编译时期就绑定对象方法的机制,那么跟C#互通起来难度就增加了学多。到今天为止,C#和C++最靠谱的互通机制还是把C++ API打包成C风格的接口,供C#的P/Invoke调用。

参考资源

苹果文档

Xamarin

其他参考