Router can use custom configuration to transfer custom parameters.
When the destination class uses custom initializers to create instance, router needs to get required parameter from the caller.
When your module contains multi components, and you need to pass parameters to those components. And those parameters do not belong to the destination.
For example, when you pass a model to a VIPER module, the destination is the view in VIPER, and the view is not responsible for accepting any models.
You need a module config protocol to store them in configuration, and configure components' dependencies inside the router.
Module protocol is for declaring parameters used by the module, conformed by the configuration of the router.
Instead of EditorViewInput
, we use another routable protocol EditorViewModuleInput
as config protocol for routing:
protocol EditorViewModuleInput: class {
// Factory method for transferring parameters and making destination
var makeDestinationWith: (viewModel: EditorViewModel, _ note: Note) -> EditorViewInput? { get }
}
Objective-C Sample
@protocol EditorViewModuleInput <ZIKViewModuleRoutable>
// Factory method for transferring parameters and making destination
@property (nonatomic, copy, readonly) id<EditorViewInput> _Nullable(^makeDestinationWith)(EditorViewModel *viewModel, Note *note);
@end
In general, a module config protocol only contains makeDestinationWith
, for declaring parameters and destination type. You can also add other properties or methods.
You can use a configuration subclass and store parameters on its properties.
// Configuration subclass conforming to EditorViewModuleInput
class EditorViewModuleConfiguration<T>: ZIKViewMakeableConfiguration<NoteEditorViewController>, EditorViewModuleInput {
// Factory method. User is responsible for calling makeDestinationWith and giving parameters
var makeDestinationWith: (viewModel: EditorViewModel, _ note: Note) -> EditorViewInput? {
return { viewModel, note in
// Prepare the destination
self.__prepareDestination = { destination in
let presenter = EditorPresenter()
let interactor = EditorInteractor()
destination.presenter = presenter
presenter.view = destination
presenter.interactor = interactor
// Pass note to the data manager
interactor.note = note
}
// Capture parameters in makeDestination, so we don't need configuration subclass to hold the parameters
// MakeDestination will be used for creating destination instance
self.makeDestination = { [unowned self] () in
// Use custom initializer, pass view model to view
let destination = NoteEditorViewController(viewModel: viewModel)
return destination
}
if let destination = self.makeDestination?() {
// The router won't make and prepare destination again when perform with this configuration
self.makedDestination = destination
return destination
}
return nil
}
}
}
func makeEditorViewModuleConfiguration() -> ZIKViewMakeableConfiguration<NoteEditorViewController> & EditorViewModuleInput {
return EditorViewModuleConfiguration<Any>()
}
Objective-C Sample
// Configuration subclass conforming to EditorViewModuleInput
@interface EditorViewModuleConfiguration: ZIKViewMakeableConfiguration<NoteEditorViewController *><EditorViewModuleInput>
@end
@implementation EditorViewModuleConfiguration
// Factory method. User is responsible for calling makeDestinationWith and giving parameters
- (id<EditorViewInput> _Nullable(^)(Note *))makeDestinationWith {
return ^id<EditorViewInput> _Nullable(EditorViewModel *viewModel, Note *note) {
// Prepare the destination
self._prepareDestination = ^(NoteEditorViewController *destination) {
EditorPresenter *presenter = [EditorPresenter alloc] init];
EditorInteractor *interactor = [EditorInteractor alloc] init];
destination.presenter = presenter;
presenter.view = destination;
presenter.interactor = interactor;
// Pass note to the data manager
interactor.note = note;
};
// Capture parameters in makeDestination, so we don't need configuration subclass to hold the parameters
// MakeDestination will be used for creating destination instance
self.makeDestination = ^ NoteEditorViewController * _Nullable{
// Use custom initializer, pass view model to view
NoteEditorViewController *destination = [NoteEditorViewController alloc] initWithViewModel:viewModel];
return destination;
};
// Set makedDestination so router will use this destination when performing
self.makedDestination = self.makeDestination();
return self.makedDestination;
};
}
@end
ZIKViewMakeableConfiguration<NoteEditorViewController *> * makeEditorViewModuleConfiguration() {
return [EditorViewModuleConfiguration new];
}
Swift generic class won't be in the __objc_classlist
section of the Mach-O file. So it won't affect the app launching time.
Transferring parameters with makeDestinationWith
block can reduce much glue code. We don't need to store parameters in some properties, just pass them through block.
If the protocol is very simple and you don't need a configuration subclass, or you're using Objective-C and don't want too many subclass, you can choose generic classViewMakeableConfiguration
andZIKViewMakeableConfiguration
:
extension ViewMakeableConfiguration: EditorViewModuleInput where Destination == EditorViewInput, Constructor == (EditorViewModel, Note) -> EditorViewInput? {
}
// ViewMakeableConfiguration with generic arguments works as the same as EditorViewModuleConfiguration
// The config works like EditorViewModuleConfiguration<Any>()
func makeEditorViewModuleConfiguration() -> ViewMakeableConfiguration<EditorViewInput, (EditorViewModel, Note) -> EditorViewInput?> {
let config = ViewMakeableConfiguration<EditorViewInput, (EditorViewModel, Note) -> EditorViewInput?>({ _,_ in})
// Factory method. User is responsible for calling makeDestinationWith and giving parameters
config.makeDestinationWith = { [unowned config] (viewModel, note) in
// Prepare the destination
config._prepareDestination = { destination in
let presenter = EditorPresenter()
let interactor = EditorInteractor()
destination.presenter = presenter
presenter.view = destination
presenter.interactor = interactor
// Pass note to the data manager
interactor.note = note
};
// Capture parameters in makeDestination, so we don't need configuration subclass to hold the parameters
// MakeDestination will be used for creating destination instance
config.makeDestination = { () in
// Use custom initializer, pass view model to view
let destination = NoteEditorViewController(viewModel: viewModel)
return destination
}
if let destination = config.makeDestination?() {
// The router won't make and prepare destination again when perform with this configuration
config.makedDestination = destination
return destination
}
return nil
}
return config
}
Objective-C Sample
Generic classZIKViewMakeableConfiguration
has propertymakeDestinationWith
withid(^)()
type. id(^)()
means the block can accept any parameters. So you can declare your custom parameters of makeDestinationWith
in protocol.
// The config works like EditorViewModuleConfiguration
ZIKViewMakeableConfiguration<NoteEditorViewController *> * makeEditorViewModuleConfiguration(void) {
ZIKViewMakeableConfiguration<NoteEditorViewController *> *config = [ZIKViewMakeableConfiguration<id<EditorViewInput>> new];
__weak typeof(config) weakConfig = config;
// User is responsible for calling makeDestinationWith and giving parameters
config.makeDestinationWith = ^id<EditorViewInput> _Nullable(EditorViewModel *viewModel, Note *note) {
// Prepare the destination
config._prepareDestination = ^(id<EditorViewInput> destination) {
EditorPresenter *presenter = [EditorPresenter alloc] init];
EditorInteractor *interactor = [EditorInteractor alloc] init];
destination.presenter = presenter;
presenter.view = destination;
presenter.interactor = interactor;
// Pass note to the data manager
interactor.note = note;
};
// Capture parameters in makeDestination, so we don't need configuration subclass to hold the parameters
// MakeDestination will be used for creating destination instance
weakConfig.makeDestination = ^ NoteEditorViewController * _Nullable{
// Use custom initializer, pass view model to view
NoteEditorViewController *destination = [NoteEditorViewController alloc] initWithViewModel:viewModel];
return destination;
};
// Set makedDestination so router will use this destination when performing
weakConfig.makedDestination = weakConfig.makeDestination();
if (weakConfig._prepareDestination) {
weakConfig._prepareDestination(weakConfig.makedDestination);
}
return weakConfig.makedDestination;
};
return config;
}
OverridedefaultRouteConfiguration
in router to use your custom configuration:
class EditorViewRouter: ZIKViewRouter<NoteEditorViewController, ZIKViewMakeableConfiguration<NoteEditorViewController>> {
override class func registerRoutableDestination() {
// Register class
registerView(NoteEditorViewController.self)
// Register module config protocol, then we can use this protocol to fetch the router
register(RoutableViewModule<EditorViewModuleInput>())
}
// Use custom configuration
override class func defaultRouteConfiguration() -> ZIKViewMakeableConfiguration<NoteEditorViewController> {
return makeEditorViewModuleConfiguration()
}
override func destination(with configuration: ZIKViewMakeableConfiguration<NoteEditorViewController>) -> NoteEditorViewController? {
if let makeDestination = configuration.makeDestination {
return makeDestination()
}
return nil
}
}
Objective-C Sample
@interface EditorViewRouter: ZIKViewRouter<NoteEditorViewController, ZIKViewMakeableConfiguration<NoteEditorViewController *>>
@end
@implementation EditorViewRouter {
+ (void) registerRoutableDestination {
// Register class
[self registerView:[NoteEditorViewController class]];
// Register module config protocol, then we can use this protocol to fetch the router
[self registerModuleProtocol:ZIKRoutable(EditorViewModuleInput)];
}
// Use custom configuration
+(ZIKViewMakeableConfiguration<NoteEditorViewController *> *)defaultRouteConfiguration() {
return makeEditorViewModuleConfiguration();
}
- (NoteEditorViewController *)destinationWithConfiguration:(ZIKViewMakeableConfiguration<NoteEditorViewController *> *)configuration {
if (configuration.makeDestination) {
return configuration.makeDestination();
}
return nil;
}
}
If you're not using router subclass, you can register config factory to create route:
// Register EditorViewModuleInput and factory function of custom configuration
ZIKAnyViewRouter.register(RoutableViewModule<EditorViewModuleInput>(),
forMakingView: NoteEditorViewController.self,
making: makeEditorViewModuleConfiguration)
Objective-C Sample
// Register EditorViewModuleInput and factory function of custom configuration
[ZIKModuleViewRouter(EditorViewModuleInput)
registerModuleProtocol:ZIKRoutable(EditorViewModuleInput)
forMakingView:[NoteEditorViewController class]
factory: makeEditorViewModuleConfiguration];
ViewMakeableConfiguration
ZIKViewMakeableConfiguration
ServiceMakeableConfiguration
ZIKServiceMakeableConfiguration
all conform toZIKConfigurationMakeable
. The didMakeConfiguration
in these configuration will be automatically invoked.
The user can use the module with its module config protocol and transfer parameters:
var viewModel = ...
var note = ...
Router.makeDestination(to: RoutableViewModule<EditorViewModuleInput>()) { (config) in
// Transfer parameters and get EditorViewInput
let destination = config.makeDestinationWith(note)
}
Objective-C Sample
EditorViewModel *viewModel = ...
Note *note = ...
[ZIKRouterToViewModule(EditorViewModuleInput)
performPath:ZIKViewRoutePath.showFrom(self)
configuring:^(ZIKViewRouteConfiguration<EditorViewModuleInput> *config) {
// Transfer parameters and get EditorViewInput
id<EditorViewInput> destination = config.makeDestinationWith(note);
}];
In this design pattern, we reduce much glue code for transferring parameters, and the module can re-declare their parameters with generic arguments and module config protocol.
You can use the generic configuration to reduce subclass count. And You can also transfer complicated parameters with a configuration subclass, such as multi makeDestinationWith
for multi situations.
protocol EditorViewModuleInput: class {
var makeDestinationWith: (_ viewModel: EditorViewModel, _ note: Note) -> EditorViewInput? { get }
var makeDestinationForNewNoteWith: (_ noteName: String) -> EditorViewInput? { get }
}
extension ViewMakeableConfiguration: EditorViewModuleInput where Destination == EditorViewInput, Constructor == (EditorViewModel, Note) -> EditorViewInput? {
var makeDestinationForNewNoteWith: (String) -> EditorViewInput? {
get {
if let block = self.constructorContainer["makeDestinationForNewNoteWith"] as? (String) -> EditorViewInput? {
return block
}
return { _ in return nil }
}
set {
self.constructorContainer["makeDestinationForNewNoteWith"] = newValue
}
}
}
Objective-C Sample
@protocol EditorViewModuleInput <ZIKViewModuleRoutable>
@property (nonatomic, copy, readonly) id<EditorViewInput> _Nullable(^makeDestinationWith)(EditorViewModel *viewModel, Note *note);
@property (nonatomic, copy, readonly) id<EditorViewInput> _Nullable(^makeDestinationForNewNoteWith)(EditorViewModel *viewModel, Note *note);
@end
@interface ZIKViewMakeableConfiguration (EditorViewModuleInput) <EditorViewModuleInput>
@end
@implementation ZIKViewMakeableConfiguration (EditorViewModuleInput)
- (ZIKMakeBlock)makeDestinationForNewNoteWith {
return self.constructorContainer[@"makeDestinationForNewNoteWith"];
}
- (void)setMakeDestinationForNewNoteWith:(ZIKMakeBlock)block {
self.constructorContainer[@"makeDestinationForNewNoteWith"] = block;
}
@end