The introduction of

In a complex application system, it is difficult to define a unified abstraction for inter-module communication because of different parameters, different data returned and different ways of return. At this time, we think of the client to the server interface call, which is completely separated, and is carried out through a unified network framework, based on which we can imitate the network framework to design the inter-module communication scheme.

The communication data between modules can be roughly divided into UI interface and logical operation data. The UI interface in iOS is usually UIViewController. In the client system, page hopping through routing is a common choice, but the general routing scheme is weak in supporting logical data interaction. Therefore, the implementation scheme of this paper has reformed the general routing scheme to strengthen its general data communication capability.

Network requests are made between the client and the server. The client initiates the request, expects to get the data, is the data requester; The server accepts and processes the request as the data returnee. The same is true for communication between modules. Any module can be client and server. When a module is a data requester, it is a client; when it is a data returnee, it is a server.

implementation

The main structure

When the application starts, the application reads the URL and handler name from the configuration file, registers the RouterHandlerProtocol through the RouterCenter, and stores it in memory. During the program running, the routing requester initiates a request for RouterRequestProtocol, and the RouterCenter finds the matching RouterHandlerProtocol and runs a process to return the RouterResponseProtocol to the routing requester.

Main class resolution

The main classes that this implementation contains are

  • RouterResponseProtocol: Interface for communicating data between modules
  • RouterRequestProtocol: Indicates the route request interface
  • RouterHandlerProtocol: Route handler interface
  • RouterCenter: Registers The RouterHandlerProtocol, stores routing nodes, queries matching routing nodes, and receives routing requests.
  • RouterHandlerLoader: Reads urls and handlers from configuration files and registers them with The RouterCenter.

RouterResponseProtocol

All returned data must comply with this protocol, including an arbitrary type of returned data, usually dictionary type: [String:Any]. For UI page type, data is UIViewController, which is implemented as follows:

protocol RouterResponseProtocol { var data:Any? ; Var error: error? ; / / error}Copy the code

RouterRequestProtocol

Request protocol, which contains a URL corresponding to a RouterHandlerProtocol; Parameters are nil. ResponseClosure is the callback returned for data.

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

RouterHandlerProtocol

  • Generic route handler protocol
public protocol RouterHandlerProtocol { func handleRouter(url:String, parameters:[String:Any]? , completion:((RouterResponseProtocol) -> Void)?) }Copy the code
  • Static route handler protocol. Sometimes the route handler may not want to instantiate, but just need a static method. Define a protocol with static methods as follows
public protocol RouterStaticHandlerProtocol { static func handleRouter(url:String, parameters:[String:Any]? , completion:((RouterResponseProtocol) -> Void)?) }Copy the code
  • UI route handler protocol. UI page class data can be implemented using the above two protocols, passing UIViewController as data in RouterResponseProtocol. But to define a special agreement RouterViewControllerProtocol should be a better choice, when UIViewController initialization need additional parameters can implement the agreement, of course also can not achieve, it can call the base class init () method.
public protocol RouterViewControllerProtocol { init? (url:String, parameters:[String:Any]?) ; }Copy the code

RouterCenter

The routing processing center mainly contains the functions of storing routing nodes, registering routing handlers, and processing routing requests. # # # # # storage routing node storing rules according to the scheme – host – path3 level storage, support the path parameter setting, with a colon: for identification, such as http://anyrouter/some/path/:id, then the path contains a parameter name is id key values. Key stores the value of scheme, host, or Path. HandlerObject, handlerClass, viewControllerClass is the route handler. Children is the Node of the next level. Scheme contains a number of hosts, host contains a number of paths, and only the last level of path holds a handler reference, as follows

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

Register a route handler

Three handlers agreement can register, register RouterHandlerProtocol instantiation processing, static handler RouterStaticHandlerProtocol, page responder 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 } }Copy the code
Processing routing requests
extension RouterCenter { func matchedNode(url:String) -> (node:Node, pathArgs:[String:String]?) ? {/ / return query node (the 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) } } } } }Copy the code

The matchedNode(URL :String) method returns a node node, pathArgs represents the parameter key-value pairs that may exist in path. The routing system is not responsible for opening the UI page, it’s just responsible for building the UIViewController, whether it’s push or present, up to the requestor.

RouterHandlerLoader

Modules can register with RouterCenter manually, but this method is inefficient, especially for a large number of UIViewControllers. Modules can configure plist files to register route handlers as follows:

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

The plist file format is as follows:

podspec

It consists of two sub-pods, and can only use AnyRouter/Core part during development. Loader only contains 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
Copy the code

For example,

In a social application similar to wechat, the wechat friend details page requires access to the first few pictures in the circle of friends. The friend details page is the initiator of the request and the circle of friends is the handler of the request.

  1. When the circle of friends module is initialized, register the route handler:
struct FriendPhotos : RouterHandlerProtocol { func handleRouter(url:String, parameters:[String:Any]? , completion:((RouterResponseProtocol) -> Void)?) if let userId = parameters? [" user_id "] the as? Let response = RouteResponse(data:[photos]); let response = RouteResponse(data:[photos]); completion? (response); }}Copy the code

Register with the routing center

RouterCenter. Register (url: "http://friend_timeline/photos/:user_id", handlerObject: friendPhotoInstance);Copy the code
  1. Wechat friend details page, initiate a request
Let Request = SimpleRouterRequest(URL: "http://friend_timeline/photos/1234abc") request.responseClosure = {response in if let photos = response.data as? [photoUrl] {/ / get the image url and displays a}} RouterCenter. StartRequest (request);Copy the code

Write in the last

The system SDK is a good reference for us, in which the type structure division, naming methods are worth our reference.

Git homepage