Xamarin.iOS 的类型 registrar

本文档介绍 Xamarin.iOS 使用的类型注册系统。

托管类和方法的注册

在启动期间,Xamarin.iOS 将注册:

  • 具有 [Register] 属性作为 Objective-C 类的类。
  • 具有 [Category] 属性作为 Objective-C 类别的类。
  • 具有 [Protocol] 属性作为 Objective-C 协议的接口。
  • 具有 [Export] 的成员,使 Objective-C 可以访问它们。

例如,考虑 Xamarin.iOS 应用程序中常见的托管 Main 方法:

UIApplication.Main (args, null, "AppDelegate");

此代码告知 Objective-C 运行时使用名为 AppDelegate 的类型作为应用程序的委托类。 若要使 Objective-C 运行时能够创建 C# AppDelegate 类的实例,必须注册该类。

Xamarin.iOS 在运行时(动态注册)或编译时(静态注册)自动执行注册。

动态注册在启动时使用反射来查找要注册的所有类和方法,并将其传递给 Objective-C 运行时。 默认情况下,动态注册用于模拟器生成。

静态注册在编译时检查应用程序使用的程序集。 它确定用于注册 Objective-C 并生成嵌入到二进制文件中的映射的类和方法。 然后,在启动时,它会将映射注册到 Objective-C 运行时。 静态注册用于设备生成。

类别

从 Xamarin.iOS 8.10 开始,可以使用 C# 语法创建 Objective-C 类别。

若要创建类别,请使用 [Category] 属性并指定要扩展的类型。 例如,以下代码会扩展 NSString

[Category (typeof (NSString))]

类别的每个方法都有一个 [Export] 属性,使其可供 Objective-C 运行时使用:

[Export ("today")]
public static string Today ()
{
    return "Today";
}

所有托管扩展方法都必须是静态的,但可以使用扩展方法的 C# 语法来创建 Objective-C 实例方法:

[Export ("toUpper")]
public static string ToUpper (this NSString self)
{
    return self.ToString ().ToUpper ();
}

扩展方法的第一个参数将是在其上调用该方法的实例。

[Category (typeof (NSString))]
public static class MyStringCategory
{
    [Export ("toUpper")]
    static string ToUpper (this NSString self)
    {
        return self.ToString ().ToUpper ();
    }
 }

此示例将向 NSString 类添加本机 toUpper 实例方法。 可以从 Objective-C 调用此方法:

[Category (typeof (UIViewController))]
public static class MyViewControllerCategory
{
    [Export ("shouldAutoRotate")]
    static bool GlobalRotate ()
    {
        return true;
    }
}

协议

从 Xamarin.iOS 8.10 开始,拥有属性 [Protocol] 的接口将导出到 Objective-C 作为协议:

[Protocol ("MyProtocol")]
interface IMyProtocol
{
    [Export ("method")]
    void Method ();
}

class MyClass : IMyProtocol
{
    void Method ()
    {
    }
}

此代码将 IMyProtocol 导出到 Objective-C 作为名为 MyProtocol 的协议,以及用于实现该协议的名为 MyClass 的类。

新注册系统

从稳定的 6.2.6 版本和 beta 6.3.4 版本开始,我们添加了一个新的静态 registrar。 在 7.2.1 版本中,我们新增了 registrar 默认值。

此新注册系统提供以下新功能:

  • 程序员错误的编译时检测:

    • 注册具有相同名称的两个类。
    • 导出多个方法以响应同一选择器
  • 删除未使用的本机代码:

    • 新的注册系统将添加对静态库中使用的代码的强引用,从而允许本机链接器从生成的二进制文件中去除未使用的本机代码。 在 Xamarin 的示例绑定上,大多数应用程序至少变小 30 万。
  • 支持泛型子类 NSObject;有关详细信息,请参阅 NSObject 泛型。 此外,新的注册系统将捕获以前在运行时导致随机行为的不受支持的泛型构造。

新 registrar 捕获的错误

下面是新 registrar 捕获的错误的一些示例。

  • 在同一类中多次导出同一选择器:

    [Register]
    class MyDemo : NSObject
    {
        [Export ("foo:")]
        void Foo (NSString str);
        [Export ("foo:")]
        void Foo (string str)
    }
    
  • 导出具有相同 Objective-C 名称的多个托管类:

    [Register ("Class")]
    class MyClass : NSObject {}
    
    [Register ("Class")]
    class YourClass : NSObject {}
    
  • 导出泛型方法:

    [Register]
    class MyDemo : NSObject
    {
        [Export ("foo")]
        void Foo<T> () {}
    }
    

新 registrar 的限制

有关新 registrar 的一些注意事项:

  • 必须更新某些第三方库才能使用新的注册系统。 有关更多详细信息,请参阅下面的必要的修改

  • 短期缺点是,如果使用帐户框架,则必须使用 Clang(这是因为 Apple 的 accounts.h 标头只能由 Clang 编译)。 如果使用的是 Xcode 4.6 或更早版本(Xamarin.iOS 将自动在 Xcode 5.0 或更高版本中选择 Clang),请将 --compiler:clang 添加到其他 mtouch 参数以使用 Clang。

  • 如果使用 Xcode 4.6(或更早版本),且导出的类型名称包含非 ASCII 字符,则必须选择 GCC/G++(这是因为 Xcode 4.6 附带的 Clang 版本不支持 Objective-C 代码中的标识符中的非 ASCII 字符)。 将 --compiler:gcc 添加到其他 mtouch 参数以使用 GCC。

选择 registrar

可以通过向项目的 iOS 生成设置中的其他 mtouch 参数添加以下选项之一来选择其他 registrar:

  • --registrar:static – 设备内部版本的默认值
  • --registrar:dynamic – 模拟器内部版本的默认值

注意

Xamarin 的 Classic API 支持其他选项,例如 --registrar:legacystatic--registrar:legacydynamic。 但是,Unified API 不支持这些选项。

旧注册系统的缺点

旧注册系统具有以下缺点:

  • 第三方本机库中没有对 Objective-C 类和方法的(本机)静态引用,这意味着我们无法要求本机链接器删除实际未使用的第三方本机代码(因为会删除所有内容)。 这是每个第三方绑定必须执行 -force_load libNative.a(或 [LinkWith] 属性中的等效 ForceLoad=true)的原因。
  • 可以导出两个具有相同 Objective-C 名称的托管类型,且不触发警告。 一个罕见的方案是以不同命名空间中的两个 AppDelegate 类结尾。 在运行时,选择哪一个是完全随机的(事实上,因运行甚至未重新生成的应用而有所不同,这会使得调试体验非常令人费解和沮丧)。
  • 可以导出具有相同 Objective-C 签名的两种方法。 然而,同样的,从 Objective-C 调用哪一个是随机的(但这个问题不像前一个那样常见,主要是因为实际体验此 bug 的唯一方法是替代运气欠佳的托管方法)。
  • 导出的方法集在动态生成和静态生成之间略有不同。
  • 导出泛型类(在运行时执行的确切泛型实现是随机的,这实际上会导致不确定的行为)时无法正常工作。

新增 registrar:对绑定进行所需的更改

本部分介绍使用新增 registrar 前所必须进行的绑定更改。

协议必须具有 [Protocol] 属性

协议现在必须具有 [Protocol] 属性。 如果不这样做,则会出现本机链接器错误,例如:

Undefined symbols for architecture i386: "_OBJC_CLASS_$_ProtocolName", referenced from: ...

选择器必须具有有效数量的参数

所有选择器必须正确指示参数数。 以前,这些错误会被忽略,并可能导致运行时问题。

简言之,冒号数必须与参数数匹配:

  • 无参数:foo
  • 一个参数:foo:
  • 两个参数:foo:parameterName2:

下面是不正确的用法:

// Invalid: export takes no arguments, but function expects one
[Export ("apply")]
void Apply (NSObject target);

// Invalid: exported as taking an argument, but the managed version does not have one:
[Export ("display:")]
void Display ();

在 Export 中使用 IsVariadic 参数

Variadic 函数必须使用 [Export] 特性的参数 IsVariadic

[Export ("variadicMethod:", IsVariadic = true)]
void VariadicMethod (NSObject first, IntPtr subsequent);

无法绑定本机库中不存在的类。 如果已从本机库中删除或重命名类,请更新绑定以确保匹配。