澳门新萄京iOS开发 — 定制功能模块化。Xcode —— 使用Multi-Target管控相似 App

近年当整理公司的色结构。想方总结一篇有关于定制功能模块化的文章。
享受了10几乎首文章,还木有介绍了自家平常之iOS开发产品,由于店的成品是地理信息规划软件,平时之客户基本还是地理信息部门,所以一般还是客户提出作用需求,公司马上边又提供软件方案,且基本还是对的模块化功能、只供于一定用户。
据此,啊左的庄采取的是苹果店级开发者账号(碰了很多坑,有趣味之童鞋也得交流下),平时付出的APP都不放在AppStore上面的,属于企业级分发软件。
这般尽管来只问题,不同客户要不同之软件,且基本功能都是大半,不同的成品只是是增删改一组成部分。这样的话,需要有些法代码呢?
实则是可仅所以相同模仿代码的。
此次想分享的就是是:基本上只项目(Target)共用平等仿代码


多只职能的兑现,其实还多的凡技术点的积,在开难度及,属于业务类的开发。所以在开始着手前期基本功能都到,业务支出主导就后,更要的凡职能的模块化,也即是路组织优化。
这就是说,怎么当包功效的贯彻去开展组织的优化,已臻可维护性与可重用性的提高也?

前言

以公司发展进程遭到,除了支付保护自来品牌外,针对有实力发生潜质的客户,公司还会见承受OEM「贴牌开发」的协作方式。在硬件产品方面,OEM方式根本反映为「外观还开模改丝印」,「PCB重新layout」和「功能定制开发」;在App方面,主要反映为「App
Logo修改」,「欢迎页面修改」,「关于我们页面修改」,「App背景颜色修改」和「功能定制开发」。
眼下店家之客户中,大概发生5单客户是坐OEM的方式展开合作之,所以,对于App,需要采用一个好保障的法子来管控公司暨所有OEM客户的App。


题材讲述

对「前言」中讲述的情状,需要缓解之问题可以归纳如下:

  • 6只App的代码用国有,尽量减少代码间的距离
  • 每个App的「应用名称」、「应用图标」、「欢迎页面」及「关于我们页面」均不同
  • 以独家功能还是页面在实现上,每个App均发生一定差异性
  • 每个App对应的开发者帐号均不同

事情代码

没日没夜地撸代码,王者是匪容许的,砖石也转想了,反正就是事关。不断把天敌(我说之尽管是项目经理)提出的职能需求堆积出来。

【当然,也扭转把代码来得够呛乱,起码哪个部分是形容的哎而明。】


缓解方案

在Xcode中,有一个「Mutil-Target」功能,在苹果官方文档丁对于Target是这么描述的:

「A target specifies a product to build and contains the instructions
for building the product form a set of files in a project or workspace.
A target defines a single product; it organizes the inputs into the
build system — the source files and instructions for processing those
source files — required to build that product. Projects can contain one
or more targets, each of which productes one product.」

立段话大概可以领略为「同一个Xcode工程被,每一个target可以对应一个独立的App,通过target相关的布,可以定义每个Target(即App)的build属性:例如「需要编译哪些文件」、「App名称」、「App的Bundle
ID」等」。这么看来,使用Target功能,即可满足此次要求。

补给加Target的历程可分为以下几步:

功能(业务)模块化

“啊左的APP”功能定制 2017-06-05 17.29.45.png

面是刚刚创立的于基础之效应定制性列表。这是咱们项目的基点,增删查改大部分成效都好当面操作,也会见是接下我们追究的模块化设计模式。

【本次开发条件: Xcode:7.2 iOS Simulator:iphone6S By:啊左
本文Demo下充斥链接:CustomMap-Demo】

<u>一、使用“定制功能属性列表”</u>

始建好定制的.plist文件,在本文中,我们誉为“定制功能属性列表”,“Custom-one.plist”(其中,“Custom-”是前缀;
“one”是项目名称;)
(这种性质的读取方式,各个需要用的字段都得以置身一个公共类“AllCommons”里面为调用):

#import "AllCommons.h"
@implementation AllCommons

//获取项目定制信息;
+(NSDictionary *)customSettings
{
    NSString *prefix = [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString*)kCFBundleNameKey];
    NSString *plistFileName = [NSString stringWithFormat:@"%@.plist", prefix];

    //以字典的格式,读取资源包里的"项目名.plist"文件。
    NSDictionary *customizedSetting = [NSDictionary dictionaryWithContentsOfFile:
                    [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:plistFileName]];
    return [NSDictionary dictionaryWithDictionary:customizedSetting];
}
@end

连下,举三单例子。
事例一样:读取方式(例如AppTitle字段):

    /*--- 主程序   ---*/
    NSString *appTitle = [[AllCommons customSettings]objectForKey:@"AppTitle"];
    NSLog(@"appTitle的值:%@",appTitle);

输出”Custom-项目.plist”的”AppTitle”对诺信息(字符串)。
事例二:是否打开APP时提醒更新?

    NSString * updateUrlStr = [[AllCommons customSettings]objectForKey:@"AppUpdateURL"];
    NSString * isupDate =[[AllCommons customSettings]objectForKey:@"UpdateCheck-permission"];
    if(isupDate)
    {
        NSURL *updateUrl = [NSURL URLWithString:[updateUrlStr stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
        if (![[UIApplication sharedApplication] openURL:updateUrl]) {
            NSLog(@"%@%@", @"Failed to open url:", [updateUrl description]);
        }
    }

例子三:自定义.xib。
在tableViewController中:

    CustomShowViewController *customXibViewController = nil;
    NSDictionary *dictForXib =[[AllCommons customSettings]objectForKey:@"MeasureViewController_xib"];
    NSString *customXibName = [dictForXib objectForKey:@"xib_name"];
    //判断是否定制信息中.xib为空
    BOOL iscustomXib = (customXibName == nil || customXibName.length == 0);
    //这里读到的字符串为“MeasureViewController_az”,可做为控制器的xib视图
    if(iscustomXib)  //没有自定义.xib则创建新控制器
    {
        customXibViewController = [[CustomShowViewController alloc] init];
    }else{                 //.xibName存在则通过创建.xib实例化控制器
        customXibViewController = [[CustomShowViewController alloc] initWithNibName:customXibName bundle:nil];
    }
//这里得到的customXibViewController就是不同项目.plist自定义的控制器了。

任何的功效字段类似这样,可起定义是否数据,以及是否动该意义;
诸如当”MyTools”中若得起定义添加需要的成效,当然项目遭到必在已实现该功能的代码。

<u>二、通过Target管理大多单一般之档次</u>

新建Target

据悉需要,新Target的多数内容和原Target相似,所以我们运用「Duplicate」的办法来「复制生成」新Target。点击Target名称可以老方便地改名,假要名称变更吗「NewTarget」。

澳门新萄京 1

1. Duplicate两个个target:

如若图,右键选择Duplicate(复制)两独新的target。

丰富一个新的Target

设置「App名称」

2.区瓜分不同target的处理。

修改target的称呼如下:

修改target名称

然后”run”键旁边的target管理,”manage Schemes”

manage Schemes

展开Scheme管理,也就是是Scheme部分target名称的改:

Scheme部分target名称的修改

啊左用的凡Xcode7,复制另个target出来后,可以看2单新的”info.plist”,这是每个独立型都见面有的系统文件配置,如果一旦修改名字吧,记得在target的安方面的装为共同下。

有限只体系的文本配置

设置info.plist文件
  1. 加上「NewTarget」后,可以窥见工程被多了一个「Target_copy-info.plist」,在这个,我们将该名变更也「NewTarget-info.plist」,一般的话,此文件内容未需改。

  2. 每当「TARGETS」中摘「NewTarget」,进入工程的「Build
    Setting」页面,找到「Packaging」分类,把里面的「info.plist
    File」改吧「NewTarget-info.plist」

  3. 这会儿回「General」页面,就可以对Bundle
    Identifier」和「Team」等选项进行修改了。

3、<u>重点</u>:不同target使用不同之效益定制

诸如前“使用“定制功能属性列表”部分,创建不同需求的种之习性列表。建议不同的target可像截图一样,创建不同之公文夹,放置该档的数量信息;
(.plist前面的称号记得跟品种雷同。)

今非昔比类别之不比功效定制

此地用特别注意的是:
随便是.plist文件,还是图片资源,在导入的时刻记得在“Target
Membership”下面准确关联到有关的类,例如”CustomMap-one.plist”就是涉到“CustomMap-one”,如果”CustomMap-two”也打钩的话,那就算是“CustomMap-two”这个路中的资源包“CustomMap-two.app”也会见蕴藏这个文件。

Target Membership与品种一起

上述,当target的个设置完后,读取one和two的“定制功能属性列表”中之字段”AppTtitle”字段,

    NSString *appTitle = [[self customSettings] objectForKey:@"AppTitle"];
    NSLog(@"app的名字:%@",appTitle);

我们可以看出输出结果个别是CustomMap-one和CustomMap-two.
建议:不同之target可以以代码都好的时候在创立,就毫无每次创建新的类时,“Target
Membership”都设摘任何。因为Duplicate就曾经将“CustomMap”这个基础图层的保有关乎代码依旧引用上了。

<u>▲三、定制生产线——工厂设计模式</u>

打前文例子三,可以视由定义不同之.xib可以在tableViewController中利用initwithNibName读取,那么,如何保证不同门类也会定制不同的成效类似?下面我们对此不同档次定制不同地图处理功能的进程进展辨析。
每当急需丰富地图操作功能的门类必将制.plist文件中,增加这同段落功能:

定制功能看似

设置infoPlist.strings文件
  1. 在工程目录中新建文件夹「NewTargetInfo」。

  2. 找到工程文件夹着之infoPlist.strings文件(注意,每种语言都有一个infoPlist.strings文件),将其复制到文件夹「NewTargetInfo」中。

  3. 每当Xcode工程被新建Group「NewTarget-Info」,将「NewTarget-info.plist」拖到该Group中,同时,通过「Add
    Files to
    “Target”」选项将步骤2)中的infoPlist.strings文件添加至Group「NewTarget-Info」。

![](https://upload-images.jianshu.io/upload_images/81342-6e579bfb316d9834.jpg)
  1. 入选「infoPlist.strings」,在「Utilities View」中的「Target
    Membership」选项,选择是「infoPlist.strings」属于哪个Target,注意,这里不克多选择,只能选中「NewTarget」。
![](https://upload-images.jianshu.io/upload_images/81342-20628723b533b26e.jpg)
  1. 打开「infoPlist.strings」文件,在一一语言文件被补充加下述语句,其中「AppName」根据需要填写。

    “CFBundleDisplayName” = “AppName”;

1.创建一个父类Operation类

概念,但保留重要措施的实现;
MapOperation.h中:

@property (nonatomic, strong)NSDictionary *dict;
-(NSDictionary *)createOperationForMap:(NSString *)map withArea:(double)area;

MapOperation.m中:

-(NSDictionary *)createOperationForMap:(NSString *)map withArea:(double)area
{
    NSDictionary *dict = nil;
    //父类创建一个对map的area面积进行某种处理的操作,由子类继承,并返回一个字典给外部;
    return dict;
}

增长「App图标」及「启动页面」

2.创办不同target需求的Operation子类:

例如CustomMap-one项目用之是“MapOperation_analyze”功能、
CustomMap-two需要之是“MapOperation_measure”等,并以.m中分头实现父类关于操作的措施;
MapOperation_analyze.m中:
-(NSDictionary *)createOperationForMap:(NSString *)map withArea:(double)area { NSMutableDictionary *dict = [NSMutableDictionary dictionary]; [dict setValue:@"分析" forKey:@"City"]; //加上20.2,与measure区别开来 area = area + 20.2; [dict setValue:[NSNumber numberWithDouble:area] forKey:@"count"]; return dict; }
MapOperation_measure中:
-(NSDictionary *)createOperationForMap:(NSString *)map withArea:(double)area { NSMutableDictionary *dict = [NSMutableDictionary dictionary]; [dict setValue:@"测量" forKey:@"City"]; [dict setValue:[NSNumber numberWithDouble:area] forKey:@"count"]; return dict; }

添加「App图标」

增长「App图标」相对简便易行

  1. 进「Images.xcassets」中,通过「按停Option按钮拖拽」的主意,得到原来的「AppIcon」的正片,将那个改名为「NewTarget
    AppIcon」;

  2. 右键「Show in
    Finder」,将新的App图标按照同样之名目拷贝进文件夹,覆盖原有图标。

  3. 进去「NewTarget」的「General」页面,找到「App Icons and Launch
    Images」分类,在「App Icons Source」选项中摘「NewTarget
    AppIcon」;

3.创办工厂类“MapOperationFactory”(继承给NSObject)搭好生产线,读取ClassName,获取对应class,进行class的创建。

代码中,创建:

+ (MapOperation *)createMaoperation
{
    //1.初识化操作
    id operation;

    //2.读取定制信息
    NSString *targetClassName = [[AllCommons customSettings:@"MapOperation"] objectForKey:@"classsName"];
    Class targetClass = NSClassFromString(targetClassName);
    //这里其实也可以不用Class,而用switch判断生成各自的子类实例,只是用Class相比这种简单也清晰多了。
    if (targetClass != nil) { 
        operation = [[targetClass alloc]init];
    }
    //3.可再进一步对实例化对象进行处理

   //产生一个实例化的操作对象:
    return operation;
}

Operation是子类的一起父类。
OperationFactory是子类们工厂方法。需要以始发导入各子类的头文件,读取定制className,进行对象实例化。
切实的以如下:

    //Factory类中已根据.plist文件中的ClassName实例化对象;
    MapOperation *operation = [MapOperationFactory createMaoperation];
    NSDictionary *dict = [operation createOperationForMap:@"地图" withArea:1214.12];
    NSLog(@"操作类型:%@\n数量:%@",[dict objectForKey:@"City"],[dict objectForKey:@"count"]);

Runnig~…
如果是Custom-one项目,输出:
操作类型:分析 数量:1234.32

如果是Custom-two项目,输出:
操作类型:测量 数量:1214.12


增长「启动页面」

「启动页面」分点儿栽情景,一种植是IOS7与以下版本的「启动页面」,一种植是IOS8以及以上版本的「启动页面」

别细节介绍

对IOS7与以下版本

参考「App图标」的老三步配置,即可为「澳门新萄京NewTarget」创建新的「启动页面」

一如既往、经常出错的地方:

1.字段之拼写错误;
2.代码且干有品种没关系,因为国有同一法代码。重点是资源(例如图片、bundle包):
是否涉嫌勾选正确的“Target Membership”。
是否多勾选了,因为就是不使用,也会见多浮动项目之内存;
3.业务层是否足够灵活,避免写好、增加扩展的地方,例如在工厂类吃,增加一个Other子类;

对此IOS8和以上版本

由于IOS8及以上版本的起步页面使用的凡「Launch Screen」,而「Launch
Screen」的体制只能通过修改.xib页面实现,不可编写任何代码。假设「Launch
Screen」中不过发生一个ImageView元件,ImageView元件的image为「welcomePage.png」,那么,我们得以当工程中上加多个与名但不同内容的welcomePage.png,并透过「Target
Membership」来定义不同之「welcomePage.png」分别涉及哪个Target,即可兑现不同Target使用不同「启动页面」的目的。

澳门新萄京 2

第二、这种优化的利害:

1.特色:项目优化、功能模块化。使得项目更是条理清晰、增加重用性、封装性。
2.力量接口化:每部分的效能还像一个接口,就如每个标有功能间,只提供一个窗口,放一个数额包进去,最后会起之窗口出来一个甩卖完的result;
3.人性化:
对此客户:可根据需要,定制信息;
于项目经理:无需询问代码,可根据范例.plist文件,主动采取需要的法力,且能提供于上一级领导清晰的事体开发进度。
4.缺点:
代码最基础的作业功能容易写很。所以最初需要巧设计,后期需要大修改的语句,需要以及活、项目经理协商,避免代码块的修改,影响到other项目。
釜底抽薪方案:
a.前期要拿逻辑抽离开来,避免耦合度过高(例如有需要时转移页面的构造工作职能,可进行规划几乎独.xib,例如截图中第9接触。那个_az就是自背负的品种所属之页面)
b.于有片小大的修改。可继续原来的作用父类,为子类添加新的作用、覆盖旧的组织。(例如,Operation下之逐条子类)
d.项目经理这样叫惯了然后,会当添加一个效很爱,不纵是增长一个配段么你那么边还略”协调”一下吗。。。
然于端正来说,整个开发进程,流程清晰,日报、项目进度,自己分内工作产生显著申报,养成规划之好习惯;

长预编译宏

对不同Target间代码级别之歧异,我们得以经过预编译宏来实现代码预编译。进入「Target」的「Build
Settings」界面,找到「Preprocessor
Macros」选项,在「Debug」及「Release」选项中,按需加宏定义。例如,为「NewTarget」添加宏定义

"TARGET_TYPE"="NEW_TARGET"

这就是说以代码中,即可通过预编译语句实现代码的出入编译。

#if TARGET_TYPE == NEW_TARGET
    //do something for NewTarget
#else
    //do something for OtherTarget
#end
老三、其他补给:

1.公共类:AllCommons
而外提供定制.plist文件字段的接口数据;
啊堪把其他经常以的代码块放置这里:
譬如沙盒的读取、数据库的读取、文件复制和移动操作相当而提供联合用粗功能模块;

2、开发执行
这项目的名是

CustomMap总项目

用,可以看成一个毕竟项目test target,测试功能和代码;
创办一个”CustomMap.plist”,把需要测试的plist字段填好,需要的资源事关打钩,可资纯功能的测试,而不用每次都生成one/two等品种;

本,不是具有项目都适合这种支付模式,但是,如果是偏于受定制功能,可以采取这种方法。

Manage Schemes

终极一步!将系统默认生成的 build Scheme 名称改成吧「New Target
Release」即可,当然,还可以需加上不同的 scheme.

尾声:

天堂为你编了所有代码,你永远不知晓不用的模块是呀效果,人之生平,就是当您的性能列表上面,努力地去丰富不同之性,然后,不断Debug它,直到它顺畅运行。


(转载请标明原文出处,谢谢支持 ~ ~)
 by:啊左~

坑!!!

奇怪之编译问题

_Warning: The Copy Bundle Resources build phase contains this target's Info.plist file 'Simart-Info.plist'._

由:在「Target-Info.plist」的「Target
Membership」选项中误选了某个Target,正常状况下,「Target-Info.plist」在「Target
Membership」选项中是不选择任何一个Target的。

Unable to run command 'CpResource Simart.app' - this target might include its own product.
Unable to run command 'Touch Simart.app' - this target might include its own product.

缘由:在「Products」Group中之之一product的「Target
Membership」中吃误选了某个Target,正常情况下product不应当提到到其它一个Target。

App名称无法修改

由:在改「NewTarget」的「InfoPlist.strings」文件时,发现无论如何修改,App名称还见面显示OriginalTarget的App名称,排查后发觉,「OriginalTarget」的「InfoPlist.strings」的「Target
Membership」中除去勾选了「OriginalTarget」外,还勾选了「NewTarget」,这虽造成了在编译「NewTarget」时,错误地念博了「OriginalTarget」的「InfoPlist.strings」文件。

更检查一一体所有Target「InfoPlist.strings」的「Target
Membership」选项,保证关联的唯一性和对即可。

「启动页面」无法显示

是问题应运而生于IOS8.0暨以上版本被。

题目具体表现为:无论采取模拟器或者真机调试,在切换Target时,均会「随机」出现「启动页面」变成白色。检查了众百分之百各个Target的配置,并未发现问题。

末了,Achive每个Target的AD
HOC版本,并通过fir.im进行分发测试,发现每个Target的「启动页面」均正常,难道就是Xcode-7.2.1的Bug?目前不得而知,待后续跟进…

总结

如上所述,使用「Multi-Target」方案,算是「完美地」解决了本次要求。同时,此方案吧可是用来「发布独立的测试版本」「发布有限定的App版本」等急需。

总而言之,只要知道了A target defines a single product,具体怎么用就扣留项目的其实需要了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注