×
广告

iOS - 创建大量相似App的另外一种选择

96
Startry
2016.02.24 11:32* 字数 2532

本篇文章主要针对iOS应用开发中, 针对需要创建许多相似的应用App提出一种新颖的解决方案。

关于如何创建大量相似的App,iOS大神@唐巧曾在他的博文《猿题库iOS客户端的技术细节(一):使用多target来构建大量相似App》提出了一种可行性非常高的解决方案。我本人也将该实现方案应用到了某二手车应用开发中, 通过创建多个target的方式创建了N个某某拍的应用。但是这种方案真的适用于所有场景么? 除了使用这种方案能否有其它的方式去解决这个问题呢?

基于多Target的应用实践

我刚开始接触到开发多个相似App应用的需求的时候, 也采用了多个target的解决方案。主要做了以下工作:

  1. 建立多个Target (通过Duplicate行为)
  2. 为每一个Target指定LaunchImage和IconImage, LauchImage和IconImage由同一个image assert管理
  3. 为每一个Target指定了Info.PlistInfoPlist.strings, InfoPlist.strings的作用仅仅是为了指定CFBundleDisplayName
  4. 为每一个Target创建了一个用于配置应用特征的JSON描述文件, 用于对每个Target的特征进行配置修改。
  5. 部署自动化打包平台,防止有N个Target就手动打N次包。
配置Configuration的各个xcconfig

在上述工作中, 1、2、3均和配置项有关, 5与项目开发无关, 4是和具体的开发业务相关的。每一项的配置都没有什么技术深度和难度, 4的实现和具体需求相关, 对于极度相似的应用更多的行为是换肤和换key。

这里稍微提以下关于InfoPlist.strings的指定, 每一个Target只能识别一个InfoPlist.strings, 而且还不能重命名。需要为每一个Target创建一个物理文件夹, 然后在对应的文件夹下放置InfoPlist.strings防止命名冲突, 每一个InfoPlist.strings只能指定唯一识别的Target对象。(原理我还没有找到, 找到我就更新下博文哈~)

差异性较大的Target处理

什么? 差异性大你还放在一个工程里? 架构就有问题。是的, 差异性较大的工程就应该拆分成不同的工程, 然后共享的代码通过framework以及静态库引用的方式抽离出去。<font color='orange'>但是, 时间是道坎!</font> 假如你时间很紧怎么办? 本文给出一种时间很紧时候的<font color='red'>临时</font>解决方案(注意: 决必是临时的, 时间是海绵, 需要去挤的!)

在时间非常紧的情况下, 可以通过拆分AppDelegate来实现(代价其实非常沉重, 会link好多无用的类)。拆分AppDelegate其实就要在main.m里面赋值不同的AppDelegate即可实现。main函数中argv包含了app的名字, 可以通过该名字去鉴别载入的AppDelegate。

#import <UIKit/UIKit.h>

#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: 切记, 临时解决方案, 如需根治, <font style="font-size:1.5em">拆分工程</font>!

基于多Target实现的好处

  1. 直观

    一目了然, 可以看到所有已创建的Target醒目的列在Build列表中。每一个Target都有对应的Tagret配置界面可以看到每一个项目配置图标以及Info.plist对应信息。

  2. 灵活性高

    可以根据项目需要Link需要的类, 根据Target来指定链接不同的类和资源文件, 而不用一口气全部都Link进来。

基于多Target遇到的坑

如果没有遇到坑, 那就不会去重新寻找一个更好的解决方案了。基于多Target的方式去创建大量相似的App的坑主要提现在多人协作上。

个人之前在实现多Target项目的时候遇到的问题不多, 但是随着时间推移, 维护开发遇到了两个比较明显的问题:

  1. 类的Target指定遗漏

    在多个Target的环境下, 我们每新建一个类文件都要给类文件指定对应的Target, 如果不小心忘记指定对应的Target, 则会会在编译阶段报错。

    配置Configuration的各个xcconfig
  2. 配置文件描述庞大, 难以修改

    多个Target会导致项目的pbxproj臃肿, 因为pbxproj文件维护了项目的所有文件id和group层级关系, 多一个target就几乎多了一倍的描述信息, 可想而知, 这个pbxporj文件是有多庞大。

    光文件庞大顶多引起Xcode项目的配置文件加载慢, 但是遇到冲突的时候可就头疼了, 几万行的描述文件。

    配置Configuration的各个xcconfig
  3. 配置文件修改不同步

配置文件修改不同步是基于已创建N个Target的前提下, 因为项目的推进, 需要对每一个项目文件进行固定的修改, 但是存在修改遗漏的情况。

对于这种场景, 有一种比较好的方案是自己动手写脚本来替换编译配置项, 保证每一个Target的配置项目均被替换。Mac开发工具中自带的PlistBuddy在处理配置项目替换上绝对是个神器。

重新思考

虽然在项目中遇到了不少坑,但是解决这些坑并不需要大量的时间(那是因为时间被打散了, 组合起来估计也不少了),所以我个人并没有去重新思考怎么去解决遗漏Target编译报错以及项目配置文件不断冲突的问题。

触发我重新思考是一次机缘, 经过花瓣网某iOS研发高手(我不知道他名字哇)提点, 他问我基于Cocoapods能否有更好的办法去创建大量相似的App。基于Cocoapods本身就是基于Hook, Hook本身就是动态修改项目配置项, 换言之, <font color='red'>能否通过动态修改Target的项目配置项去创建大量相似的App呢</font>?

回到文章前面的基于多Target的应用实践的5个步骤, 逐一用替换项目的配置文件(pbcproj)的方式去重新审视。

  1. 不需要建立多个Target, 只维护一个Target
  2. 主要是icon和launch image的修改, 有两种方案:
    • 在image.assert预先放置多个不同名字的资源, 通过修改pbxproj来指定不同的图片资源
    • 所有的icon和launch image都是用相同名字, 通过脚本动态替换image.assert中的资源文件(推荐)
  3. 主要针对info.plist和InfoPlist.strings的修改, InfoPlist.string可以通过sed命令去动态替换, info.plist也可以采取两种方案来实现:
    • 预先防止多个Info.plist文件, 通过修改pbxcproj来指定不同的info.plist文件
    • target永远指定一个Info.plist, 通过脚本动态替换修改Info.plist(推荐)
  4. 通过JSON描述特性的文件可以单独防止在工程里, 通过脚本拷贝替换, 也可以利用cocoapods-keys等工具进行外部注入
  5. 前面的4个步骤都是依赖于基本动态替换, 自动化构建平台通过将指定Target的方式, 修改为在编译器执行对应的任务脚本即可完成。

进一步优化

重新思考<font color='black'>通过外部修改配置项目和资源文件的方式来实现多个类似应用功能</font>, 省去了维护多个target产生的冲突和配置过大的问题。但是, 外部脚本本身也是一个实现成本, 这里针对替换外部脚本提出一个优化策略(不一定最优)。

  1. 维护每个项目的文件夹

    每一个项目就是指原来的每一个target, 文件夹可以保持和原先的target名字保持同名。该目录文件夹不参与项目引用, 即不在pbxcproj文件中被描述。该目录文件夹纯粹是提供给外部脚本使用, 与逻辑工程保持独立。

  2. 在第一步的文件夹中抽离变化项目到同一个JSON文件中

    该json文件中描述了所有需要替换的内容, 包含image.assert的替换规则以及info.plist替换规则等等。

  3. 在第一步的文件夹中抽离资源文件

    在该文件夹中防止所有可变化的资源文件, 包含.pnginfo.plist等等所有可变化差异的项目。

配置Configuration的各个xcconfig

在前面三步的基础下, 主要是为了一个目的, 一行脚本替换所有可变信息。(实际上就是提前将变化项维护在独立的文件夹中了)

## 动态变化 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)可能会是更好的一种选择喔~

水平有限, 有错误之处或者有什么地方没有描述清楚, 请大家及时指出哇~

参考文件:

  1. http://blog.devtang.com/blog/2013/10/17/the-tech-detail-of-ape-client-1/
Startry技术博文
Web note ad 1