ios组件的生命周期
淘宝搜:【天降红包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]; |
?
蘑菇街是和混用的方式,两种实现的调用方式不同,但大体调用逻辑和实现思路类似,所以下面的调用流程二者差不多。在不能满足需求或调用不方便时,就可以通过的方式调用。
- 在进入程序后,先使用对服务方组件进行注册。每个对应一个的实现,中的代码就是服务方对外提供的服务,调用方可以通过调用这个服务。
- 调用方通过调用方法,并将被调用代码对应的传入,会根据查找对应的实现,从而调用服务方组件的代码进行通信。
- 调用和注册时,有一个字典用来传递参数。这样的优势就是参数类型和数量理论上是不受限制的,但是需要很多硬编码的名在项目中。
蘑菇街组件化方案有两种,和的方式,但都需要进行操作。注册的是,注册的是,注册表是一个类型的字典,而字典的拥有者又是一个单例对象,这样会造成内存的常驻。
下面是对两种实现方式内存消耗的分析:
- 首先说一下实现方式可能导致的内存问题,如果使用不当,很容易造成循环引用的问题。 经过暴力测试,证明并不会导致内存问题。被保存在字典中是一个对象,而对象本身并不会占用多少内存。在调用后会对体中的方法进行执行,执行完成后体中的对象释放。 而自身的实现只是一个结构体,也就相当于字典中存放的是很多结构体,所以内存的占用并不是很大。
- 对于协议这种实现方式,和内存常驻方式差不多。只是将存储的对象换成对象,如果不是已经实例化的对象,内存占用还是比较小的。
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< 3月28日,阿里巴巴集团董事会主席兼CEO张勇发布全员信,宣布启动“1+6+N”组织变革,重组为六大主要业务集团,包括阿里云智能、淘宝天猫商业、本地生活、菜鸟、国际数字商业、大文娱(还有多家业务公司),都能独立融资和上市。 有媒体分析认为,阿里这轮变革从集团顶层入手,重新定义和构造阿里集团与各业务的治理关系,可以说是“阿里24年来最重要的一次组织变革”。 据第一财经报道,3月28日晚间,在一则阿里内网的答问视频中,张勇详细解读了这次变革,直言这是阿里24年发展历史上前所未有的,是变化最大的一次,也是生产关系变革最剧烈的一次。 简单地说,这次变革就是要让阿里集团的组织更简单、更敏捷,释放活力,加速决策、响应,也让员工在心态上真正走向为自己而战,为自己所在的领域而战。 张勇表示,“1+6+N”中的“1”就是阿里集团,作为阿里上市公司主体不变,但集团的运营重心会从具体业务抽离出来。” 他形象地说:“孩子大了,要走出去,独立面对市场。阿里集团更像一个大底座,给他们做好支撑。条件成熟一个,上市一个。” 张勇希望,阿里未来能培育出若干个上市公司,过几年继续“生儿育女”,长出更多上市公司,而且上市不仅仅是为了上市,更是独立面对市场的更高要求。阿里ceo张勇说未来 张勇为什么当阿里巴巴ceo
阿里ceo张勇,阿里ceo张勇本科专业,阿里ceo张勇微博,阿里ceo张勇身世