美文网首页iOS学习开发iOS DeveloperiOS
把玩CocoaPods post_install 和 pre_i

把玩CocoaPods post_install 和 pre_i

作者: CaffreySun | 来源:发表于2018-08-29 15:16 被阅读128次

    在日常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有一些帮助。

    相关文章

      网友评论

        本文标题:把玩CocoaPods post_install 和 pre_i

        本文链接:https://www.haomeiwen.com/subject/uaffwftx.html