CocoaPod高级篇

目录

  • xcconfig文件

  • CocoaPod的实现

    • pod install的过程

xcconfig文件

作用:工程的配置文件。

可以通过在文件中的代码指定工程中依赖的库(OTHER_LDFLAGS),header_search_path(HEADER_SEARCH_PATHS)。在cocoapods里面还有其他的功能

参考:https://www.objc.io/issues/6-build-tools/cocoapods-under-the-hood/

CocoaPod的实现

源码位置:/Library/Ruby/Gems/#gem版本号#/gems/cocoapods-#版本号#

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 Install < Command
      def run
        verify_podfile_exists!
        installer = installer_for_config
        installer.repo_update = repo_update?(:default => true)
        installer.update = true
        installer.install!
      end
    end
    end
  end

Podfile的解析:

通过ruby的eval将读取到的文件字符串转换成ruby代码,然后有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加入已有的源数组中,然后更新原有的source对应的值。

稍微复杂一些的是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

上面的流程就完成了对podfile的解析。

安装依赖的过程

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

  • 解析Podfile中的依赖
  • 下载依赖
  • 创建Pods.xcodeproj工程
  • 继承workspace
解析Podfile中的依赖
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 就是从这里输出到终端的。

下载依赖

在依赖关系解决返回了一系列Specification对象之后,就到了Pod install 的第二部分,下载依赖:

def install_pod_sources
  @installed_specs = []
  pods_to_install = sandbox_state.added | sandbox_state.changed
  title_options = { :verbose_prefix => '-> '.green }
  root_specs.sort_by(&:name).each do |spec|
    if pods_to_install.include?(spec.name)
      if sandbox_state.changed.include?(spec.name) && sandbox.manifest
        previous = sandbox.manifest.version(spec.name)
        title = "Installing #{spec.name} #{spec.version} (was #{previous})"
      else
        title = "Installing #{spec}"
      end
      UI.titled_section(title.green, title_options) do
        install_source_of_pod(spec.name)
      end
    else
      UI.titled_section("Using #{spec}", title_options) do
        create_pod_installer(spec.name)
      end
    end
  end
end

在这个方法中你会看到更多熟悉的提示,CocoaPods会使用沙盒(sandbox)存储已有依赖的数据,在更新现有的依赖时,会根据依赖的的不同状态显示不同的提示信息:

-> Using AFNetworking (3.1.0)

-> Using AKPickerView (0.2.7)

-> Using BlocksKit (2.2.5) was (2.2.4)

-> Installing MBProgressHUD (1.0.0)
...

下载的方法在CocoaPods-Download中:

def self.download_source(target, params)
  FileUtils.rm_rf(target)
  downloader = Downloader.for_target(target, params)
  downloader.download
  target.mkpath

  if downloader.options_specific?
    params
  else
    downloader.checkout_options
  end
end

方法中调用的for_target根据不同的源会创建一个下载器,因为依赖可能通过不同的协议或者方式进行下载,比如说Git/HTTP/SVN等等,组件CocoaPods-Downloader就会根据Podfile中依赖的参数选项使用不同的方法下载依赖。

大部分的依赖都会被下载到~/Library/Caches/CocoaPos/Pods/Release这个文件夹中,然后从这个这里复制到项目工程目录下的./Pods中,这也就完成了整个CocoaPods的下载流程。

生成Pods.xcodeproj

CocoaPods通过组件CocoaPods-Downloader已经成功将所有的依赖下载到了当前的工程中,这里会将所有的依赖打包到Pods.xcodeproj中:

def generate_pods_project(generator = create_generator)
  UI.section 'Generating Pods project' do
    generator.generate!
    @pods_project = generator.project
    run_podfile_post_install_hooks
    generator.write
    generator.share_development_pod_schemes
    write_lockfiles
  end
end

generate_pods_project中会执行PodsProjectGenerator的实例方法generate!:

def generate!
  prepare
  install_file_references
 install_libraries
  set_target_dependencies
end

这个方法做了几件小事:

  • 生成Pods.xcodeproj工程
  • 将依赖中的文件加入工程
  • 将依赖中的Library加入工程
  • 设置目标依赖(Target Dependencies)
    这几件事情都离不开CocoaPods的另外一个组件Xcodeproj,这个一个可以操作一个Xcode工程中的Group以及文件的组件,我们都知道对对Xcode工程的修改大多数情况下都是对一个名为project.pdxproj的文件进行修改,而Xcodeproj这个组件就是CocoaPods团队开发的用于操作这个文件的第三方库。
生成workspace

最后的这一部分与生成Pods.xcodeproj的过程有一些相似,这里使用的类是UserProjectIntegrator,调用方法integrate!时,就会开始集成工程所需要的Target:

def integrate!
  create_workspace
  integrate_user_targets
  warn_about_xcconfig_overrides
  save_projects
end

整个Pod install的过程就结束了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容