Skip to content

Quick start

Rakuyo edited this page Apr 26, 2020 · 8 revisions

The following content will tell you how to use the library, but it basically does not involve "Why do you do this" and "What does this type mean"

See other wikis for more details. For complete code examples, please refer to the demo provided with the warehouse (under the Examples directory).

Protocol-oriented

RaRouter is developed based on protocol-oriented principles. Before the official start, I hope this picture will give you a preliminary understanding of the organizational relationship between RaRouter protocols:

protocol diagram

Execute route

RaRouter provides the following three routing operations by default:

  • do: execute certain operations:
let router = "rakuyo://moduleA/do/something"

_ = Router<Global>.do(router, param: ("parameter", 1))
  • get: execute some operation and get its return value:
let router = "rakuyo://moduleA/calculate/frame"

let result = Router<Global>.get(of: String.self, from: router, param: "parameter")

switch result {
case .success(let string):
    print(string)
    
case .failure(let error):
    print(error)
}

// Or use default values
let string = Router<Global>.get(of: String.self, from: router, param: "parameter").get(default: "defaultString")

// When an error occurs, "defaultString" will be printed
print(string)
  • viewController: execute certain operations and get the returned UIViewController subclass:
let router = "rakuyo://moduleA/create"

let result = Router<Global>.viewController(from: router)

switch result {
case .success(let controller):
    print(controller) // `UIViewController`
    
case .failure(let error):
    print(error)
}

Some explanations:

  1. The Router type is a default type provided by RaRouter that complies with the RaRouter protocol and is used to execute and register routes. Please refer to its statement: Router.

  2. For the contents of the RaRouter protocol, please refer to the wiki or the demo provided with the warehouse.

  3. The Router type requires a generic type, which represents the module to which the route belongs. The Global generic type stands for "global module". Related concepts and content will be introduced in detail in Advanced Tutorial.

  4. For the param parameter, it is of type Any?, And the default is nil. This means that you can pass any parameters you want without any restrictions.

    For example, in the do example above, a tuple of type (String, Int) was passed as a parameter, while in the get example only a String was passed. In viewController, because "rakuyo://moduleA/create" does not require any parameters (if the code is written), so you can omit the param parameter directly.

  5. The viewController operation is not like the get operation, and the type parameter is added to the method to convert the type.

    Because the definition of Controller is often in the core component, and RoRouter requires the routing component to be independent and not dependent on other components as much as possible. So under normal circumstances, when calling the viewController method, the user cannot get the real type of ViewController.

    When performing the get operation, because we can define the model class in the routing component, we designed the type parameter and provided related errors when the conversion failed.

Encapsulation

We can also optimize the above code, for example, encapsulate router:

public enum ModuleA: ModuleRouter {
    
    public typealias Table = RouterTable
    
    public enum RouterTable: String, RouterTableProtocol {
        
        public var url: String { rawValue }
        
        case create         = "rakuyo://moduleA/create"
        case doSomething    = "rakuyo://moduleA/do/something"
        case calculateFrame = "rakuyo://moduleA/calculate/frame" 
    }
}

The above code defines a module ModuleA, which encapsulates the "function" it contains.

Then you can also encapsulate the "operation":

public extension Router where Module == ModuleA {
    
    static func doSomething(start: Date, end: Date) -> DoResult {
        return Router.do(.doSomething, param: (start, end))
    }

    static func calculateFrame(with screenWidth: CGFloat) -> GetResult<CGRect> {
        return Router.get(of: CGRect.self, from: .calculateFrame, param: screenWidth)
    }
    
    static func create() -> ViewControllerResult {
        return Router.viewController(from: .create)
    }
}

Now, when we execute the test code in the Execute route section, we can write:

// for `do`
_ = Router<ModuleA>.doSomething(start: Date(), end: Date())

// for `get`
if case let .success(frame) = Router<ModuleA>.calculateFrame(with: 375) { }

// for `viewController`
if case let .success(controller) = Router<ModuleA>.create() { }

Register

Finally, we need to register the route we just defined.

For the three operations provided by default, each operation has a different registration method. The difference between them lies in the return value of the closure:

  • do

The return value of its closure is specified as DoResult ( Result<Void, RouterError>):

Router<Global>.register(for: "your router") { (url, value) -> DoResult in
    // do something
    return .success(())
}
  • get

The return value of its closure is specified as GetResult<AnyResult> (Result<Any?, RouterError>):

Router<Global>.register(for: "your router") { (url, value) -> GetResult<AnyResult> in
    return .success(.zero)
}
  • viewController

The return value of its closure is specified as ViewControllerResult ( Result<UIViewController, RouterError>):

Router<Global>.register(for: "your router") { (url, value) -> ViewControllerResult in
    return .success(UIViewController()) // your controller
}

For the example in the Encapsulation section, we should write the registration code corresponding to ModuleA as follows:

// Following the `RouterRegister` protocol is the key
private class ModuleARegister: RouterRegister {
    
    static func register() {
        
        let router = Router<ModuleA>.self
        
        router.register(for: .doSomething) { (url, value) -> DoResult in
            
            guard let param = value as? (start: Date, end: Date) else {
                return .failure(.parameterError(url: url, parameter: value))
            }
            
            print("We are doing these things from \(param.start) to \(param.end)")
            return .success(())
        }
        
        router.register(for: .calculateFrame) { (url, value) -> GetResult<AnyResult> in
            
            guard let screenWidth = value as? CGFloat else {
                return .failure(.parameterError(url: url, parameter: value))
            }
            
            return .success(CGRect(x: 0, y: 0, width: screenWidth * 0.25, height: screenWidth))
        }
        
        router.register(for: .create) { (url, value) -> ViewControllerResult in
            return .success(UIViewController())
        }
    }
}

At the end, the registration code is executed in the application (_ :, didFinishLaunchingWithOptions:) method of AppDelegate.swift:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    // some codes with higher priority than registered routes

    // initialize modules
    Router<Modules>.initialize()

    // some other code ..
}

中文


English

Clone this wiki locally