Podfile 解析最佳实践

基本语法


首先需要做的是,看懂一个 Podfile。那么需要了解一些最基本的 ruby 语法,这部分非常简单:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
pod 'FLEX', :configurations => ['Debug'], :branch => 'develop'
use_frameworks!

以上三行代码是 Podfile 中最为常见的,其实这三行是在调用不同的方法。

方法调用

Ruby 中,方法调用的参数列表可以以空格形式接在方法名后,多个参数以逗号隔开,所以等价于:

source 'https://github.com/CocoaPods/Specs.git'
# =>
source('https://github.com/CocoaPods/Specs.git')

platform :ios, '8.0'
# =>
platform(:ios, '8.0')

如果是最后一个参数是字典,那么字典的大括号也可以省略,所以 pod 的调用等价于:

pod 'FLEX', :configurations => ['Debug'], :branch => 'develop'
#=>
map = {
  :configurations => ['Debug'],
  :branch => 'develop'
}
pod('FLEX', map)

符号(Symbol)

Symbol 是 Ruby 中的一种对象类型,一般作为名称标签,为了不影响阅读,我把 Symbol 的定义放在最后,这里可以暂且把它当做前面加了 : 的 string。

所以,上面的代码中,出现的 :ios:configuration:branch 以及常见的 :git:tag 等都是 Symbol。

方法定义

Ruby 的方法定义更加灵活,语义也更加丰富。

方法名

比如 :nil?, empty?, merge! 这类方法。

方法名小写,可包含 !? 这类符号。用法可以学习系统的定义:

? 常用于判断,取代了 is_ 开头的定义习惯。
! 常用于需要注意的方法,比如 arr.merge!(other_arr) 表示合并到 arr;与之对应的是 arr.merge(other_arr),表示合并,但不修改 arr,而是返回合并后的结果。
在很多开源库中,! 的用法就比较巧妙,有可能并不表示在当前对象上进行修改,仅仅为了优雅好看也是可能的。

所以,Podfile 中出现的 use_frameworks! 也是在调用方法。

参数列表
为了简单,这里仅介绍可空的参数定义。还是以 pod 方法举栗子:

pod 'Masonry'
pod 'pop', '~> 1.0.7'
pod 'Reachability', :git => 'xxx.git', :tag => 'v3.2.1'

常见的 pod 调用如上,通过调用就能猜出 pod 方法的声明:

# pname: 库名
# version: 指定版本,且可空
# map: 用键值对接收其他参数
def pod(pod_name, version = nil, **map)
  # ...
end

大致就是这样,这里的 * 和指针没关系 🌚。完整参数列表的定义方式,我写在文末吧。

返回值
其实解析这部分用不上返回值,不过可以介绍一下。Ruby 返回值有以下几个特点:

如果是最后一行,可以不写 return。
支持多个返回值。
代码块(Block)
这个和 Objective-C 差不多,常用于回调。当然 Podfile 也不缺少:

target :Meitu do
  pod 'Masonry'
end

do…end 可以看成大括号,:Meitu 是 target 方法的第一个参数。综合之前介绍的语法,target 的定义就呼之欲出了:

# tname: target 名称
# block: 回调
def target(tname, &block)
  # ...
  # 调用
  yield if block_given?
end

语法到这里就基本够用了,接着介绍如何解析。

解析


既然 Podfile 中是 Ruby 代码,也就表示,可以通过调用 Ruby 脚本的方式,直接执行 Podfile。

ruby ~/Desktop/Podfile

然后就报错了…(编译器又不知道 source,pod 这都是些什么方法…

定义方法

首先需要定义解析需要调用的方法,让指定的变量乖乖的被对应参数接收。最简易的版本,需要实现 target 和 pod 两个方法:

target

工程可能对应多个 target,具体要解析哪个 target,需要对应到打包时指定的 target,所以采用外部传入的方式:$target_argv:

def target(target_name = nil, &block = nil)
  # target name 可能是 String,可能是 Symbol,统一 to_s 一下
  # 如果不是当前打包的 target,直接返回就行了
  return if target_name.to_s != $target_argv
  # 调用 block
  yield if block_given?
end

pod

实现 pod 以后,就可以通过参数读取这种值了。同样,pod 可能包含 configuration 信息,这也是需要对应打包的 configuration 参数的:

def pod(pod_name, version = nil, **args)
  git = args[:git]
  branch = args[:branch]
  tag = args[:tag]
  commit = args[:commit]
  configurations = args[:configurations]

  # 如果 pod 指定了 configuration,则判断是否包含当前 configuration
  unless configurations.nil?
    return unless configurations.include?($configuration)
  end

  # 通过哪种方式引用,这里可以通过 tag、commit、branch 的 nil? 来判断来源
  comes_from = "tag"

  # $map 为全局变量
  $map[pod_name] = {
	comes_from: comes_from,
    version: version
  }
end

method_missing
除了 target 和 pod 方法外,Podfile 中还存在 source、platform 等各种各样的方法,一一实现是不可能的。对此,Ruby 提供了 method_missing 方法,该方法的作用类似于消息转发。当程序调用没有实现的方法时,统一走 method_missing。

# m: 方法名
# args: 位置参数(也就是数组)
def method_missing(m, *args); end

因为我们不需要处理未实现方法的逻辑,所以方法体是空的,不需要实现。

导出
到此,整个解析就已经完成了,比起以前用正则写的版本,清爽了很多。最后一步,将解析结果导出为 JSON 文件。代码很简单:

File.open($result_path, "w") do |f|
  f.write(JSON.pretty_generate($map))
end

Podfile.lock

这里简单提一下 lock 文件,因为 lock 文件中有准确的版本号,所以对应引用版本都从 lock 当中读取。而 lock 文件其实是 yaml 格式的,可以通过 yaml 库将它解析为 hash 和 array 进行读取。

整个流程

那么,应该如何将解析和导出两个步骤串起来呢?方法需要定义在代码开头,导出需要放在代码末尾,所以有了以下结构:

定义

eval(File.read(podfile_path))

导出

这里用到了非常强大的 eval 函数,也就是将 podfile 内容读取为将字符串并当做代码执行。

ruby inject_template.rb target_name configuration_name result_path podfile_path

🚣‍ 🚣 🚣‍ 🚣‍

最后
Symbol
Symbol 是 Ruby 中最为基础的对象类型,存储在 Symbol Table 中,可以看做 name 和 ID 的对应。Symbol 不可写,地址不变,全局唯一。这和 String 不同,两个值相同的 String,其实是不同的地址。

"some_string".object_id == "some_string".object_id #=> false
:some_string.object_id == :some_string.object_id #=> true

类似于 Java 的 String 和 static String,一个是用完重新分配,一个是始终是一个存储单元。针对于这个特性,Symbol 的效率会比 String 高一些。常用于成员变量名,hash 的 key 等。

如果想继续了解 Symbol,推荐阅读:

Understanding Differences Between Symbols & Strings in Ruby
Ruby Symbols
官方文档
方法定义

def foo(a, b="b_default", *c, d:, e: "e_default", **f, &g)
  # do stuff
end

a:Positional argument,位置参数。也就是说,声明在第一个,第一个传入的就一定是 a 接收。
b:和 a 含义相同,且有默认值。
c:连续多个 Positional argument。
d:Keyword argument,对位置没要求,接收指定 keyword 对应的值。
e:和 d 含义相同,且有默认值。
f:连续多个 Keyword argument。
g:block。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值