本篇文章主要针对iOS应用开发中, 针对需要创建许多相似的应用App提出一种新颖的解决方案。
关于如何创建大量相似的App,iOS大神@唐巧曾在他的博文《猿题库iOS客户端的技术细节(一):使用多target来构建大量相似App》提出了一种可行性非常高的解决方案。我本人也将该实现方案应用到了某二手车应用开发中, 通过创建多个target的方式创建了N个某某拍的应用。但是这种方案真的适用于所有场景么? 除了使用这种方案能否有其它的方式去解决这个问题呢?
基于多Target的应用实践
我刚开始接触到开发多个相似App应用的需求的时候, 也采用了多个target的解决方案。主要做了以下工作:
- 建立多个Target (通过Duplicate行为)
- 为每一个Target指定LaunchImage和IconImage, LauchImage和IconImage由同一个image assert管理
- 为每一个Target指定了Info.Plist和InfoPlist.strings, InfoPlist.strings的作用仅仅是为了指定CFBundleDisplayName
- 为每一个Target创建了一个用于配置应用特征的JSON描述文件, 用于对每个Target的特征进行配置修改。
- 部署自动化打包平台,防止有N个Target就手动打N次包。
在上述工作中, 1、2、3均和配置项有关, 5与项目开发无关, 4是和具体的开发业务相关的。每一项的配置都没有什么技术深度和难度, 4的实现和具体需求相关, 对于极度相似的应用更多的行为是换肤和换key。
这里稍微提以下关于InfoPlist.strings的指定, 每一个Target只能识别一个InfoPlist.strings, 而且还不能重命名。需要为每一个Target创建一个物理文件夹, 然后在对应的文件夹下放置InfoPlist.strings防止命名冲突, 每一个InfoPlist.strings只能指定唯一识别的Target对象。(原理我还没有找到, 找到我就更新下博文哈~)
差异性较大的Target处理
什么? 差异性大你还放在一个工程里? 架构就有问题。是的, 差异性较大的工程就应该拆分成不同的工程, 然后共享的代码通过framework以及静态库引用的方式抽离出去。但是, 时间是道坎! 假如你时间很紧怎么办? 本文给出一种时间很紧时候的临时解决方案(注意: 决必是临时的, 时间是海绵, 需要去挤的!)
在时间非常紧的情况下, 可以通过拆分AppDelegate来实现(代价其实非常沉重, 会link好多无用的类)。拆分AppDelegate其实就要在main.m里面赋值不同的AppDelegate即可实现。main函数中argv包含了app的名字, 可以通过该名字去鉴别载入的AppDelegate。
#import
#import "STAppDelegate.h"
#import "STPAppDelegate.h"
int main(int argc, char * argv[])
{
@autoreleasepool {
char demoStr[] = "/stdemo.app"; // 检查stdemo target
char *p= strstr(*argv, demoStr);
if(NULL != p){
return UIApplicationMain(argc, argv, nil, NSStringFromClass([STAppDelegate class]));
}else{
return UIApplicationMain(argc, argv, nil, NSStringFromClass([STPAppDelegate class]));
}
}
}
PS: 切记, 临时解决方案, 如需根治, 拆分工程!
基于多Target实现的好处
-
直观
一目了然, 可以看到所有已创建的Target醒目的列在Build列表中。每一个Target都有对应的Tagret配置界面可以看到每一个项目配置图标以及Info.plist对应信息。
-
灵活性高
可以根据项目需要Link需要的类, 根据Target来指定链接不同的类和资源文件, 而不用一口气全部都Link进来。
基于多Target遇到的坑
如果没有遇到坑, 那就不会去重新寻找一个更好的解决方案了。基于多Target的方式去创建大量相似的App的坑主要提现在多人协作上。
个人之前在实现多Target项目的时候遇到的问题不多, 但是随着时间推移, 维护开发遇到了两个比较明显的问题:
-
类的Target指定遗漏
在多个Target的环境下, 我们每新建一个类文件都要给类文件指定对应的Target, 如果不小心忘记指定对应的Target, 则会会在编译阶段报错。
-
配置文件描述庞大, 难以修改
多个Target会导致项目的pbxproj臃肿, 因为pbxproj文件维护了项目的所有文件id和group层级关系, 多一个target就几乎多了一倍的描述信息, 可想而知, 这个pbxporj文件是有多庞大。
光文件庞大顶多引起Xcode项目的配置文件加载慢, 但是遇到冲突的时候可就头疼了, 几万行的描述文件。
配置文件修改不同步
配置文件修改不同步是基于已创建N个Target的前提下, 因为项目的推进, 需要对每一个项目文件进行固定的修改, 但是存在修改遗漏的情况。
对于这种场景, 有一种比较好的方案是自己动手写脚本来替换编译配置项, 保证每一个Target的配置项目均被替换。Mac开发工具中自带的PlistBuddy在处理配置项目替换上绝对是个神器。
重新思考
虽然在项目中遇到了不少坑,但是解决这些坑并不需要大量的时间(那是因为时间被打散了, 组合起来估计也不少了),所以我个人并没有去重新思考怎么去解决遗漏Target编译报错以及项目配置文件不断冲突的问题。
触发我重新思考是一次机缘, 经过花瓣网某iOS研发高手(我不知道他名字哇)提点, 他问我基于Cocoapods能否有更好的办法去创建大量相似的App。基于Cocoapods本身就是基于Hook, Hook本身就是动态修改项目配置项, 换言之, 能否通过动态修改Target的项目配置项去创建大量相似的App呢?
回到文章前面的基于多Target的应用实践
的5个步骤, 逐一用替换项目的配置文件(pbcproj)的方式去重新审视。
- 不需要建立多个Target, 只维护一个Target
- 主要是icon和launch image的修改, 有两种方案:
- 在image.assert预先放置多个不同名字的资源, 通过修改pbxproj来指定不同的图片资源
- 所有的icon和launch image都是用相同名字, 通过脚本动态替换image.assert中的资源文件(推荐)
- 主要针对info.plist和InfoPlist.strings的修改, InfoPlist.string可以通过
sed
命令去动态替换, info.plist也可以采取两种方案来实现:- 预先防止多个Info.plist文件, 通过修改pbxcproj来指定不同的info.plist文件
- target永远指定一个Info.plist, 通过脚本动态替换修改Info.plist(推荐)
- 通过JSON描述特性的文件可以单独防止在工程里, 通过脚本拷贝替换, 也可以利用
cocoapods-keys
等工具进行外部注入 - 前面的4个步骤都是依赖于基本动态替换, 自动化构建平台通过将指定Target的方式, 修改为在编译器执行对应的任务脚本即可完成。
进一步优化
重新思考通过外部修改配置项目和资源文件的方式来实现多个类似应用功能, 省去了维护多个target产生的冲突和配置过大的问题。但是, 外部脚本本身也是一个实现成本, 这里针对替换外部脚本提出一个优化策略(不一定最优)。
-
维护每个项目的文件夹
每一个项目就是指原来的每一个target, 文件夹可以保持和原先的target名字保持同名。该目录文件夹不参与项目引用, 即不在pbxcproj文件中被描述。该目录文件夹纯粹是提供给外部脚本使用, 与逻辑工程保持独立。
-
在第一步的文件夹中抽离变化项目到同一个JSON文件中
该json文件中描述了所有需要替换的内容, 包含image.assert的替换规则以及info.plist替换规则等等。
-
在第一步的文件夹中抽离资源文件
在该文件夹中防止所有可变化的资源文件, 包含
.png
、info.plist
等等所有可变化差异的项目。
在前面三步的基础下, 主要是为了一个目的, 一行脚本替换所有可变信息。(实际上就是提前将变化项维护在独立的文件夹中了)
## 动态变化 demo1 Target
./st_muti_target st_demo1/muti_target.json
## 动态变化 demo2 Target
./st_muti_target st_demo2/muti_target.json
想要st_muti_target.sh
的源码? 这个自己写吧。。每个项目都不一样的。
总结
基于建立多个相似App的需求, 和本人实际在项目应用中遇到的坑, 提出了一种基于脚本不断替换配置项目和资源文件的解决方案。该方案主要解决了多Target所带来的配置文件过大和容易冲突的问题, 但是同时又引入了脚本的维护成本。本文也提供了一种降低脚本使用成本和项目耦合的一种方案, 但是仍需要进一步优化, 并不是最终的解决方案版本。
多一种方案多一种选择么, 对于擅长书写脚本的童鞋们, 用这种方式做大量类似的App(换肤App)可能会是更好的一种选择喔~
水平有限, 有错误之处或者有什么地方没有描述清楚, 请大家及时指出哇~
参考文件:
- http://blog.devtang.com/blog/2013/10/17/the-tech-detail-of-ape-client-1/