iOS开发——CocoaPods

 

记录下自己学习CocoaPods的心路历程

一、CoaoaPods简介

CocoaPods是专门为iOS工程提供第三方依赖库的管理工具,通过CocoaPods,我们可以更方便地管理每个第三方库的版本,而且不需要我们做太多的配置,就可以直观、集中和自动化地管理我们项目的第三方库。

CocoaPods将所有依赖的库都放在一个名为Pods的项目下,然后让主项目依赖Pods项目。然后,我们编码工作都从主项目转移到Pods项目。Pods项目最终会编译为一个libPod-项目名.a静态库,主项目依赖于这个静态库。

对于资源文件,CocoaPods 提供了一个名为 Pods-resources.sh 的 bash 脚本,该脚本在每次项目编译的时候都会执行,将第三方库的各种资源文件复制到目标目录中。

CocoaPods 通过一个名为 Pods.xcconfig 的文件来在编译时设置所有的依赖和参数。

CocoaPods是用 Ruby 写的,并由若干个 Ruby 包 (gems) 构成的。在解析整合过程中,最重要的几个 gems 分别是: CocoaPods/CocoaPodsCocoaPods/Core, 和 CocoaPods/Xcodeproj

CocoaPod的核心组件

  • CocoaPods/CocoaPod
    这是一个面向用户的组件,每当执行一个 pod 命令时,这个组件都将被激活。该组件包括了所有使用 CocoaPods 涉及到的功能,并且还能通过调用所有其它的 gems 来执行任务。

  • CocoaPods/Core
    Core 组件提供支持与 CocoaPods 相关文件的处理,文件主要是 Podfile 和 podspecs。

  • Podfile
    Podfile 是一个文件,用于定义项目所需要使用的第三方库。该文件支持高度定制,你可以根据个人喜好对其做出定制。更多相关信息,请查阅 Podfile 指南。

  • Podspec
    .podspec 也是一个文件,该文件描述了一个库是怎样被添加到工程中的。它支持的功能有:列出源文件、framework、编译选项和某个库所需要的依赖等。

  • CocoaPods/Xcodeproj
    这个 gem 组件负责所有工程文件的整合。它能够创建并修改 .xcodeproj 和 .xcworkspace 文件。它也可以作为单独的一个 gem 包使用。如果你想要写一个脚本来方便地修改工程文件,那么可以使用这个 gem。

二、CocoaPods的常用命令

pod install,最常用的指令; pod update,更新repo并重新解析依赖; pod install --repo-update,类似pod update; pod install --no-repo-update,忽略Pod库更新,直接用本地repo进行install; pod update --no-repo-update,类似pod install; pod update AFNetworking,更新指定库;

以上所有指令都可以添加 --verbose ,查看更详细的信息; xcconfig在新增configuration之后,需要重新pod install,并修改xcconfig。

Pod install 的过程

pod install 这个命令到底做了什么?首先,在 CocoaPods 中,所有的命令都会由 Command 类派发到将对应的类,而真正执行 pod install 的类就是 Install

module Pod
  class Command
	class Install < Command
	  def run
		verify_podfile_exists!
		installer = installer_for_config
		installer.repo_update = repo_update?(:default => false)
		installer.update = false
		installer.install!
	  end
	end
  end
end


这里面会从配置类的实例 config 中获取一个 Installer 的实例,然后执行 install! 方法,这里的 installer 有一个 update 属性,而这也就是 pod install 和 update 之间最大的区别,其中后者会无视已有的 Podfile.lock 文件,重新对依赖进行分析:

module Pod
  class Command
	class Update < Command
	  def run
		...

		installer = installer_for_config
		installer.repo_update = repo_update?(:default => true)
		installer.update = true
		installer.install!
	  end
	end
  end
end

Podfile 的解析

Podfile 中依赖的解析其实是与我们在手动解析 Podfile 章节所介绍的差不多,整个过程主要都是由 CocoaPods-Core 这个模块来完成的,而这个过程早在 installer_for_config 中就已经开始了:

def installer_for_config
  Installer.new(config.sandbox, config.podfile, config.lockfile)
end

这个方法会从 config.podfile 中取出一个 Podfile 类的实例:

def podfile
  @podfile ||= Podfile.from_file(podfile_path) if podfile_path
end

类方法 Podfile.from_file 就定义在 CocoaPods-Core 这个库中,用于分析 Podfile 中定义的依赖,这个方法会根据 Podfile 不同的类型选择不同的调用路径:

Podfile.from_file
`-- Podfile.from_ruby
	|-- File.open
	`-- eval

from_ruby 类方法就会像我们在前面做的解析 Podfile 的方法一样,从文件中读取数据,然后使用 eval 直接将文件中的内容当做 Ruby 代码来执行。

def self.from_ruby(path, contents = nil)
  contents ||= File.open(path, 'r:utf-8', &:read)

  podfile = Podfile.new(path) do
	begin
	  eval(contents, nil, path.to_s)
	rescue Exception => e
	  message = "Invalid `#{path.basename}` file: #{e.message}"
	  raise DSLError.new(message, path, e, contents)
	end
  end
  podfile
end

在 Podfile 这个类的顶部,我们使用 Ruby 的 Mixin 的语法来混入 Podfile 中代码执行所需要的上下文:

include Pod::Podfile::DSL

Podfile 中的所有你见到的方法都是定义在 DSL 这个模块下面的:

module Pod
  class Podfile
	module DSL
	  def pod(name = nil, *requirements) end
	  def target(name, options = nil) end
	  def platform(name, target = nil) end
	  def inhibit_all_warnings! end
	  def use_frameworks!(flag = true) end
	  def source(source) end
	  ...
	end
  end
end

这里定义了很多 Podfile 中使用的方法,当使用 eval 执行文件中的代码时,就会执行这个模块里的方法,在这里简单看一下其中几个方法的实现,比如说 source 方法:

def source(source)
  hash_sources = get_hash_value('sources') || []
  hash_sources << source
  set_hash_value('sources', hash_sources.uniq)
end

该方法会将新的 source 加入已有的源数组中,然后更新原有的 sources 对应的值。

稍微复杂一些的是 target 方法:

def target(name, options = nil)
  if options
	raise Informative, "Unsupported options `#{options}` for " \
	  "target `#{name}`."
  end

  parent = current_target_definition
  definition = TargetDefinition.new(name, parent)
  self.current_target_definition = definition
  yield if block_given?
ensure
  self.current_target_definition = parent
end

这个方法会创建一个 TargetDefinition 类的实例,然后将当前环境系的 target_definition 设置成这个刚刚创建的实例。这样,之后使用 pod 定义的依赖都会填充到当前的 TargetDefinition 中:

def pod(name = nil, *requirements)
  unless name
	raise StandardError, 'A dependency requires a name.'
  end

  current_target_definition.store_pod(name, *requirements)
end

当 pod 方法被调用时,会执行 store_pod 将依赖存储到当前 target 中的 dependencies 数组中:

def store_pod(name, *requirements)
  return if parse_subspecs(name, requirements)
  parse_inhibit_warnings(name, requirements)
  parse_configuration_whitelist(name, requirements)

  if requirements && !requirements.empty?
	pod = { name => requirements }
  else
	pod = name
  end

  get_hash_value('dependencies', []) << pod
  nil
end

总结一下,CocoaPods 对 Podfile 的解析与我们在前面做的手动解析 Podfile 的原理差不多,构建一个包含一些方法的上下文,然后直接执行 eval 方法将文件的内容当做代码来执行,这样只要 Podfile 中的数据是符合规范的,那么解析 Podfile 就是非常简单容易的。

安装依赖的过程

Podfile 被解析后的内容会被转化成一个 Podfile 类的实例,而 Installer 的实例方法 install! 就会使用这些信息安装当前工程的依赖,而整个安装依赖的过程大约有四个部分:

  • 解析 Podfile 中的依赖
  • 下载依赖
  • 创建 Pods.xcodeproj 工程
  • 集成 workspace
def install!
  resolve_dependencies
  download_dependencies
  generate_pods_project
  integrate_user_project
end

在上面的 install 方法调用的 resolve_dependencies 会创建一个 Analyzer 类的实例,在这个方法中,你会看到一些非常熟悉的字符串:

def resolve_dependencies
  analyzer = create_analyzer

  plugin_sources = run_source_provider_hooks
  analyzer.sources.insert(0, *plugin_sources)

  UI.section 'Updating local specs repositories' do
	analyzer.update_repositories
  end if repo_update?

  UI.section 'Analyzing dependencies' do
	analyze(analyzer)
	validate_build_configurations
	clean_sandbox
  end
end

在使用 CocoaPods 中经常出现的 Updating local specs repositories 以及 Analyzing dependencies 就是从这里输出到终端的,该方法不仅负责对本地所有 PodSpec 文件的更新,还会对当前 Podfile 中的依赖进行分析:

def analyze(analyzer = create_analyzer)
  analyzer.update = update
  @analysis_result = analyzer.analyze
  @aggregate_targets = analyzer.result.targets
end

analyzer.analyze 方法最终会调用 Resolver 的实例方法 resolve

def resolve
  dependencies = podfile.target_definition_list.flat_map do |target|
	target.dependencies.each do |dep|
	  @platforms_by_dependency[dep].push(target.platform).uniq! if target.platform
	end
  end
  @activated = Molinillo::Resolver.new(self, self).resolve(dependencies, locked_dependencies)
  specs_by_target
rescue Molinillo::ResolverError => e
  handle_resolver_error(e)
end

这里的 Molinillo::Resolver 就是用于解决依赖关系的类。

三、CocoaPods的使用

CocoaPods的使用流程很简单:(假设已经安装CocoaPods)

1、在xcodeproj所在目录下,新建Podfile文件; 2、描述依赖信息,以demo为例,有AFNetworking和SDWebImage两个第三方库:

target 'LearnPod' do
        pod 'AFNetworking'
        pod 'SDWebImage'
end

3、打开命令行,执行pod install ; 4、打开生成xcworkspace,就可以继续开发;

PodFile的写法

1、普通的写法; pod 'AFNetworking' 或者 pod 'AFNetworking', '3.2.1',前者是下载最新版本,后者是下载指定版本。

2、指向本地的代码分支;

pod 'AFNetworking', :path => '/Users/loyinglin/Documents/Learn/AFNetworking'
指向的本地目录要带有podspec文件。

3、指定远端的代码分支;

pod 'AFNetworking', :git => 'https://github.com/AFNetworking/AFNetworking.git', :branch => 'master'

指向的repo仓库要带有podspec文件。

4、针对特定的configurations用不同的依赖库

`pod 'AFNetworking', :configurations => ['Release']`

如上,只有Release的configurations生效;(同理,可以设置Debug)

5、多target的PodFile

platform :ios, '8.0'

def commonPods #通用pods集
    pod 'AFNetworking', '~> 2.0'
    pod 'Masonry'
end

def appOnlyPods #app专用pods集
    pod 'MBProgressHUD'
end

def extensionPods #扩展专用pods集
    pod 'GTSDKExtension'
end

target :TestCocoaPods do
    commonPods
    appOnlyPods

    target :TestCocoaPodsTests do
    inherit! :search_paths
    # Pods for testing
    end

    target :TestCocoaPodsUITests do
        inherit! :search_paths
        # Pods for testing
    end
end

target :SecondTarget do
    commonPods
end

四、Pods工程和主工程的关系

在实际的开发过程,容易知道Pods工程是先编译,编译完再执行主工程的编译;因为主工程的Linked Libraries里面有libPods-LearnPod.a的文件。(LearnPod是target的名字,下面的示例图都是用LearnPod作为target名)

对于资源文件,CocoaPods 提供了一个名为 Pods-resources.sh 的 bash 脚本,该脚本在每次项目编译的时候都会执行,将第三方库的各种资源文件复制到目标目录中。 CocoaPods 通过一个名为 Pods.xcconfig 的文件来在编译时设置所有的依赖和参数。 在编译之前会检查pod的版本是否发生变化(manifest和.lock文件对比),以及执行一些自定义的脚本。 Pod库的子target在指定armv7和arm64两个架构的时候,会分别编译生成armv7和arm64的.a文件;然后再进行一次合并操作,得到一个.a文件。 编译完成后进行链接,在armv7和arm64都指定时,会分别进行链接,最后合并得到可执行文件。 得到可执行文件后,会进行asset、storyboard等资源文件的处理;还会执行pod的脚本,把pod的资源复制过来。 全部准备就绪,就会生成符号表,包括.a文件里面的符号。 最后进行签名、校验,得到.app文件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值