Development Pods

如果你的 iOS 项目是使用 Development Pods 来做组件化的话,这篇文章或许值得一看。

起源

知乎的 iOS 项目大概在 2016 年 Q4 开始进行组件化的工作,当时的计划就是把主 target 内的文件以 Development Pods 的形式拆分为 Basic,Core,Middleware,Module 四种类别的 Development Pods 供主 target 进行依赖。
这几层结构之间有下面这个下面这个约束关系,Pods 只能依赖比它们层级低的而不能依赖比它们层级高的,而且同层间也尽量避免依赖关系。换名话说,Core 可以依赖 Basic 但是 Basic 不能依赖 Core。这样做的原因很简单,就是避免循环引用的问题。
notion image
组件化的目的之一是减少依赖,可以让一个组件独立于主 target 之外独立运行,这样为了这个组件建立单独的工程,独立编译、开发,提升效率。

为某个 Development Pods 创建独立工程

但是当我为一个组件单独创建工程的时候,遇到了一个问题,姑且把它叫作组件 R 吧。作为一个 Pod,R 的依赖关系是由 R.podspec 体现的,它只需要关心它直接依赖的东西就好了,对于它依赖的依赖它是不需要关心的,Cocoapods 已经帮我们处理好了。
Pod::Spec.new do |s|
  s.name = "R"
  # some other fields...
  s.dependency 'A'
  s.dependency 'C'
  s.dependency 'E'
end
比如,上图的 R 依赖了 A、C、E 三个 Pods。
为 R 创建依赖独立工程的时候,需要把 R 作为这个工程的一个依赖,使用路径的形式引入。
# Podfile

target 'Example' do
  pod 'R', :path => '../../R',
end
如果你以为这样写就大功告成了,就大错特错了。执行 pod install 后就会发现 未能找到 A 类似的错误。为什么会出现这样的问题呢?其实我也不知道具体的原因😂,但是一个可能的猜测是 Podfile 中的依赖关系必须要明确,尤其是这些 pods 都是以本地路径关系相互依赖的情况下。而 podspec 中的依赖不需要那么明确,Cocoapods 会帮你处理。
因为我们使用 Development Pods,所以所有的 Pods 必然以本地路径的关系进行依赖。所以在为 R 创建独立工程的时候,需要把 R 依赖的所有 Pods 的路径标记出来给 CocoaPods 看。所以你可能会写出下面的 Podfile
target 'Example' do
  pod 'R', :path => '../../R'
  pod 'A', :path => '../../A'
  pod 'C', :path => '../../C'
  pod 'E', :path => '../../E'
end
满欣欢喜地跑去 pod install ,结果又失败了,原来 C 还依赖了 B!于是你又跑去给 podfile 加了一行,
target 'Example' do
  pod 'R', :path => '../../R'
  pod 'A', :path => '../../A'
  pod 'B', :path => '../../B'
  pod 'C', :path => '../../C'
  pod 'E', :path => '../../E'
end
好吧,你又失败了,E 还依赖了 D...... Stop! 如果总是这样一次又一次 pod install 才能知道自己全部依赖的 Pods 有什么是不是太搓了?如果依赖少还好,但是对于依赖很多情况,这样一次次处理就有点 2 了。能不能有什么别的方法能一次性知道某个 Development Pod 的全部依赖呢?
答案是可以的,感兴趣的可以直接移步这里 X140Yu/development-pods-dependency-checker

Development Pods Dependency Checker

效果在 repo 里的 README 就可以看到了,它能找出工程中的所有依赖,并且把每个 Pod 的依赖展开,显示出它的全部依赖,而不是 podspec 中的那一小部分。
它的原理很简单,在 pod install 成功之后, Pods/Local Podspecs 目录中会出现一堆 *.podspec.json 文件,你可以把它们理解为 podspec 的 JSON 版本,这对 JS 处理更加友好。
// R.podspec.json
{
  "name": "R",
  "version": "0.0.1",
  // ...
  "dependencies": {
  "A": [],
  "C": [],
  "E": []
  }
}
而作为 localpods 的 A, C, E 也会有类似这样的文件。所以我们要做的事情就很简单了,挨个 parse 各个 JSON 文件,递归处理 dependencies 中的每一项,直到它的 dependencies 是空为止。其实核心的逻辑也就下面这一小段,
function trigger(pods) {
  let podsRet =[]
  pods.forEach(ele => {
    podsRet.push(recursiveFindDependencies(ele, pods))
  })
  return podsRet
}

function recursiveFindDependencies(ele, pods) {
  let pod ={}
  if (ele.name == undefined) {
    return
  }
  pod.name = ele.name
  pod.dependencies =[]
  ele.dependencies.forEach(dep => {
    if (pod.dependencies.filter(es => es == dep).length == 0) {
      pod.dependencies.push(dep)
    }
    let deps = findDependencies(pods, dep).forEach(e => {
      Array.prototype.push.apply(pod.dependencies, recursiveFindDependencies(e, pods))

      if (pod.dependencies.filter(es => es == e).length == 0) {
        pod.dependencies.push(e)
      }
    })
  })
  return pod
}
有了这个工具以后,就能够看清一个 pod 到底依赖了什么,首先它更便于为 pod 创建独立运行的工程,其次,也能在开发阶段知道依赖关系,避免同层依赖及低层向更高层的 pod 依赖。
关于这个小工具,还想说两句。它是用 electron 写的,其实这东西完全用不上 electron 的跨平台便利性,因为它只可能在 mac 上运行,但我还是脑一抽用它写了,毕竟 HTML & CSS 比原生开发效率高得多。

© Xinyu 2014 - 2024