iOS工程文件整理

在iOS开发过程的当中不知道大家有没有遇到过这样的问题

  1. 文件的物理位置不对应
  2. 多余的未被工程引用的文件

这两个问题在工程变得比较大的时候,对工程的影响就比较让人难受了,于是我决定对工程进行整理。

因为我们的业务还是蛮复杂的,工程就特别大,手动查找和整理简直就是一场灾难,于是我决定使用脚本来帮助我完成这项虽然简单但是巨大的工程。

文件物理位置不对应的原因

说到这个问题,我们就需要提一下project.pbxproj文件了,对,就是那个每次合并冲突都让人头疼的文件。

project.pbxproj文件本质上是一种旧风格的plist,因为可读性不是很好,逐渐被弃用,但是苹果却一直沿用,在这个文件当中包含了Xcode工程的各项配置和参数。project.pbxproj文件采用UDID作为key索引来保证文件的唯一性,同时又在value当中指定其对应子节点的UDID值,这样就将一个树形结构的数据用列表形式表示了出来(UDID是根据硬件和时间戳生成的唯一标识,这里不再详细解释其过程),工程的虚拟文件目录就是这么来的。

下面是一个转换成标准plist的结构,我们可以看到它的引用方式

20170731-1

因此我们解决物理位置不对应的问题只需要读取project.pbxproj的虚拟目录,并检查当前目录下是否有该文件,如果没有,到其对应的物理路径下,将其剪切过来即可。

针对第二个问题,我们可以扫描工程的整个物理目录,将每个文件去虚拟目录当中查找,如果没有被引用,直接删除即可。

脚本的编写

我们使用python来完成这次的任务,为什么不用shell?因为bash 3不支持key-value结构,要升级到bash 4才可以,而python比较方便,就使用它了,推荐使用python 3,因为它简化了蛮多操作的,比如编码什么的,不推荐使用预装的python 2。

1.读取project.pbxproj

我们先需要使用以下命令将project.pbxproj转换成标准的plist,然后使用python库biplist读取

value=`plutil -convert xml1 -o - project.pbxproj` && echo $value >mPlistPath

2.检查虚拟目录下对应的文件是否存在,并进行相应操作

3.扫描工程物理路径,检查文件是否被引用,并进行操作

脚本如下:

# !/usr/bin/python
# coding=utf-8

from biplist import *
import os

global rootObjects
global rootArr
global plistPath

####################变量设置区#################################
rootPath= "/Users/yzq/Desktop/Test/Test"
projectFilePath = "/Users/yzq/Desktop/Test/Test.xcodeproj"
plistPath = "/Users/yzq/Desktop/123.plist"
##############################################################

def checkfile(filePath,filekey):
    global rootObjects

    dic = rootObjects[filekey]
    # print(filekey) 

    strPath = ""
    if "path" in dic:
        strPath = dic["path"]
    else:
        strPath = dic["name"]

    path = filePath + "/" + strPath
    if "children" in dic:
        if len(dic["children"]) < 1:
            print("空文件夹"+"------"+path)
        else:
            for item in dic["children"]:
                checkfile(path,item)
    else:
        if os.path.exists(path) == False:
            print("文件不存在"+path)


def startCheckFile(mRootPath):
    global rootObjects
    global plistPath

    plist = readPlist(plistPath)
    rootObjects = plist["objects"]

    for dic in rootObjects.values():
        if ("name" in dic) and dic["name"] == "dfc_v3":
            rootArr = dic["children"]
            break

    print(rootArr)

    for item in rootArr:
        print("开始检查item"+item+"-------------------------------")
        checkfile(mRootPath,item)


def checkFileIsInProject(mPlistObject, mFileName, mFilePath):
    for item in mPlistObject.values():
        if (("path" in item) and (item["path"] == mFileName)) or (("name" in item) and (item["name"] == mFileName)):
            # print("发现文件引用"+mFileName)
            return
    print("未发现文件应用:"+mFileName)
    print(mFilePath)


def checkProject(mPlistObject, mFilePath):
    dirList = os.listdir(mFilePath)
    for item in dirList:
        itemPath = os.path.join(mFilePath,item)
        if os.path.isfile(itemPath):
            checkFileIsInProject(mPlistObject,item,itemPath)
        elif os.path.isdir(itemPath):
            checkProject(mPlistObject,itemPath)
        else:
            print("当前路径出错:"+itemPath)


def startCheckProject(mRootPath):
    global plistPath

    plist = readPlist(plistPath)
    rootObjects = plist["objects"]

    checkProject(rootObjects,mRootPath)


def getPlist(mProjectFilePath,mPlistPath):
    command = "rm -rf "+mPlistPath
    os.system(command)
    command = "value=`plutil -convert xml1 -o - "+mProjectFilePath+"/project.pbxproj` && echo $value >"+mPlistPath
    os.system(command)
    print("生成plist文件:"+mPlistPath)


print("这是一个帮你检查工程文件的脚本,请输入以下数字选项执行")
print("0.准备工作")
print("1.检查工程列表当中的文件是否在对应的物理位置")
print("2.检查工程当中是否有文件未加入引用")
choice = input("请输入:")
if choice == "0":
    print("请设置脚本rootPath、projectFilePath、plistPath三个变量的路径值")
    print("rootPath:工程根目录,dfc_v2的那个文件夹目录")
    print("projectFilePath:工程文件目录,dfc_v2.xcodeproj文件目录")
    print("plistPath:生成plist文件所在位置,该位置任意,具备读写权限即可")
elif choice == "1":
    getPlist(projectFilePath,plistPath)
    startCheckFile(rootPath)
elif choice == "2":
    getPlist(projectFilePath,plistPath)
    startCheckProject(rootPath)
else:
    print("未输入正确的操作")

因为贸然的使用脚本改动工程有一定的风险,所以我在脚本当中把有问题的文件及其目录输出,手动去改动。

执行脚本

执行脚本,让后根据输出的结果修改工程,因为Supporting Files下的文件直接是在主目录下的,它下面的文件直接忽略即可。

python3 my.py