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插件机制的基本思想: 在项目代码中,你认为要扩展(暂时不扩展)的地方放置一 ...
随机推荐
- push自定义动画
// // ViewController.m // ViewControllerAnimation // // Created by mac on 15/5/26. // Copyright ...
- Hadoop文件操作常用命令
1.创建目录 #hdfs dfs -mkidr /test 2.查询目录结构 #hdfs dfs -ls / 子命令 -R递归查看//查看具体的某个目录:例如#hdfs dfs -ls /test 3 ...
- 对已有的docker容器增加新的端口映射
一般在运行容器时,我们都会通过参数 -p(使用大写的-P参数则会随机选择宿主机的一个端口进行映射)来指定宿主机和容器端口的映射,例如 docker run -it -d --name [contain ...
- 第11、12章等待方法和alter框处理
11.等待方法 time sleep webdriver implicitly_wait() 设置浏览器等待时间 WebDriverWait 等待条件满足或超时后退出 12.alter对话框处理 ...
- 3、前端--伪元素选择器、选择器优先级、字体、背景、边框、display、盒子模型
伪元素选择器 # 首字调整>>>:也是一种文档布局的方式 p:first-letter { font-size: 48px; /*字体大小*/ color: red; } # 在文本 ...
- Solution -「LOJ #150」挑战多项式 ||「模板」多项式全家桶
\(\mathcal{Description}\) Link. 给定 \(n\) 次多项式 \(F(x)\),在模 \(998244353\) 意义下求 \[G(x)\equiv\left\{ ...
- mysql 查询附近N公里内数据
mysql 查询一个地点(经纬度) 附近N公里内的数据.(根据一个地点的经纬度查询这个地点方圆几公里内的数据)1.创建测试表 CREATE TABLE `location` ( `id` int(10 ...
- 解决Chrome94之后非安全网站请求localhost报CORS问题
问题 自从谷歌浏览器升级到chrome94版本后,在非安全网站下通过请求本地接口就会出现以下错误: Access to XMLHttpRequest at 'http://127.0.0.1:1000 ...
- 注意!你的 Navicat 可能被下毒了...
大家早上好,我是程序猿DD! 刚刚看到一份来自微步在线发布的威胁情报通报,其中提到了被我们广泛应用的数据库管理工具Navicat Premium被投毒消息!如果你有用过相关版本的话,可能当前正处于数据 ...
- .Net Core AOP之AuthorizeAttribute
一.简介 在.net core 中Filter分为以下六大类: 1.AuthorizeAttribute(权限验证) 2.IResourceFilter(资源缓存) 3.IActionFilter(执 ...