设置一个Xcode项目以开发可共享通用代码的Cocoa和Cocoa Touch框架,并为MacOS和iOS应用程序提供可重用和可重新分发的UI和非UI组件

缺少这样的教程,或者至少当我第一次尝试执行标题(尽可能长,但尽可能短)时,自己还没有找到。

我来到了来自C#编程的Apple开发生态系统,只是试图通过新的(很有希望的)macOS和iOS目标扩展我公司已经为Windows和Web开发提供的产品集(主要是组件库)。

由于我必须通过尝试和重试来学习所有内容,而且这并不容易(至少不应该像恕我直言,这本来应该如此简单),所以我认为写下已建立的步骤并没有什么害处(随后一些屏幕截图),也许他们也可以帮助其他人-谁知道? – 在将来。 因此,让我们开始吧。

一个项目,多个目标,没有显式工作区

我了解到的第一件事是(与.NET世界中Visual Studio解决方案通常将更多项目分组的情况不同),我不需要Xcode工作区来对多个项目进行分组。 仅仅是因为我不需要多个项目。 因为单个Xcode项目可以定义多个目标,所以每个目标都有其自己的类型和目标,并且每个目标都具有针对特定平台(例如macOS或iOS)构建的二进制文件。 (但是,顺便说一下,无论何时创建项目,无论如何都会为其创建内部工作区。)

在我的研究期间,我也没有发现对通用的跨平台层的真正技术需求(尽管我曾经习惯于在.NET中开发共享的跨平台类库。)仅仅是因为Xcode中的每个源代码文件项目可以是其一个或多个目标的一部分,而无需任何源代码重复,并且目标可以根据需要完全自定义。

因此,让我们在Xcode中创建我们的第一个项目。 我们可能会尝试从一个更具体的项目类型开始,但是我发现如果首先选择一个Empty模板(从“跨平台”选项卡下;为什么从那里选择)最简单,那是因为我们肯定会为多个平台)。

因为目标(和生成的产品)必须具有唯一的名称,所以我不得不将其中一个作为主要(我选择了macOS),将另一个作为iOS(辅助)。 对于后者的名字,我刚刚附加了“ Touch”来区分它(这个想法来自您可能已经猜到的苹果公司Cocoa和Cocoa Touch框架的名字。)

分组源代码文件

下一个挑战是找到一种在项目中考虑或不考虑其指定目标的好方法,将源代码文件分组。

我最终选择使用简单分组,并为每组源代码文件生成一个文件夹。 (Xcode确实支持不带文件夹的项目内分组,某种程度上类似于Visual Studio解决方案文件夹,但至少在我开始使用的版本9中,此功能存在一个丑陋的错误-拖动非文件夹分组的项目有时会崩溃。 )

最终,考虑到它包含的源代码的功能区域,以及一个组包含的代码对于我的项目目标(macOS和iOS)是公共的还是特定于一个的,我决定进行多级分组。

请注意,默认情况下,为每个目标Xcode创建了一个组(和一个文件夹),我还希望将该分隔保留为我的主层次结构分组。 但是,这再次要求一个目标(我选择了macOS)成为主文件夹,并同时包含特定的和通用的源代码文件,而另一个目标(iOS目标)获得了仅保留特定源代码文件的辅助文件夹。

如果您想将其复制粘贴到您的Xcode中,这是实际的源代码:

不过,我在使用示例应用程序时发现的一个重要方面是,我们不应该尝试通过将它们移动到项目结构下的公共文件夹(在那里成为子文件夹)来对应用程序进行分组,因为它似乎有些Xcode应用程序设置不要在此过程中进行更新,并且会在构建时生成特定的警告。

但是,最终我能做的是解决这个问题,即创建一个不带名为“ Samples”的文件夹的组,然后将所有示例应用程序文件夹拖到该文件夹​​中,并将它们全部放在项目结构中的单个可折叠树节点下。 (重要的是文件路径保持不变。)

部署产品

对于每个目标,Xcode都会生成一个框架文件,其中包含为该目标构建的二进制文件和一些元数据,消费者可以使用这些元数据来访问您的组件。

您可以在项目结构中为您生成的Products文件夹中看到框架文件,并且可以通过Finder的上下文菜单在Finder中打开每个文件的位置。 然后,您可以从Finder复制框架文件并将其托管在任意位置,例如可以从Web服务器下载的文件,也可以通过您管理的任何分发渠道发布该文件。

发布配置

您可能已经看到输出框架是使用Debug配置构建的,并且您可能希望在实际部署它们之前切换到Release。 要改为选择发布配置,只需从Xcode的顶部菜单中单击目标组合框,然后单击“编辑方案”。 然后,对于“运行”选择,将“构建配置”从“调试”更改为“发行”,关闭对话框,然后重新构建目标。

体系结构:iOS模拟器不是ARM!

但是你不流行。 您最终会注意到,即使您使用Release配置构建了iOS框架(以Touch后缀命名),也无法在所有架构上正常运行。

例如,如果您使用真实设备(或具有ARM体系结构的通用设备)构建框架,则输出框架将不适用于以模拟器为目标(使用x86体系结构)的iOS应用。 反之亦然。

因此,您还需要定义一个聚合目标,以便能够运行脚本以将为不同体系结构构建的框架“合并”为一个聚合的“胖”对象。

为此,请在新目标对话框的“跨平台”选项卡中创建一个类型为Aggregate的新目标,为其命名,如“ FramisTouch-aggregate”,并为其添加一个新的运行脚本阶段(使用“ +”按钮目标的“构建阶段”部分),并定义一些命令来执行所需的操作:

然后,可以通过构建聚合目标(很重要:之后)轻松地执行上述命令:通过从目标中选择通用iOS设备和模拟器,您已经在Xcode中构建了设备和模拟器框架(即针对两种类型的体系结构)组合框,然后重复构建输出。

您当然想知道这些命令实际上会做什么:

  • 创建一个新目录(例如“ Release-iphoneaggregated”)作为目标(在项目的构建目录中);
  • 将基于设备的框架的结构复制到新目录中;
  • 将swift模块从基于模拟器的框架复制到聚合的框架中;
  • 将设备和基于模拟器的框架的二进制文件合并到一个聚合文件中(替换原来复制的文件);
  • (可选)在Finder中显示聚合框架的目录,以便您轻松复制它以进行部署。

源代码部署

另一方面,如果您想分发源代码(而不是分发),则事情要简单得多。

如果选择了该选项,那么自创建项目以来,您就已经为其准备了一个git repo(否则,您可以在项目文件夹下立即设置git。)然后,发布源代码所需要做的就是确保将所有内容提交(大概)在仓库的主分支下,确保将其发布/推送到您选择的远程对象(例如GitHub或BitBucket),并且(可选)您已定义并发布了适当的包(元数据引用了通过您首选的软件包管理器(如Swift软件包管理器或CocoaPods)。

同样,您可以为自己的框架共享一个Demos应用程序的源代码,然后可以部署它的框架二进制文件(包括肥胖的iOS二进制文件!),但是在此我不会列出更多步骤…

帮助您的客户开发人员获得Interface Builder支持

重要的是要知道,如果框架的客户端开发人员无需源代码(链接其二进制文件)即可直接将其绑定到他们的应用程序,尽管它在运行时可以很好地运行,则默认情况下Xcode的Interface Builder支持将不可用!

(至少还没有-Apple,您在听吗?)

为了解决这个问题,客户(或您在Demos应用程序中为他们服务)也必须执行以下操作:

  • 对于macOS客户端应用程序目标,将“ @loader_path /../ Frameworks”添加到“运行路径搜索路径”列表中(在“构建设置”下):这使Interface Builder能够正确地实际找到“外部”框架(在默认的“ @ executable_path /下” ../Frameworks”无法使用,它会给您一个“找不到图像”错误);
  • 对于macOS和iOS应用程序目标,请为情节提要中实际使用的组件定义空的“ @IBDesignable”扩展名(Xcode根本不会从原始类本身读取属性):

今天,这就是所有人。 现在开始创建那些很棒的框架,在开始阅读这篇漫长的文章之前,您已经考虑过这些框架-现在您已经准备就绪。 或者至少准备得更好一些。 祝好运!