原文链接

背景

虽然做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. ubuntu memcached安装与配置

    转载请注明来源:https://www.cnblogs.com/hookjc/ 关于Memcache与memcachedMemcache是项目名,memcached是服务名.让很多初接触的人感觉很是莫 ...

  2. Linux下Wordpress忘记密码后的解决方法

    进入Wordpress的数据库,找到wp_users表,使用MD5('你的密码')函数添加密码 示例: 修改admin的密码为123456 UPDATE wp_users SET user_pass= ...

  3. CentOS下搭建自动化测试基础框架:Jenkins+Maven+TestNG+ReportNG

    1. 安装JDK 1.1 卸载系统默认已安装的open-jdk rpm -qa|grep java 查出来openjdk相关的应用,把查出来的所有都要通过下面的命令给卸载掉 rpm -e --node ...

  4. endl与\n的区别

    看C++Primer的时候看到的,然后去百度了一下: 比较明白的解释: 1.区别在于: \n只代表换行的转义字符 endl除了代表换行,还紧跟着清出缓冲槽 2.接下来我们看一下具体内容的辨析: 要明白 ...

  5. docker平时使用异常记录

    GPU主机重启后,启动使用GPU的容器报错 docker: Error response from daemon: Unknown runtime specified nvidia. 解决办法:修改/ ...

  6. python——平时遇到问题记录

    # pyhs2安装 #centos yum install groupinstall 'development tools' yum install python34-devel yum instal ...

  7. 经常使用的系统类Math、Arrays、System、BigInteger和BigDecimal以及日期类,时间戳

    一.Math 常用类: //看看Math常用的方法(静态方法)//1.abs绝对值int abs = Math . abs(-9);System. out . printLn(abs);//9//2. ...

  8. Vue 子组件更新父组件的值

    今天在使用Vue中遇到了一个新的需求:子组件需要修改由父组件传递过来的值,由于子组件的值是由父组件传递过来的,不能直接修改属性的值, 我们想改变传递过来的值只能通过自定义事件的形式修改父组件的值达到修 ...

  9. Spring cloud是什么? 核心总结

    Spring Cloud 是一套完整的微服务解决方案,基于 Spring Boot 框架,准确的说,它不是一个框架,而是一个大的容器,它将市面上较好的微服务框架集成进来,从而简化了开发者的代码量. S ...

  10. 关于oracle中(+)的运用

    一.基础 1.1 SQL查询的基本原理 第一.单表查询:根据WHERE条件过滤表中的记录,形成中间表(这个中间表对用户是不可见的):然后根据SELECT的选择列选择相应的列进行返回最终结果.第二.两表 ...