引入
在一个业务较为复杂的应用系统中,对于模块间通信,传递的参数不同,返回的数据也不同,返回的方式也有可能不同,定义一个统一的抽象似乎比较困难。这时我们想到客户端对服务器接口的调用,这个是完全分离的,而且是通过一套统一的网络框架进行的,基于此我们可以仿照网络框架来设计这个模块间通信方案。
对于模块间的通信数据大致可以分为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.init()
}
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
举例
在一个类似微信的社交应用中,微信好友详情页中需要访问朋友圈中前几张图片。好友详情页作为请求发起者,朋友圈作为请求处理者。
- 朋友圈模块初始化的时候,注册路由处理者:
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);
- 微信好友详情页面,发起请求
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主页
网友评论