Interface-oriented router for discovering modules, and injecting dependencies with protocol in Objective-C and Swift.

Overview

ZIKRouter

ZIKRouter Carthage compatible license

An interface-oriented router for managing modules and injecting dependencies with protocol.

The view router can perform all navigation types in UIKit / AppKit through one method.

The service router can discover and prepare corresponding module with its protocol.


一个用于模块间解耦和通信,基于接口进行模块管理和依赖注入的组件化路由工具。用多种方式最大程度地发挥编译检查的功能。

通过 protocol 寻找对应的模块,并用 protocol 进行依赖注入和模块通信。

Service Router 可以管理任意自定义模块。View Router 进一步封装了界面跳转。

中文文档


Features

  • Swift and Objective-C support
  • iOS, macOS and tvOS support
  • File template for quickly creating router
  • Routing for UIViewController / NSViewController, UIView / NSView and any class
  • Dependency injection, including dynamic injection and static injection
  • Declaration of routable protocol for compile-time checking. Using undeclared protocol will bring compiler error. This is one of the most powerful feature
  • Module matching with its protocol
  • URL routing support
  • Configure the module with its protocol rather than a parameter dictionary
  • Required protocol and provided protocol for making thorough decouple
  • Adapter for decoupling modules and add compatible interfaces
  • Storyboard support. Views from a segue can be auto prepared
  • Encapsulation for all transition methods and unwind methods in UIKit / AppKit, and also custom transition
  • Error checking for view transition
  • AOP for view transition
  • Memory leak detection
  • Custom events handling
  • Auto registration
  • Highly scalable

Quick Start Guide

  1. Create Router
    1. Router Subclass
    2. Simple Router
  2. Declare Routable Type
  3. View Router
    1. Transition directly
    2. Prepare before Transition
    3. Make Destination
    4. Required Parameter and Special Parameter
    5. Perform on Destination
    6. Prepare on Destination
    7. Remove
    8. Adapter
    9. Modularization
    10. URL Router
    11. Other Features
  4. Service Router
  5. Demo and Practice
  6. File Template

Documentation

Design Idea

Design Idea

Basics

  1. Router Implementation
  2. Module Registration
  3. Routable Declaration
  4. Type Checking
  5. Perform Route
  6. Remove Route
  7. Transfer Parameters with Custom Configuration

Advanced Features

  1. Error Handle
  2. Storyboard and Auto Create
  3. AOP
  4. Dependency Injection
  5. Circular Dependency
  6. Module Adapter
  7. Unit Test

FAQ

Requirements

  • iOS 7.0+
  • Swift 3.2+
  • Xcode 9.0+

Installation

Cocoapods

Add this to your Podfile.

For Objective-C project:

pod 'ZIKRouter', '>= 1.1.1'

# or only use ServiceRouter
pod 'ZIKRouter/ServiceRouter' , '>=1.1.1'

For Swift project:

pod 'ZRouter', '>= 1.1.1'

# or only use ServiceRouter
pod 'ZRouter/ServiceRouter' , '>=1.1.1'

Carthage

Add this to your Cartfile:

= 1.1.1 ">
github "Zuikyo/ZIKRouter" >= 1.1.1

Build frameworks:

carthage update

Build DEBUG version to enable route checking:

carthage update --configuration Debug

Remember to use release version in production environment.

For Objective-C project, use ZIKRouter.framework. For Swift project, use ZRouter.framework.

Getting Started

This is the demo view controller and protocol:

///Editor view's interface
protocol EditorViewInput: class {
    weak var delegate: EditorDelegate? { get set }
    func constructForCreatingNewNote()
}

///Editor view controller
class NoteEditorViewController: UIViewController, EditorViewInput {
    ...
}
Objective-C Sample
///editor view's interface
@protocol EditorViewInput 

    @property (
    nonatomic, 
    weak) 
    id
    
      delegate;
- (
     void)
     constructForCreatingNewNote;

     @end
    
   
///Editor view controller
@interface NoteEditorViewController: UIViewController 

    @end

    @implementation 
    NoteEditorViewController

    @end
   

There're 2 steps to create route for your module.

1. Create Router

To make your class become modular, you need to create router for your module. You don't need to modify the module's code. That will reduce the cost for refactoring existing modules.

1.1 Router Subclass

Create router subclass for your module:

import ZIKRouter.Internal
import ZRouter

class NoteEditorViewRouter: ZIKViewRouter
    {
    override class func registerRoutableDestination() {
        // Register class with this router. A router can register multi views, and a view can be registered with multi routers
        registerView(NoteEditorViewController.self)
        // Register protocol. Then we can fetch this router with the protocol
        register(RoutableView<EditorViewInput>())
    }
    
    // Return the destination module
    override func destination(with configuration: ViewRouteConfig) -> NoteEditorViewController? {
        // In configuration, you can get parameters from the caller for creating the instance
        let destination: NoteEditorViewController? = ... /// instantiate your view controller
        return destination
    }
    
    override func prepareDestination(_ destination: NoteEditorViewController, configuration: ViewRouteConfig) {
        // Inject dependencies to destination
    }
}
Objective-C Sample
//NoteEditorViewRouter.h
@import ZIKRouter;

@interface NoteEditorViewRouter : ZIKViewRouter
@end

//NoteEditorViewRouter.m
@import ZIKRouter.Internal;

@implementation NoteEditorViewRouter

+ (void)registerRoutableDestination {
    // Register class with this router. A router can register multi views, and a view can be registered with multi routers
    [self registerView:[NoteEditorViewController class]];
    // Register protocol. Then we can fetch this router with the protocol
    [self registerViewProtocol:ZIKRoutable(EditorViewInput)];
}

// Return the destination module
- (NoteEditorViewController *)destinationWithConfiguration:(ZIKViewRouteConfiguration *)configuration {
    // In configuration, you can get parameters from the caller for creating the instance 
    NoteEditorViewController *destination = ... // instantiate your view controller
    return destination;
}

- (void)prepareDestination:(NoteEditorViewController *)destination configuration:(ZIKViewRouteConfiguration *)configuration {
    // Inject dependencies to destination
}

@end

Each router can control their own routing, such as using different custom transition. And the router can be very easy to add additional features.

Read the documentation for more details and more methods to override.

1.2 Simple Router

If your module is very simple and don't need a router subclass, you can just register the class in a simpler way:

ZIKAnyViewRouter.register(RoutableView<EditorViewInput>(), forMakingView: NoteEditorViewController.self)
Objective-C Sample
[ZIKViewRouter registerViewProtocol:ZIKRoutable(EditorViewInput) forMakingView:[NoteEditorViewController class]];

or with custom creating block:

ZIKAnyViewRouter.register(RoutableView<EditorViewInput>(), 
                 forMakingView: NoteEditorViewController.self) { (config, router) -> EditorViewInput? in
                     let destination: NoteEditorViewController? = ... // instantiate your view controller
                     return destination;
        }
Objective-C Sample
[ZIKViewRouter
    registerViewProtocol:ZIKRoutable(EditorViewInput)
    forMakingView:[NoteEditorViewController class]
    making:^id _Nullable(ZIKViewRouteConfiguration *config, ZIKViewRouter *router) {
        NoteEditorViewController *destination = ... // instantiate your view controller
        return destination;
 }];

or with custom factory function:

function makeEditorViewController(config: ViewRouteConfig) -> EditorViewInput? {
    let destination: NoteEditorViewController? = ... // instantiate your view controller
    return destination;
}

ZIKAnyViewRouter.register(RoutableView<EditorViewInput>(), 
                 forMakingView: NoteEditorViewController.self, making: makeEditorViewController)
Objective-C Sample
id 
    makeEditorViewController(ZIKViewRouteConfiguration *config) {
    NoteEditorViewController *destination = ... 
    // instantiate your view controller
    
    return destination;
}

[ZIKViewRouter
    
    registerViewProtocol:
    ZIKRoutable(EditorViewInput)
    
    forMakingView:[NoteEditorViewController 
    class]
    
    factory:makeEditorViewController];
   

2. Declare Routable Type

The declaration is for checking routes at compile time, and supporting storyboard.

// Declare NoteEditorViewController is routable
// This means there is a router for NoteEditorViewController
extension NoteEditorViewController: ZIKRoutableView {
}

// Declare EditorViewInput is routable
// This means you can use EditorViewInput to fetch router
extension RoutableView where Protocol == EditorViewInput {
    init() { self.init(declaredProtocol: Protocol.self) }
}
Objective-C Sample
// Declare NoteEditorViewController is routable
// This means there is a router for NoteEditorViewController
DeclareRoutableView(NoteEditorViewController, NoteEditorViewRouter)

// If the protocol inherits from ZIKViewRoutable, it's routable
// This means you can use EditorViewInput to fetch router
@protocol EditorViewInput 
    
@property (nonatomic, weak) id
    
      delegate;
- (
     void)constructForCreatingNewNote;
@end
    
   

If you use an undeclared protocol for routing, there will be compile time error. So it's much safer and easier to manage protocols and to know which protocols are routable.

Unroutable error in Swift:

Unroutable-error-Swift

Unroutable error in Objective-C:

Unroutable-error-OC

Now you can get and show NoteEditorViewController with router.

View Router

Transition directly

Transition to editor view directly:

class TestViewController: UIViewController {

    // Transition to editor view directly
    func showEditorDirectly() {
        Router.perform(to: RoutableView<EditorViewInput>(), path: .push(from: self))
    }
}
Objective-C Sample
@implementation TestViewController

- (void)showEditorDirectly {
    // Transition to editor view directly
    [ZIKRouterToView(EditorViewInput) performPath:ZIKViewRoutePath.pushFrom(self)];
}

@end

You can change transition type with ViewRoutePath:

enum ViewRoutePath {
    case push(from: UIViewController)
    case presentModally(from: UIViewController)
    case presentAsPopover(from: UIViewController, configure: ZIKViewRoutePopoverConfigure)
    case performSegue(from: UIViewController, identifier: String, sender: Any?)
    case show(from: UIViewController)
    case showDetail(from: UIViewController)
    case addAsChildViewController(from: UIViewController, addingChildViewHandler: (UIViewController, @escaping () -> Void) -> Void)
    case addAsSubview(from: UIView)
    case custom(from: ZIKViewRouteSource?)
    case makeDestination
    case extensible(path: ZIKViewRoutePath)
}

Encapsulating view transition can hide the UIKit detail, then you can perform route outside the view layer (presenter, view model, interactor, service) and be cross-platform.

Prepare before Transition

Prepare it before transition to editor view:

class TestViewController: UIViewController {

    // Transition to editor view, and prepare the destination with EditorViewInput
    func showEditor() {
        Router.perform(
            to: RoutableView<EditorViewInput>(),
            path: .push(from: self),
            configuring: { (config, _) in
                // Route config
                // Prepare the destination before transition
                config.prepareDestination = { [weak self] destination in
                    //destination is inferred as EditorViewInput
                    destination.delegate = self
                    destination.constructForCreatingNewNote()
                }
                config.successHandler = { destination in
                    // Transition succeed
                }
                config.errorHandler = { (action, error) in
                    // Transition failed
                }                
        })
    }
}
Objective-C Sample
@implementation TestViewController

- (void)showEditor {
    // Transition to editor view, and prepare the destination with EditorViewInput
    [ZIKRouterToView(EditorViewInput)
	     performPath:ZIKViewRoutePath.pushFrom(self)
	     configuring:^(ZIKViewRouteConfig *config) {
	         // Route config
	         // Prepare the destination before transition
	         config.prepareDestination = ^(id
     destination) {
	             destination.
    delegate = self;
	             [destination 
    constructForCreatingNewNote];
	         };
	         config.
    successHandler = ^(
    id
    
      destination) {
	             
     // Transition is completed
	         };
	         config.
     errorHandler = ^(ZIKRouteAction routeAction, 
     NSError * error) {
	             
     // Transition failed
	         };
	     }];
}


     @end
    
   

For more detail, read Perform Route.

Make Destination

If you don't want to show a view, but only need to get instance of the module, you can use makeDestination:

// destination is inferred as EditorViewInput
let destination = Router.makeDestination(to: RoutableView<EditorViewInput>())
Objective-C Sample
id
     destination = [
    ZIKRouterToView(EditorViewInput) 
    makeDestination];
   

Required Parameter and Special Parameter

Some parameters can't be delivered though destination's protocol:

  • the destination class uses custom initializers to create instance, router needs to get required parameter from the caller

  • the module contains multi components, and you need to pass parameters to those components. Those parameters do not belong to the destination, so they should not exist in destination's protocol

You can use module config protocol and a custom configuration to transfer parameters.

Instead of EditorViewInput, we use another routable protocol EditorViewModuleInput as config protocol for routing:

// In general, a module config protocol only contains `makeDestinationWith`, for declaring parameters and destination type. You can also add other properties or methods
protocol EditorViewModuleInput: class {
    // Factory method for transferring parameters and making destination
    var makeDestinationWith: (_ note: Note) -> EditorViewInput? { get }
}
Objective-C Sample
// In general, a module config protocol only contains `makeDestinationWith`, for declaring parameters and destination type. You can also add other properties or methods
@protocol EditorViewModuleInput 
 
    // Factory method for transferring parameters and making destination

    @property (
    nonatomic, 
    copy, 
    readonly) 
    id
     
     _Nullable(^makeDestinationWith)(Note *note);

     @end
    
   

This configuration works like a factory for the destination with EditorViewModuleInput protocol. It declares parameters for creating the destination.

Now the user can use the module with its module config protocol and transfer parameters:

var note = ...
Router.makeDestination(to: RoutableViewModule<EditorViewModuleInput>()) { (config) in
     // Transfer parameters and get EditorViewInput
     let destination = config.makeDestinationWith(note)
}
Objective-C Sample
Note *note = ...
[ZIKRouterToViewModule(EditorViewModuleInput)
    performPath:ZIKViewRoutePath.showFrom(self)
    configuring:^(ZIKViewRouteConfiguration
     *config) {
        
    // Transfer parameters and get EditorViewInput
        
    id
    
      destination = config.
     makeDestinationWith(note);
 }];
    
   

For more detail, read Transfer Parameters with Custom Configuration.

Perform on Destination

If you get a destination from other place, you can perform on the destination with its router.

For example, an UIViewController supports 3D touch, and implments UIViewControllerPreviewingDelegate:

class SourceViewController: UIViewController, UIViewControllerPreviewingDelegate {
    func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
        // Return the destination UIViewController to let system preview it
        let destination = Router.makeDestination(to: RoutableView<EditorViewInput>())
        return destination
    }
    
    func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
        guard let destination = viewControllerToCommit as? EditorViewInput else {
            return
        }
        // Show the destination
        Router.to(RoutableView<EditorViewInput>())?.perform(onDestination: destination, path: .presentModally(from: self))
}
Objective-C Sample
@implementation SourceViewController

- (nullable UIViewController *)previewingContext:(id 
    )
    previewingContext 
    viewControllerForLocation
    :(
    CGPoint)
    location {
    
    //Return the destination UIViewController to let system preview it
    UIViewController
    
      *destination = [
     ZIKRouterToView(EditorViewInput) 
     makeDestination];
    
     return destination;
}

- (
     void)
     previewingContext
     :(
     id 
     
      )
      previewingContext 
      commitViewController
      :(UIViewController *)
      viewControllerToCommit {
    
      // Show the destination
    UIViewController
      
        *destination;
    
       if ([viewControllerToCommit 
       conformsToProtocol:
       @protocol(EditorViewInput)]) {
        destination = viewControllerToCommit;
    } 
       else {
        
       return;
    }
    [
       ZIKRouterToView(EditorViewInput) 
       performOnDestination:destination 
       path:ZIKViewRoutePath.
       presentModallyFrom(
       self)];
}


       @end
      
     
    
   

Prepare on Destination

If you don't want to show the destination, but just want to prepare an existing destination, you can prepare the destination with its router.

If the router injects dependencies inside it, this can properly setting the destination instance.

var destination: DestinationViewInput = ...
Router.to(RoutableView<EditorViewInput>())?.prepare(destination: destination, configuring: { (config, _) in
            config.prepareDestination = { destination in
                // Prepare
            }
        })
Objective-C Sample
UIViewController
     *destination = ...
[
    ZIKRouterToView(EditorViewInput) 
    prepareDestination:destination 
    configuring:^(ZIKViewRouteConfiguration *config) {
            config.
    prepareDestination = ^(
    id
    
      destination) {
                
     // Prepare
            };
        }];
    
   

Remove

You can remove the view by removeRoute, without using pop / dismiss / removeFromParentViewController / removeFromSuperview:

class TestViewController: UIViewController {
    var router: DestinationViewRouter
   ?
    
    
   func 
   showEditor() {
        
   // Hold the router

           router 
   = Router.
   perform(
   to: RoutableView
   <EditorViewInput
   >(), 
   path: .
   push(
   from: 
   self))
    }
    
    
   // Router will pop the editor view controller

       
   func 
   removeEditorDirectly() {
        
   guard 
   let router 
   = router, router.
   canRemove 
   else {
            
   return
        }
        router.
   removeRoute()
        router 
   = 
   nil
    }
    
    
   func 
   removeEditorWithResult() {
        
   guard 
   let router 
   = router, router.
   canRemove 
   else {
            
   return
        }
        router.
   removeRoute(
   successHandler: {
            
   print(
   "remove success")
        }, 
   errorHandler: { (action, error) 
   in
            
   print(
   "remove failed, error: \(error)")
        })
        router 
   = 
   nil
    }
    
    
   func 
   removeEditorAndPrepare() {
        
   guard 
   let router 
   = router, router.
   canRemove 
   else {
            
   return
        }
        router.
   removeRoute(
   configuring: { (config) 
   in
            config.
   animated 
   = 
   true
            config.
   prepareDestination 
   = { destination 
   in
                
   // Use destination before remove it

               }
        })
        router 
   = 
   nil
    }
}
  
Objective-C Sample
*destination) { // Use destination before remove it }; }]; self.router = nil; } @end ">
@interface TestViewController()
@property (nonatomic, strong) ZIKDestinationViewRouter(id
    ) *router;

    @end

    @implementation 
    TestViewController

- (
    void)
    showEditorDirectly {
    
    // Hold the router
    self.
    router = [
    ZIKRouterToView(EditorViewInput) 
    performPath:ZIKViewRoutePath.
    pushFrom(
    self)];
}


    // Router will pop the editor view controller
- (
    void)
    removeEditorDirectly {
    
    if (![
    self.router 
    canRemove]) {
        
    return;
    }
    [
    self.router 
    removeRoute];
    self.
    router = 
    nil;
}

- (
    void)
    removeEditorWithResult {
    
    if (![
    self.router 
    canRemove]) {
        
    return;
    }
    [
    self.router 
    removeRouteWithSuccessHandler:^{
        
    NSLog(
    @"pop success");
    } 
    errorHandler:^(ZIKRouteAction routeAction, 
    NSError *error) {
        
    NSLog(
    @"pop failed,error: %@",error);
    }];
    self.
    router = 
    nil;
}

- (
    void)
    removeEditorAndPrepare {
    
    if (![
    self.router 
    canRemove]) {
        
    return;
    }
    [
    self.router 
    removeRouteWithConfiguring:^(ZIKViewRemoveConfiguration *config) {
        config.
    animated = 
    YES;
        config.
    prepareDestination = ^(UIViewController
    
      *destination) {
            
     // Use destination before remove it
        };
    }];
    self.
     router = 
     nil;
}


     @end
    
   

For more detail, read Remove Route.

Adapter

You can use another protocol to get router, as long as the protocol provides the same interface of the real protocol. Even the protocol is little different from the real protocol, you can adapt two protocols with category, extension and proxy.

Required protocol used by the user:

/// Required protocol to use editor module
protocol RequiredEditorViewInput: class {
    weak var delegate: EditorDelegate? { get set }
    func constructForCreatingNewNote()
}
Objective-C Sample
/// Required protocol to use editor module
@protocol RequiredEditorViewInput 

    @property (
    nonatomic, 
    weak) 
    id
    
      delegate;
- (
     void)
     constructForCreatingNewNote;

     @end
    
   

In the host app context, connect required protocol and provided protocol:

/// In the host app, add required protocol to editor router
class EditorViewAdapter: ZIKViewRouteAdapter {
    override class func registerRoutableDestination() {
        // If you can get the router, you can just register RequiredEditorViewInput to it
        NoteEditorViewRouter.register(RoutableView<RequiredEditorViewInput>())
        
        // If you don't know the router, you can use adapter
        register(adapter: RoutableView<RequiredEditorViewInput>(), forAdaptee: RoutableView<EditorViewInput>())
    }
}

/// Make NoteEditorViewController conform to RequiredEditorViewInput
extension NoteEditorViewController: RequiredEditorViewInput {
}
Objective-C Sample
/// In the host app, add required protocol to editor router

//EditorViewAdapter.h
@interface EditorViewAdapter : ZIKViewRouteAdapter
@end

//EditorViewAdapter.m
@implementation EditorViewAdapter

+ (void)registerRoutableDestination {
	// If you can get the router, you can just register RequiredEditorViewInput to it
	[NoteEditorViewRouter registerViewProtocol:ZIKRoutable(RequiredEditorViewInput)];
	// If you don't know the router, you can use adapter
	[self registerDestinationAdapter:ZIKRoutable(RequiredEditorViewInput) forAdaptee:ZIKRoutable(EditorViewInput)];
}

@end

/// Make NoteEditorViewController conform to RequiredEditorViewInput
@interface NoteEditorViewController (Adapter) 

    @end

    @implementation 
    NoteEditorViewController (Adapter)

    @end
   

After adapting, RequiredEditorViewInput and EditorViewInput can get the same router.

UseRequiredEditorViewInputto get module:

class TestViewController: UIViewController {

    func showEditorDirectly() {
        Router.perform(to: RoutableView<RequiredEditorViewInput>(), path: .push(from: self))
    }
}
Objective-C Sample
@implementation TestViewController

- (void)showEditorDirectly {
    [ZIKRouterToView(RequiredEditorViewInput) performPath:ZIKViewRoutePath.pushFrom(self)];
}

@end

Use required protocol and provided protocol to perfectly decouple modules, adapt interface and declare dependencies of the module. And you don't have to use a public header to manage those protocols.

Modularization

Separating required protocol and provided protocol makes your code truly modular. The caller declares its required protocol, and the provided module can easily be replaced by another module with the same required protocol.

Read the ZIKLoginModule module in demo. The login module depends on an alert module, and the alert module is different in ZIKRouterDemo and ZIKRouterDemo-macOS. You can change the provided module without changing anything in the login module.

For more detail, read Module Adapter.

URL Router

ZIKRouter also provides a default URLRouter. It's easy to communicate with modules via url.

URLRouter is not contained by default. If you want to use it, add submodule pod 'ZIKRouter/URLRouter' to your Podfile , and call [ZIKRouter enableDefaultURLRouteRule] to enable URLRouter.

You can register router with a url:

class NoteEditorViewRouter: ZIKViewRouter
    {
    override class func registerRoutableDestination() {
        registerView(NoteEditorViewController.self)
        register(RoutableView<EditorViewInput>())
        // Register url
        registerURLPattern("app://editor/:title")
    }
}
Objective-C Sample
@implementation NoteEditorViewRouter

+ (void)registerRoutableDestination {
    [self registerView:[NoteEditorViewController class]];
    [self registerViewProtocol:ZIKRoutable(EditorViewInput)];
    // Register url
    [self registerURLPattern:@"app://editor/:title"];
}

@end

Then you can get the router with it's url:

ZIKAnyViewRouter.performURL("app://editor/test_note", path: .push(from: self))
Objective-C Sample
[ZIKAnyViewRouter performURL:@"app://editor/test_note" path:ZIKViewRoutePath.pushFrom(self)];

And handle URL Scheme:

public func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
    let urlString = url.absoluteString
    if let _ = ZIKAnyViewRouter.performURL(urlString, fromSource: self.rootViewController) {
        return true
    } else if let _ = ZIKAnyServiceRouter.performURL(urlString) {
        return true
    } else {
        return false
    }
}
Objective-C Sample
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary
    id> *)options {
    
    if ([ZIKAnyViewRouter 
    performURL:urlString 
    fromSource:
    self.rootViewController]) {
        
    return 
    YES;
    } 
    else 
    if ([ZIKAnyServiceRouter 
    performURL:urlString]) {
        
    return 
    YES;
    } 
    else {
        
    return 
    NO;
    }
}
   

If your project has different requirements for URL router, you can write your URL router by yourself. You can create custom ZIKRouter as parent class, add more powerful features in it. See ZIKRouter+URLRouter.h.

Other Features

There're other features, you can get details in the documentation:

Service Router

Instead of view, you can also get any service modules:

/// time service's interface
protocol TimeServiceInput {
    func currentTimeString() -> String
}
class TestViewController: UIViewController {
    @IBOutlet weak var timeLabel: UILabel!
    
    func callTimeService() {
        // Get the service for TimeServiceInput
        let timeService = Router.makeDestination(
            to: RoutableService<TimeServiceInput>(),
            preparation: { destination in
            // prepare the service if needed
        })
        //Use the service
        timeLabel.text = timeService.currentTimeString()
    }
}
Objective-C Sample
/// time service's interface
@protocol TimeServiceInput 
    
- (
    NSString *)
    currentTimeString;

    @end
   
@interface TestViewController ()
@property (weak, nonatomic) IBOutlet UILabel *timeLabel;
@end

@implementation TestViewController

- (void)callTimeService {
   // Get the service for TimeServiceInput
   id
     timeService = [
    ZIKRouterToService(TimeServiceInput) 
    makeDestination];
   self.
    timeLabel.
    text = [timeService 
    currentTimeString];    
}

   

Demo and Practice

ZIKRouter is designed for VIPER architecture at first. But you can also use it in MVC or anywhere.

The demo (ZIKRouterDemo) in this repository shows how to use ZIKRouter to perform each route type. Open Router.xcworkspace to run it.

If you want to see how it works in a VIPER architecture app, go to ZIKViper.

File Template

You can use Xcode file template to create router and protocol code quickly:

File Template

The template ZIKRouter.xctemplate is in Templates.

Copy ZIKRouter.xctemplate to ~/Library/Developer/Xcode/Templates/ZIKRouter.xctemplate, then you can use it in Xcode -> File -> New -> File -> Templates.

License

ZIKRouter is available under the MIT license. See the LICENSE file for more info.

Comments
  • ___cxa_demangle ld: symbol(s) not found

    ___cxa_demangle ld: symbol(s) not found

    Undefined symbols for architecture arm64: "___cxa_demangle", referenced from: -[NSString(Demangle) demangledAsCPP] in libZIKRouter.a(NSString+Demangle.o) "std::__1::basic_string<char, std::__1::char_traits, std::__1::allocator >::__init(char const*, unsigned long)", referenced from: _demangleSymbolAsString(char const*, unsigned long, DemangleOptions const&) in libZIKRouter.a(NSString+Demangle.o) "std::__1::basic_string<char, std::__1::char_traits, std::__1::allocator >::~basic_string()", referenced from: -[NSString(Demangle) demangledAsSwift] in libZIKRouter.a(NSString+Demangle.o) -[NSString(Demangle) demangledAsSimplifiedSwift] in libZIKRouter.a(NSString+Demangle.o) "___gxx_personality_v0", referenced from: +[ZIKImageSymbol enumerateImages:] in libZIKRouter.a(ZIKImageSymbol.o) +[ZIKImageSymbol findSymbolInImage:name:] in libZIKRouter.a(ZIKImageSymbol.o) +[ZIKImageSymbol findSymbolInImage:matching:] in libZIKRouter.a(ZIKImageSymbol.o) -[NSString(Demangle) demangledAsSwift] in libZIKRouter.a(NSString+Demangle.o) _demangleSymbolAsString(char const*, unsigned long, DemangleOptions const&) in libZIKRouter.a(NSString+Demangle.o) -[NSString(Demangle) demangledAsSimplifiedSwift] in libZIKRouter.a(NSString+Demangle.o) -[NSString(Demangle) demangledAsCPP] in libZIKRouter.a(NSString+Demangle.o) ... ld: symbol(s) not found for architecture arm64

    opened by manajay 5
  • 'The router (XMDModulesViewRouter) doesn't support makeDestination'

    'The router (XMDModulesViewRouter) doesn't support makeDestination'

    请问一下这个具体是什么原因, 找了挺久的, 大神帮我看看 2018-09-27 22:38:54.110016+0800 iOS-More[56319:8936026] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'The router (XMDModulesViewRouter) doesn't support makeDestination' *** First throw call stack: ( 0 CoreFoundation 0x000000010abbf29b __exceptionPreprocess + 331 1 libobjc.A.dylib 0x000000010a157735 objc_exception_throw + 48 2 CoreFoundation 0x000000010abbf022 +[NSException raise:format:arguments:] + 98 3 Foundation 0x0000000109b5bb47 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 194 4 ZIKRouter 0x00000001099d27c6 +[ZIKRouter makeDestinationWithConfiguring:] + 694 5 ZIKRouter 0x0000000109a013f5 +[ZIKViewRouter makeDestinationWithConfiguring:] + 517

    还有一个疑问: 界面路由 与 界面模块路由, 其实两者之间的区别, 除了模块解耦, 那项目中, 具体说如何选用, 打个比方, 一个 tarbar 可以说更对应一个 界面模块路由吧?

    opened by JanXu-Dev 4
  •  Can not get userinfo from url.

    Can not get userinfo from url.

    if use performURL it work well. but if use perform method it does not work.

    let url =  "abc://bvc/a?title=1"
    //        ZIKAnyViewRouter.performURL(url, path: ZIKViewRoutePath.push(from: self))
            let b = ZIKAnyViewRouter.router(forURL: url)
                    b?.perform(ZIKViewRoutePath.push(from: self), configuring: { (config) in
                print("config:\(config.userInfo)")
    //            config.addUserInfo(<#T##userInfo: [String : Any]##[String : Any]#>)
                config.prepareDestination = { destination in
                    if let destination = destination as? UIViewController{
                        destination.modalPresentationStyle = .overFullScreen
                    }
                }
    
            })
    
    opened by imphila 3
  • zix_checkMemoryLeak 在线程 com.zuik.router.object_leak_check_queue 中的逻辑导致卡顿/崩溃

    zix_checkMemoryLeak 在线程 com.zuik.router.object_leak_check_queue 中的逻辑导致卡顿/崩溃

    在 DEBUG 模式下 , zix_checkMemoryLeak 在线程 com.zuik.router.object_leak_check_queue 中触发 - (NSString *)description 方法, 在该方法中, 如果调用了一些 UI 上的行为, 导致卡顿, 如图: bug1 bug4 bug2 bug3 分别调用 UITableView 和 UIScrollView 的一些必须在 主线程 中操作的属性. 问题:

    1. 是否可以关闭zix_checkMemoryLeak, 影响如何?
    2. 是否可以在不关闭的前提下, 有其他解决方案? 暂时是在这几处加上了主线程调用, 但是因为也是第三方库, 希望尽量不修改到其代码.
    opened by JanXu-Dev 3
  • will zikrouter provide an api to shutdown the ZIKROUTER_CHECK function

    will zikrouter provide an api to shutdown the ZIKROUTER_CHECK function

    whenever developing a component ,i found it's hard to run the demo of the component because of checking the missing protocol function. Especially when cycle dependency appears.

    opened by manajay 3
  • ZIKROUTER_CHECK 在 DEBUG 下总是崩溃, 我检查了都遵守了

    ZIKROUTER_CHECK 在 DEBUG 下总是崩溃, 我检查了都遵守了

    + (void)registerViewProtocol:(Protocol *)viewProtocol {
        NSAssert(!ZIKViewRouteRegistry.registrationFinished, @"Only register in +registerRoutableDestination.");
    #if ZIKROUTER_CHECK
        NSAssert1(protocol_conformsToProtocol(viewProtocol, @protocol(ZIKViewRoutable)), @"%@ should conforms to ZIKViewRoutable in DEBUG mode for safety checking", NSStringFromProtocol(viewProtocol));
    #endif
        [ZIKViewRouteRegistry registerDestinationProtocol:viewProtocol router:self];
    }
    

    .h 文件

    #import "ZIKViperRouter.h"
    #import <ZIKRouter/ZIKViewRoute.h>
    
    #import <Foundation/Foundation.h>
    
    @interface LSRecommendListWireFrame : ZIKViewRouter <ZIKViperRouter>
    @end
    
    
    #import "ZIKViewRouter+ZIKViper.h"
    #import "ZIKViperViewPrivate.h"
    #import <ZIKRouter/ZIKRouter.h>
    #import <ZIKRouter/ZIKRouterInternal.h>
    
    #import "LSRecommendListWireFrame.h"
    #import "LSRecommendListInteractor.h"
    #import "LSRecommendListPresenter.h"
    #import "LSRecommendListController.h"
    
    #import "LSRemoteCourseDataManager.h"
    #import "LSDeviceClock.h"
    #import "LSRouterConst.h"
    #import "LSRecommendListViewInterface.h"
    
    @interface LSRecommendListController (LSRecommendListWireFrame) <ZIKRoutableView>
    @end
    @implementation LSRecommendListController (LSRecommendListWireFrame)
    @end
    
    @interface LSRecommendListWireFrame ()
    
    @end
    
    @implementation LSRecommendListWireFrame
    
    + (void)registerRoutableDestination {
        [self registerView:[LSRecommendListController class]];
        [self registerViewProtocol:ZIKRoutable(LSRecommendListViewInterface)];
        [self registerIdentifier:HomeMatchSymbol];
    }
    
    - (UIViewController *)destinationWithConfiguration:(ZIKViewRouteConfiguration *)configuration {
        LSRecommendListController *listViewController = [[LSRecommendListController alloc] init];
        return listViewController;
    }
    
    - (BOOL)destinationFromExternalPrepared:(id)destination {
        NSParameterAssert([destination isKindOfClass:[LSRecommendListController class]]);
        NSParameterAssert([destination conformsToProtocol:@protocol(ZIKViperViewPrivate)]);
        if (![ZIKViewRouter isViperAssembledForView:destination]) {
            return NO;
        }
        return YES;
    }
    
    - (void)prepareDestination:(id)destination configuration:(__kindof ZIKViewRouteConfiguration *)configuration {
        NSParameterAssert([destination isKindOfClass:[LSRecommendListController class]]);
        NSParameterAssert([destination conformsToProtocol:@protocol(ZIKViperViewPrivate)]);
        if (![self isViperAssembled]) {
            [self assembleViper];
        } else {
            [self attachRouter];
        }
    }
    
    #pragma mark Viper assembly
    
    - (void)assembleViper {
        id<ZIKViperViewPrivate> view = self.destination;
        NSAssert(view, @"Can't assemble viper when view is nil");
        
        LSRemoteCourseDataManager *dataManager = [[LSRemoteCourseDataManager alloc] init];
        LSDeviceClock *clock = [[LSDeviceClock alloc] init];
        
        LSRecommendListPresenter *presenter = [[LSRecommendListPresenter alloc] init];
        LSRecommendListInteractor *interactor = [[LSRecommendListInteractor alloc] initWithRemoteDataManager:dataManager clock:clock];
        interactor.output = presenter;
        
        [self assembleViperForView:view
                         presenter:(id<ZIKViperPresenterPrivate>)presenter
                        interactor:interactor];
    }
    
    - (void)attachRouter {
        id<ZIKViperViewPrivate> view = self.destination;
        NSAssert(view, @"Can't assemble viper when view is nil");
        [self attachRouterForView:view];
    }
    
    #pragma mark AOP
    
    + (void)router:(ZIKViewRouter *)router willPerformRouteOnDestination:(id)destination fromSource:(id)source {
        NSAssert([ZIKViewRouter isViperAssembledForView:destination], @"Viper should be assembled");
    }
    
    
    @end
    

    协议

    #import <Foundation/Foundation.h>
    #import <ZIKRouter/ZIKRouter.h>
    
    @protocol LSRecommendListViewInterface <NSObject>
    @end
    

    这个怎么回事

    opened by manajay 3
  • 获取模块makeDestination 当destination是view而不是controller时崩溃

    获取模块makeDestination 当destination是view而不是controller时崩溃

    你好 我用makeDestination 获取子模块view时崩溃 检查源码是- (void)_performGetDestination:(id)destination fromSource:(nullable id)source函数中self.stateBeforeRoute = [destination zix_presentationState];造成的,因为zix_presentationState成controller分类中的扩展方法。然而我们在获取模块的时候这个模块可能是controller 也可能是view 不应该只是controller,希望您能给出解决方案,最后我想说的是 我爱死你这套路由方案了!!!

    opened by KelyCocoa 3
  • Compiler error on ZRouter under xCode 10.1 swift 4

    Compiler error on ZRouter under xCode 10.1 swift 4

    "_ZIKRouteActionPerformRoute", referenced from: closure #1 (A) -> () in ZRouter.ViewRouterType.perform(path: ZRouter.ViewRoutePath, completion: (Swift.Bool, A?, __C.ZIKRouteAction, Swift.Error?) -> ()) -> ZRouter.ViewRouter<A, B>? in ViewRouter.o closure #1 (A) -> () in ZRouter.ViewRouter.performRoute(completion: (Swift.Bool, A?, __C.ZIKRouteAction, Swift.Error?) -> ()) -> () in ViewRouter.o closure #1 (A) -> () in ZRouter.ServiceRouterType.perform(completion: (Swift.Bool, A?, __C.ZIKRouteAction, Swift.Error?) -> ()) -> ZRouter.ServiceRouter<A, B>? in ServiceRouter.o closure #1 (A) -> () in ZRouter.ServiceRouter.performRoute(completion: (Swift.Bool, A?, __C.ZIKRouteAction, Swift.Error?) -> ()) -> () in ServiceRouter.o ld: symbol(s) not found for architecture x86_64 clang: error: linker command failed with exit code 1 (use -v to see invocation)

    opened by lzhlewis2015 2
  • 求助大神 如果我有一个路由像下面的那样 我如何得到destination呢 id<LivePlayerPublicInterface> destination = [_livePlayerViewRouter destination];会报错

    求助大神 如果我有一个路由像下面的那样 我如何得到destination呢 id destination = [_livePlayerViewRouter destination];会报错

    • (ZIKDestinationViewRouter(id) *)livePlayerViewRouter { if (!_livePlayerViewRouter) { _livePlayerViewRouter = [ZIKRouterToView(LivePlayerPublicInterface) performFromSource:self.routeSource configuring:^(ZIKViewRouteConfiguration * _Nonnull config) { config.routeType = ZIKViewRouteTypeAddAsSubview; config.prepareDestination = ^(id _Nonnull destination) { [destination setFrame:CGRectMake(0, 0, kUIScreenWidth, kUIScreenWidth*3.0/4.0)]; }; }]; } return _livePlayerViewRouter; }
    opened by KelyCocoa 2
  • 遇到内存泄漏问题 麻烦大神

    遇到内存泄漏问题 麻烦大神

    当router支持的路由类型是ZIKViewRouteTypeMaskViewDefault时Presenter如果强持有router会造成view的内存泄漏,这种情况下我觉得是router强制持有了绑定的view。我查看了一下源码,仍然没有找到问题的原因,希望大神指点,另外当router绑定的是controller类型时是正常的,view presenter Interactor 等的持有关系我反复核查过,问题并不出现在这个地方。谢谢

    opened by KelyCocoa 1
  • ZIKViewRouter Error

    ZIKViewRouter Error

    router's action (ZIKRouteActionPerformRoute) catch error: (Error Domain=kZIKViewRouteErrorDomain Code=4 "Unbalanced calls to begin/end appearance transitions for destination. This error occurs when you try and display a view controller before the current view controller is finished displaying. This may cause the UIViewController skips or messes up the order calling -viewWillAppear:, -viewDidAppear:, -viewWillDisAppear: and -viewDidDisappear:, and messes up the route state. Current error reason is already removed destination but destination appears again before -viewDidDisappear: 请问这个是什么原因?

    opened by zx2696108 1
  • ZIKViewRouter.m:2112 Assert 错误

    ZIKViewRouter.m:2112 Assert 错误

    在 WKWebView 中的输入框输入表情之后,收起键盘出现闪退

    *** Assertion failure in -[UIKeyboardMediaServiceRemoteViewController willMoveToParentViewController:]
    ZIKRouter/ZIKRouter/ViewRouter/ZIKViewRouter.m:2112
    
    "0   CoreFoundation                      0x00007fff23c7127e __exceptionPreprocess + 350",
    "1   libobjc.A.dylib                     0x00007fff513fbb20 objc_exception_throw + 48",
    "2   CoreFoundation                      0x00007fff23c70ff8 +[NSException raise:format:arguments:] + 88",
    "3   Foundation                          0x00007fff256e9b51 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:descript
    "4   ZIKRouter                           0x000000010f6f46df -[ZIKViewRouter ZIKViewRouter_hook_willMoveToParentViewController:] + 399"
    "5   UIKitCore                           0x00007fff47b2cb4c -[UIKeyboardMediaController releaseRecentlyUsedMediaViewIfNeeded] + 46",
    "6   UIKitCore                           0x00007fff47e96539 -[UIKeyboardImpl setDelegate:force:] + 4931",
    "7   UIKitCore                           0x00007fff47b56c12 -[UIInputResponderController _reloadInputViewsForKeyWindowSceneResponder:]
    "8   UIKitCore                           0x00007fff47b563b3 -[UIInputResponderController _reloadInputViewsForResponder:] + 144",
    "9   UIKitCore                           0x00007fff480c4c8b -[UIResponder(UIResponderInputViewAdditions) reloadInputViews] + 133",
    "10  WebKit                              0x00007fff2d46821b -[WKContentView(WKInteraction) _hideKeyboard] + 106",
    "11  WebKit                              0x00007fff2d468c54 -[WKContentView(WKInteraction) _elementDidBlur] + 296",
    "12  WebKit                              0x00007fff2cf4582e _ZN3IPC18MessageReceiverMap15dispatchMessageERNS_10ConnectionERNS_7Decoder
    "13  WebKit                              0x00007fff2d190448 _ZN6WebKit15WebProcessProxy17didReceiveMessageERN3IPC10ConnectionERNS1_7De
    "14  WebKit                              0x00007fff2cf31802 _ZN3IPC10Connection15dispatchMessageENSt3__110unique_ptrINS_7DecoderENS1_1
    "15  WebKit                              0x00007fff2cf345a4 _ZN3IPC10Connection24dispatchIncomingMessagesEv + 408",
    "16  JavaScriptCore                      0x00007fff268a11f4 _ZN3WTF7RunLoop11performWorkEv + 228",
    "17  JavaScriptCore                      0x00007fff268a1482 _ZN3WTF7RunLoop11performWorkEPv + 34",
    "18  CoreFoundation                      0x00007fff23bd4471 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17",
    "19  CoreFoundation                      0x00007fff23bd439c __CFRunLoopDoSource0 + 76",
    "20  CoreFoundation                      0x00007fff23bd3b74 __CFRunLoopDoSources0 + 180",
    "21  CoreFoundation                      0x00007fff23bce87f __CFRunLoopRun + 1263",
    "22  CoreFoundation                      0x00007fff23bce066 CFRunLoopRunSpecific + 438",
    "23  GraphicsServices                    0x00007fff384c0bb0 GSEventRunModal + 65",
    "24  UIKitCore                           0x00007fff48092d4d UIApplicationMain + 1621",
    "25  HuoBan_DEBUG                        0x000000010bc7e300 main + 112",
    "26  libdyld.dylib                       0x00007fff5227ec25 start + 1"
    
    opened by LittleYuuuuu 0
Owner
Zuik
iOS developer
Zuik
Eugene Kazaev 713 Dec 25, 2022
SwiftRouter - A URL Router for iOS, written in Swift

SwiftRouter A URL Router for iOS, written in Swift, inspired by HHRouter and JLRoutes. Installation SwiftRouter Version Swift Version Note Before 1.0.

Chester Liu 259 Apr 16, 2021
A bidirectional Vapor router with more type safety and less fuss.

vapor-routing A routing library for Vapor with a focus on type safety, composition, and URL generation. Motivation Getting started Documentation Licen

Point-Free 68 Jan 7, 2023
A bidirectional router with more type safety and less fuss.

swift-url-routing A bidirectional URL router with more type safety and less fuss. This library is built with Parsing. Motivation Getting started Docum

Point-Free 242 Jan 4, 2023
Crossroad is an URL router focused on handling Custom URL Scheme

Crossroad is an URL router focused on handling Custom URL Scheme. Using this, you can route multiple URL schemes and fetch arguments and parameters easily.

Kohki Miki 331 May 23, 2021
A demonstration to the approach of leaving view transition management to a router.

SwiftUI-RouterDemo This is a simplified demonstration to the approach of leaving view transition management to a router.

Elvis Shi 3 May 26, 2021
An experimental navigation router for SwiftUI

SwiftUIRouter ?? An ⚠️ experimental ⚠️ navigation router for SwiftUI Usage ?? Check out ExampleApp for more. Define your routes: import SwiftUIRouter

Orkhan Alikhanov 16 Aug 16, 2022
URLScheme router than supports auto creation of UIViewControllers for associated url parameters to allow creation of navigation stacks

IKRouter What does it do? Once you have made your UIViewControllers conform to Routable you can register them with the parameters that they represent

Ian Keen 94 Feb 28, 2022
An extremely lean implementation on the classic iOS router pattern.

Beeline is a very small library that aims to provide a lean, automatic implementation of the classic iOS router pattern.

Tim Oliver 9 Jul 25, 2022
Helm - A graph-based SwiftUI router

Helm is a declarative, graph-based routing library for SwiftUI. It fully describ

Valentin Radu 99 Dec 5, 2022
DZURLRoute is an Objective-C implementation that supports standard-based URLs for local page jumps.

DZURLRoute Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements s.dependency 'DZVie

yishuiliunian 72 Aug 23, 2022
Appz 📱 Launch external apps, and deeplink, with ease using Swift!

Appz ?? Deeplinking to external applications made easy Highlights Web Fallback Support: In case the app can't open the external application, it will f

Kitz 1.1k May 5, 2021
🎯Linker Lightweight way to handle internal and external deeplinks in Swift for iOS

Linker Lightweight way to handle internal and external deeplinks in Swift for iOS. Installation Dependency Managers CocoaPods CocoaPods is a dependenc

Maksim Kurpa 128 May 20, 2021
A simple, powerful and elegant implementation of the coordinator template in Swift for UIKit

A simple, powerful and elegant implementation of the coordinator template in Swift for UIKit Installation Swift Package Manager https://github.com/bar

Aleksei Artemev 22 Oct 16, 2022
LiteRoute is easy transition for your app. Written on Swift 4

LiteRoute Description LiteRoute is easy transition between VIPER modules, who implemented on pure Swift. We can transition between your modules very e

Vladislav Prusakov 90 Mar 12, 2021
📱📲 Navigate between view controllers with ease. 💫 🔜 More stable version (written in Swift 5) coming soon.

CoreNavigation ?? ?? Navigate between view controllers with ease. ?? ?? More stable version (written in Swift 5) coming soon. Getting Started API Refe

Aron Balog 69 Sep 21, 2022
A library for managing complex workflows in Swift

Welcome SwiftCurrent is a library that lets you easily manage journeys through your Swift application. It comes with built-in support for UIKit and Sw

WWT 290 Jan 9, 2023
ZPPRouter 组件化路由 swift

ZPPRouter 面向组件协议 组件获取实例为协议类型 注: SPM 每一个组件最终生成的都是 framwork库 意味着组件存在命名空间(优点) Example To run the example project, clone the repo, and run pod install fro

Zpp 5 Jun 6, 2022
A deep copy of Pinterest in Swift

Demo YouTube: Demo (2 minutes) 优酷:http://v.youku.com/v_show/id_XMzE3OTc5NDY2MA==.html?spm=a2h3j.8428770.3416059.1 The app is actually smoother than sh

Andy Tong 73 Sep 14, 2022