CocoaPods 为开发者提供了插件注册功能,可以使用 pod plugins create NAME
命令创建插件,并在 Podfile 中通过 plugin 'NAME'
语句引入插件。虽然在一般情况下很少使用这个功能,但在某些场景下,利用插件能比较方便快捷地解决问题,比如清除 input
,output
文件、创建 Podfile DSL 等。
刚开始写 CocoaPods 插件时,对其怎么执行没有 require
的插件代码比较好奇,但限于对 ruby 以及 gem 知识的了解,也就没有进一步地探索其实现原理,毕竟首要任务是解决工作问题。如今回过头来看这个问题,发现实现起来还是比较简单的。来看下 CocoaPods 是如何实现的。
实现探索
首先,由于 pod install
过程会涉及到插件的加载,所以直接查看 installer.rb
文件:
1 | # Runs the registered callbacks for the plugins post install hooks. |
其中 run_plugins_pre_install_hooks
和 run_plugins_post_install_hooks
分别执行了插件注册的 pre_install
和 pod_install
方法, ensure_plugins_are_installed
则确认插件是否已被安装。
接下来看下 Command::PluginManager
,这个类在 claide/command/plugin_manager
文件内,属于 claide
gem :
1 | # @return [Array<Gem::Specification>] Loads plugins via RubyGems looking |
以上代码调用几个的 Gem::Specification
方法如下:
1 | # 获取最新 spec 集合 |
可以看到在 loaded_plugins[plugin_prefix]
为空的情况下,程序会执行 plugin_gems_for_prefix
方法,plugin_gems_for_prefix
方法通过 latest_specs
获取了最新的 spec ,并通过 spec 的 matches_for_glob
方法对文件进行匹配,当 spec 中存在匹配 "#{prefix}_plugin#{Gem.suffix_pattern}"
格式的文件时,则视其为 CocoaPods 插件。在拿到插件及其匹配文件后,safe_activate_and_require
方法将文件加入 $LOAD_PATH 中并 require 之。
另外 CLAide::Command
类会在 run
类方法中加载所有插件,然后根据解析后的信息,执行对应的命令:
1 | # @param [Array, ARGV] argv |
对于通过 pod plugin create
命令创建的插件来说,lib 目录下都会自动生成一个 cocoapods_plugin.rb
文件,这个文件就是用来标识此 gem 为 CocoaPods 插件的。如果想手动创建 CocoaPods 插件,需要满足以下两个条件:1
2
3
4
5
6
7
8
9
10
11
12
13# Handles plugin related logic logic for the `Command` class.
#
# Plugins are loaded the first time a command run and are identified by the
# prefix specified in the command class. Plugins must adopt the following
# conventions:
#
# - Support being loaded by a file located under the
# `lib/#{plugin_prefix}_plugin` relative path.
# - Be stored in a folder named after the plugin.
# - 支持通过 `lib/#{plugin_prefix}_plugin` 路径的文件加载
# (也就是说,如果要对外暴露插件内部存的方法,需要在此文件中 require 之,比如自定义的 Podfile DSL 文件)
# - 保存在以插件命名的文件夹中
在 CocoaPods 上下文中,以上的 plugin_prefix
如下:1
self.plugin_prefixes = %w(claide cocoapods)
小结
如果需要外部 gem 以插件的形式提供某些功能,可以通过和 CocoaPods 一样的方式实现,即规定特定的命名规则,然后通过 Gem::Specification
提供的方法获取满足条件的 gem ,再 require 入口文件:
1 | spec = Gem::Specification.find_by_name('naruto') |