Unified API 概述

使用 Xamarin Unified API 可在 Mac 和 iOS 之间共享代码,该 API 还支持使用相同二进制文件的 32 和 64 位应用程序。 在新的 Xamarin.iOS 和 Xamarin.Mac 项目中会默认使用 Unified API。

重要

Xamarin Classic API(Unified API 的前身)已被弃用。

  • 支持 Classic API (monotouch.dll) 的最后一个 Xamarin.iOS 版本是 Xamarin.iOS 9.10。
  • Xamarin.Mac 仍然支持 Classic API,但不再更新。 由于 Classic API 已被弃用,开发人员应将其应用程序迁移到 Unified API。

更新基于 Classic API 的应用

按照平台的相关说明操作:

将代码更新为 Unified API 的提示

无论迁移哪些应用程序,都请查看这些提示,它们可帮助你成功更新到 Unified API。

库拆分

从现在开始,我们的 API 将以两种方式出现:

  • Classic API:限制为 32 位(仅限),并在 monotouch.dllXamMac.dll 程序集中公开。
  • Unified API:同时支持 32 位和 64 位开发,有单个 API 可在 Xamarin.iOS.dllXamarin.Mac.dll 程序集中使用。

这意味着,对于企业开发人员(不面向 App Store),你可以继续使用现有的 Classic API,因为我们将永远维护它们,或者你可以升级到新的 API。

命名空间更改

为了更顺畅地在 Mac 和 iOS 产品之间共享代码,我们将更改产品中 API 的命名空间。

对于数据类型,我们将从 iOS 产品中删除“MonoTouch”前缀,从 Mac 产品中删除“MonoMac”。

这样,可以更轻松地在 Mac 和 iOS 平台之间共享代码,而无需进行条件编译,并减少源代码文件顶部的干扰。

  • Classic API:命名空间使用 MonoTouch.MonoMac. 前缀。
  • Unified API:无命名空间前缀

运行时默认值

Unified API 默认使用 SGen 垃圾回收器,并使用新的引用计数系统来跟踪对象所有权。 此功能已移植到 Xamarin.Mac。

这解决了开发人员使用旧系统时面临的诸多问题,还简化了内存管理

请注意,即使对于 Classic API 也可启用新的 New Refcount,但默认值是保守的,不需要用户进行任何更改。 借助 Unified API,我们有机会来更改默认值,并在开发人员重构和重新测试其代码的同时为他们提供所有改进。

API 更改

Unified API 删除了已弃用的方法;在一些实例中,当 API 名称在 Classic API 中绑定到原始 MonoTouch 和 MonoMac 名称空间时,这些名称存在拼写错误。 这些实例已在新的 Unified API 中得到纠正,需要在组件、iOS 和 Mac 应用程序中更新。 下面是你可能会遇到的最常见名称的列表:

Classic API 方法名称 Unified API 方法名称
UINavigationController.PushViewControllerAnimated() UINavigationController.PushViewController()
UINavigationController.PopViewControllerAnimated() UINavigationController.PopViewController()
CGContext.SetRGBFillColor() CGContext.SetFillColor()
NetworkReachability.SetCallback() NetworkReachability.SetNotification()
CGContext.SetShadowWithColor CGContext.SetShadow
UIView.StringSize UIKit.UIStringDrawing.StringSize

有关在从 Classic API 切换到 Unified API 时出现的更改的完整列表,请参阅我们的 Classic API (monotouch.dll) 与 Unified API (Xamarin.iOS.dll) 文档。

更新到 Unified

Classic API 中的几个旧的/损坏/弃用的 API 在 Unified API 中不可用。 在开始(手动或自动)升级之前修复 CS0616 警告可能更容易,因为你将有 [Obsolete] 属性消息(警告的一部分)来知道你使用正确的 API。

请注意,我们将发布 Classic API 与 Unified API 变化的差异,你可在项目更新之前或更新后使用这些更改。 在 Classic 中修复过时的调用通常会节省时间(查找的文档更少)。

按照这些说明将现有 iOS 应用Mac 应用更新到 Unified API。 查看此页面的其余部分,并查看这些提示,详细了解如何迁移代码。

NuGet

以前通过 Classic API 支持 Xamarin.iOS 的 NuGet 包使用 Monotouch10 平台名字对象发布了其程序集。

Unified API 为兼容的包引入了新的平台标识符 - Xamarin.iOS10。 需要更新现有的 NuGet 包,以便根据 Unified API 进行构建来添加对此平台的支持。

重要

如果在将应用程序转换为 Unified API 之后收到以下形式的错误,这通常是因为项目中有组件或 NuGet 包尚未更新到 Unified API:“错误 3 无法在相同的 Xamarin.iOS 项目中同时包含 "monotouch.dll" 和 "Xamarin.iOS.dll" - "Xamarin.iOS.dll" 被显式引用,而 "monotouch.dll" 按 'xxx, Version=0.0.000, Culture=neutral, PublicKeyToken=null" 引用”。 需要删除现有组件/NuGet,更新到支持 Unified API 的版本并执行干净生成。

改为使用 64 位

有关支持 32 位和 64 位应用程序的背景信息和框架相关信息,请参阅 32 位和 64 位平台注意事项

新数据类型

区别的核心在于,Mac API 和 iOS API 都使用特定于体系结构的数据类型,这些数据类型在 32 位平台上始终为 32 位,在 64 位平台上始终为 64 位。

例如,Objective-C 在 32 位系统上将 NSInteger 数据类型映射到 int32_t,在 64 位系统上则映射到 int64_t

为了匹配此行为,在 Unified API 上,我们将以前使用的 int(它在 .NET 中定义为始终是 System.Int32)替换为新的数据类型:System.nint。 可以将“n”理解为“native”(原生),也就是平台的原生整数类型。

我们将引入 nintnuintnfloat,并在必要时提供基于它们构建的数据类型。

若要详细了解这些数据类型的变化,请参阅原生类型文档。

如何检测 iOS 应用的体系结构

在某些情况下,应用程序需要知道它是在 32 位还是 64 位 iOS 系统上运行。 以下代码可用于检查体系结构:

if (IntPtr.Size == 4) {
    Console.WriteLine ("32-bit App");
} else if (IntPtr.Size == 8) {
    Console.WriteLine ("64-bit App");
}

数组和 System.Collections.Generic

由于 C# 索引器期望类型为 int,因此必须将 nint 值显式强制转换为 int 才能访问集合或数组中的元素。 例如:

public List<string> Names = new List<string>();
...

public string GetName(nint index) {
    return Names[(int)index];
}

这是预期行为,因为在 64 位系统上从 int 强制转换为 nint 是有损的,因此不会进行隐式转换。

将 DateTime 转换为 NSDate

使用 Unified API 时,不再执行从 DateTimeNSDate 值的隐式转换。 这些值需要从一种类型显式转换为另一种类型。 以下扩展方法可用于自动执行此过程:

public static DateTime NSDateToDateTime(this NSDate date)
{
    // NSDate has a wider range than DateTime, so clip
    // the converted date to DateTime.Min|MaxValue.
    double secs = date.SecondsSinceReferenceDate;
    if (secs < -63113904000)
        return DateTime.MinValue;
    if (secs > 252423993599)
        return DateTime.MaxValue;
    return (DateTime) date;
}

public static NSDate DateTimeToNSDate(this DateTime date)
{
    if (date.Kind == DateTimeKind.Unspecified)
        date = DateTime.SpecifyKind (date, /* DateTimeKind.Local or DateTimeKind.Utc, this depends on each app */)
    return (NSDate) date;
}

已弃用的 API 和拼写错误

在 Xamarin.iOS Classic API (monotouch.dll) 中,[Obsolete] 属性以两种不同的方式使用:

  • 已弃用的 iOS API:出现的情况是 Apple 提示你停止使用某个 API,因为它被更新的 API 取代。 Classic API 仍然很好,并且通常被需要(如果你支持较旧版本的 iOS)。 此类 API(和 [Obsolete] 属性)包含在新的 Xamarin.iOS 程序集中。
  • 错误的 API:某些 API 的名称存在拼写错误。

对于原始程序集(monotouch.dll 和 XamMac.dll),我们保留了旧代码来确保兼容行,但是我们从 Unified API 程序集(Xamarin.iOS.dll 和 Xamarin.Mac)中删除了它们

NSObject 子类 .ctor(IntPtr)

每个 NSObject 子类都有一个接受 IntPtr 的构造函数。 这就是我们可以从本机 ObjC 句柄实例化新的托管实例的方式。

在 Classic 中,这是一个 public 构造函数。 但是,在用户代码中很容易滥用此功能,例如为单个 ObjC 实例创建多个托管实例,或者创建(对于子类而言)缺少预期托管状态的托管实例。

为了避免此类问题,IntPtr 构造函数在 Unified API 中接受 protected,仅用于子类。 这将确保使用正确/安全的 API 从句柄创建托管实例,如下所示

var label = Runtime.GetNSObject<UILabel> (handle);

此 API 将返回现有托管实例(如果已存在),或者创建新的托管实例(如果需要)。 它已在 Classic API 和 Unified API 中提供。

请注意,.ctor(NSObjectFlag) 现在也接受 protected,但它很少在子类之外使用。

NSAction 替换为 Action

使用 Unified API 时,NSAction 已删除,取而代之的是标准 .NET Action。 这是一个很大的改进,因为 Action 是一种常见的 .NET 类型,而 NSAction 特定于 Xamarin.iOS。 它们的用途完全相同,但它们是不同且不兼容的类型,因此必须编写更多代码才能实现相同的结果。

例如,如果现有的 Xamarin 应用程序包含以下代码:

UITapGestureRecognizer singleTap = new UITapGestureRecognizer (new NSAction (delegate() {
    ShowDropDownAnimated (tblDataView);
}));

现在可以将它替换为简单的 Lambda:

UITapGestureRecognizer singleTap = new UITapGestureRecognizer (() => ShowDropDownAnimated(tblDataView));

以前,这会出现编译器错误,因为无法将 Action 分配给 NSAction,但是由于 UITapGestureRecognizer 现在采用 Action 而不是 NSAction,它在 Unified API 中有效。

自定义委托替换为 Action<T>

在 Unified 中,一些简单的 .Net 委托(例如一个参数)被替换为 Action<T> 例如

public delegate void NSNotificationHandler (NSNotification notification);

现在可以用作 Action<NSNotification>。 这可促进代码重用,并减少 Xamarin.iOS 和你自己的应用程序内的代码重复。

Task<bool> 替换为 Task<Boolean,NSError>>

在 Classic 中,有一些异步 API 返回 Task<bool> 但是,其中的一些是在 NSError 包含在签名中时使用的,在这种情况下,bool 已经为 true,并且你必须捕获异常才能获取 NSError

由于某些错误很常见,并且返回值没有用处,所以此模式在 Unified 中更改,现在返回 Task<Tuple<Boolean,NSError>> 这样,你可以检查异步调用期间可能发生的成功和任何错误。

NSString 与 string

在少数情况下,某些常量必须从 string 更改为 NSString(例如 UITableViewCell

经典

public virtual string ReuseIdentifier { get; }

Unified

public virtual NSString ReuseIdentifier { get; }

通常,我们更喜欢 .NET System.String 类型。 但是,尽管有 Apple 指南,但一些本机 API 会比较常量指针(而不是字符串本身),这只能在我们以 NSString 的形式公开常量时正常工作。

Objective-C 协议

原始 MonoTouch 不完全支持 ObjC 协议,我们添加了一些非最佳 API 来支持最常见的场景。 这个限制已经不存在了,但为了后向兼容性,在 monotouch.dllXamMac.dll 中保留了几个 API。

在 Unified API 上删除并清理了这些限制。 大多数更改如下所示:

经典

public virtual AVAssetResourceLoaderDelegate Delegate { get; }

Unified

public virtual IAVAssetResourceLoaderDelegate Delegate { get; }

I 前缀意味着 Unified 公开一个接口(而不是特定类型)供 ObjC 协议使用。 这可简化你不希望将 Xamarin.iOS 提供的特定类型子类化的情况。

它还使得某些 API 更加精确且易于使用,例如:

经典

public virtual void SelectionDidChange (NSObject uiTextInput);

Unified

public virtual void SelectionDidChange (IUITextInput uiTextInput);

现在,无需参考文档即可更轻松地使用此类 API,而且 IDE 代码补全将基于协议/接口为你提供更有用的建议。

NSCoding 协议

我们的原始绑定为每种类型都包括一个 .ctor(NSCoder),即使它不支持 NSCoding 协议也是如此。 NSObject 中存在单个 Encode(NSCoder) 方法用来编码对象。 但是,只有当实例符合 NSCoding 协议时,此方法才起作用。

在 Unified API 上,我们解决了这个问题。 仅当类型符合 NSCoding 时,新的程序集才具有 .ctor(NSCoder)。 此外,这些类型现在还有一个符合 INSCoding 接口的 Encode(NSCoder) 方法。

影响很小:在大多数情况下,此更改不会影响应用程序,因此无法使用旧的已删除的构造函数。

其他提示

有关需要注意的其他更改,可查看有关将应用更新到 Unified API 的提示