原文链接

背景

虽然做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. Redis 分布式锁使用不当,酿成一个重大事故,超卖了100瓶飞天茅台!!!(转)

    基于Redis使用分布式锁在当今已经不是什么新鲜事了. 本篇文章主要是基于我们实际项目中因为redis分布式锁造成的事故分析及解决方案.我们项目中的抢购订单采用的是分布式锁来解决的,有一次,运营做了一 ...

  2. 我对JavaWeb中中文URL编码的简单总结

    1.application/x-www-form-urlencoded 它是一种编码类型.当URL地址里包含非西欧字符的字符串时,系统会将这些字符转换成application/x-www-form-u ...

  3. JAVA多线程学习四 - CAS(乐观锁)

    本文讲解CAS机制,主要是因为最近准备面试题,发现这个问题在面试中出现的频率非常的高,因此把自己学习过程中的一些理解记录下来,希望能对大家也有帮助. 什么是悲观锁.乐观锁?在java语言里,总有一些名 ...

  4. HttpClient的NoHttpResponse问题

       调用第三方接口时会报NoHttpResponse异常,原因是上次的连接已经断掉了,但是客户端并未知道,复用上次连接就报错了,所以要解决这个问题,就是要校验上次链接是否断掉了  1. httpCl ...

  5. Cocos2d-JS环境配置教程

    最近感觉还有时间,就在CSDN上再写一篇,顺便把一些错误纠正,下面开始正文,这里的Cocos2d-JS是3.0版本.这里环境配置还是有一些坑的,希望可以帮到看博客的你. 一.准备工作 1.安装pyth ...

  6. iOS时间处理之时间对比 by Nicky.Tsui

    通过项目需求, 服务器返回了一个 order_canceled_time 订单自动取消时间   如果我要跟当前时间做一个对比,然后生成出一个倒计时的时间  那么首先我们要知道 order_cancel ...

  7. 鸟哥的Linux私房菜学习笔记——文件权限与目录配置

    Linux的文件权限和目录配置 在linux中的每个用户必需属于一个组,不能独立于组外.在linux中每个文件有所有者.所在组.其它组的概念. (1)所有者 一般为文件的创建者,谁创建了该文件,就是天 ...

  8. WebKit Inside: DOM树的构建

    当客户端App主进程创建WKWebView对象时,会创建另外两个子进程:渲染进程与网络进程.主进程WKWebView发起请求时,先将请求转发给渲染进程,渲染进程再转发给网络进程,网络进程请求服务器.如 ...

  9. Java的泛型机制

    Java的泛型机制 泛型是 Java 从 JDK5 开始引入的新特性,本质上是参数化类型,即所操作的数据类型被指定为一个参数.这意味着编写的代码可以被很多不同类型的对象所重用. 1. 泛型的使用方式 ...

  10. 北大博士生提出CAE,下游任务泛化能力优于何恺明MAE

    大家好,我是对白. 何恺明时隔两年发一作论文,提出了一种视觉自监督学习新范式-- 用掩蔽自编码器MAE,为视觉大模型开路. 这一次,北大博士生提出一个新方法CAE,在其下游任务中展现的泛化能力超过了M ...