一个用于模块间解耦和通信,基于接口进行模块管理和依赖注入的组件化路由工具。用多种方式最大程度地发挥编译检查的功能。
通过 protocol 寻找对应的模块,并用 protocol 进行依赖注入和模块通信。
Service Router 可以管理任意自定义模块。View Router 进一步封装了界面跳转。
ZRouter
为 Swift 提供更加 Swifty、更加安全的路由方式。
- 支持 Swift 和 Objective-C,以及两者混编
- 支持 iOS、macOS、tvOS
- 提供规范化的代码模板,一键生成代码
- 支持界面路由和任意 OC 模块、swift 模块的路由,无需修改模块代码即可让其支持路由
- 支持对模块进行静态依赖注入和动态依赖注入
- 明确声明可用于路由的 protocol,进行编译时检查,使用未声明的 protocol 会产生编译错误。这是 ZIKRouter 最特别的功能之一
- 用 protocol 获取模块并传递参数,基于接口进行类型安全的交互
- 支持 URL 路由
- 使用 required protocol 和 provided protocol 指向同一个模块,解除 protocol 依赖,支持各模块独立编译
- 支持 storyboard,可以对从 segue 中跳转的界面自动执行依赖注入
- 用 adapter 对两个模块进行解耦和接口兼容
- 封装 UIKit / AppKit 里的所有界面跳转方式,同时支持界面回退和自定义跳转
- 完备的错误检查,可以检测界面跳转时的大部分问题
- 支持界面跳转过程中的 AOP 回调
- 检测界面跳转和移除时的内存泄露
- 发送自定义事件给 router 处理
- 高性能的自动注册方式,也支持手动注册
- 支持多种路由实现方式: 强大的 router 子类、简单的工厂 block 和 C 函数
- 高可扩展性,可以轻松地加入各种自定义功能
- iOS 7.0+
- Swift 3.2+
- Xcode 9.0+
可以用 Cocoapods 安装 ZIKRouter:
pod 'ZIKRouter', '>= 1.1.1'
# 或者只使用 ServiceRouter 子模块
pod 'ZIKRouter/ServiceRouter' , '>=1.1.1'
如果是 Swift 项目,则使用 ZRouter:
pod 'ZRouter', '>= 1.1.1'
# 或者只使用 ServiceRouter 子模块
pod 'ZRouter/ServiceRouter' , '>=1.1.1'
添加到 Cartfile 文件:
github "Zuikyo/ZIKRouter" >= 1.1.1
编译 framework:
carthage update
编译 DEBUG 版本,开启运行时路由检查:
carthage update --configuration Debug
记得不要把 debug 版本的库用在 release 版本的 app 中。一定要在 release 版本的 app 中使用 release 版本的库。
对于 Objective-C 的项目,使用 ZIKRouter.framework
。对于 Swift 项目,使用ZRouter.framework
。
下面演示 router 的基本使用。演示用的界面和 protocol:
///Editor 模块的接口和依赖
protocol EditorViewInput: class {
weak var delegate: EditorDelegate? { get set }
func constructForCreatingNewNote()
}
///Editor view controller
class NoteEditorViewController: UIViewController, EditorViewInput {
...
}
Objective-C Sample
///Editor 模块的接口和依赖
@protocol EditorViewInput <ZIKViewRoutable>
@property (nonatomic, weak) id<EditorDelegate> delegate;
- (void)constructForCreatingNewNote;
@end
///Editor view controller
@interface NoteEditorViewController: UIViewController <EditorViewInput>
@end
@implementation NoteEditorViewController
@end
创建路由只需要2步。
将一个类模块化时,你需要为模块创建对应的 router。整个过程无需对模块本身做出任何修改,因此能够最大程度地减少模块化改造的成本。
为你的模块创建 router 子类:
import ZIKRouter.Internal
import ZRouter
class NoteEditorViewRouter: ZIKViewRouter<NoteEditorViewController, ViewRouteConfig> {
override class func registerRoutableDestination() {
// 注册 class;一个 router 可以注册多个界面,一个界面也可以使用多个 router
registerView(NoteEditorViewController.self)
// 注册 protocol;之后就可以用这个 protocol 获取 此 router
register(RoutableView<EditorViewInput>())
}
// 创建模块
override func destination(with configuration: ViewRouteConfig) -> NoteEditorViewController? {
let destination: NoteEditorViewController? = ... ///实例化 view controller
return destination
}
override func prepareDestination(_ destination: NoteEditorViewController, configuration: ViewRouteConfig) {
//为 destination 注入依赖
}
}
Objective-C Sample
//NoteEditorViewRouter.h
@import ZIKRouter;
@interface NoteEditorViewRouter : ZIKViewRouter
@end
//NoteEditorViewRouter.m
@import ZIKRouter.Internal;
@implementation NoteEditorViewRouter
+ (void)registerRoutableDestination {
// 注册 class;一个 Router 可以注册多个界面,一个界面也可以使用多个 Router
[self registerView:[NoteEditorViewController class]];
// 注册 protocol;之后就可以用这个 protocol 获取 此 router
[self registerViewProtocol:ZIKRoutable(EditorViewInput)];
}
// 创建模块
- (NoteEditorViewController *)destinationWithConfiguration:(ZIKViewRouteConfiguration *)configuration {
// 可以从configuration 中获取外部传入的参数,用于创建实例
NoteEditorViewController *destination = ... // 实例化 view controller
return destination;
}
- (void)prepareDestination:(NoteEditorViewController *)destination configuration:(ZIKViewRouteConfiguration *)configuration {
// 为 destination 注入依赖
}
@end
ZIKRouter 的路由是离散式的管理方式,让每个模块各自管理路由过程,同时也能带来极强的可扩展性,可以进行非常多的自定义功能扩展。
关于更多可用于 override 的方法,请参考详细文档。
如果你的类很简单,并不需要用到 router 子类,直接注册类即可:
ZIKAnyViewRouter.register(RoutableView<EditorViewInput>(), forMakingView: NoteEditorViewController.self)
Objective-C Sample
[ZIKViewRouter registerViewProtocol:ZIKRoutable(EditorViewInput) forMakingView:[NoteEditorViewController class]];
或者用 block 自定义创建对象的方式:
ZIKAnyViewRouter.register(RoutableView<EditorViewInput>(),
forMakingView: NoteEditorViewController.self) { (config, router) -> EditorViewInput? in
let destination: NoteEditorViewController? = ... // 实例化 view controller
return destination;
}
Objective-C Sample
[ZIKViewRouter
registerViewProtocol:ZIKRoutable(EditorViewInput)
forMakingView:[NoteEditorViewController class]
making:^id _Nullable(ZIKViewRouteConfiguration *config, ZIKViewRouter *router) {
NoteEditorViewController *destination = ... // 实例化 view controller
return destination;
}];
或者指定用 C 函数创建对象:
function makeEditorViewController(config: ViewRouteConfig) -> EditorViewInput? {
let destination: NoteEditorViewController? = ... // 实例化 view controller
return destination;
}
ZIKAnyViewRouter.register(RoutableView<EditorViewInput>(),
forMakingView: NoteEditorViewController.self, making: makeEditorViewController)
Objective-C Sample
id<EditorViewInput> makeEditorViewController(ZIKViewRouteConfiguration *config) {
NoteEditorViewController *destination = ... // 实例化 view controller
return destination;
}
[ZIKViewRouter
registerViewProtocol:ZIKRoutable(EditorViewInput)
forMakingView:[NoteEditorViewController class]
factory:makeEditorViewController];
对路由进行声明,用于编译检查和支持 storyboard。
//声明 NoteEditorViewController 为 routable
//这表明 NoteEditorViewController 至少存在一个 对应的 router
extension NoteEditorViewController: ZIKRoutableView {
}
//声明 EditorViewInput 为 routable
//这份声明意味着我们可以用 EditorViewInput 来获取路由
extension RoutableView where Protocol == EditorViewInput {
init() { self.init(declaredProtocol: Protocol.self) }
}
Objective-C Sample
//声明 NoteEditorViewController 为 routable
//这表明 NoteEditorViewController 至少存在一个 对应的 router
DeclareRoutableView(NoteEditorViewController, NoteEditorViewRouter)
///当 protocol 继承自 ZIKViewRoutable, 就是 routable 的
//这份声明意味着我们可以用 EditorViewInput 来获取路由
@protocol EditorViewInput <ZIKViewRoutable>
@property (nonatomic, weak) id<EditorDelegate> delegate;
- (void)constructForCreatingNewNote;
@end
如果获取路由时,protocol 未经过声明,将会产生编译错误。这是 ZIKRouter 最特别的功能之一,可以让你更安全、更简单地管理所使用的路由接口,不必再用其他复杂的方式进行检查和维护。
Swift 中使用未声明的 protocol:
Objective-C 中使用未声明的 protocol:
现在你可以用所声明的 protocol 进行路由操作了。
直接跳转到 editor 界面:
class TestViewController: UIViewController {
//直接跳转到 editor view controller
func showEditorDirectly() {
Router.perform(to: RoutableView<EditorViewInput>(), path: .push(from: self))
}
}
Objective-C Sample
@implementation TestViewController
- (void)showEditorDirectly {
//直接跳转到 editor view controller
[ZIKRouterToView(EditorViewInput) performPath:ZIKViewRoutePath.pushFrom(self)];
}
@end
可以用 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)
}
封装界面跳转可以屏蔽 UIKit 的细节,此时界面跳转的代码就可以放在非 view 层(例如 presenter、view model、interactor、service),并且能够跨平台,也能轻易地通过配置切换跳转方式。
可以在跳转前配置页面,传递参数:
class TestViewController: UIViewController {
//跳转到 editor 界面;通过 protocol 获取对应的 router 类,同时用 protocol 配置界面
func showEditor() {
Router.perform(
to: RoutableView<EditorViewInput>(),
path: .push(from: self),
configuring: { (config, _) in
//路由相关的设置
//跳转前配置界面
config.prepareDestination = { [weak self] destination in
//destination 自动推断为 EditorViewInput
destination.delegate = self
destination.constructForCreatingNewNote()
}
config.successHandler = { destination in
//跳转成功
}
config.errorHandler = { (action, error) in
//跳转失败
}
})
}
}
Objective-C Sample
@implementation TestViewController
- (void)showEditor {
//跳转到 editor 界面;通过 protocol 获取对应的 router 类,同时用 protocol 配置界面
[ZIKRouterToView(EditorViewInput)
performPath:ZIKViewRoutePath.pushFrom(self)
configuring:^(ZIKViewRouteConfig *config) {
//路由相关的设置
//跳转前配置界面
config.prepareDestination = ^(id<EditorViewInput> destination) {
destination.delegate = self;
[destination constructForCreatingNewNote];
};
config.successHandler = ^(id<EditorViewInput> destination) {
//跳转结束
};
config.errorHandler = ^(ZIKRouteAction routeAction, NSError * error) {
//跳转失败
};
}];
}
@end
更详细的内容,可以参考执行路由。
如果不想执行界面跳转,只是想获取模块,执行自定义操作,可以使用makeDestination
:
//destination 自动推断为 EditorViewInput
let destination = Router.makeDestination(to: RoutableView<EditorViewInput>())
Objective-C Sample
id<EditorViewInput> destination = [ZIKRouterToView(EditorViewInput) makeDestination];
有些参数不能直接通过 destination 的接口传递:
-
模块有自定义初始化方法,需要从外部传入一些必需参数后才能创建实例
-
需要传递的参数并不能都通过 destination 的接口设置,例如参数不属于 destination,而是属于模块内其他组件。如果把参数都放到 destination 的接口上,会导致接口被污染
传递必需参数需要使用工厂模式。此时可以对 router 的 configuration 进行扩展,用于保存参数。
之前用于路由的EditorViewInput
是由 destination 遵守的,现在使用EditorViewModuleInput
,由自定义的 configuration 遵守,用于声明模块需要的参数:
// protocol 里一般只需要 makeDestinationWith,用于声明参数类型和 destination 类型;也可以添加其他自定义的属性参数或者方法
protocol EditorViewModuleInput: class {
// 工厂方法,传递参数,用于创建模块;这里声明了需要一个 Note 类型的参数,并返回一个 EditorViewInput
var makeDestinationWith: (_ note: Note) -> EditorViewInput? { get }
}
Objective-C Sample
// 一般只需要 makeDestinationWith,用于声明参数类型和 destination 类型;也可以添加其他自定义的属性参数或者方法
@protocol EditorViewModuleInput <ZIKViewModuleRoutable>
// 工厂方法,传递参数,用于创建模块; protocol 里声明了需要一个 Note 类型的参数,并返回一个 EditorViewInput
@property (nonatomic, copy, readonly) id<EditorViewInput> _Nullable(^makeDestinationWith)(Note *note);
@end
此时的 EditorViewModuleInput
就类似于一个工厂类,通过它来声明和创建 destination。
使用者在调用模块时就能动态传入必需参数,调用时代码将会变成这样:
var note = ...
Router.makeDestination(to: RoutableViewModule<EditorViewModuleInput>()) { (config) in
// 传递参数,得到 EditorViewInput
let destination = config.makeDestinationWith(note)
}
Objective-C Sample
Note *note = ...
[ZIKRouterToViewModule(EditorViewModuleInput)
performPath:ZIKViewRoutePath.showFrom(self)
configuring:^(ZIKViewRouteConfiguration<EditorViewModuleInput> *config) {
// 传递参数,得到 EditorViewInput
id<EditorViewInput> destination = config.makeDestinationWith(note);
}];
更详细的内容,可以参考自定义 configuration 传参。
如果你从其他地方得到了一个 destination 对象,你可以用对应的 router 在这个 destination 上执行路由。
例如,某个 UIViewController 支持 3D touch,实现了UIViewControllerPreviewingDelegate
:
class SourceViewController: UIViewController, UIViewControllerPreviewingDelegate {
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
//返回 destination UIViewController, 让系统执行预览
let destination = Router.makeDestination(to: RoutableView<EditorViewInput>())
return destination
}
func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
guard let destination = viewControllerToCommit as? EditorViewInput else {
return
}
//跳转到 destination
Router.to(RoutableView<EditorViewInput>())?.perform(onDestination: destination, path: .presentModally(from: self))
}
Objective-C Sample
@implementation SourceViewController
- (nullable UIViewController *)previewingContext:(id <UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location {
//返回 destination UIViewController, 让系统执行预览
UIViewController<EditorViewInput> *destination = [ZIKRouterToView(EditorViewInput) makeDestination];
return destination;
}
- (void)previewingContext:(id <UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit {
//跳转到 destination
UIViewController<EditorViewInput> *destination;
if ([viewControllerToCommit conformsToProtocol:@protocol(EditorViewInput)]) {
destination = viewControllerToCommit;
} else {
return;
}
[ZIKRouterToView(EditorViewInput) performOnDestination:destination path:ZIKViewRoutePath.presentModallyFrom(self)];
}
@end
如果你并不想执行路由,而只是想配置某个 destination 对象,可以用 router 执行 prepare 操作。这样, router 内部对 destination 对象执行的所有依赖注入操作就都会生效,destination 就被正确地配置好了。
var destination: DestinationViewInput = ...
Router.to(RoutableView<EditorViewInput>())?.prepare(destination: destination, configuring: { (config, _) in
config.prepareDestination = { destination in
// Prepare
}
})
Objective-C Sample
UIViewController<EditorViewInput> *destination = ...
[ZIKRouterToView(EditorViewInput) prepareDestination:destination configuring:^(ZIKViewRouteConfiguration *config) {
config.prepareDestination = ^(id<EditorViewInput> destination) {
// Prepare
};
}];
执行路由后,可以用removeRoute
一键移除界面,无需区分调用 pop / dismiss / removeFromParentViewController / removeFromSuperview:
class TestViewController: UIViewController {
var router: DestinationViewRouter<EditorViewInput>?
func showEditor() {
//持有 router
router = Router.perform(to: RoutableView<EditorViewInput>(), path: .push(from: self))
}
//Router 会对 editor view controller 执行 pop 操作,移除界面
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
//在消除界面之前调用界面的方法
}
})
router = nil
}
}
Objective-C Sample
@interface TestViewController()
@property (nonatomic, strong) ZIKDestinationViewRouter(id<EditorViewInput>) *router;
@end
@implementation TestViewController
- (void)showEditorDirectly {
//持有 router
self.router = [ZIKRouterToView(EditorViewInput) performPath:ZIKViewRoutePath.pushFrom(self)];
}
//Router 会对 editor view controller 执行 pop 操作,移除界面
- (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<EditorViewInput> *destination) {
//在消除界面之前调用界面的方法
};
}];
self.router = nil;
}
@end
更详细的内容,可以参考移除路由。
可以用另一个 protocol 获取 router,只要两个 protocol 提供了相同功能的接口即可,因此模块不会和某个固定的 protocol 耦合。即便接口有稍微不同,也可以通过 category、extension、proxy 等方式进行接口适配。
使用者需要用到的接口:
/// 使用者需要用到的 editor 模块的接口
protocol RequiredEditorViewInput: class {
weak var delegate: EditorDelegate? { get set }
func constructForCreatingNewNote()
}
Objective-C Sample
/// 使用者需要用到的 editor 模块的接口
@protocol RequiredEditorViewInput <ZIKViewRoutable>
@property (nonatomic, weak) id<EditorDelegate> delegate;
- (void)constructForCreatingNewNote;
@end
由宿主 app 对接 required protocol 和 provided protocol:
/// 在宿主 app 中,为 router 添加 required protocol
class EditorViewAdapter: ZIKViewRouteAdapter {
override class func registerRoutableDestination() {
//如果可以获取到 router 类,可以直接为 router 添加 RequiredEditorViewInput
NoteEditorViewRouter.register(RoutableView<RequiredEditorViewInput>())
//如果不能得到对应模块的 router,可以注册 adapter
register(adapter: RoutableView<RequiredEditorViewInput>(), forAdaptee: RoutableView<EditorViewInput>())
}
}
/// 让 NoteEditorViewController 支持 RequiredEditorViewInput
extension NoteEditorViewController: RequiredEditorViewInput {
}
Objective-C Sample
/// 在宿主 app 中,为 router 添加 required protocol
//EditorViewAdapter.h
@interface EditorViewAdapter : ZIKViewRouteAdapter
@end
//EditorViewAdapter.m
@implementation EditorViewAdapter
+ (void)registerRoutableDestination {
//如果可以获取到 router 类,可以直接为 router 添加 RequiredEditorViewInput
[NoteEditorViewRouter registerViewProtocol:ZIKRoutable(RequiredEditorViewInput)];
//如果不能得到对应模块的 router,可以注册 adapter
[self registerDestinationAdapter:ZIKRoutable(RequiredEditorViewInput) forAdaptee:ZIKRoutable(EditorViewInput)];
}
@end
/// 让 NoteEditorViewController 支持 RequiredEditorViewInput
@interface NoteEditorViewController (Adapter) <RequiredEditorViewInput>
@end
@implementation NoteEditorViewController (Adapter)
@end
适配之后,RequiredEditorViewInput
和 EditorViewInput
就会指向同一个 router。
使用RequiredEditorViewInput
获取模块:
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
使用 required protocol 和 provided protocol,就可以让模块间完美解耦,并进行接口适配,同时还能用 required protocol 声明模块所需的依赖。并且 required protocol 只需要是 provided protocol 的子集,让使用者只接触到自己用到的那些接口,限制接口的粒度。此时不再需要用一个公共库来集中存放所有的 protocol,即便模块间有互相依赖,也可以各自单独进行编译。
区分了required protocol
和provided protocol
后,就可以实现真正的模块化。在调用者声明了所需要的required protocol
后,被调用模块就可以随时被替换成另一个相同功能的模块。
参考 demo 中的ZIKLoginModule
示例模块,登录模块依赖于一个弹窗模块,而这个弹窗模块在ZIKRouterDemo
和ZIKRouterDemo-macOS
中是不同的,而在切换弹窗模块时,登录模块中的代码不需要做任何改变。
更详细的内容,可以参考模块化和解耦。
ZIKRouter 也实现了一个默认的 URLRouter,可以方便地通过 url 调用模块,让模块能够统一处理 url 路由和接口调用。
ZIKRouter 默认没有附带此功能,如果需要,则在 Podfile
中添加子模块:pod 'ZIKRouter/URLRouter'
,并且调用[ZIKRouter enableDefaultURLRouteRule]
开启 URL router。
你可以给 router 注册 url:
class NoteEditorViewRouter: ZIKViewRouter<NoteEditorViewController, ViewRouteConfig> {
override class func registerRoutableDestination() {
//注册 url
registerURLPattern("app://editor/:title")
}
}
Objective-C Sample
@implementation NoteEditorViewRouter
+ (void)registerRoutableDestination {
//注册 url
[self registerURLPattern:@"app://editor/:title"];
}
@end
之后就可以用相应的 url 获取 router:
ZIKAnyViewRouter.performURL("app://editor/test_note", path: .push(from: self))
Objective-C Sample
[ZIKAnyViewRouter performURL:@"app://editor/test_note" path:ZIKViewRoutePath.pushFrom(self)];
以及处理 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<UIApplicationOpenURLOptionsKey,id> *)options {
if ([ZIKAnyViewRouter performURL:urlString fromSource:self.rootViewController]) {
return YES;
} else if ([ZIKAnyServiceRouter performURL:urlString]) {
return YES;
} else {
return NO;
}
}
每个项目对 URL 路由的需求都不一样,你可以按照项目需求实现自己的 URL 路由。基于 ZIKRouter 强大的可扩展性,你可以使用一个自定义的 ZIKRouter 作为父类,重写方法并添加自定义的功能。参考 ZIKRouter+URLRouter.h
。
还有其他功能,可以阅读详细的文档:
- 每个 router 自定义界面跳转,例如首页切换 tabbar
- 支持 storyboard
- 界面跳转的 AOP 回调
- 自定义事件处理
除了界面模块,也可以用 service router 获取普通模块:
///time service 的接口
protocol TimeServiceInput {
func currentTimeString() -> String
}
class TestViewController: UIViewController {
@IBOutlet weak var timeLabel: UILabel!
func callTimeService() {
//获取 TimeServiceInput 模块
let timeService = Router.makeDestination(to: RoutableService<TimeServiceInput>())
//使用service
timeLabel.text = timeService.currentTimeString()
}
}
Objective-C Sample
///time service 的接口
@protocol TimeServiceInput <ZIKServiceRoutable>
- (NSString *)currentTimeString;
@end
@interface TestViewController ()
@property (weak, nonatomic) IBOutlet UILabel *timeLabel;
@end
@implementation TestViewController
- (void)callTimeService {
//获取 TimeServiceInput 模块
id<TimeServiceInput> timeService = [ZIKRouterToService(TimeServiceInput) makeDestination];
self.timeLabel.text = [timeService currentTimeString];
}
ZIKRouter 是为了实践 VIPER 架构而开发的,但是也能用于 MVC、MVVM,并没有任何限制。
打开 Router.xcworkspace
来运行 demo。Demo 中的 ZIKRouterDemo 展示了如何用 ZIKRouter 进行各种界面跳转以及模块获取,并且展示了 Swift 和OC 混编的场景,以及模块解耦的演示。
想要查看 router 是如何应用在 VIPER 架构中的,可以参考这个项目:ZIKViper。
可以用 Xcode 的文件模板快速生成 router 和 protocol 的代码:
模板ZIKRouter.xctemplate
可以在这里获取 Templates。
把ZIKRouter.xctemplate
拷贝到~/Library/Developer/Xcode/Templates/ZIKRouter.xctemplate
,就可以在Xcode -> File -> New -> File -> Templates
中直接使用了。
ZIKRouter is available under the MIT license. See the LICENSE file for more info.