在日常iOS开发中,对CocoaPods使用最长见得形式如下:
platform :ios, '9.0'
target 'TestCocoaPods' do
pod 'CFYNavigationBarTransition', '1.2.2'
pod 'SDWebImage', '4.4.2'
end
但有时候我们想在pod install/update时做一些除了第三方库安装以外的事情,比如关闭所有target的Bitcode功能。这时就要用到CocoaPods中的钩子(Hooks),关于钩子(Hooks)的官方介绍在这里:CocoaPods Hooks
一、初识Hooks
现在使用Hooks来实现上文说的"关闭所有target的Bitcode功能",Podfile如下:
platform :ios, '9.0'
target 'TestCocoaPods' do
pod 'CFYNavigationBarTransition', '1.2.2'
pod 'SDWebImage', '4.4.2'
end
# 实现post_install Hooks
post_install do |installer|
# 1. 遍历项目中所有target
installer.pods_project.targets.each do |target|
# 2. 遍历build_configurations
target.build_configurations.each do |config|
# 3. 修改build_settings中的ENABLE_BITCODE
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end
在上面的Podfile使用了一个 "post_install" Hooks,这个Hooks允许你在生成的Xcode project写入硬盘或者其他你想执行的操作前做最后的改动。
对CocoaPods稍微有了解的话,应该知道CocoaPods是用Ruby开发的,其实Podfile就是一个Ruby代码文件,从Ruby的角度来看"post_install"这个Hooks,其实它就是一个Ruby中的Block,如果对Ruby Block没有了解,可以类比到OC中的Block。
post_install block接收一个"installer"参数,通过对"installer"修改来完成我们想要执行的特殊操作。
还有另一个Hooks叫做"pre_install",它的作用是允许你在Pods被下载后但是还未安装前对Pods做一些改变。写法和post_install一样,这里不再赘述。
二、初探Hooks
在Ruby语言中,万物皆为对象。上文提到CocoaPods的两个Hooks是Ruby中的Block,并且都接收一个"installer"参数。installer就是对象,那么就来看看这个installer对象的属性和方法信息,继续探索Hooks。
如果对Ruby语言熟悉的话,可以直接阅读CocoaPods源码来深入研究installer对象。但我是个Ruby小白,相信很多读者也是Ruby小白,那么我们就用小白的方式来研究一下installer对象。
先来捋一捋思路:
- Podfile是Ruby代码文件,那么我们可以在里面写Ruby代码;
- installer是对象;
- 可以通过写简单的Ruby代码来打印对象的属性和方法;
再来一点Ruby简单知识:
- 每个Ruby对象都有"public_methods"方法,这个方法返回对象公开方法名列表;
- 每个Ruby对象都有"instance_variables"方法,这个方法返回对象的属性名列表;
- 每个Ruby对象都有"instance.instance_variable_get"方法,调用这个方法并传入属性名,就可以得到属性名称对应的对象;
- Array类型类似OC中的NSArray;Hash类型类似OC中的NSDictionary;Array和Hash对象可以使用each方法来遍历;
- puts 是ruby中的打印方法
总结来说:在Podfile文件里写一下简单的Ruby代码来打印installer对象的属性和方法。Podfile代码如下:
platform :ios, '9.0'
target 'TestCocoaPods' do
pod 'CFYNavigationBarTransition', '1.2.2'
pod 'SDWebImage', '4.4.2'
end
post_install do |installer|
# puts 为在终端打印方法
puts "##### post_install start #####"
# 为了打印的日志方便查看,使用╟符号修饰
puts "╟ installer"
# 获取属性名称列表,并遍历
installer.instance_variables.each do |variableName|
# 打印属性名称
puts " ╟ #{variableName}"
end
puts " ╟ installer.public_methods"
# 获取方法名称列表,并遍历
installer.public_methods.each do |method|
# 打印方法名称
puts " ┣ #{method}"
end
puts "##### post_install end #####"
end
接下来运行pod install
我们会看到如下输出:
##### post_install start #####
╟ installer
╟ @sandbox
╟ @podfile
╟ @lockfile
╟ @use_default_plugins
╟ @has_dependencies
╟ @repo_update
╟ @update
╟ @installation_options
╟ @analysis_result
╟ @aggregate_targets
╟ @installed_specs
╟ @pod_installers
╟ @pods_project
╟ installer.public_methods
┣ update
┣ sandbox
┣ podfile
┣ lockfile
┣ repo_update?
┣ repo_update
┣ repo_update=
┣ update=
┣ install!
┣ pods_project
┣ has_dependencies
┣ has_dependencies?
┣ use_default_plugins
┣ use_default_plugins?
┣ prepare
┣ resolve_dependencies
┣ download_dependencies
┣ installation_options
┣ aggregate_targets
┣ pod_targets
┣ analysis_result
┣ names_of_pods_to_install
┣ installed_specs
┣ has_dependencies=
┣ development_pod_targets
┣ use_default_plugins=
┣ installed_specs=
┣ installation_options=
┣ config
┣ suppress_warnings
┣ instance_variables
┣ instance_variable_set
┣ instance_variable_get
┣ public_methods
##### post_install end #####
在这里贴出的installer.public_methods只是一部分,因为太多,只贴出主要的。
从输出中能看到一些熟悉的身影:
- @podfile 是否对应Podfile文件?
- @lockfile 是否对应Podfile.lock文件?
- @pods_project 上面已经见过了,修改bitcode属性就是用了它。
下我们来印证一下上面的猜想,这两个属性"@podfile"、"@lockfile"是不是对应文件,我们可以继续打印这两个属性,Podfile代码如下:
platform :ios, '9.0'
target 'TestCocoaPods' do
pod 'CFYNavigationBarTransition', '1.2.2'
pod 'SDWebImage', '4.4.2'
end
post_install do |installer|
# puts 为在控制台打印方法
puts "##### post_install start #####"
# 为了打印的日志方便查看,使用╟符号修饰
puts "╟ podfile"
# 打印podfile属性列表
installer.podfile.instance_variables.each do |variableName|
# 遍历属性并打印
puts " ╟ #{variableName}"
end
puts "╟ lockfile"
# 打印lockfile属性列表
installer.lockfile.instance_variables.each do |variableName|
# 遍历属性并打印
puts " ╟ #{variableName}"
end
# 暂时只看属性,不打印方法列表,因为暂时只看属性就够了
puts "##### post_install end #####"
end
可以看到如下打印:
##### post_install start #####
╟ podfile
╟ @defined_in_file
╟ @internal_hash
╟ @root_target_definitions
╟ @current_target_definition
╟ @pre_install_callback
╟ @post_install_callback
╟ lockfile
╟ @internal_data
╟ @defined_in_file
╟ @external_sources_data
╟ @dependencies
╟ @pod_names
╟ @pod_versions
##### post_install end #####
通过观察打印中出属性,可以看到一些关键点:
- podfile中的属性pre_install_callback、post_install_callback和Podfile中的pre_install和post_install好像是一一对应的。
- lockfile中的属性dependencies、pod_versions和Podfile.lock文件中的DEPENDENCIES:、COCOAPODS:好像是一一对应的。
这里使用了"好像"一词,因为通过现在打印的信息还不能100%确定是否一一对应。下面我们继续探索。
三、深探Hooks
上面使用一下简单的Ruby代码进行了一些简单的探索,想要继续打印属性中的属性中的属性,如果用上面方法会被累死,上面的方式有两个缺点:
- 每次要打印一个属性,都需要手动调整Podfile,不能一次性打印多层级属性。
- public_methods会打印出对象都有的公共方法,影响我们观察。
针对以上缺点,改进一下:
- 定义一个方法,将对象传给方法,就可以打印对象的属性和方法信息,并通过instance_variable_get方法取出对象的属性对象,递归调用方法自己将对象的属性传入,继续打印下一层级属性,直到没有属性列表或者超出设置的最大层级为止。
- 定义一个类TempClass, TempClass的对象会自带公共属性列表,然后再在TempClass里定义一些要过滤的实例方法。通过TempClass的实例的public_methods方法就可以取出想要过滤的方法列表,在打印对象的方法列表前,就可以进行过滤。
根据以上思路,Podfile是这样:
# 临时的Class,用来提取Class公共的方法列表和要手动过滤方法列表
class TempClass
# 定义一下需要过滤的方法
def initialize
end
def to_yaml
end
def +(other)
end
def -(other)
end
def *(other)
end
def /(other)
end
def -@
end
end
# 这里创建了全局变量,来存储TempClass对象的方法列表
# 这个全局变量是为了后面做方法列表滤用。
$classPublicMethos = TempClass.new().public_methods
# 定义一个方法,打印对象属性和方法列表,并递归打印对象的属性
# param: instance 要打印的对象
# param: name 对象的名称
# param: currentLayer 当前对象距离顶层的层级
# param: maxLayer 要打印的最大的层级
def putsInstanceVariables(instance,name,currentLayer=0,maxLayer=3)
# 当前层级是否在最大层级内
if currentLayer < maxLayer
if instance.nil?
# instance是空值
# 一个字符串乘以一个�整数,就是对应整数个字符串拼接
# ' '*2 结果为 ' '
# "#{' '*(currentLayer+1)}╟ "作用就是方便查看层级,使用VSCode编辑器就可以折叠对应的层级来查看
puts "#{' '*(currentLayer+1)}╟ #{name} : nil"
elsif instance.instance_of? Numeric
# instance是数字
puts "#{' '*(currentLayer+1)}╟ #{name} : #{instance}"
elsif instance.instance_of? TrueClass
# instance是ture值
puts "#{' '*(currentLayer+1)}╟ #{name} : true"
elsif instance.instance_of? FalseClass
# instance是false值
puts "#{' '*(currentLayer+1)}╟ #{name} : false"
elsif instance.instance_of? Pathname
# instance是路径
puts "#{' '*(currentLayer+1)}╟ #{name} : #{instance.to_s}"
elsif instance.instance_of? Array
# instance为数组对象
puts "#{' '*(currentLayer+1)}╟ #{name} : Array(Length: #{instance.length})"
# 遍历数组
instance.each_index do |index|
item = instance.at(index)
# 递归调用,打印数组中的对象,名称为index,层级+1
putsInstanceVariables item, "#{index}", currentLayer+1
end
elsif instance.instance_of? Hash
# instance为Hash对象,为<Key,Value>形式的集合
puts "#{' '*(currentLayer+1)}╟ #{name} : Hash(Length: #{instance.length})"
# 遍历Hash,取出key,value
instance.each do |key,value|
# 递归调用,打印Hash中的对象,名称为key,层级+1
putsInstanceVariables value, "#{key}", currentLayer+1
end
else
# instance为普通对象
puts "#{' '*(currentLayer+1)}╟ #{name} : #{instance.to_s}"
# 遍历对象所有属性名称
instance.instance_variables.each do |variableName|
# 根据名称获取属性
variable = instance.instance_variable_get(variableName)
# 递归调用,打印属性对象信息,名称为属性名,层级+1
putsInstanceVariables variable, variableName, currentLayer+1
end
# 过滤掉通用方法
# 数组使用"-"号就可以进行过滤
publicMethods = (instance.public_methods - $classPublicMethos)
# 打印公开方法
puts "#{' '*(currentLayer+2)}╟ public_methods : Array(Length: #{publicMethods.length})"
# 过滤Class都有的公共的方法
publicMethods.each do |method|
puts "#{' '*(currentLayer+3)}┣ #{method}"
end
end
end
end
platform :ios, '9.0'
target 'TestCocoaPods' do
pod 'CFYNavigationBarTransition', '1.2.2'
pod 'SDWebImage', '4.4.2'
end
pre_install do |installer|
puts ""
puts ""
puts "##### pre_install start #####"
# 打印installer信息
putsInstanceVariables installer, "installer"
puts "##### pre_install end #####"
puts ""
puts ""
end
post_install do |installer|
puts ""
puts ""
puts "##### post_install start #####"
# 打印installer信息
putsInstanceVariables installer, "installer"
puts "##### post_install end #####"
puts ""
puts ""
end
因为这样会打印很多信息,直接在终端里查看很不方便,所以将输出存到文件里,然后使用VSCode查看会很方便,VSCode可以折叠对应的层级。所以要这样执行pod install > PodInstallLog
。
pod install
完成以后,就可以打开PodInstallLog
来查看输出信息,里面会有很全面的installer的信息。
上面所说的podfile属性和Podfile文件是否一一对应,通过这次的打印就可以很明了的看出来,如下图:
两个Hooks的installer参数里包含了pod运行的很多信息,巧妙利用这些信息可以做很多事情来提高我们的开发效率。
笔者是一个Ruby小白,就看了一个小时的Ruby教程,上述方法可能很笨拙,如果你有更好的方法, 欢迎留言指点。
本文只做抛转引玉,这里只是对podfile属性进行了一些探索,如果想对其他属性进行探索,可以使用上面的对象打印方法,把对象打印出来观察。希望这篇文章对你把玩CocoaPods有一些帮助。