基于路由的iOS模块间通信方案

1,942 阅读5分钟

引入

    在一个业务较为复杂的应用系统中,对于模块间通信,传递的参数不同,返回的数据也不同,返回的方式也有可能不同,定义一个统一的抽象似乎比较困难。这时我们想到客户端对服务器接口的调用,这个是完全分离的,而且是通过一套统一的网络框架进行的,基于此我们可以仿照网络框架来设计这个模块间通信方案。

    对于模块间的通信数据大致可以分为UI界面和逻辑运算数据。在iOS中UI界面通常为UIViewController。在客户端系统中,通过路由进行页面跳转是常见的选择,但一般的路由方案对于逻辑数据交互的支持能力较弱,所以本文的实现方案对一般的路由方案进行了改造,以加强其通用数据的通信能力。

    网络请求在客户端和服务器之间进行。客户端发起请求,期望得到数据,为数据请求者;服务器接受并处理请求,为数据返回者。模块间通信也是如此,任何一个模块都可以是客户端和服务器,当一个模块作为请求数据者的时候,它就是客户端,当它作为数据返回者时,它就是服务器。

实现

主体结构

结构.png

一般在应用启动的时候,程序从配置文件读取url和处理者名称,然后通过RouterCenter注册RouterHandlerProtocol,并存储在内存中。程序运行过程中,路由请求者发起请求RouterRequestProtocol,RouterCenter查找到匹配的RouterHandlerProtocol,并运行处理,将RouterResponseProtocol返回给路由请求者。

主要类解析

该实现包含的主要类有

  • RouterResponseProtocol:模块之间通信数据的接口
  • RouterRequestProtocol:路由请求接口
  • RouterHandlerProtocol:路由处理者接口
  • RouterCenter: 负责注册RouterHandlerProtocol,存储路由节点,查询匹配路由节点,并接受路由请求。
  • RouterHandlerLoader:负责从配置文件读取url和handler,并注册到RouterCenter中.

RouterResponseProtocol

所有返回的数据都要遵从该协议,包含一个任意类型的返回数据,一般为字典类型:[String:Any],对于UI页面类型,data为UIViewController,实现如下:

protocol RouterResponseProtocol {
	var data:Any?; //返回的有效数据
	var error:Error?; //错误
}

RouterRequestProtocol

请求协议,包含一个url,对应一个RouterHandlerProtocol;parameters为参数,可为nil;responseClosure为数据返回的回调。

protocol RouterRequestProtocol {
	var url:String;
	var parameters:[String:Any]?
	var responseClosure:((RouterResponseProtocol) -> Void)?
}

RouterHandlerProtocol

  • 通用的路由处理者协议
public protocol RouterHandlerProtocol {
    func handleRouter(url:String, parameters:[String:Any]?, completion:((RouterResponseProtocol) -> Void)?)
}
  • 静态路由处理者协议,有的时候路由处理者可能不想实例化,只需要一个静态方法,如下定义一个拥有静态方法的协议
public protocol RouterStaticHandlerProtocol {
    	static func handleRouter(url:String, parameters:[String:Any]?, completion:((RouterResponseProtocol) -> Void)?)
}
  • UI路由处理者协议,对于UI页面类的数据,可以通过以上2个协议实现,将UIViewController作为RouterResponseProtocol中的data传递。但是定义一个特殊的协议RouterViewControllerProtocol应该是一个更好的选择,当UIViewController初始化需要额外的参数时可以实现这个协议,当然也可以不实现,那就只能调用基类的init()方法了。
public protocol RouterViewControllerProtocol {
    	init?(url:String, parameters:[String:Any]?);
}

RouterCenter

路由处理中心,主要包含存储路由节点,注册路由处理者,处理路由请求等功能。 #####存储路由节点 存储规则按照 scheme-host-path3个级别存储,path支持参数设置,以冒号:为标识,如http://anyrouter/some/path/:id,则该path包含一个参数名是id的键值。 存储结构为,如下,key存储scheme、host或path的值,handlerObject, handlerClass, viewControllerClass为路由处理者,3者有其一即可,children为下一级的Node,scheme包含若干host,host包含若干path,只有最后一级的Path,才持有处理者的引用,如下

class Node { 
		var key:String 
		var handlerObject:RouterHandlerProtocol? 
		var handlerClass:RouterStaticHandlerProtocol.Type? 
		var viewControllerClass:UIViewController.Type? 
		var children:[Node]? 
		……
	}

url节点存储.png

注册路由处理者

可以注册3种处理者协议,注册实例化处理者RouterHandlerProtocol,静态处理者RouterStaticHandlerProtocol,页面响应者UIViewController。

class RouterCenter {
    func register(url:String) -> Node {
	//查询节点……
	return node;
    }
    public func register(url: String, handlerObject: RouterHandlerProtocol) { 
	let node = register(url: url) 
	node.handlerObject = handlerObject; 
    } 
    public func register(url: String, handlerClass: RouterStaticHandlerProtocol.Type) { 
	let node = register(url: url) 
	node.handlerClass = handlerClass 
    } 
    public func register(url:String, viewController:UIViewController.Type) { 
        let node = register(url: url) 
        node.viewControllerClass = viewController 
    }
 }
处理路由请求
extension RouterCenter {
    func matchedNode(url:String) -> (node:Node, pathArgs:[String:String]?)? {
        //查询节点
        return (node, args)
    }
    func startRequest(_ request:RouterRequestProtocol) {
        if let (node, args) = matchedNode(url:request.url) {
           if node.handlerObject != nil {
              node.handleObject!.handleRouter(...)
           }else if node.handlerClass != nil {
              node.handleClass!.handleRouter(...)
           }else if node.viewControllerClass != nil {
              var vcObject:UIViewController?
              if node.viewControllerClass! as? RouterViewControllerProtocol {
                 vcObject = node.ViewControllerClass.init(url:parameters:)
              }  
              if vcObject == nil {
                vcObject = node.ViewControllerClass()
              }
              if vcObject != nil {
                 let response = RouterResponse(data: vcObject!)
                 request.responseClosure?(response)
              }
         }
       }    
    }
}

其中matchedNode(url:String)方法,返回一个节点node, pathArgs表示path中可能存在的参数键值对。其中对UI页面的处理,路由系统并不负责打开页面,只是负责将UIViewController构建出来,具体是使用push还是present,由发起请求者决定。

RouterHandlerLoader

各个模块,可以通过手动添加的方式注册到RouterCenter,但是这种方式比较低效,尤其是数量众多的UIViewController,各个模块可以通过配置plist文件来注册路由处理者,如下:

public struct RouterHandlerLoader { 
	public static func loadFile(_ fileUrl:URL, from bundle:Bundle) {
	}
}

plist文件格式如下: router_plist.png

podspec

由2个子pod组成,开发时候可以只使用AnyRouter/Core部分,Loader只包含RouterHandlerLoader

s.subspec 'Core' do |ss|
    ss.source_files = 'AnyRouter/Classes/Core/**/*.swift'
  end
  s.subspec 'Loader' do |ss|
    ss.source_files = 'AnyRouter/Classes/Loader/**/*.swift'
    ss.dependency 'AnyRouter/Core'
  end

举例

在一个类似微信的社交应用中,微信好友详情页中需要访问朋友圈中前几张图片。好友详情页作为请求发起者,朋友圈作为请求处理者。

  1. 朋友圈模块初始化的时候,注册路由处理者:
struct FriendPhotos : RouterHandlerProtocol {
	func handleRouter(url:String, parameters:[String:Any]?, completion:((RouterResponseProtocol) -> Void)?)
	if let userId = parameters?[“user_id”] as? String {
		//查询到图片数据
		let response = RouteResponse(data:[photos]);
		completion?(response);
	}
}

注册到路由中心

RouterCenter.register(url:”http://friend_timeline/photos/:user_id”, handlerObject:friendPhotoInstance);
  1. 微信好友详情页面,发起请求
let request = SimpleRouterRequest(url:”http://friend_timeline/photos/1234abc”)
request.responseClosure = { response in
	if let photos = response.data as? [photoUrl] {
		//得到照片url并显示
	}
}
RouterCenter.startRequest(request);

写在最后

系统SDK是我们一个很好的参照,其中的类型结构划分,命名方法都值得我们借鉴。

git主页