Light
Light
目录
  1. 前言
  2. 介绍
  3. 安装
  4. 内容
  5. 实战
  6. 原理
  7. 使用场景
  8. 相关链接

通过Xcodeproj深入探究Xcode工程文件 二

前言

上文介绍了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安装,打开终端键入

1
$ [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的强大,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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开始,注意我代码中的注释!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 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中缺少的文件和资源。
  • 将一些繁琐的配置操作写成一个脚本,省时省力

如果还有好的idea,欢迎补充~

相关链接