bench_ios框架的设计思考,基础库、runtime和组件化

bench_ios提供的核心是一种扩展模式,代码是其次,肯定还有更好的代码优化可以做。这种模式是一种在物理上有内核,逻辑上是分布的结构。我们参考一些例子:
1、一般AI科幻片都有一个中央人工智能,它可以调度城市所有的机器人工作,毁坏它就能停止整个系统。
2、再看互联网,互联网有主根服务器,关闭了主根服务器互联网就会瘫痪。
3、目前互联网app的开发90%都是基于客户端-服务端的模式,服务端保管着大量数据,关闭了服务端,app大部分功能将不能使用。

从这些例子也证明了我们存在物理空间,一切的结构都是基于物质实体的。尽管我们努力去中心化,将物理部分虚拟化隐藏起来,但本质都是有一个内核在暗处工作。bench_ios就有这样一个实例在app运行时常驻,它可以随时控制方法的调度、内存的管理等工作。有了这个核心使得app工作得以监管,通过组件化分类的方案可以无限扩展bench_ios,只需将扩展的业务模块通过bench_ios的内核实例分发消息调度,就可以把组件和业务都通过内核管理起来。就如同变形金刚它有一颗能量石作为心脏,只要心脏在,就可以拼接其他汽车人组合起来。不同的app有不同的组件,bench_ios的API也就跟随变动组成新的形态。这样就是前面说的,逻辑上业务模块独立,实际上通过内核实例调度的意思。

https://github.com/gwh111/bench_ios
有目录的开发指南

首先,做这套框架的意义,为了解决一系列问题:

1、开发时每人代码不统一,有各自喜好,用各种方式创建、描述对象。修改他人代码时比较吃力,需要提供一套调用api规范。

2、一些app都需要的功能反复拷贝,使用第三方工具和不熟悉的库可能有风险,需要一套可靠的多个app验证过的工具库。

3、随着业务模块增多,需要维护的库列表增大,需要降低库使用成本和减少模块间的引用。

4、为业务模块快速封装成库给其他app使用提供一套方案,节省重复开发相同业务和功能的成本。

5、提供一套工具监控每个模块的性能问题,快速定位有问题的模块,找到可以优化的模型。

runtime

在iOS上app的启动都是从main函数开始的,由UIKit中的UIApplicationMain函数注册app的代理,即对app的启动、退到后台、回到前台等函数的监听。因为无法得知UIApplicationMain是如何实现的,所以我们从UIApplicationMain之后接管app。main函数由xcode默认生成如下:

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        id class = [AppDelegate class];
        return UIApplicationMain(argc, argv, nil, NSStringFromClass(class));
    }
}

使用AppDelegate去继承CC_AppDelegate获得控制权限,CC_AppDelegate之后也会分发代理函数给子模块。修改后如下:

#import <UIKit/UIKit.h>

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, @"CC_AppDelegate");
    }
}

设计runtime接管app生命周期,分配app生命周期的事件给调度方使用,对于组件化的模块可以有各自独立的生命周期去管理,这样各自模块可以独立运行起来,无需依赖主工程的生命周期。

扩展周期过程中,同时加入模块分析量化功能,每个模块Init的耗时均可计算出来,为性能优化做到数据上的支持。一个App的业务增多过程中,通过分析定位模块的Init耗时可以确定需要优化的模块。

开发人员在开发新App过程中涉及相同功能的模块,无需重复造轮子,直接移植,开发一个App如同拼装积木,能组合需要的功能业务。

img1

bench_ios内提供组件化方案

基于Spring的Service理念,使模块间的具体实现与接口解耦,使用模块先注册相应模块到底层协议管理器,再分发到各模块使用。但这样无法避免模块对接口类的依赖关系,所以考虑剥离注册环节。

在组件化的过程中,注册URL并不是充分必要条件,注册了Url之后,会造成不必要的内存常驻,如果只是注册Class,内存常驻量就小一点,如果是注册实例,内存常驻量就大了。

考虑使用oc自身分类特性,把编译前的任务推迟到运行时,在运行时分发消息注册组件,对于使用方无需import或注册组件,直接在bench_ios的调解模块调用分类方案使用组件。核心是由本地应用调用为远程应用调用提供服务。这种方式可以严格区分远程app调用和本地组件调用。

img2

如果使用分类(category)这套方案,相比路由,相当于我们把对组件注册代码的维护从使用方移到了组件提供方,使用路由,肯定需要维护一份注册代码,同时需要一份注册文档告知新来的开发具体注册方法和字段。使用category,开发使用pod下载组件代码后即可在ccs的分类获取组件api的调用方法,对于组件开发者来说需要多写一份类似协议的分类接口,与协议不同的是,分类完全不会侵入业务组件代码,因为它是ccs(在后面解释CC_Share)的分类,在ccs的调度模块以消息的方式在运行时通知获取对应组件,在编译时完全不需要关心app与组件的联系。

对于新业务的组件扩展也相对方便,因为所有组件是基于bench_ios的,那么在做完一个业务需求后想要把这个业务做成组件,就对ccs添加这个组件的category,组件封装者想要暴露什么api出去都可以控制。一般使用者看方法名就知道它是干什么的,需要传什么参数,需要补充的可以添加到头文件的说明中。路由相对传参比较拘谨,一个简单的url无法传递非常规对象,或者说还需要转义和解析。

开展组件化的时机。组件化对于一个小项目而言,真正发挥优势的地方是在未来的半年甚至一年之后。因为趁着人少项目小,实施组件化的成本就也很小,三四天就可以实施完毕。但如果等到项目大了,人手多了再去实施组件化,那时候实施组件化的复杂度肯定比现在规模还很小的时候的复杂度要大得多,三四天肯定搞不定,而且实施过程还会非常艰辛。

bench_ios基础库丰富

将bench_ios从之前的插件升级为平台,平台统一提供对象创建和管理方案。对app核心模块做扩展,统一接口给上层使用。这期要做的有CC_CoreFoundation(消息转发的管理),CC_Foundation(所有基本对象的管理,如string、array这些类族的分类),CC_LibUI(所有ui对象的管理),CC_LibAnimation(动画效果),CC_LibStorage(存储方案,包括bundle,沙盒,大数据存储),CC_LibData(数据的解析转换),CC_LibSecurity(各种加密方案),CC_LibAudio(音频音效管理),CC_LibNetwork(网络相关)…参考中央管理地方制度,中央有最高调度权,各省各自管理,特殊情况才可一国两制。由ccs有最高调度权限,没有管理权限。管理由各个模块各自管理,分布式架构,ccs可以获取访问权限。管理者管理子类直接的调度,子类自己管理自己的事情。

img3

每个库的独立是必须要做的,就如组成人的各个器官,也许不同架构有不同组合他们的方式,但他们本身是以各自形态存在的。所以基础库的丰富是必要工作,库的质量提现了封装者的水平,每个库的启动和运行中状态交给bench_ios监控,可以及时发现有问题的库找对应的人修改。

如果库各自分散,使用者在开发时需要明确知道有哪些库去import,即使采用方法不引用使用如路由方式也需知道对应的url,那么也就需要维护一份库的列表,在开发时或开发前学习库的列表文档,这样会文档更新不及时就会产生文档和实际库不一致问题,对于一些比较紧急的修改对维护人员要求更高了,需要改完代码再改文档才能下班。所以考虑bench_ios里的库都由CC_Share统一调度。使用者都去CC_Share获取对应内容,只需输入相应关键字,如音频audio,存储storage都会出现相应的联想,再通过代码提示去开发。

对于之前常用的ui封装类如CC_Mask、CC_Notice、CC_Alert等考虑存放的位置是在CC_Share上还是下。如果放在CC_Share上,那么就可以使用ccs调度来封装。但用户使用便无法用CC_Share调度了。如果放在CC_Share下层,那么在继承时需要用CC_UIKit提供的方法构建,无法使用CC_Share构建,实际上就成了和CC_UIKit并列的工具库了。

整个目录的层次如:

img4

前面说的用ccs控制各个模块的启动和调度弊端就是入口api太庞大,有点类似秦汉时期的宰相,权利过于大了,全国的事情无所不管。虽说2000多年的封建王朝被推翻了,但能存在这么长时间的模式也是有他的优越性的。统一管理避免了部分格格不入者做出一些越界的事情,但一定程度也阻碍了个性化的发展。好在我们的框架每个人都可以参与,如果有更优方案也考虑整合进去,建这套框架的初衷也是方便应用的开发,取之于民用之于民,我们把市面上好的构思和大家的意见结合选出一个相对最优方案,开发时做一件事情不需要很多方法,只需使用我们提供的’最优’方案,减少开发在做业务时方案选型的纠结,在做业务时只关注业务功能本身,不分散注意力到选择方案上。在业务之余可以进方法内学习方法是如何实现的来提高个人技术能力。

学习iOS11要出的SwiftUI发现他们把所有UI的刷新放到了body操作,我们把app的开发放到了ccs操作,趋势是一样的,就是减少开发者关注的点,需要关注的模块越少,开发越简单。

把库串起来

上面有两个问题:一是通过ccs调用库只是多了一层包装,似乎没有实际意义。二是一些抽象函数无处安放,比如哪一些数据做对比返回一个结果。

一、oc本质是消息转发,所有函数在执行时都会封装在一个对象里,他的方法像这样[obj foo],编译器转成消息发送objc_msgSend(obj, foo),Runtime时执行的流程是这样的:1. 通过obj的isa指针找到它的 class;2. 在 class 的 method list 找 foo;3. 如果 class 中没到 foo,继续往它的 superclass 中找;4. 一旦找到 foo 这个函数,就去执行它的实现IMP。

所以我封装了一个cc_message类,统一通过(obj, foo)方式发送消息给对应库,这样在cc_message这层可以控制库直接的调用,扩展一下,比如线上调用哪个库的某个方法会闪退,可以及时在这层拦调这次调用。

二、因为比较零碎的函数一下子找不到合适的库放置,如果单独开一个库也比较浪费,所以统一开辟了一块空间存放”一遍过“的执行函数,就如脚本一样只是执行需要。所以增加了CC_Function,如下图。如果CC_Lib新增一个库,有些函数能找到实体了,再移植过去。

img5

库之间的调用形式

通过分类+CC_Share的模式,上层应用无需区分ccs是由哪些库的组合形成,统一从ccs中获取对象,包括业务模块,在导入业务代码的同时已经默认扩展了ccs。CC_Share就如同变形金刚(核心),他可以用3个、4个任意个机器人(业务模块)组合而成。

为了区分内部调用,组件调用,以及外部app调用的形式,我们分别使用了如下方式:

1、内部lib调用:直接引用头文件,不经过ccs。

2、应用层调bench_ios库:由ccs通过cc_message发送消息给指定类,暴露class类型,由编译器链接库。

3、应该层调组件:由ccs通过cc_message发送消息给指定类,编译器不链接库,在运行时发送消息给指定的库。

4、app之间调用:通过url形式传递数据。

img6

开发就像打仗

之前的开发就像打仗时武器都在仓库了,需要开发人员自己去对应的仓库取,有了ccs,就多了一个助手,就像dota里帮忙回家买装备的信使,让他回城去买你想要的装备。

而助手也不是亲自去跑一趟,ccs是通过发消息的方式,发送一条短信到对应仓库,仓库会快递派发过来需要的东西,这样助手就可以一直留在前线开发人员身边。同时,每条消息都可以记录下来,可以很快地统计出应用调用特定的方法有几次,也可以做排名。

子模块的生命周期

+ (void)load{
    [ccs registerAppDelegate:self];
}

在模块load时注册生命周期,因为生命周期会起调launch,那么在app启动时就需要监听,把模块做成懒加载意义也不大了,所以在load时便注册到内存中。

因为注册了,bench_ios可以控制每个模块的生命周期。

注册完模块内就可以使用事件的监听

- (void)cc_didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

}

单例注册

创建单例的方法
bench内注册使用

+ (instancetype)shared{
	return [ccs registerShared:self];
}

应用层注册使用ccs registerShared:
bench内可以直接调库,应用层使用ccs调度

bench可以监控每个单例所占内存的大小和峰值,提供工具对内存消耗排名,找到可以优化的模块

函数(function)和方法(method)的区别

函数(function)和方法(method)的区别:

function是独立的功能,与对象无关,需要显示的传递数据。

method方法与对象和类相关,依赖对象而调用,可以直接处理对象上的数据,也就是隐式传递数据。

以上是搜到的定义。

我的理解:

function是去做事情,可以协调很多人去完成一件事情,可能自身不获利。

method是对自己的事情做处理,可能需要借助别人的力量,但操作对象是自己或自己参与。

继承对象的创建

对于创建继承父类的对象,起先有两种方案。

第一种是:

id t1 = [ccs model:[TestModel class]];

另一种是:

id t1 = [ccs model:@“TestModel"];

如果使用第二种,虽然可以不引入头文件可以加快编译速度,但在编译时无法找出错误,实际上没有太大必要,编译器能帮我们做的事情尽量不要去过渡包装了,所以还是采用第一种方案。

类与实例的关系

oc是面向对象的,把要做的事情封装成对象,我们所有的方法都是依赖实体的,所以cc_message实际上是一个函数类,但它依赖实体CC_Base,基于CC_Base发送消息。这个流程是和马克思主义理论相辅相成的。

辩证唯物主义认为:世界的统一性在于它的物质性,物质是世界所发生的一切变化的基础。运动是物质的存在形式,物质的运动是绝对的,静止是相对的。物质不是精神的产物,精神只是运动着的物质的最高形式。

cc_message(函数)就相当于精神层面,他是依赖CC_Base(实例)这个物质的,脱离物质的精神是不存在的。

马克思主义最重要的理论品质是坚持一切从实际出发,理论联系实际,实事求是,在实践中检验真理和发展真理。这种与时俱进的理论品质,是160多年来马克思主义始终保持蓬勃生命力的关键所在。

我个人是主张支持马克思主义思想的,是否支持没有对错之分,但这套框架是以这个理念为基石搭建的。

以CC_HttpTask发起请求为例讲述调用过程

CC_HttpManager实际上已经是一个伪管理者,因为CC_HttpTask也有自己实例,他们分别注册到了CC_Base中,严格来说CC_HttpTask应该是挂在CC_HttpManager下,由CC_HttpManager控制管理,但bench_ios本身提供了管理者的管理功能,所以与其嵌套一层不如平铺开来,一些CC_HttpTask需要的配置工作会请求CC_HttpManager来完成。这样所有的实例都是单层的,从访问管理者-访问管理者下的实例转变成直接访问管理者下的实例,更加快捷。这种情况下用CC_HttpHelper似乎更为恰当。

img7

加上CC_Share后的流程

img8

消息是直接发送给CC_HttpTask的,只是CC_HttpTask的实例是放在CC_Base上的,所以每次操作实际上是对CC_Base上的CC_HttpTask访问。

cc_message

cc_message是如何工作的。

+ (id)cc_class:(Class)class method:(SEL)selector;

+ (id)cc_class:(Class)class method:(SEL)selector params:(id)param,...;

+ (id)cc_instance:(id)instance method:(SEL)selector;

+ (id)cc_instance:(id)instance method:(SEL)selector params:(id)param,...;

我们通过这四个方法分别对类和实例发送消息,SEL是一个方法名标志,类成员方法的指针,与C的函数指针不一样,函数指针直接保存了方法的地址,而SEL只是方法的编号。

在作为所有类的根类的NSObject 中.isa的成员变量,所以所有的对象都有一个isa的变量,而isa变量指向该对象的类。

在runtime.h的查看类objc_class的时候,我们可以看到一个包含isa指针的结构体。所以类也是一个对象,同时它也必须是另一个类的实例,这份类就是元类。

元类同时也是一个对象,它指向的是根元类,根元类本身的isa 指针指向自己,就形成了闭环。

img8

接着上面继续,所有的对象都有一个isa的变量,而isa变量指向该对象的类。类其实也是实体的存在, 程序运行时每个类都有自己的存储空间,而isa 便指向这样一个类的空间,便建立了类和对象的对应关系,类空间包含了该类的成员变量以及方法实现,还包含指向父类空间的指针。

找到了方法,有时我们需要传参,这里的问题是传参可以是1个、2个,也可能是多个,如果我们看NSObject的api发现:

/*	NSObject.h

	Copyright (c) 1994-2012, Apple Inc. All rights reserved.

*/

- (id)performSelector:(SEL)aSelector;

- (id)performSelector:(SEL)aSelector withObject:(id)object;

- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

苹果只提供了最多两个参数的发送api,这肯定是不够的。

首先想到的是包装数组,如果使用数组传递,上面api的方式会变成:

+ (id)cc_class:(Class)class method:(SEL)selector params:(NSArray *)params;

这样的对应关系变得很模糊,因为我们不能向数组插入空对象,除非新建一个NSNull类,这样在调用时是很不方便的,如果我们把空的过滤掉,那么参数的对应关系就无法匹配上了。就是说selector方法里要传3个参数,但数组里你加了2个对象,因为一个是空的,那么在函数里就无法处理加的2个对象是对应第几个参数。

所以我们采用了

va_list去扫描参数,通过va_start()和va_end()去逐个拿取传入参数。这样即使有空对象传入也可以对其占位处理,和方法形成一一对应关系。

va_list params;
va_start(params, param);
int i = 0;
for (id tmpObject = param; (id)tmpObject; tmpObject = va_arg(params, id)) {
    if (tmpObject) {
        [paramsList addObject:tmpObject];
    }else{
        [paramsList addObject:NSNull.new];
    }
    i++;
    if (i >= paramsCount) {
        break;
    }
}
va_end(params);

CC_UIKit

手机适配一直是一个繁琐的问题,CC_UIKit为适配提供了方案。

img9

我们默认采用方案B 所以嵌套getRH和getRFS函数来实现缩放,为什么字体不也用getRH呢?因为字体缩放系数和frame的缩放系数不同,会稍微低一些,如果字体用等比缩放会在小设备看上去适合,但在大屏幕由于放大过多有点像老年机。

之后对所有布局的数字包一层RH()函数,如:

float x = 30;

变成

float x = RH(30)

通过联想不需要手动打括号。代码可以写成:

UILabel *l = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, RH(200), RH(40))];
l.font = RF(14);
[self.view addSubview:l];

链式布局

我们看过SwfitUI可以发现它的布局是这样写的:

VStack {
   Badge()
      .frame(width: 300, height: 300)
      .animation(.basic())
   Text(name)
      .font(.title)
      .animation(Animation.basic().delay(0.25))
}

省去了变量名,通过链式返回对象自己可以无限扩展下去。但在oc中并没有封装这样的功能,但是我们可以封装一个block,以返回block的形式返回对象自己。这样我们在用对象时从声明对象变成了描述对象,直接描述这个ui控件的样子:

ccs.label
.cc_name(@"mylabel")
.cc_frame(RH(10),RH(100),RH(100),RH(100))
.cc_backgroundColor(HEXA(@"FFD700", 1));

此外,又添加了一个cc_bindText()方法,目的是更新数据源后和视图控件绑定。
我们经常会遇到更新了model没有刷新ui或者,改变了ui没有更新model的情况,如果把数据和视图绑定,那么每次更新数据的时候视图也会跟着变化,就不会有不同步的事情发生。这也是SwiftUI具有的功能,我们也通过cc_bindText()来实现:

// 绑定string
NSString *str = [ccs string:@"abc%@%d",@"a",34];
// 方法三
ccs.label
.cc_name(@"mylabel")
.cc_frame(RH(10),RH(100),RH(100),RH(100))
.cc_backgroundColor(HEXA(@"FFD700", 1))
.cc_textColor(HEXA(@"9B30FF", 1))
.cc_bindText(str)
.cc_addToView(self)
.cc_tappedInterval(0.1,^(id view) {
    // 改变labele内的富文本
    NSMutableAttributedString *att = [ccs mutAttributedString];
    [att cc_appendAttStr:@"abc" color:COLOR_LIGHT_ORANGE];
    [att cc_appendAttStr:@"123" color:[UIColor greenColor] font:RF(22)];
    CC_Label *v = view;
    v.attributedText = att;
    // 延时5秒后退出控制器
    [ccs delay:5 block:^{
        [ccs popViewController];
    }];
});

// 3秒后更新string view跟踪变化
[ccs delay:3 block:^{
    // 无需获取控件,更新数据源自动更新视图控件
    [str cc_update:@"cvb"];
}];

如果我们在控制器的另一个函数中要获取控件对象,我们可以声明它为全局变量或者属性:

@interface TestViewController2 () {
    UILabel *label;
}
@end

也可以使用viewWithTag()来取对象,但用tag会很不方便,如果要直观还需将tag声明成static,所以我们提供了viewWithName()的函数来取对象:

- (void)funtionB {
    id v = [self cc_viewWithName:@"abc"];
}

CC_Controller和CC_ViewController的关系

哪个大,CC_ViewController大。为什么取CC_ViewController去包含CC_Controller,有几个原因:
1、整个app是从UIKit中的方法启动的,可见view是app应用层的支柱,apple没有做controller这个类,只有ViewController。
2、如果反过来和iOS默认开发模式有冲突,必须比之前多建一个Controller去管理ViewController,操作上也不方便。
3、使用多个Controller更好拆分,分发给别的模块。

传统MVC开发时常遇到的问题是C非常臃肿,因为C承担着view的工作,因为C和view之间交互太紧密,如果严格按照代理模式会使代码量增加一倍以上,因此也有了新模式MVVM,MVP的产生,都是为了减轻C的压力。我们采用多个Controller的原理也是分担ViewController的工作,CC_ViewController更像是一个领导,管理者各个部门主管(CC_Controller),由部门主管再去处理子类的工作,CC_ViewController只需要管理这些CC_Controller,这样可以将CC_ViewController扩展到很大,比较利于社交这些拥有复杂ViewController的应用。

CC_Controller通过start()函数初始化,我们在CC_Controller中方便地提供了代理,通过代理分发回调到CC_ViewController来交互,传统方法需要声明代理,以及响应判断。
将原来要写的代码:

// 属性声明
@property(nonatomic,assign) id <CC_LabelGroupDelegate>delegate;

// 代理
if ([self.delegate respondsToSelector:@selector(labelGroup:initWithButton:)]) {
    [self.delegate labelGroup:self initWithButton:button];
}

变成直接代理:

[self.cc_delegate cc_performSelector:@selector(methd2withA:b:) params:@"",@""];

在对应的CC_ViewController中便可接收到此方法。

监控

我们存在的体系是由两个维度决定的,时间和空间。代码也一样,时间对应的就是耗时,空间对应内存开销,对这两个维度的监控基本就覆盖了所有问题,在组件化越来越多,提交者越来越多时,并不能及时一行行review代码,这时我们可以由监控的报告来看出哪个模块有问题,再定位到相应代码,可以更有效地展开工作。

监控依靠CC_Monitor来完成。
如果我们把生命周期分发出去,那么无法把控每个模块的耗时,所以要对其监控,防止某个模块耗时太大对app启动造成影响。因为app由CC_Appdelage分发生命周期,实际上app启动由CC_Appdelage控制,那么子模块的耗时工作放到子线程也没有关系,除非是打开就要使用的模块,否则可以将某些初始化移到运行中去,减轻启动的压力。
依旧使用消息分发,在回调代理前使用:

[CC_Monitor.shared watchStart:name method:method];

在代理回调后使用

[CC_Monitor.shared watchEnd:name method:method];

那么CC_Monitor便可以统计这段时间发生的异常(占用的时间和内存)。

在CC_AppDelegate启动后就参与工作,会对启动时分发的生命周期进行监控,来找出启动耗时高的模块进行优化,看模块的工作是否能移到子线程或稍后启动。之后开启一个常驻线程监听所有注册的对象。开启子线程的目的是不影响正常app的使用。
所以目前CC_Monitor的几大功能是:
1、监控app本身和组件的生命周期(包括耗时和内存)。
2、监控app运行时的异常(包括耗时、内存和异常)。

可以通过CC_Monitor的属性来设置:

// 启动监控 默认开启
@property (nonatomic,assign) BOOL startLaunchMonitor;
// 启动监控日志 默认开启
@property (nonatomic,assign) BOOL startLaunchMonitorLog;
// 定期检查 默认开启
@property (nonatomic,assign) BOOL startPatrolMonitor;
// 定期检查日志 默认关闭
@property (nonatomic,assign) BOOL startPatrolMonitorLog;

域名配置

在我们app只有1人开发时域名的配置会使用:

NSArray *domainArrs = nil;
if (net==0) {
    //线下
    domainArrs = @[xxx]
} else {
    domainArrs = @[xxx]
}

这样的形式,或者有人喜欢直接注释来切换,这样很容易造成冲突,并且会配错。

我们知道xcode里有配置选项,通过配置可以指定debug和release版本:

img10

但是如果加上分支,这两个切换便不够用了。所以在bench里整合了一套切换域名的方案。

config使用方法

新建一个CC_CommonConfig.xcconfig文件,在里面写上相应配置:

GCC_PREPROCESSOR_DEFINITIONS = $(inherited) CCBUILDTAG='$(CCBUILDTAG)'

新建发布(CC_ReleaseConfig.xcconfig)、主干(CC_TrunkConfig.xcconfig)、分支1(CC_Branch1Config.xcconfig)等.xcconfig文件,在里面写上tag值和导入’CC_CommonConfig.xcconfig’文件,release=0,trunk=1,branch1=2 …

CCBUILDTAG=0
#include "CC_CommonConfig.xcconfig"

使这个文件关联工程,之后只需修改info里的configurations来区分线上、主干和分支。

img11

我们传入动态域名的地址来获取正确的配置域名:

[ccs configureDomainWithReqGroupList:@[@[线上地址1,线上地址2...], @[主干地址1,主干地址2...], @[分支1地址1,分支1地址2...] ...] andKey:@"eh_doctor_api" cache:NO pingTest:YES block:^(HttpModel *result) {
    //从result获取域名
}];

bench_ios可以无限扩展,我们所做的仅仅是个开始。

参考资料

BeeHive框架全面解析——iOS开发主流方案比较
https://xiaozhuanlan.com/topic/4052613897

在现有工程中实施基于CTMediator的组件化方案
https://casatwy.com/modulization_in_action.html

iOS应用架构谈 组件化方案
https://casatwy.com/iOS-Modulization.html

BeeHive,一次iOS模块化解耦实践
https://mp.weixin.qq.com/s?__biz=MzUxMzcxMzE5Ng==&mid=2247488305&idx=1&sn=0f0a2e4d5febe3024adf0578f092b020

NSInvocation在获取返回值后crash问题
https://blog.csdn.net/zengconggen/article/details/38024625

不一样的方式实现performSelector接收多个参数
https://www.jianshu.com/p/0c45b496686a

IOS SEL(@selector)原理以及应用
https://www.jianshu.com/p/96842c912a04

iOS-Runloop常驻线程/性能优化
https://www.jianshu.com/p/f3079ea36775

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值