iOS组件化之:业务组件准备篇
1. 背景
基础组件拆分到一定程度时,主工程剩下的就多数与视图界面(vc、view等)相关的类文件。此时可以考虑对业务组件进行设计和规划,但是在此时,工程还存在如下问题:
- 冗余文件:
- 未被引用到Project,但存在在工程目录中的冗余的类文件
- 非冗余但未被使用的文件
- 未在其他类中使用到的类文件
为了减轻业务组件的工作量和复杂度,在此之前最好能净化/清理一下工程
具体采用人工 or 脚本 进行清理:
根据我们进行估算,执行的时间复杂度为 n²,如果主工程(还未进行业务组件抽离)里还有1000个文件,则遍历一遍则需要进行100万次检索,m2 芯片执行一次需要2小时左右。人工检索时间更长,并且人工检索将呈现边际效用递增的问题。
脚本解决上述问题,并能实现近乎为0的边际成本,可以沉淀具体算法,供其他项目使用
2. 工程清理
2.1 冗余文件清理
通过递归遍历工程目录,将遍历后的类名逐一与XXX.xcodeproj/project.pbxproj 文件内容进行匹配,匹配失败即为:
1
未被引用到Project,但存在在工程目录中的冗余的类文件
具体思路可以这样实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 设置项目目录
project_directory="工程项目目录"
project_path="xxx.xcodeproj/project.pbxproj的目录"
# 查找所有以ViewController.m结尾的文件并输出列表
# 查找所有以ViewController.m结尾的文件并输出不包含后缀名的文件名
all_file_path=$(find "$project_directory" -type f \( -name "*.h" \))
for src_file in $all_file_path; do
# echo $src_file
filename=$(basename "$src_file" .h)
if ! grep -q "\b$filename\b" "$project_path"; then
echo "project不包含:"$filename
fi
done
2.2 未使用文件清理
2.2.1 fui工具
1
2
3
4
sudo gem install fui -n /usr/local/bin
fui find #当前目录执行
fui --path=~/source/project/Name find #指定目录执行
fui工具的源码,暂时还没去详细看,但是发现一个问题,他无法识别注释的取消引用:
举个例子,A import B,但是import 当前行注释掉了:
//
//
// A.m
// Example
//
// Created by PSC on 2023/8/15
// Copyright © 2023 PSC. All rights reserved.
//
#import "A.h"
//#import "B.h"
……
在fui的执行结果中,不会认为B没有被使用。
2.2.2 自建工具
为了解决上述fui工具的问题,并且做出一些优化,自建工具的设计思路为:
- 遍历工程目录的所有实现类:.m文件(不包含category)
- 将类名与工程内除自身以外所有文件进行进逐行匹配(包含category)
- 当匹配到不包含// 与 #import 开头的行,则算为有引用,否则为未引用
- 将未匹配到的文件路径进行输出
- 将上述匹配出来的文件先进行移除,再执行一遍(由于第一层被移除,可能会暴露出更多没被引用的类)
- 直到没有匹配到未被调用的类为止
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/bin/bash
# [背景]
# 基础组件拆分到一定程度时,主工程剩下的就多数与视图界面(vc、view等)相关的类文件。此时可以考虑对业务组件进行设计和规划,但是在此时,工程还存在如下问题:
# 冗余文件:
# 非冗余但未被使用的文件
#
# author: Chenyp34
#
# [目的]
# 此脚本解决 "非冗余但未被使用的文件" 的清理
#
# [目录]
# - find_unused_class.sh
# [说明]
# 1 遍历工程目录的所有实现类:.m文件(不包含category)
# 2 将类名与工程内除自身以外所有文件进行进逐行匹配(包含category)
# 3 当匹配到不包含// 与 #import 开头的行,则算为有引用,否则为未引用
# 4 将未匹配到的文件路径进行输出
# 5 将上述匹配出来的文件先进行移除,再执行一遍(由于第一层被移除,可能会暴露出更多没被引用的类)
# 6 直到没有匹配到未被调用的类为止
#
# [参数]
# 1、参数1,指定xcode工程目录 (未指定则为当前目录)
#
# [调用说明]
# 1、未指定目录,则取当前目录:sh find_unused_class.sh
# 2、指定目录 :sh find_unused_class.sh /Users/xxxxx/Project-IOS/Project/Project
#!/bin/bash
#是否重复执行到没有找到不可用文件
export cycle_deal=1
export cycle_idx=1
total_unused_file_name=''
function main() {
# 设置项目目录
project_directory=$1
if [ -z "$1" ]; then
#未传参为当前目录
project_directory=.
fi
#初始化参数:
unused_files=''
# 查找所有以ViewController.m结尾的文件并输出列表
all_class_path=$(find "$project_directory" -type f \( -name "*.m" \) | grep -v '+')
all_file_path=$(find "$project_directory" -type f \( -name "*.m" -o -name "*.h" \) )
for file in $all_class_path; do
filename=$(basename "$file" .m)
echo "***********开始遍历文件名:"$filename
is_referenced=false
for other_file in $all_file_path; do
other_file_name=$(basename "$other_file")
#获取除自身以外的类名:不包含拓展的名称,为了避免在自身的.h .m进行引用判断
other_file_name_without_extension=$(echo "$other_file_name" | cut -d. -f1)
#获取category的主类名称
other_file_name_without_extension_before_plus=$(echo "$other_file_name_without_extension" | awk -F'+' '{print $1}')
if [ "$other_file_name_without_extension_before_plus" != "$filename" ] ; then
# if [ "$other_file" != "$file" ] && ([ "${other_file##*.}" = "m" ] ); then
if grep -q "\b$filename\b" "$other_file"; then
# 检查文件中是否有注释行
has_comment=false
while IFS= read -r line; do
echo "逐行匹配:"$line
if ! grep -qE "^[[:space:]]*//" <<< "$line"; then
if [[ ! $line =~ ^[[:space:]]*#import ]]; then
has_comment=true
echo "在文件:"$other_file"匹配"$filename"存在 并且不是 注释代码 或者 import 的 break("$line
break
fi
fi
done <<< "$(grep "\b$filename\b" "$other_file")"
if [ "$has_comment" = true ]; then
is_referenced=true
break
fi
fi
fi
done
if [ "$is_referenced" = false ]; then
echo "没有被引用到的文件:"$file
unused_files="$unused_files$file\n"
fi
echo "***********"
done
# 输出未被引用的文件列表
if [ -n "$unused_files" ]; then
echo -e "Unused class files:\n$unused_files"
total_unused_file_name="$total_unused_file_name$unused_files\n"
#将检索出来的文件 mv 出来
mkdir -p ./unused_files/$cycle_idx/
array=(`echo $unused_files | tr '\n' ' '`)
for element in "${array[@]}"; do
element=$(echo "$element" | tr -d '\n')
echo "Unused class = "$element
element_filename=$(basename "$element" .m)
element_dirname=$(dirname "$element")
mv -f ${element_dirname}/${element_filename}.h ./unused_files/$cycle_idx/$element_filename.h
mv -f ${element_dirname}/${element_filename}.m ./unused_files/$cycle_idx/$element_filename.m
done
#cycle_deal参数为空则不循环执行
if [ -n "$cycle_deal" ]; then
exit
else
#cycle_deal 不为空 循环执行,直到不再有新文件检出
echo -e "循环执行"
cycle_idx=$((cycle_idx+1))
main $1
done
else
echo "No unused ViewController files found."$all_file_path
echo "**************************"
echo -e "total_unused_file_name class files:\n$total_unused_file_name"
echo "**************************"
exit 0
fi
}
main $1
2.2.3 尚有不足之处
当前工具只是匹配一层未引用,也可能如下情况未适配:
- 存在2个以上的类存在互相引用,但却未被其他类用到,但是以上脚本暂时无法识别
- 存在某些类存在在其他API类、通用类、工具类等方法中,但是此方法未被调用,则此方法和该类也可以被移除,但是以上脚本暂时无法识别