淘优惠

淘优惠

ios组件的生命周期

热门文章 0

淘宝搜:【天降红包222】领超级红包,京东搜:【天降红包222】
淘宝互助,淘宝双11微信互助群关注公众号 【淘姐妹】


2019独角兽企业重金招聘Python工程师标准>>>

随着移动互联网的不断发展,很多程序代码量和业务越来越多现有架构已经不适合公司业务的发展速度了,很多都面临着重构的问题。 在公司项目开发中,如果项目比较小,普通的就可以满足大多数需求了。但是像淘宝、蘑菇街、微信这样的大型项目,原有的单工程架构就不足以满足架构需求了。

就拿淘宝来说,淘宝在13年开启的战略中,就将阿里系大多数业务都加入到手机淘宝中,使客户端出现了业务的爆发。在这种情况下,单工程架构则已经远远不能满足现有业务需求了。所以在这种情况下,淘宝在13年开启了插件化架构的重构,后来在14年迎来了手机淘宝有史以来最大规模的重构,将其彻底重构为组件化架构

在一个项目越来越大,开发人员越来越多的情况下,项目会遇到很多问题。

  • 业务模块间划分不清晰,模块之间耦合度很大,非常难维护。
  • 所有模块代码都编写在一个项目中,测试某个模块或功能需要编译运行整个项目

?

耦合严重的工程

为了解决上面的问题,可以考虑加一个中间层来协调模块间的调用,所有的模块间的调用都会经过中间层中转(注意看两张图的箭头方向)

?

添加中间层

但是发现增加这个中间层后,耦合还是存在的。中间层对被调用模块存在耦合,其他模块也需要耦合中间层才能发起调用。这样还是存在之前的相互耦合的问题,而且本质上比之前更麻烦了。

所以应该做的是,只让其他模块对中间层产生耦合关系,中间层不对其他模块发生耦合。 对于这个问题,可以采用组件化的架构,将每个模块作为一个组件。并且建立一个主项目,这个主项目负责集成所有组件。这样带来的好处是很多的:

  • 业务划分更佳清晰,新人接手更佳容易,可以按组件分配开发任务。
  • 项目可维护性更强,提高开发效率。
  • 更好排查问题,某个组件出现问题,直接对组件进行处理。
  • 开发测试过程中,可以只编译自己那部分代码,不需要编译整个项目代码。

?

组件化结构

进行组件化开发后,可以把每个组件当做一个独立的app每个组件甚至可以采取不同的架构,例如分别使用、、等架构。

蘑菇街通过实现中间层,通过进行组件间的消息转发,从名字上来说更像是路由器。实现方式大致是,在提供服务的组件中提前注册,然后在调用方组件中通过调用,下面是调用方式。

架构设计

?

MGJRouter组件化架构

是一个单例对象,在其内部维护着一个格式的注册表,通过这个注册表来保存服务方注册的,以及使调用方可以通过映射出,并通过对服务方发起调用。

在服务方组件中都对外提供一个接口类,在接口类内部实现的注册工作,以及对外提供服务的代码实现。每一个都对应着一个,调用方可以通过对发起调用。

在程序开始运行时,需要将所有服务方的接口类实例化,以完成这个注册工作,使中所有服务方的可以正常提供服务。在这个服务注册完成后,就可以被调用方调起并提供服务。

蘑菇街项目使用作为版本控制工具将每个组件都当做一个独立工程,并建立主项目来集成所有组件。集成方式是在主项目中通过来集成,将所有组件当做二方库集成到项目中。详细的集成技术点在下面章节中会讲到。

MGJRouter调用

代码模拟对详情页的注册、调用,在调用过程中传递参数。下面是注册的示例代码:

?

?

1

2

3

4

[MGJRouter?registerURLPattern:@"mgj://detail?id=id"?toHandler:^(NSDictionary *routerParameters)?{

?// 下面可以在拿到参数后,为其他组件提供对应的服务

?NSString?uid?=?routerParameters[@"id"];

}];

通过方法传入的参数,对详情页已经注册的方法发起调用。调用方式类似于请求,地址后面拼接参数。

?

?

1

[MGJRouter?openURL:@"mgj://detail?id=404"];

也可以通过字典方式传参,提供了带有字典参数的方法,这样就可以传递非字符串之外的其他类型参数

?

?

1

[MGJRouter?openURL:@"mgj://detail?"?withParam:@{@"id"?:?@"404"}];

?

组件间传值

有的时候组件间调用过程中,需要服务方在完成调用后返回相应的参数。蘑菇街提供了另外的方法,专门来完成这个操作。

?

?

1

2

3

[MGJRouter?registerURLPattern:@"mgj://cart/ordercount"?toObjectHandler:^id(NSDictionary *routerParamters){

?return?@42;

}];

通过下面的方式发起调用,并获取服务方返回的返回值,要做的就是传递正确的和参数即可。

?

?

1

NSNumber *orderCount?=?[MGJRouter?objectForURL:@"mgj://cart/ordercount"];

?

短链管理

这时候会发现一个问题,在蘑菇街组件化架构中,存在了很多硬编码的URL和参数。在代码实现过程中编写出错会导致调用失败,而且参数是一个字典类型,调用方不知道服务方需要哪些参数,这些都是个问题。

对于这些数据的管理,蘑菇街开发了一个页面,这个页面统一来管理所有的和参数,和都使用这一套,可以保持统一性。

基础组件

在项目中存在很多公共部分的东西,例如封装的网络请求、缓存、数据处理等功能,以及项目中所用到的资源文件。

蘑菇街将这些部分也当做组件,划分为基础组件,位于业务组件下层。所有业务组件都使用同一个基础组件,也可以保证公共部分的统一性。

整体架构

?

Protocol方案的中间件

为了解决方案中硬编码,以及字典参数类型不明确等问题,蘑菇街在原有组件化方案的基础上推出了方案。方案由两部分组成,进行组件间通信的类以及协议类。

通过中间件进行消息的调用转发,在内部维护一张映射表,映射表由之前的变成。 在中间件中创建文件,服务方组件将可以用来调用的方法都定义在中,将所有服务方的都分别定义到文件中,如果协议比较多也可以分开几个文件定义。这样所有调用方依然是只依赖中间件,不需要依赖除中间件之外的其他组件。

方案中每个组件也需要一个“接口类”,此类负责实现当前组件对应的协议方法,也就是对外提供服务的实现。在程序开始运行时将自身的注册到中,并将反射出字符串当做。这个注册过程和是类似的,都需要提前注册服务

示例代码

创建类当做模块的服务类,并在中定义协议,由类实现协议中定义的方法,完成对外提供服务的过程。下面是协议定义:

?

?

1

2

3

@protocol?MGJUserProtocol

-?(NSString *)getUserName;

@end

遵守协议并实现定义的方法,外界通过获取的实例化为对象,调用服务方实现的协议方法。

的协议注册方法,注册时将反射为字符串当做存储的,将实现协议的当做值存储。通过取的时候,就是通过从中将映射出来。

?

?

1

[ModuleManager?registerClass:MGJUserImpl?forProtocol:@protocol(MGJUserProtocol)];

调用时通过从中映射出注册的,将获取到的实例化,并调用实现的协议方法完成服务调用。

?

?

1

2

3

Class?cls?=?[[ModuleManager?sharedInstance]?classForProtocol:@protocol(MGJUserProtocol)];

id?userComponent?=?[[cls?alloc]?init];

NSString *userName?=?[userComponent?getUserName];

?

蘑菇街是和混用的方式,两种实现的调用方式不同,但大体调用逻辑和实现思路类似,所以下面的调用流程二者差不多。在不能满足需求或调用不方便时,就可以通过的方式调用。

  1. 在进入程序后,先使用对服务方组件进行注册。每个对应一个的实现,中的代码就是服务方对外提供的服务,调用方可以通过调用这个服务。
  2. 调用方通过调用方法,并将被调用代码对应的传入,会根据查找对应的实现,从而调用服务方组件的代码进行通信。
  3. 调用和注册时,有一个字典用来传递参数。这样的优势就是参数类型和数量理论上是不受限制的,但是需要很多硬编码的名在项目中。

蘑菇街组件化方案有两种,和的方式,但都需要进行操作。注册的是,注册的是,注册表是一个类型的字典,而字典的拥有者又是一个单例对象,这样会造成内存的常驻

下面是对两种实现方式内存消耗的分析:

  • 首先说一下实现方式可能导致的内存问题,如果使用不当,很容易造成循环引用的问题。 经过暴力测试,证明并不会导致内存问题。被保存在字典中是一个对象,而对象本身并不会占用多少内存。在调用后会对体中的方法进行执行,执行完成后体中的对象释放。 而自身的实现只是一个结构体,也就相当于字典中存放的是很多结构体,所以内存的占用并不是很大。
  • 对于协议这种实现方式,和内存常驻方式差不多。只是将存储的对象换成对象,如果不是已经实例化的对象,内存占用还是比较小的。

casatwy组件化方案分为两种调用方式,远程调用和本地调用,对于两个不同的调用方式分别对应两个接口。

  • 远程调用通过代理方法传递到当前应用后,调用远程接口并在内部做一些处理,处理完成后会在远程接口内部调用本地接口,以实现本地调用为远程调用服务
  • 本地调用由方法负责,但调用方一般不直接调用方法。会对外提供明确参数和方法名的方法,在方法内部调用方法和参数的转换。

?

casatwy提出的组件化架构

casatwy是通过类实现组件化的,在此类中对外提供明确参数类型的接口,接口内部通过方法调用服务方组件的、。由于类的调用是通过主动发现服务的,所以服务方对此类是完全解耦的。

但如果类对外提供的方法都放在此类中,将会对造成极大的负担和代码量。解决方法就是对每个服务方组件创建一个的,并将对服务方的调用放在对应的中,这些都属于中间件,从而实现了感官上的接口分离。

?

casatwy组件化实现细节

对于服务方的组件来说,每个组件都提供一个或多个类,在类中声明方法。类是当前组件对外提供的一个“服务类”,将当前组件中所有的服务都定义在里面,通过主动发现服务

在中的所有方法,都只有一个字典参数,所以可以传递的参数很灵活,这也是casatwy提出的去化的概念。在的方法实现中,对传进来的字典参数进行解析,再调用组件内部的类和方法。

casatwy为我们提供了一个Demo,通过这个可以很好的理解casatwy的设计思路,下面按照我的理解讲解一下这个。

?

文件目录

打开后可以看到文件目录非常清楚,在上图中用蓝框框出来的就是中间件部分,红框框出来的就是业务组件部分。我对每个文件夹做了一个简单的注释,包含了其在架构中的职责。

在中定义远程调用和本地调用的两个方法,其他业务相关的调用由完成。

?

?

1

2

3

4

// 远程App调用入口

-?(id)【【淘密令】】:(NSURL *)url?completion:(void(^)(NSDictionary *info))completion;

// 本地组件调用入口

-?(id)【【淘密令】】:(NSString *)targetName?action:(NSString *)actionName?params:(NSDictionary *)params;

在中定义的的,对外提供了一个获取控制器并跳转的功能,下面是代码实现。由于casatwy的方案中使用的方式进行调用,所以涉及到很多硬编码字符串的问题casatwy采取定义常量字符串来解决这个问题,这样管理也更方便。

?

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

#【【微信】】 "CTMediator+CTMediatorModuleAActions.h"

?

NSString *?const?kCTMediatorTargetA?=?@"A";

NSString *?const?kCTMediatorActionNati【【微信】】roller?=?@"nati【【微信】】roller";

?

@implementation?CTMediator?(CTMediatorModuleAActions)

?

-?(UIViewController *)CTMediator_【【微信】】Detail?{

UIViewController *viewController?=?[self?【【淘密令】】:kCTMediatorTargetA

action:kCTMediatorActionNati【【微信】】roller

params:@{@"key":@"value"}];

if?([viewController?isKindOfClass:[UIViewController?class]])?{

// 【【微信】】 交付出去之后,可以由外界选择是push还是present

return?viewController;

}?else?{

// 这里处理异常场景,具体如何处理取决于产品

return?[[UIViewController?alloc]?init];

}

}

下面是组件中提供的服务,被定义在类中,这些服务可以被通过的方式调用,这个过程就叫做发现服务

我们发现,在这个方法中其实做了参数处理和内部调用的功能,这样就可以保证组件内部的业务不受外部影响,对内部业务没有侵入性

?

?

1

2

3

4

5

6

-?(UIViewController *)Action_nati【【微信】】roller:(NSDictionary *)params?{

// 对传过来的字典参数进行解析,并调用ModuleA内部的代码

DemoModuleADetailViewController *viewController?=?[[DemoModuleADetailViewController?alloc]?init];

viewController.valueLabel.text?=?params[@"key"];

return?viewController;

}

?

在大型项目中代码量比较大,需要避免命名冲突的问题。对于这个问题casatwy采取的是加前缀的方式,从casatwy的中也可以看出,其组件的命名为,被调用的命名为。

casatwy将类和方法的命名,都统一按照其功能做区分当做前缀,这样很好的将组件相关和组件内部代码进行了划分。

这个章节叫做“标准组件化架构设计”,对于项目架构来说并没有绝对意义的标准之说。这里说到的“标准组件化架构设计”只是因为采取这样的方式的人比较多,且这种方式相比而言较合理。

在上面文章中提到了casatwy<


阿里ceo张勇说未来 张勇为什么当阿里巴巴ceo

阿里ceo张勇,阿里ceo张勇本科专业,阿里ceo张勇微博,阿里ceo张勇身世

3月28日,阿里巴巴集团董事会主席兼CEO张勇发布全员信,宣布启动“1+6+N”组织变革,重组为六大主要业务集团,包括阿里云智能、淘宝天猫商业、本地生活、菜鸟、国际数字商业、大文娱(还有多家业务公司),都能独立融资和上市。

有媒体分析认为,阿里这轮变革从集团顶层入手,重新定义和构造阿里集团与各业务的治理关系,可以说是“阿里24年来最重要的一次组织变革”。

据第一财经报道,3月28日晚间,在一则阿里内网的答问视频中,张勇详细解读了这次变革,直言这是阿里24年发展历史上前所未有的,是变化最大的一次,也是生产关系变革最剧烈的一次。

简单地说,这次变革就是要让阿里集团的组织更简单、更敏捷,释放活力,加速决策、响应,也让员工在心态上真正走向为自己而战,为自己所在的领域而战。

张勇表示,“1+6+N”中的“1”就是阿里集团,作为阿里上市公司主体不变,但集团的运营重心会从具体业务抽离出来。”

他形象地说:“孩子大了,要走出去,独立面对市场。阿里集团更像一个大底座,给他们做好支撑。条件成熟一个,上市一个。”

张勇希望,阿里未来能培育出若干个上市公司,过几年继续“生儿育女”,长出更多上市公司,而且上市不仅仅是为了上市,更是独立面对市场的更高要求。