前言
上文介绍了Xcode的配置文件project.pbxproj
里面的内容并且提到了Cocoapods正是利用Xcodeproj这个组件实现修改该文件达到改变Xcode工程结构的效果。本文将着重介绍Xcodeproj
这个组件,通过本文你将会了解这个组件的内容、原理和使用该组件的应用场景。
介绍
Xcodeproj
作为Cocoapods
的组件之一,它能够允许你用Ruby语言创建或者修改Xcode工程,脚本化枯燥的管理任务和构造友好的Xcode库,它同时支持Xcode workspaces (.xcworkspace)
、configuration files (.xcconfig)
和Xcode Scheme files (.xcscheme)
。
它的API文档在这里
安装
Xcodeproj
通过RubyGems
安装,打开终端键入
| $ [sudo] gem install xcodeproj
|
结束后,输入gem list
查看Xcodeproj
是否完成安装,正常情况下你会在list中看到xcodeproj (1.2.0, 1.1.0, 0.28.2)
这一行。
内容
让我们来大体瞅一眼Xcodeproj
的内容(Class List),如图1
图1
看到库里面的各个类,是不是有点小激动?没错,就是上篇文章介绍过的project.pbxproj
里面的各个元素,连名字都是一样!单独看下PBXProject
中的各个Attributes(图2),再拿上文中project.pbxproj(图3)
里的进行对比
图2
图3
你会发现Xcode配置文件中元素每个属性都能在这个库同名类中找到对应的属性。值得注意的是,Xcodeproj
中所有的类都继承于AbstractObject
,这个类是个基类,里面有isa
,uuid
,project
,其中uuid
就是唯一标识符,还有其他一些基本的method。这个唯一标识符的生成过程在uuid_generator.rb
这个类中,笔者水平有限,仅能看出uuid
的生成算法加入了文件路径的MD5
。
实战
下面你们可以通过下面这三个实战例子感受下Xcodeproj
的强大,代码如下:
|
require 'xcodeproj'
project_path = `.......` # 工程的全路径 注意这里用单引号''不要用``会出问题的
project = Xcodeproj::Project.open(project_path)
# 1、显示所有的target
project.targets.each do |target|
puts target.name
end
# 2、显示第一个target的所有Compile Sources
target = project.targets.first
files = target.source_build_phase.files.to_a.map do |pbx_build_file|
pbx_build_file.file_ref.real_path.to_s
end.select do |path|
path.end_with?(".m", ".mm", ".swift")
end.select do |path|
puts path
end
# 3、创建一个target 并添加文件
app_target = project.new_target(:application, 'demo', :ios, '6.0')
header_ref = project.main_group.new_file('./Class.h')
implm_ref = project.main_group.new_file('./Class.m')
app_target.add_file_references([implm_ref])
project.save()
|
大家可以写个ruby脚本依次将三个实例执行下,注意观察终端输出和Xcode目录结构的变化。
原理
如果你已经执行了上线的操作,那么一定好奇,这个库是怎么操作project.pbxproj
文件的?首先需要知道的是,在这个库操作project.pbxproj
之前,需要把Xcode工程的全路径给它,那我们就从Project
入手,它对应的是上篇文章中提到的根元素,从open
开始,注意我代码中的注释!
|
# File 'lib/xcodeproj/project.rb', line 96
def self.open(path)
path = Pathname.pwd + path
unless Pathname.new(path).exist?
raise "[Xcodeproj] Unable to open `#{path}` because it doesn't exist."
end
project = new(path, true)
project.send(:initialize_from_file) # 执行这个方法之前会判断path的正确性
project
end
# File 'lib/xcodeproj/project.rb', line 96
def initialize_from_file
pbxproj_path = path + 'project.pbxproj' # 拿到包内容中的配置文件,这个地方操作的是根元素
plist = Plist.read_from_path(pbxproj_path.to_s)
root_object.remove_referrer(self) if root_object
@root_object = new_from_plist(plist['rootObject'], plist['objects'], self) # new_from_plist方法拿到rootObject,正式开始操作
@archive_version = plist['archiveVersion']
@object_version = plist['objectVersion']
@classes = plist['classes']
@dirty = false
......
end
# File 'lib/xcodeproj/project.rb', line 252
def new_from_plist(uuid, objects_by_uuid_plist, root_object = false)
attributes = objects_by_uuid_plist[uuid]
if attributes
klass = Object.const_get(attributes['isa'])
object = klass.new(self, uuid)
objects_by_uuid[uuid] = object
object.add_referrer(self) if root_object
object.configure_with_plist(objects_by_uuid_plist) # 分析plist
object
end
end
|
到了这里,从根元素进入,分析objects属性内的所有元素,configure_with_plist
中使用objects的uuid去分析包装相应元素,将其装变为库中的对应类的对象,同时isa
也被复制过去。
最终,project.pbxproj
中的所有元素对应的信息,都转化为Ruby对象,然后增删改查等操作都变为对象操作,使用起来非常方便。
使用场景
- 你可以做一个Ruby脚本,放在打包测试流程中去,用来分析项目中不同target中缺少的文件和资源。
- 将一些繁琐的配置操作写成一个脚本,省时省力
原文链接:http://www.tomorjm.com/year/10/06/通过Xcodeproj深入探究Xcode工程文件%20二/