1. 如何在你的iOS框架内使用CocoaPods

在我之前关于如何将特性构建为框架的文章中,我讨论了如何通过在项目workspace中直接使用iOS框架来提高团队的工作效率。 我所介绍的示例非常简单,并为这种方法提供了一个起点,但我们中的许多人需要构建更复杂的东西,通常包括第三方依赖关系。当CocoaPods的结构是这样时,将其集成到项目中可能有点棘手,所以这就是我想在本教程中解释的内容。

1.1. CocoaPods or Carthage?

在为你的iOS项目选择依赖管理器时,大多数人会在两个选项中做出选择:CocoaPods或Carthage。CocoaPods多年来一直是标准的承载者,它是一个非常成熟的代码库,有大量的包可供选择。它将代码集成到你的项目中是通过为每个包含的Pods创建一个框架目标,构建框架,并使用一个特殊的脚本将它们复制到你的主应用模块。

Carthage的工作方式有点不同。它并不依赖于对项目的集成,而是将每个依赖项的代码下载并构建到它们自己的框架中,然后将这些框架导入到项目中。 这意味着依赖项必须手动集成到项目中,但它使项目独立于Carthage本身。

选择什么作为包管理器取决于您,如果需要,您甚至可以同时使用两者。Carthage的优点是不重写任何项目文件,而CocoaPods只需要几个自动步骤就可以让您开始。 在本文中,我将介绍如何将CocoaPods集成到您的内部框架中。

1.2. Getting started with CocoaPods

让我们从我在前一篇文章中所写的小示例项目开始。我在repo中的一个名为use-cocoapods的分支上做了以下所有更改,但您可以从主分支上已经存在的内容开始。如果遇到困难,请检查代码以查看最终结果。

确保您已经安装了CocoaPods,无论是在系统范围内还是在Gemfile中定义的CocoaPods中使用Bundler。

如果您以前使用过CocoaPods,您可能知道开始使用它的通常方法是在包含.xcproject文件的目录中运行pod init。 运行此命令将创建一个工作区,CocoaPods可以使用该工作区来管理依赖项。 因为我们已经创建了包含内部依赖项的自己的工作区,所以我们需要做一些不同的事情。

使用文本编辑器,在git repo的根目录下创建一个名为Podfile的新文件。添加以下几行:

use_frameworks!
workspace 'SimpleCounter.xcworkspace'

这告诉CocoaPods您希望使用现有的工作区来包含您的Pod依赖项。 在同一个目录下运行pod install以确认它是否正常工作。这不会安装任何东西,但会将Pods项目集成到该工作区中。

1.3. Integrate a CocoaPod

让我们将UICircularProcessingRing Pod 集成到应用程序和框架中。是一个简单的环形进度条,我们可以用它来显示朝着指定目标的进度。

avatar

Add the following to your Podfile:

target 'SimpleCounterFeature' do
    project './SimpleCounterFeature/SimpleCounterFeature.xcodeproj' 
    pod 'UICircularProgressRing', '6.1.0'
end

这将把UICircularProgressRing CocoaPod应用到SimpleCounterFeature框架目标。这些看起来都很熟悉,除了指定了需要依赖项的确切的.xcodeproj文件。我们需要这样做是因为在.xcworkspace文件的根目录中,CocoaPods并不清楚需要依赖项的目标在哪里。 通常,应用主模块的.xcproject文件和CocoaPods生成的.xcworkspace文件位于同一个目录中,因此CocoaPods将在默认情况下找到它。

然后,运行pod install。这将下载并应用UICircularProgressRing依赖项到SimpleCounterFeature框架目标。

现在,让我们测试一下集成是否成功。打开CounterViewController.swift文件并添加

import UICircularProgressRing

在文件的最上面。在目标列表中选择SimpleCounterFeature目标并build。构建应该成功。

现在尝试构建并运行SimpleCounterApp目标,看看会发生什么。构建将成功,但应用程序在启动时崩溃! 我们得到以下错误消息:

dyld: Library not loaded: @rpath/UICircularProgressRing.framework/UICircularProgressRing
Referenced from: /Users/akfreas/Library/Developer/Xcode/DerivedData/SimpleCounter-awbumravmznuqofjwyrvfwfaoohc/Build/Products/Debug-iphonesimulator/SimpleCounterFeature.framework/SimpleCounterFeature
Reason: image not found

这是为什么呢?我们将依赖添加到SimpleCounterFeature中,它构建得很好,应用也是如此!为什么说库没有加载?

What’s missing is the actual compiled UICircularProgressRing binary that the SimpleCounterFeature requests when it is loaded. This is because iOS frameworks are dynamically linked, meaning they only reference their dependencies rather than packaging them with the compiled binary image. Doing this generally reduces app size, as dependencies that are shared between binaries in the executable image (in this case, our app module) are not duplicated and instead loaded only once by the dynamic linker. In this case the linker is an Apple utility called dyld, which threw the fatal error above.

缺少的是加载时SimpleCounterFeature请求的实际编译的UICircularProgressRing二进制文件。这是因为iOS框架是动态链接的,这意味着它们只引用它们的依赖项,而不是将它们与编译后的二进制映像打包。这样做通常会减少应用程序的大小,因为在可执行映像(在本例中,我们的app模块)的二进制文件之间共享的依赖不会重复,而是由动态链接器加载一次。在本例中,链接器是一个名为dyld的苹果实用程序,它抛出了上面的致命错误。

换句话说,应用程序不知道在哪里可以找到UICircularProgressRing二进制文件,因为它既没有与SimpleCounterFeature框架打包,也没有与主应用程序模块打包。 当dyld加载SimpleCounterFramework时,请求该二进制文件,但无法找到,导致崩溃。

我们需要做的是将UICircularProgressRing框架添加到主应用模块的链接二进制文件中。这可以通过将这些行添加到Podfile中来完成:

target 'SimpleCounterApp' do
    project './SimpleCounterApp/SimpleCounterApp.xcodeproj'
    pod 'UICircularProgressRing', '6.1.0'
end

更好的方式 😊

def simpleCounterFeature_pods
   pod 'UICircularProgressRing', '6.1.0'
end

target 'SimpleCounterFeature' do
    project './SimpleCounterFeature/SimpleCounterFeature.xcodeproj' 
    simpleCounterFeature_pods
end

target 'SimpleCounterApp' do
    project './SimpleCounterApp/SimpleCounterApp.xcodeproj'
    simpleCounterFeature_pods
end

现在,运行pod install,清理应用程序目标,并尝试再次构建和运行应用程序。你可能不得不从你的模拟器或设备删除应用程序,如果你没有看到计数器接口启动。

1.5. Dependencies sorted, let’s build something!

在我们有了两个目标可用的UI二进制文件,我们可以使用它了。我将进度视图集成到框架中,你可以在github上的代码中检出。这里使用框架进行构建并没有什么独特之处,所以我将把它留给您去研究。我们剩下的是这样一个界面:

avatar

1.6. Common Issues

在将更多依赖项集成到应用模块和框架中时,你可能会遇到一些问题。

1.6.1. App is still saying library not loaded

If you are getting the same error we had above, where dyld is complaining that it cannot find your internal framework, make sure you have the framework listed in both Linked Frameworks and Libraries and in Embedded Binaries in the General section of your build settings for your main app module. It should look like this:

avatar

1.6.2. Issues when integrating into another architecture (watchOS, MacOS)

如果您正在为另一个体系结构(如WatchOS)构建框架,则需要在框架项目中创建另一个构建目标,该目标将为该体系结构构建代码。

avatar

通过将Base SDK设置为watchOS,将Valid Architectures设置为armv7k,你可以构建你的watchOS框架,并将其集成到你的主watchOS应用程序模块中,就像你在iOS应用程序中所做的一样。还有一些其他的困难可能会导致障碍,但是我在这里就不详细讨论了。