1. 程式人生 > >通過Xcodeproj深入探究Xcode工程檔案 二

通過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安裝,開啟終端鍵入

$ [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中缺少的檔案和資源。
  • 將一些繁瑣的配置操作寫成一個指令碼,省時省力