/LDBusBundle_IOS

LDBus的目标:(保持业务组件的相对独立性,尽量解耦业务组件之间的代码依赖)

Primary LanguageObjective-CMIT LicenseMIT

LDBus 使用说明

LDBus的作用

随着应用需求逐步迭代,应用的代码体积将会越来越大,为了更好的管理应用工程,我们开始借助CocoaPods版本管理工具对原有应用工程进行拆分。但是仅仅完成代码拆分还不足以解决业务之间的代码耦合,为了更好的让拆分出去的业务工程能够独立运行,实现组件服务化,我们在此引入LDBus去完成业务组件工程之间的UI跳转(UI总线)和Service服务化(ServiceBus).

LDBus的目标:(保持业务组件的相对独立性,尽量解耦业务组件之间的代码依赖)

  • 主工程只负责组装业务组件,按需所取,组合不同业务组件生成不同应用;
  • 业务组件之间的UI跳转不引用任何其他业务组件工程的头文件;
  • 业务组件之间的服务调用只提供服务的接口头文件,不提供任何服务实现文件;

LDBus如何使用

(1)Bus如何配置

每一个业务组件工程对应一个Bus配置,将Bus配置文件放在每个独立的业务组件工程中,主要是为了让最熟悉业务逻辑的开发人员去配置。

配置步骤如下:

  • 创建.Bundle资源包文件,添加一个命名为“busconfig.xml”

  • 在busconfig.xml文件配置URL导航Controller和提供的Service

      <?xml version="1.0" encoding="utf-8"?>
      <bundle name="LDBusDemo" connectorClass="LDCustomConnector" version="0.1.11" customWebContainer="LDMPopWebViewController">
          <url_handler_list>
      		<ViewController name="menu" webquery="menuID=(initWithMenu:)" class="MenuController" type="share">
          	</ViewController>
          	<ViewController name="food" class="ContentController" type="push">
      	    	<URLPattern name="foo/(initWithFood:)" webquery=""/>
          		<URLPattern name="type/(initWithType:)"/>
          		<URLPattern name="popover/(initWithFood:)" type="pop"/>
          		<URLPattern name="modeview" webquery="param=(initWithFood:)" type="modal"/>
          		<URLPattern name="about/(initWithUs:)/(others:)" parent="menu?menuID=5" type="push"/>
      		</ViewController>
      </url_handler_list>
      <service_list>
      	<service name="loginService" class="LDLoginServiceImpl"  protocol="LDLoginService"/>
      </service_list>
      </bundle>
    

配置选项说明:

  • bundle 选项说明

      name: 组件名称 
      
      version: 组件版本
      
      connectorClass: 自定义UIBus Connector的实现类名(注意需要集成LDMUIBusConnector)可选
      
      host: URL降级host,如“http://piao.163.com” 可选
      
      customWebContainer: URL降级的外部定制的WebContainer(需要遵循LDMWebContainerProtocol)可选,默认为Bus的简单Web容器
    
  • url_handler_list UI总线配置

  • ViewController UI总线配置的可导航Controller (URL)

      class: UI总线URL对应的Controller实现类名
      
      type:Controller被创建或者打开的方式,目前有share(共享controller)、push(通过Navigation Push打开)、modal(Modal或者Present打开)、pop(ipad上的popover打开) 可选 默认为push方式
      
      parent: 配置当前Controller的parentController的URL 可选
      
      name:UI总线URL的host+path选项,如:**或者**/**/**
      
      webpath: URL降级的path选项配置,如:/wap/index.html 可选 (当class未实现且webpath也未配置情况下,编译阶段提示)
      
      webquery: UI总线URL的query选项,用于初始化Controller的参数传递,如: XX=(XX:)&YY=(YY:)  可选 默认通过initWithNavigation:query: 或者initWithNibName:bundle: 或者init 方式打开
    
  • URLPattern 用于配置ViewController被多种方式打开的情况下

      tip:path选项继承ViewController的name选项,其他选项和Controller相同
    
  • service_list Service总线配置

  • service 服务总线选项配置

      name: 服务名称,一般在提供protocol中通过宏定义对外提供
      
      protocol: 服务遵守的接口协议类名
      
      class: 服务具体的实现类名,实现类必须遵守服务的接口协议 服务protocol和implemention分开
    

(2)Bus启动

在主工程和各个业务组件工程中皆可使用Bus,业务组件可以使用Bus调用其他组件提供的URL跳转配置和提供的服务; 主工程在拆分结束之前也可以使用Bus进行配置以及调用其他组件提供的bus服务(只是业务组件工程无法依赖主工程,也就无法做依赖主工程的业务调试)。

所有的Bus调用接口均封装在LDMBusContext中,使用者可以查看头文件确定具体使用方式。

tip:由于集成Bus之后,对Bus头文件的引用会比较频繁,建议将LDMBusContext.h 放到头文件预处理中。

在AppDelegate中完成Bus启动,启动时机在appdelegate的window初始化成功之后:

//bus容器初始化
[LDMBusContext initialBundleContainerWithWindow:self.window andRootViewController:nil];

tip: rootViewController可以不作为启动参数(因为在tabController设置为rootViewController前,可能需要通过URL获取各个tab),bus会在启动window设置rootViewController自动设置Bus导航器navigator的rootViewController。

(3)如何使用UIBus和服务Bus

使用UIBus的几种情形,通过[LDMBusContext XXMethod]完成调用

(1) 简单数字和字符参数可以配置到URL中传递,调用方式如下:

	+(BOOL)openURL:(NSString *)url;
	
(2) 如果还需要传递Object参数,将object参数封装到query中,controller实例化方法从中获取:
	
	+(BOOL)openURL:(NSString *)url query:(NSDictionary *)query;
	
(3) 如果需要传递更多展示Controller的参数,如parentURLPath(父Controller的URL), animated(是否展示动画), sourceRect(popOver的源view), sourceViewController(已经初始化的父亲Controller), 则可以调用TTURLAction.h 进行封装, 调用如下:

	+(BOOL)openURLWithAction:(TTURLAction *)action;

(4)如果只是想获得一个Controller实例,而不想马上展示,可以调用如下方法获得:

	+(UIViewController *)controllerForURL:(NSString *)url;
	+(UIViewController *)controllerForURL:(NSString *)url query:(NSDictionary *)query;

另外还可以判断是否能够通过URL导航:

+(BOOL)canOpenURL:(NSString *)url;

业务组件提供的服务Bus使用如下:

  • (1)引用业务组件提供的服务接口文件;(接口文件可以定义一些消息Notification的name)

      #import <LDCILogin/LDCILoginSessionService.h>
      
      #define SERVICE_LDCILOGINSESSION      @"ldci_loginsessionService"
      #define kAccountLoginNotification     @"AccountLoginNotifcation"
      #define kAccountLogoutNotification    @"AccountLogoutNotifcation"
      
      @protocol LDCILoginSessionService <NSObject>
      
      /**
       *  自动登录
       */
      - (void)autoLogin;
      
      @end
    
  • (2)通过Bus获取Service实例,调用服务接口方法即可

      [[LDMBusContext getService:SERVICE_LDCILOGINSESSION]  autoLogin];
    

UIBus的Connector扩展以及兼容JLRoute

UIBus提供了基于Connector基础上的URL跳转扩展,主要是基于以下两个原因:

  1. 之前彩票项目使用了JLRoute进行页面跳转,JLRoute导航有如下特点:

(1) 如果没有在调用之前统一register短链,则需要在使用之前先register短链,然后再通过JLRoute进行导航; (2) 每次注册需要引入短链的注册头文件; (3) 每个业务组件工程通过代码完成对外提供的短链,代码部分主要完成viewController的创建和展示;

  1. 其次在具体使用UIBus过程中,某些URL可能需要通过一个URL在不同情况下跳转到不同的Controller,或者说是想自行去完成页面动画的展示效果;

Connector的扩展需要继承Bus提供LDMUIBusConnector,然后在其基础上重写几个类方法实现。拿彩票的扩展举个例子:

  • (1)继承

      #import "LDMUIBusConnector.h"
    
      @interface LDCPHostConnector : LDMUIBusConnector {
      }
      @end
    
  • (2)重写部分方法(参看 @interface LDMUIBusConnector(ToBeOverwrite))彩票Host的connector举例:

      //初始化的时候注册通过JLRoute导航的短链注册
      -(id)init
      {
          if (self = [super init]) {
      		//注册routes
              [CTabBarController registerRoutes];
           }
      }
      
      
      #pragma mark -
      #pragma mark - UIBusConnector
    
      /**
       * host的优先处理级最低,最后处理
       */
      -(LDMConnectorPriority)connectorPriority{
          return LDMConnectorPriority_HOSTLOW;
      }
    
    
      /**
       * 判断是否能够处理URL
       */
      -(BOOL)canOpenInBundle:(NSString *)url{
          if (!url || ![url length]) {
      	    return NO;
      	}
    
          _isOpenWithJLRoute = YES;
          if([super canOpenInBundle:url]){
      		_isOpenWithJLRoute = NO;
              return YES;
          } else {
      		NSURL *URL = [NSURL URLWithString:url];
              //短链接通过JLRoute判断
      		if ([[XXX sharedApplication] isInAppUrl:URL]) {
                  return [JLRoutes canRouteURL:URL];
      		}
      
              //JLRoute也可以处理web链接
      		else {
                  NSString *scheme = URL.scheme;
      		    if ([scheme compare:@"http" options:NSCaseInsensitiveSearch] == 						NSOrderedSame || [scheme compare:@"https" 						options:NSCaseInsensitiveSearch] == NSOrderedSame) {
      	                return YES;
      	        }
      		}
      
      		return NO;
      	}
      }
    
    
    
      /**
       * 根据URLAction同时完成生成ViewController和展示ViewController的过程
       */
      -(BOOL) dealWithURLMessageFromBus:(TTURLAction *)action{
          if(_isOpenWithJLRoute){
      		NSURL *URL = [NSURL URLWithString:action.urlPath];
       		//登录特殊处理
          	if (URL.host && [URL.host caseInsensitiveCompare:@"login"] == 					NSOrderedSame) {
              	return [self processLoginUrl:URL 					withTopmostViewController:self.navigator.topViewController];
          	}
      
          	//需要登录,保存URL,登录成功后重新调用
      		else if([[XXX sharedApplication] isNeedLogin:URL]){
          		return YES;
      		}
      
          	//通过JLRoute进行导航
          	else{
      	    	if(action.sourceViewController == nil){
          	    	action.sourceViewController = self.navigator.topViewController;
          		}
          
          		if([JLRoutes routeURL:[NSURL URLWithString:action.urlPath] withParameters:@{kLDRouteViewControllerKey:action.sourceViewController}]){
              		return YES;
              	} else {
                  	return NO;
      	    	}
      		}//else
      	}
    
      	//通过UIBus的config文件配置进行导航
      	else {
      		return [super dealWithURLMessageFromBus:action];
      	}
      }
    
  • (3)在busconfig.xml文件中配置自定义的connector,只需要配置connectorClass的值即可

      <?xml version="1.0" encoding="utf-8"?>
      <bundle name="LDCPBHost" connectorClass="LDCPHostConnector">
          <url_handler_list>
          </url_handler_list>
      	<service_list>
          </service_list>
      </bundle>
    

特殊Scheme的Web处理容器配置

LDBus中目前并没有对scheme进行匹配,对特殊scheme(主要是http,https,files)的处理继承了JLRoute的方式,

  • (1)注册特殊scheme的web处理容器,可以以Modal或者Push方式打开:

      //注册特殊scheme的web处理容器
      //默认通过modal方式打开
      [LDMBusContext registerSpecialScheme:@"http" addRoutes:@"*" handleController:@"LDMPopWebViewController"];
      //push方式打开
      [LDMBusContext registerSpecialScheme:@"https" addRoutes:@"*" handleController:@"LDMPopWebViewController" isModal:NO];
      [LDMBusContext registerSpecialScheme:@"file" addRoutes:@"*" handleController:@"LDMPopWebViewController"];
    
  • (2)handleController必须遵守LDMBusWebControllerProtocol协议, 用于处理从Bus匹配过来的URL:

      @protocol LDMBusWebControllerProtocol <NSObject>
      /**
       * 处理从bus总线来的url
       */
      -(BOOL)handleURLFromUIBus:(NSURL *)url;
      @end
    

扩展配置UIBus的降级处理器(WebContainer)

目前前端和客户端的开发联动性还比较薄,不过LDBus还是提供了UIBus降级处理容器的外部扩展,只需要在前文的bundle配置选项中配置: customWebContainer,如果不扩展,就调用默认的webContainer,无法使用JSbridge联动,只有页面展示。

外部扩展容器主要是为了跟每个产品自行封装的JSBridge联动,只需要在JSBridge的容器中遵守一个实例化接口协议(LDMWebContainerProtocol)即可。

#define TTDEGRADE_URL @"_ttdegrade_url_"

/**
 * @protocol 所有定制降级打开WebContainer需要继承的接口
 */
@protocol LDMWebContainerProtocol <NSObject>

@required
/**
 * 定制webContainer初始化接口
 * 可以从query对象中通过TTDEGRADE_URL作为Key获取降级urlString
 */
-(id) initWithNavigatorURL:(NSURL *)URL query:(NSDictionary *)query;

@end

技术支持


to be continued ....

庞辉, 电商技术中心,popo:huipang@corp.netease.com