Cocoapods插件机制浅析
背景
虽然做iOS开发的过程中使用过 Cocoapods, 但是对里面的细节了解其实不算太多,直到这两年做织女项目时,通过对Cocoapods进行Qt支持改造才开始深入了解部分细节,这个过程中,网上没有找到太多相关资料,本文就简单介绍下我对Cocoapods提供的插件机制的一个简单了解,希望能给大家带来一些帮助。
Ruby Open Classes
在此之前,我们简单看下 Ruby Open Classes ,这部分是为未接触过Ruby的同学准备的,熟悉的同学可以直接略过。
在Ruby中,类永远是开放的,你总是可以将新的方法加入到已有的类中,除了你自己的代码中,还可以用在标准库和内置类中,这个特性被称为Ruby Open Classes。下面我们通过一个示例简单看下。
首先,我们自定义一个类Human,放在human.rb文件中:
class Human
def greeting
puts "hello everybody"
end
def hungry
puts "I am hungry"
end
end
接着,我们新增一个main.rb:
require_relative 'human'
john = Human.new
john.greeting
# hello everybody
john.hungry
# I am hungry
之后,我们在main.rb中重新定义hungry方法,
class Human
def hungry
puts "I could eat a horse"
end
end
john.hungry
# I could eat a horse
可以看到,这里在我们新增hungry方法之后,所有的Human类的实例均调用我们的新实现了,即使是已经创建好的实例,这里故意放到两个文件中是想说明这个特性是可以跨文件甚至跨模块的,对Ruby内置方法的替换也是可以的(谨慎使用)
puts "hello".size
class String
def size
puts "goodbye"
end
end
# 5
# goodbye
puts "hello".size
这个特性是十分强大的,让我们可以很容易的对三方模块进行扩展,也是Cocoapods的插件体系所依赖的基础。
流程分析
Cocoapods的插件体系整体流程还是比较清晰的,下面我们就来逐步看下。
CLAide
首先,Cocoapods 提供了一个便捷的命令行工具库 CLAide,这个库包含很多功能,例如,一套命令基类,一套插件加载机制等。
Command基类
Command基类在lib/claide/command.rb中,这里提供了大量基础功能,包括 run 、 options、 help等等。
首先,当我们每次执行 pod xxx 命令时候,会执行 bin目录下的可执行文件pod。
require 'cocoapods'
if profile_filename = ENV['PROFILE']
# 忽略不相关内容...
else
Pod::Command.run(ARGV)
end
这里实际上是 Pod 模块从CLAide继承了子类Command < CLAide::Command,我们执行Pod命令时候,就会调用
def self.run(argv)
help! 'You cannot run CocoaPods as root.' if Process.uid == 0
verify_minimum_git_version!
verify_xcode_license_approved!
super(argv)
ensure
UI.print_warnings
end
实际上只是扩展了一些检测git版本、xcode证书等,真正核心部分还是调用的CLAide的实现:
def self.run(argv = [])
plugin_prefixes.each do |plugin_prefix|
PluginManager.load_plugins(plugin_prefix)
end
argv = ARGV.coerce(argv)
command = parse(argv)
ANSI.disabled = !command.ansi_output?
unless command.handle_root_options(argv)
command.validate!
command.run
end
rescue Object => exception
handle_exception(command, exception)
end
可以看到这里真正执行命令之前会遍历所有的插件前缀,并进行插件加载,回过头来再查看 cocoapods/command.rb 会发现,这里指定了约定的插件前缀
self.plugin_prefixes = %w(claide cocoapods)
可以看到这里的插件分为两种,我们目前只关心文件名为cocoapods前缀的插件。
PluginManager
我们深入PluginManager的具体实现看下,
def self.load_plugins(plugin_prefix)
loaded_plugins[plugin_prefix] ||=
plugin_gems_for_prefix(plugin_prefix).map do |spec, paths|
spec if safe_activate_and_require(spec, paths)
end.compact
end
def self.plugin_gems_for_prefix(prefix)
glob = "#{prefix}_plugin#{Gem.suffix_pattern}"
Gem::Specification.latest_specs(true).map do |spec|
matches = spec.matches_for_glob(glob)
[spec, matches] unless matches.empty?
end.compact
end
def self.safe_activate_and_require(spec, paths)
spec.activate
paths.each { |path| require(path) }
true
# 不相关代码略去
# ...
end
为了减小篇幅,这里只贴了核心相关代码,整体的流程大致是:
- 调用
PluginManager.load_plugins并传入插件前缀 PluginManager.plugin_gems_for_prefix对插件名进行处理,取出我们需要加载的文件,例如cocoapods前缀在这里会转换为所有包含cocoapods_plugin.rb的gem spec 信息及文件信息,例如~/cocoapods-qt/lib/cocoapods_plugin.rb- 调用
PluginManager.safe_activate_and_require进行对应的 gem spec 检验并对每个文件进行加载
至此,基本的插件加载流程大致梳理清楚了。
实操
下面我们看下如何自己扩展一个插件,关于这部分,Cocoapods其实也基本已经帮我们做了很多事情了,主要是 cocoapods-plugins, 它提供了一个插件创建的完整生命周期,包括新增、发布、检索等。
Cocoapods-plugins
执行 pod plugins create cocoapods-test 之后,发现自动帮我们创建了一个gem工程,其中的 lib 文件夹下果然存在了一个 cocoapods_plugin.rb 文件,整体的目录结构如下:
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── cocoapods-test.gemspec
├── lib
│ ├── cocoapods-test
│ │ ├── command
│ │ │ └── test.rb
│ │ ├── command.rb
│ │ └── gem_version.rb
│ ├── cocoapods-test.rb
│ └── **cocoapods_plugin.rb**
└── spec
├── command
│ └── test_spec.rb
└── spec_helper.rb
这里最核心的就是cocoapods_plugin.rb ,我们前面分析过,执行pod命令时候会主动加载所有cocoapods_plugin.rb文件,那么只要我们将需要扩展的类加到这里面,执行命令时候就会生效。
class Test < Command
self.summary = 'Short description of cocoapods-test.'
self.description = <<-DESC
Longer description of cocoapods-test.
DESC
self.arguments = 'NAME'
def initialize(argv)
@name = argv.shift_argument
super
end
def validate!
super
help! 'A Pod name is required.' unless @name
end
def run
UI.puts "Add your implementation for the cocoapods-test plugin in #{__FILE__}"
end
end
可以看到这里其实只是新增了一个 Test 命令,并加了一些描述信息。为了让我们的扩展能生效,我们可以通过几种方式,
- 本地gem源码依赖
- 安装gem产物
为了更贴近实际生产发布流程,这里我们采用第二种方式。
首先,我们编译生成gem产物,
gem build cocoapods-test.gemspec
其次,本地安装
gem install ~/CocoapodsQt/cocoapods-test/cocoapods-test-0.0.1.gem --local
此时,我们再执行 pod 命令

可以看到我们扩展的命令已经生效,接下来就可以开始愉快的coding了。
结语
至此,我们对Cocoapods的整体插件流程应该有了一个比较清晰的认识了,希望能给大家带来一些帮助。
Cocoapods插件机制浅析的更多相关文章
- typecho流程原理和插件机制浅析(第二弹)
typecho流程原理和插件机制浅析(第二弹) 兜兜 393 2014年04月02日 发布 推荐 1 推荐 收藏 14 收藏,3.7k 浏览 上一次说了 Typecho 大致的流程,今天简单说一下插件 ...
- typecho流程原理和插件机制浅析(第一弹)
typecho流程原理和插件机制浅析(第一弹) 兜兜 393 2014年03月28日 发布 推荐 5 推荐 收藏 24 收藏,3.5k 浏览 虽然新版本0.9在多次跳票后终于发布了,在漫长的等待里始终 ...
- 探寻 webpack 插件机制
webpack 可谓是让人欣喜又让人忧,功能强大但需要一定的学习成本.在探寻 webpack 插件机制前,首先需要了解一件有意思的事情,webpack 插件机制是整个 webpack 工具的骨架,而 ...
- 如何创建一个 Cocoapods 插件
原文链接 前言 我们在使用 Cocoapods 过程中,如果发现它未能满足我们的要求该怎么办呢? 最简单的粗暴的办法就是 fork 一份 Cocoapods 源码,然后自己公司内部或者个人直接针对源码 ...
- Linux模块机制浅析
Linux模块机制浅析 Linux允许用户通过插入模块,实现干预内核的目的.一直以来,对linux的模块机制都不够清晰,因此本文对内核模块的加载机制进行简单地分析. 模块的Hello World! ...
- 【学】jQuery的源码思路6——增加each,animaion,ajax以及插件机制
each() 插件机制 animation ajax //each() //这里第一个参数指定将this指向每次循环到的那个元素身上,而第三个参数element其实就是this本身所以和第一个参数是一 ...
- ImitateLogin新增插件机制以及又一个社交网站的支持
我的文章里已经多次介绍 imitate-login ,这是我最近一直在维护的一个使用c#模拟社交网站登录的开源项目,现在新增了对插件的支持以及一个新的网站(由于某种原因,会在文章结束部分介绍:而且仅会 ...
- Maven生命周期和插件机制
Maven中的一个非常重要的概念是生命周期和插件,这篇文章重点介绍下Maven的生命周期. Maven的生命周期是抽象的,具体的功能是有具体的插件来完成的,Maven有相当多的功能插件,以至于Mave ...
- php中的钩子(hook插件机制)
对"钩子"这个概念其实不熟悉,最近看到一个php框架中用到这种机制来扩展项目,所以大概来了解下. hook插件机制的基本思想: 在项目代码中,你认为要扩展(暂时不扩展)的地方放置一 ...
随机推荐
- java中使用反射获取pojo(实体)类的所有字段值
出处:https://developer.aliyun.com/article/239346 说起反射,不得不说它实在是太强大了,通过反射就可以轻轻松松拿到各种东东,如果你想在项目中解除对某个类的依赖 ...
- 自定义 RestTemplate 异常处理 (转)
转自:https://ethendev.github.io/2018/11/06/RestTemplate-error-handler/ 一些 API 的报错信息通过 Response 的 body返 ...
- IDEA中Git的一般使用场景
感谢大佬:https://www.cnblogs.com/javabg/p/8567790.html 工作中多人使用版本控制软件协作开发,常见的应用场景归纳如下: 假设小组中有两个人,组长小张,组员小 ...
- swoole错误“Uncaught Error: Class 'swoole_server' not found”的解决办法
如果你在执行swoole对应文件时,报下面的错误, PHP Fatal error: Uncaught Error: Class 'swoole_server' not found in /mnt/w ...
- 《PHP程序员面试笔试宝典》——如何应对自己不会回答的问题?
如何巧妙地回答面试官的问题? 本文摘自<PHP程序员面试笔试宝典> 在面试的过程中,对面试官提出的问题求职者并不是都能回答出来,计算机技术博大精深,很少有人能对计算机技术的各个分支学科了如 ...
- 基于TMS320C6670的软件无线电核心板
一.板卡概述 北京太速科技自主研发的TMS320C6670核心板,采用TI KeyStone系列的四核定点/浮点DSP TMS320C6670作主处理器.板卡引出处理器的全部信号引脚,便于客户二次开发 ...
- Solution -「牛客 NOIP 模拟赛」打拳
\(\mathcal{Description}\) 现 \(2^n\) 个人进行淘汰赛,他们的战力为 \(1\sim 2^n\),战力强者能战胜战力弱者,但是战力在集合 \(\{a_m\}\) 里 ...
- Solution -「APIO 2018」「洛谷 P4630」铁人两项
\(\mathcal{Description}\) Link. 给定一个 \(n\) 个点 \(m\) 条边的无向图(不保证联通),求有序三元点对 \((s,c,f)\) 的个数,满足 \(s ...
- 公式编辑器CVE-2018-0798样本分析
当前样本是一个RTF文档,内嵌一个公式编辑器对象,该对象利用Office编辑器漏洞CVE-2018-0798执行shellcode,对EQNEDT32.exe进行代码注入,执行恶意代码. 使用 ...
- CPU、进程、线程原理
巨人的肩膀 看完这篇还不懂高并发中的线程与线程池你来打我 (qq.com)