美图欣赏 | 设为首页 | 加入收藏 | 网站地图

当前位置:电脑中国 > 编程 > 移动开发 >

iOS组件化不只是架构师的事

2018-11-14 11:49|来源:未知 |作者:dnzg |点击:

iOS组件化曾今在业界是多么的火热的话题,现在在少有人再次提及这个的话题。网上也很多关于组件化的文章和思想,最经典的要是casa大神和蘑菇街关于组件化的论战。想想曾经看到这些文章的时候,觉得组件化是多么优秀的思想,觉得他们说的都有道理,而casa大神应该在很多思想上给了我等码农很多灵感。而两位大神架构师级别的论剑是否让你真正理解到组件化的重要性。是否让你在内心深处产生共鸣,最 近看到一个项目让我对组件化多了些思考。

iOS组件化不只是架构师的事

一、为什么要组件化,组件化到底有什么好处?

为什么要组件化,在看过很多优秀的文章后,你一定会问这个问题,组件化能给我们带来多大的好处?作为一个小公司而言,涉及组件化的机会很少,没有大厂的工作经验,也很难将组件化理解的很透彻。可能以为我们的业务模块还不够多,或者说,我们没有理解到他的好处,其实组件化最大的好处就是,每个组件,每个模块都可能单独成一个app,具有自己的生命周期。这样就可以分割成不同的业务组模块去处理,之前听说京东,有团队专门负责消息模块,有团队专门负责广告模块,有团队专门负责发现模块,这是你就会发现如果没有很好的组件化思想,这样的多团队合作就非常的困难,已经很难维护好这个项目的开发迭代。说了这么多,到底组件化是什么样子的呢?那我跟着我的脚步,学习分析,探讨下。

二、组件化的核心思想

组件化的话的核心思想,也是我们进行组件化的基础框架,就是通过怎么样的方式实现组件化,或者如何从架构层,业务层多个层次实现架构呢。要想实现组件化,其实就是建立一个中间转换的工具。你也可以理解为路由,通过路由的思想实现跨业务的数据沟通,从而一定程度上的降低各层数据的耦合。减少各个业务层等层级的import发生的耦合。

三、目前实现的组件化的方式

目前实现一般有下面三种思想:

  1. Procotol方案
  2. URL路由方案
  3. target-action方案

Procotol协议注册方案

关于procotol协议注册方案看人用的比较少,也很少看到有人分享,我也是在这个项目中看到,就研究了一下。通过JJProtocolManager 作为中间转化。


  1. + (void)registerModuleProvider:(id)provider forProtocol:(Protocol*)protocol;  
  2. + (id)moduleProviderForProtocol:(Protocol *)protocol; 

所有组件对外提供的procotol和组件提供的服务由中间件统一管理,每个组件提供的procotol和服务是一一对应的。

例如:

在JJLoginProvider中:load方法会应用启动的时候调用,就会在JJProtocolManager进行注册。JJLoginProvider遵守了JJLoginProvider协议,这样就可以对外根据业务需求提供一些方法。


  1. + (void)load 
  2.     [JJProtocolManager registerModuleProvider:[self new] forProtocol:@protocol(JJLoginProtocol)]; 
  3. - (UIViewController *)viewControllerWithInfo:(id)userInfo needNew:(BOOL)needNew callback:(JJModuleCallbackBlock)callback{ 
  4.     CLoginViewController *vc = [[CLoginViewController alloc] init]; 
  5.     vc.jj_moduleCallbackBlock = callback; 
  6.     vc.jj_moduleUserInfo = userInfo; 
  7.     return vc; 

这样就可以在需要登录业务模块的地方,通过JJProtocolManager取出JJLoginProtocol对应的服务提供者JJLoginProvider,直接获取。如下:


  1. id<jjwebviewvcmoduleprotocol> provider = [JJProtocolManager moduleProviderForProtocol:@protocol(JJWebviewVCModuleProtocol)]; 
  2.    UIViewController *vc =[provider viewControllerWithInfo:obj needNew:YES callback:^(id info) { 
  3.        if (callback) { 
  4.            callback(info); 
  5.        } 
  6.    }]; 
  7.    vc.hidesBottomBarWhenPushed = YES; 
  8.    [self.currentNav pushViewController:vc animated:YES];</jjwebviewvcmoduleprotocol> 

URL路由方案

URL路由方案最经典的就是蘑菇街的路由组件化,通过url的方式将调用方法,调用参数,已经回调方法封装到url中,然后在通过对url的解析获取到方法名,参数,最后通过消息转发机制调用方法。

下面是蘑菇街的路由方式:(这里要是想详细了解,可以到蘑菇街的路由组件化 中具体学习)


  1. [MGJRouter registerURLPattern:@"mgj://detail?id=:id" toHandler:^(NSDictionary *routerParameters) { 
  2.     NSNumber *id = routerParameters[@"id"]; 
  3.     // create view controller with id 
  4.     // push view controller 
  5. }]; 

首页只需调用 [MGJRouter openURL:@"mgj://detail?id=404"] 就可以打开相应的详情页。

这里可以看到,我们通过url短链的方式,通过将参数拼接到url query部分,这样就可以,通过这样解析url中的scheme,host,path,query获取到调转什么要的控制器,需要传什么什么样的参数,从而push或者present新页面。

解析scheme,host,path核心代码:


  1. NSString *scheme = [nsUrl scheme];//解析scheme 
  2.    NSString *module = [nsUrl host]; 
  3.    NSString *action = [[nsUrl path] stringByReplacingOccurrencesOfString:@"/" withString:@"_"]; 
  4.    if (action && [action length] && [action hasPrefix:@"_"]) { 
  5.        action = [action stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:@""]; 
  6.    } 
  7.   
  8.    NSString *query = nil; 
  9.    NSArray* pathInfo = [nsUrl.absoluteString componentsSeparatedByString:@"?"]; 
  10.    if (pathInfo.count > 1) { 
  11.        query = [pathInfo objectAtIndex:1]; 
  12.    } 

解析query的核心代码:


  1. NSMutableDictionary *parameters = nil; 
  2. NSString *parametersString = query; 
  3. NSArray *paramStringArr = [parametersString componentsSeparatedByString:@"&"]; 
  4. if (paramStringArr && [paramStringArr count]>0) { 
  5.     parameters = [NSMutableDictionary dictionary]; 
  6.     for (NSString* paramString in paramStringArr) { 
  7.         NSArray *paramArr = [paramString componentsSeparatedByString:@"="]; 
  8.         if (paramArr.count > 1) { 
  9.             NSString *key = [paramArr objectAtIndex:0]; 
  10.             NSString *value = [paramArr objectAtIndex:1]; 
  11.             parameters[key] = [JJRouter unescapeURIComponent:value]; 
  12.         } 
  13.     } 
  14. return parameters; 

通过这样的方式,我们就可以实现组件化,但是有时候我们会遇到一个图片编辑模块,不能传递UIImage到对应的模块上去的话,这里我们需要传个新的参数进去,为了解决这个问题,这样其实,可以把参数直接丢给后面的arg处理


  1. + (nullable id)openURL:(nonnull NSString *)urlString arg:(nullable id)arg error:( NSError*__nullable *__nullable)error completion:(nullable JJRouterCompletion)completion 

举个例子:


  1.     Action *action = [Action new]; 
  2.            action.type = JJ_WebView; 
  3.            Params *params = [[Params alloc] init]; 
  4.            //            params.pageID = JJ_LOGIN; 
  5.            action.params = params; 
  6.            NSDictionary *parms = @{Jump_Key_Action:action, Jump_Key_Param : @{WebUrlString:@"http://www.baidu.com",Name:@"小二"}, Jump_Key_Callback:[JJFunc callback:^(id  _Nullable object) { 
  7.                NSLog(@"%@",object); 
  8.            }]}; 
  9. //            ActionJump(parms); 
  10.              
  11.            [JJRouter openURL:@"router://JJActionService/showWebVC" arg: parms error:nil completion:parms[Jump_Key_Callback]]; 
  12.        } 

我看的项目,这个就是通过url解析和protocol协议注册实现组件化,只是没有像蘑菇街那样注册支持哪些 URL类型。

target-action方案

target-action方案是在学习casa大神,CTMediator 的基础上进行的

casa大神认为,

  1. 根本无法表达非常规对象,如果用url组件化的话,遇到像UIImage这样的参数,就需要添加一个参数,才能解决
  2. URL注册对于实施组件化方案是完全不必要的,且通过URL注册的方式形成的组件化方案,拓展性和可维护性都会被打折
  3. 蘑菇街没有拆分远程调用和本地间调用
  4. 蘑菇街必须要在app启动时注册URL响应者 

  1. //理论上页面之间的跳转只需 open 一个 URL 即可。所以对于一个组件来说,只要定义「支持哪些 URL」即可,比如详情页,大概可以这么做的  
  2. [MGJRouter registerURLPattern:@"mgj://detail?id=:id" toHandler:^(NSDictionary *routerParameters) { 
  3.    NSNumber *id = routerParameters[@"id"]; 
  4.    // create view controller with id 
  5.    // push view controller 
  6. }]; 

而casa的组件化主要是基于Mediator模式和Target-Action模式,中间采用了runtime来完成调用。这套组件化方案将远程应用调用和本地应用调用做了拆分,而且是由本地应用调用为远程应用调用提供服务,与蘑菇街方案正好相反。

调用方式:

先说本地应用调用,本地组件A在某处调用[[CTMediator sharedInstance] performTarget:targetName action:actionName params:@{...