原文链接

背景

虽然做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中,这里提供了大量基础功能,包括 runoptionshelp等等。

首先,当我们每次执行 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

为了减小篇幅,这里只贴了核心相关代码,整体的流程大致是:

  1. 调用PluginManager.load_plugins并传入插件前缀
  2. PluginManager.plugin_gems_for_prefix对插件名进行处理,取出我们需要加载的文件,例如cocoapods前缀在这里会转换为所有包含cocoapods_plugin.rb的gem spec 信息及文件信息,例如~/cocoapods-qt/lib/cocoapods_plugin.rb
  3. 调用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 命令,并加了一些描述信息。为了让我们的扩展能生效,我们可以通过几种方式,

  1. 本地gem源码依赖
  2. 安装gem产物

为了更贴近实际生产发布流程,这里我们采用第二种方式。

首先,我们编译生成gem产物,

gem build cocoapods-test.gemspec

其次,本地安装

gem install ~/CocoapodsQt/cocoapods-test/cocoapods-test-0.0.1.gem  --local

此时,我们再执行 pod 命令

可以看到我们扩展的命令已经生效,接下来就可以开始愉快的coding了。

结语

至此,我们对Cocoapods的整体插件流程应该有了一个比较清晰的认识了,希望能给大家带来一些帮助。

Cocoapods插件机制浅析的更多相关文章

  1. typecho流程原理和插件机制浅析(第二弹)

    typecho流程原理和插件机制浅析(第二弹) 兜兜 393 2014年04月02日 发布 推荐 1 推荐 收藏 14 收藏,3.7k 浏览 上一次说了 Typecho 大致的流程,今天简单说一下插件 ...

  2. typecho流程原理和插件机制浅析(第一弹)

    typecho流程原理和插件机制浅析(第一弹) 兜兜 393 2014年03月28日 发布 推荐 5 推荐 收藏 24 收藏,3.5k 浏览 虽然新版本0.9在多次跳票后终于发布了,在漫长的等待里始终 ...

  3. 探寻 webpack 插件机制

    webpack 可谓是让人欣喜又让人忧,功能强大但需要一定的学习成本.在探寻 webpack 插件机制前,首先需要了解一件有意思的事情,webpack 插件机制是整个 webpack 工具的骨架,而 ...

  4. 如何创建一个 Cocoapods 插件

    原文链接 前言 我们在使用 Cocoapods 过程中,如果发现它未能满足我们的要求该怎么办呢? 最简单的粗暴的办法就是 fork 一份 Cocoapods 源码,然后自己公司内部或者个人直接针对源码 ...

  5. Linux模块机制浅析

    Linux模块机制浅析   Linux允许用户通过插入模块,实现干预内核的目的.一直以来,对linux的模块机制都不够清晰,因此本文对内核模块的加载机制进行简单地分析. 模块的Hello World! ...

  6. 【学】jQuery的源码思路6——增加each,animaion,ajax以及插件机制

    each() 插件机制 animation ajax //each() //这里第一个参数指定将this指向每次循环到的那个元素身上,而第三个参数element其实就是this本身所以和第一个参数是一 ...

  7. ImitateLogin新增插件机制以及又一个社交网站的支持

    我的文章里已经多次介绍 imitate-login ,这是我最近一直在维护的一个使用c#模拟社交网站登录的开源项目,现在新增了对插件的支持以及一个新的网站(由于某种原因,会在文章结束部分介绍:而且仅会 ...

  8. Maven生命周期和插件机制

    Maven中的一个非常重要的概念是生命周期和插件,这篇文章重点介绍下Maven的生命周期. Maven的生命周期是抽象的,具体的功能是有具体的插件来完成的,Maven有相当多的功能插件,以至于Mave ...

  9. php中的钩子(hook插件机制)

    对"钩子"这个概念其实不熟悉,最近看到一个php框架中用到这种机制来扩展项目,所以大概来了解下. hook插件机制的基本思想: 在项目代码中,你认为要扩展(暂时不扩展)的地方放置一 ...

随机推荐

  1. TCP连接的状态转换图深度剖析

    转载请注明来源:https://www.cnblogs.com/hookjc/ 在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接,如图1所示. (1)第一次握手:建立连接时 ...

  2. Mac搭建Git服务器—开启SSH

    SSH开启 在osx中开启ssh访问非常简单,只需要打开"系统偏好设置"并且点击"共享"图标即可. 选中下图中的check box即允许远程登陆.server处 ...

  3. ROS::message_filters中的一个报错(mt::TimeStamp……)

    『方便检索』 ros::Time msg_time = mt::TimeStamp<typename mpl::at_c<Messages, i>::type>::value( ...

  4. Ubuntu18 用新用户登录后退格键/方向键/制表键 乱码

    Ubuntu18新建用户后,用新用户登录,此时 退格键Backspace 变成了 ^H,且方向键.制表键.Del键等均失效 这样会造成很多的麻烦,解决方式有两种: 方式1:Ctrl + Backspa ...

  5. kube-scheduler源码分析(1)-初始化与启动分析

    kube-scheduler源码分析(1)-初始化与启动分析 kube-scheduler简介 kube-scheduler组件是kubernetes中的核心组件之一,主要负责pod资源对象的调度工作 ...

  6. Zookeeper应用之一:数据发布与订阅初体验

    Zookeeper到底是什么?可以从Zookeeper提供的功能来理解.本篇小作文就是使用其提供的功能之一:数据发布与订阅. 需求:服务端开启多个实例提供服务,客户端使用服务.如果服务端某个服务下线或 ...

  7. html页面预览pdf文件使用插件pdfh5.js

    html预览pdf文件需要依赖pdf.js 移动端适配需要pdfh5.js 记录移动端适配pdfh5.js的用发 在线预览: https://www.gjtool.cn/pdfh5/pdf.html? ...

  8. Redis 学习-上

    一.Redis 概述 1.1.Redis 定义 Redis (Remote Dictionary Server):远程字典服务 是一个开源的使用 ANSI C 语言编写.支持网络.可基于内存亦可持久化 ...

  9. 私有化轻量级持续集成部署方案--06-私有镜像仓库-Harbor

    提示:本系列笔记全部存在于 Github, 可以直接在 Github 查看全部笔记 针对私有镜像仓库的问题,Docker 官方提供了搭建仓库服务的镜像服务:registry,使用此镜像就可以部署私有仓 ...

  10. IDEA tomcat启动报错----Artifact is being deployed, please wait...解决

    今天学习遇到了这个错误,记录下自己遇到的错误和解决方法! 这个报错的意思是: Artifact 正在部署中,请稍候- 实际上有可能就是jar包没有导进去.检查项目打包情况:file-->Proj ...