把玩CocoaPods post_install 和 pre_install

在日常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对象。

先来捋一捋思路:

  1. Podfile是Ruby代码文件,那么我们可以在里面写Ruby代码;
  2. installer是对象;
  3. 可以通过写简单的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代码进行了一些简单的探索,想要继续打印属性中的属性中的属性,如果用上面方法会被累死,上面的方式有两个缺点:

  1. 每次要打印一个属性,都需要手动调整Podfile,不能一次性打印多层级属性。
  2. public_methods会打印出对象都有的公共方法,影响我们观察。

针对以上缺点,改进一下:

  1. 定义一个方法,将对象传给方法,就可以打印对象的属性和方法信息,并通过instance_variable_get方法取出对象的属性对象,递归调用方法自己将对象的属性传入,继续打印下一层级属性,直到没有属性列表或者超出设置的最大层级为止。
  2. 定义一个类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文件是否一一对应,通过这次的打印就可以很明了的看出来,如下图:


PodInstallLog
Podfile

两个Hooks的installer参数里包含了pod运行的很多信息,巧妙利用这些信息可以做很多事情来提高我们的开发效率。

笔者是一个Ruby小白,就看了一个小时的Ruby教程,上述方法可能很笨拙,如果你有更好的方法, 欢迎留言指点。

本文只做抛转引玉,这里只是对podfile属性进行了一些探索,如果想对其他属性进行探索,可以使用上面的对象打印方法,把对象打印出来观察。希望这篇文章对你把玩CocoaPods有一些帮助。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容