当前位置: 首页>移动开发>正文

Xcode_Developer_Disk_Images如何使用 xcodegen

ios模块化、组件化区别

(Breaking the monolith)

Developers that take the challenge of breaking the monolith in their iOS codebase and splitting it into separate modules face another problem: How to best manage multiple .xcodeproj files and potential conflict resolution on them? Fortunately, there’s a really convenient tool called XcodeGen that makes it quite easy — it generates the .xcodeproj files based on the YAML files stored in the codebase. Using YAML files greatly simplifies conflict resolution since you no longer need to version control .xcodeproj files. Instead, you can define your project with a much simpler syntax.

挑战如何在iOS代码库中打破整体并将其拆分为单独的模块的开发人员面临另一个问题:如何最好地管理多个.xcodeproj文件并解决它们上的潜在冲突? 幸运的是,有一个非常方便的工具XcodeGen使得它非常容易-它基于存储在代码库中的YAML文件生成.xcodeproj文件。 由于不再需要版本控制.xcodeproj文件,因此使用YAML文件可以极大地简化冲突解决。 相反,您可以使用更简单的语法来定义项目。

However, when you have a bunch of dynamic framework targets with dependencies between them and other third party dependencies, like Carthage-built libraries, managing them manually with YAML files might be a pain. It’s going to be even more difficult when each of those frameworks has its own unit or UI test target.

但是,当您拥有一堆动态框架目标,并且它们之间具有依赖关系以及其他第三方依赖关系(例如迦太基构建的库)时,使用YAML文件手动管理它们可能会很麻烦。 当每个框架都有自己的单元或UI测试目标时,这将变得更加困难。

In this article, I will present a solution to simplify managing dependencies between app modules. This post assumes that you already know and use XcodeGen.

在本文中,我将提出一种解决方案,以简化应用程序模块之间的依赖关系管理。 这篇文章假定您已经知道并使用XcodeGen。

(What’s the problem?)

In our codebase we have a setup of one Xcode workspace with multiple projects per application. For the purpose of this blog post, let’s consider a sample application to have a better understanding of what we are talking about. This application consists of three modules:

在我们的代码库中,我们设置了一个Xcode工作区,每个应用程序具有多个项目。 出于本博客文章的目的,让我们考虑一个示例应用程序,以更好地了解我们所讨论的内容。 该应用程序包含三个模块:

  • API module — dynamic framework responsible for fetching application data. Let’s say it has a third party dependency, for example SwiftyJSON, used for parsing json
  • Feature module — dynamic framework containing part of application UI, responsible for displaying application data
  • Application target — responsible for application configuration and displaying view controllers provided by the Feature module.

Targets listed above depend on each other. For example, the Feature module depends on the API module and the Application target depends on the Feature module. In our setup, each of the targets has its own unit tests target too.

上面列出的目标相互依赖。 例如,功能模块取决于API模块,而应用程序目标取决于功能模块。 在我们的设置中,每个目标也都有自己的单元测试目标。

Let’s see how we define the API module’s dependencies:

让我们看看如何定义API模块的依赖关系:

targets: ApiModule:
 dependencies:
   - carthage: SwiftyJSON
     embed: false

We define that it depends on SwiftyJSON Carthage library. We usually don’t want to embed third party dependencies in our dynamic frameworks, just link them. It’s because when multiple of them depend on the same library, it would be embedded multiple times in the application package — that’s why we set embed flag to false.

我们定义它依赖于SwiftyJSON迦太基库。 我们通常不希望将第三方依赖项嵌入到我们的动态框架中,而只是链接它们。 这是因为当它们中的多个依赖于同一库时,它将被多次嵌入应用程序包中-这就是为什么我们将embed标志设置为false的原因。

The situation is different with unit tests target. It needs to link and embed the dynamic framework, as well as all its dependencies. If we don’t embed them, we’ll get runtime errors like “Library not loaded: @rpath/SwiftyJSON.framework/SwiftyJSON”. Dependencies definition for API module tests target file would look as follows:

情况与单元测试目标不同。 它需要链接和嵌入动态框架及其所有依赖项。 如果不嵌入它们,则会出现运行时错误,例如“未加载库:@ rpath / SwiftyJSON.framework / SwiftyJSON”。 API模块测试目标文件的依赖项定义如下所示:

ApiModuleTests: 
  dependencies: 
    - framework: ApiModule.framework 
      embed: true 
      implicit: true 
    - carthage: SwiftyJSON 
      embed: true

Feature module is simpler — it needs to link only API module to work:

功能模块更简单-仅需要链接API模块即可工作:

FeatureModule: 
  dependencies:
    - framework: ApiModule.framework
      embed: false 
      implicit: true

However, when we get to dependencies of the Feature unit tests target, it’s getting more interesting. It needs to link and embed the Feature target, all its dependencies, as well as dependencies of its dependencies and so on. Let’s see:

但是,当我们到达功能单元测试目标的依赖关系时,它会变得越来越有趣。 它需要链接和嵌入Feature目标,其所有依赖性以及其依赖性的依赖性等。 让我们来看看:

FeatureModuleTests: 
  dependencies: 
    - framework: ApiModule.framework 
      embed: true 
      implicit: true 
    - framework: FeatureModule.framework 
      embed: true 
      implicit: true 
    - carthage: SwiftyJSON 
      embed: true

Now imagine that you have a new “Feature2” module, similarly to Feature module depending on the API module, and you need to add another third party dependency to the API module. You will need to link and embed the new third party dependency in both Feature and Feature2 unit tests targets.

现在,假设您有一个新的“ Feature2”模块,类似于功能模块,具体取决于API模块,并且您需要向该API模块添加另一个第三方依赖关系。 您将需要在Feature和Feature2单元测试目标中链接并嵌入新的第三方依赖项。

The application target needs to link and embed all the modules, so that they are present in the resulting IPA package.

应用程序目标需要链接和嵌入所有模块,以便它们出现在生成的IPA包中。

ModularApp: 
  dependencies: 
    - framework: ApiModule.framework 
      embed: true 
      implicit: true 
    - framework: FeatureModule.framework 
      embed: true 
      implicit: true 
    - carthage: SwiftyJSON 
      embed: true

Although adding dependencies manually is not a big problem when you have only a few modules, it’s not scalable with more of them. Adding a dependency to a module at the end of the dependency chain results in the need of adding it in unit tests of all dynamic frameworks that depend on it, as well as in the application target.

尽管只有几个模块时,手动添加依赖项不是一个大问题,但无法扩展其中的更多模块。 在依赖链的末尾向模块添加依赖会导致需要在依赖它的所有动态框架以及应用程序目标的单元测试中添加它。

(What’s the solution?)

In our codebase we needed a solution that lets us specify only the direct dependencies of the modules, so that all the indirect ones are resolved automatically. We also wanted to not need to manually specify whether given dependency should be embedded or not.

在我们的代码库中,我们需要一个解决方案,该解决方案允许我们仅指定模块的直接依赖关系,以便所有间接模块都可以自动解决。 我们也不想手动指定是否应嵌入给定的依赖项。

We simplified the format of the YAML files that XcodeGen uses. In the new format, we define only the type of the dependency. Let’s see how the definitions look now.

我们简化了XcodeGen使用的YAML文件的格式。 在新格式中,我们仅定义依赖项的类型。 让我们看看现在的定义。

API module:

API模块:

ApiModule: 
  dependencies:
    - carthage: SwiftyJSON

API module unit tests:

API模块单元测试:

ApiModuleTests: 
  dependencies: 
    - framework: ApiModule.framework

Feature module:

功能模块:

FeatureModule: 
  dependencies: 
    - framework: ApiModule.framework

Feature module unit tests:

功能模块单元测试:

FeatureModuleTests: 
  dependencies: 
    - framework: FeatureModule.framework

Application:

应用:

ModularApp: 
  dependencies: 
    - framework: FeatureModule.framework 
    - framework: ApiModule.framework

Having this, we’ve created a Python script that converts the simplified format of the YAML files to the format that XcodeGen consumes. Its implementation can be found here: https://github.com/Wikia/ios-modular-example/blob/master/resolve-dependencies.py

有了这个,我们创建了一个Python脚本,该脚本将YAML文件的简化格式转换为XcodeGen使用的格式。 它的实现可以在这里找到: https : //github.com/Wikia/ios-modular-example/blob/master/resolve-dependencies.py

What it does is:

它的作用是:

  1. It reads the YAML definitions of all projects in the workspace
  2. For each target in the project, it iterates over all its dependencies and adds them with a proper embed flag, depending on the project target type — it sets embed as true for test and application target type, and false if it’s a dynamic framework.
  3. It adds all the indirect dependencies — for each dependency of the project targets, it recursively adds all its dependencies with a proper embed flag, depending on the project target type.

Dependency types that we allow are:

我们允许的依赖类型为:

  • framework — a dynamically linked framework
  • staticFramework — a framework with Mach-O type set to “Static Library”. It always resolves as a framework with embed flag set to false, as it’s linked statically to the target. We add it as an application dependency to avoid duplicate symbol errors. This type was added because some third party dependencies that we use with the help from Carthage are linked statically, like Fabric or Firebase libraries
  • sdk — one of iOS system SDKs
  • target — another target in the same Xcode project. We usually use it to add a target application of unit/UI tests target

It’s worth mentioning that we recursively add only framework and carthage dependencies of our internal framework dependencies. We’ve decided not to do this for target type because we use it to link a target application of the tests, so we don’t really need to link the dependencies of it to the tests target.

值得一提的是,我们仅在内部框架依赖项中递归地添加框架和迦太基依赖项。 我们决定不对目标类型执行此操作,因为我们使用它来链接测试的目标应用程序,因此我们实际上不需要将其依赖项链接到测试目标。

(Summary)

Using this solution gave us some noticeable benefits. First of all, we no longer need to dive into linking logic of the modules — the script does it automatically for us, we just need to add direct dependencies of the modules. Secondly, we reduced the amount of manual intervention to a minimum when adding a dependency, as now you only need to add the dependencies to definitions of targets that really use them. Last but not least, as a side effect of the dependency resolution, the script produces a very handy dependency graph in the form of a json file that you may take advantage of for different purposes like dependency visualization.

使用此解决方案给我们带来了明显的好处。 首先,我们不再需要深入研究模块的链接逻辑,而脚本会自动为我们完成它,我们只需要添加模块的直接依赖关系即可。 其次,在添加依赖项时,我们将手动干预的数量减至最少,因为现在您只需要将依赖项添加到真正使用它们的目标的定义中即可。 最后但并非最不重要的一点是,作为依赖关系解析的副作用,该脚本以json文件的形式生成了一个非常方便的依赖关系图,您可以利用它来实现诸如依赖关系可视化之类的不同目的。

You can find a sample repository with a project that was used as a background to this blog post here: https://github.com/Wikia/ios-modular-example

您可以在此处找到带有项目的示例存储库,该项目被用作此博客文章的背景: https : //github.com/Wikia/ios-modular-example

In my next blog post, I will show how we utilize the dependency graph produced by the dependency resolution script to decrease the time of Pull Request checks in our CI environment, so stay tuned!

在我的下一篇博客文章中,我将展示我们如何利用依赖关系解析脚本生成的依赖关系图来减少CI环境中“拉取请求”检查的时间,敬请关注!

Originally published at https://dev.fandom.com.

最初发布在 https://dev.fandom.com 。

翻译自: https://medium.com/fandom-engineering/enhancing-xcodegen-for-simpler-maintenance-of-dependencies-in-modular-ios-app-876d23f62949

ios模块化、组件化区别


https://www.xamrdz.com/mobile/4ky1931548.html

相关文章: