If you don't want to depend same protocol in module and module's user, use adapter to make totally decouple.
A router can register with multi protocols. The protocol provided by module itself is the provided protocol
. The protocol used inside the module's user is the required protocol
。
See Required Interface
and Provided Interface
in component diagramscomponent diagrams:
Read VIPER architecture to get more details about implementing Required Interface
and Provided Interface
.
App Context is responsible for adapting interfaces. The module's user uses Required Interface
, and the adapter forwards Required Interface
to Provided Interface
.
Add required protocol
for module with category and extension in app context.
For example, a module A needs to show a login view, and the login view can display a custom tip.
Module A:
protocol ModuleARequiredLoginViewInput {
var message: String? { get set } //Message displayed on login view
}
//Show login view in module A
Router.perform(
to RoutableView<ModuleARequiredLoginViewInput>(),
path: .presentModally(from: self)
configuring { (config, _) in
config.prepareDestination = { destination in
destination.message = "Please login to read this note"
}
})
Objective-C Sample
@protocol ModuleARequiredLoginViewInput <ZIKViewRoutable>
@property (nonatomic, copy) NSString *message;
@end
//Show login view in module A
[ZIKRouterToView(ModuleARequiredLoginViewInput)
performPath:ZIKViewRoutePath.presentModallyFrom(self)
configuring:^(ZIKViewRouteConfiguration *config) {
config.prepareDestination = ^(id<ModuleARequiredLoginViewInput> destination) {
destination.message = @"Please login to read this note";
};
}];
ZIKViewAdapter
and ZIKServiceAdapter
are responsible for registering protocols for other router.
Make login view support ModuleARequiredLoginViewInput
:
//Login Module Provided Interface
protocol ProvidedLoginViewInput {
var notifyString: String? { get set }
}
//Write in app context, make ZIKEditorViewRouter supports ModuleARequiredLoginViewInput
class LoginViewAdapter: ZIKViewRouteAdapter {
override class func registerRoutableDestination() {
//If you can get the router, you can just register ModuleARequiredLoginViewInput to it
ZIKEditorViewRouter.register(RoutableView<ModuleARequiredLoginViewInput>())
//If you don't know the router, you can use adapter
register(adapter: RoutableView<ModuleARequiredLoginViewInput>(), forAdaptee: RoutableView<ProvidedLoginViewInput>())
}
}
extension LoginViewController: ModuleARequiredLoginViewInput {
var message: String? {
get {
return notifyString
}
set {
notifyString = newValue
}
}
}
Objective-C Sample
//Login Module Provided Interface
@protocol ProvidedLoginViewInput <NSObject>
@property (nonatomic, copy) NSString *notifyString;
@end
//LoginViewAdapter.h
@interface LoginViewAdapter : ZIKViewRouteAdapter
@end
//LoginViewAdapter.m
@implementation LoginViewAdapter
+ (void)registerRoutableDestination {
//If you can get the router, you can just register ModuleARequiredLoginViewInput to it
[ZIKEditorViewRouter registerViewProtocol:ZIKRoutable(ModuleARequiredLoginViewInput)];
//If you don't know the router, you can use adapter
[self registerDestinationAdapter:ZIKRoutable(ModuleARequiredLoginViewInput) forAdaptee:ZIKRoutable(ProvidedLoginViewInput)];
}
@end
//Make LoginViewController support ModuleARequiredLoginViewInput
@interface LoginViewController (ModuleAAdapter) <ModuleARequiredLoginViewInput>
@property (nonatomic, copy) NSString *message;
@end
@implementation LoginViewController (ModuleAAdapter)
- (void)setMessage:(NSString *)message {
self.notifyString = message;
}
- (NSString *)message {
return self.notifyString;
}
@end
If you can't add required protocol
for module, for example, the delegate type in protocol is different:
protocol ModuleARequiredLoginViewDelegate {
func didFinishLogin() -> Void
}
protocol ModuleARequiredLoginViewInput {
var message: String? { get set }
var delegate: ModuleARequiredLoginViewDelegate { get set }
}
Objective-C Sample
@protocol ModuleARequiredLoginViewDelegate <NSObject>
- (void)didFinishLogin;
@end
@protocol ModuleARequiredLoginViewInput <ZIKViewRoutable>
@property (nonatomic, copy) NSString *message;
@property (nonatomic, weak) id<ModuleARequiredLoginViewDelegate> delegate;
@end
Delegate is different in provided module:
protocol ProvidedLoginViewDelegate {
func didLogin() -> Void
}
protocol ProvidedLoginViewInput {
var notifyString: String? { get set }
var delegate: ProvidedLoginViewDelegate { get set }
}
Objective-C Sample
@protocol ProvidedLoginViewDelegate <NSObject>
- (void)didLogin;
@end
@protocol ProvidedLoginViewInput <NSObject>
@property (nonatomic, copy) NSString *notifyString;
@property (nonatomic, weak) id<ProvidedLoginViewDelegate> delegate;
@end
In this situation, you can create a new router to forward the real router, and return a proxy for the real destination:
class ModuleAReqiredEditorViewRouter: ZIKViewRouter {
override class func registerRoutableDestination() {
registerView(/*proxy class*/)
register(RoutableView<ModuleARequiredLoginViewInput>())
}
override func destination(with configuration: ZIKViewRouteConfiguration) -> ModuleARequiredLoginViewInput? {
//Get real destination with ProvidedLoginViewInput's router
let realDestination: ProvidedLoginViewInput = ZIKEditorViewRouter.makeDestination()
//Proxy is responsible for forwarding ModuleARequiredLoginViewInput to ProvidedLoginViewInput
let proxy: ModuleARequiredLoginViewInput = ProxyForDestination(realDestination)
return proxy
}
}
Objective-C Sample
@implementation ZIKModuleARequiredEditorViewRouter
+ (void)registerRoutableDestination {
//Register ModuleARequiredLoginViewInput with ZIKModuleARequiredEditorViewRouter
[self registerView:/* proxy class*/];
[self registerViewProtocol:ZIKRoutable(NoteListRequiredNoteEditorProtocol)];
}
- (id)destinationWithConfiguration:(ZIKViewRouteConfiguration *)configuration {
//Get real destination with ProvidedLoginViewDelegate's router
id<ProvidedLoginViewInput> realDestination = [ZIKEditorViewRouter makeDestination];
//Proxy is responsible for forwarding ModuleARequiredLoginViewInput to ProvidedLoginViewInput
id<ModuleARequiredLoginViewInput> proxy = ProxyForDestination(realDestination);
return proxy;
}
@end
For simple objc classes, you can use NSProxy to create a proxy. For those complex classes such as UIViewController in UIKit, you can subclass the UIViewController, and override methods to adapt interface.
You don't have to always separate requiredProtocol
and providedProtocol
. It's ok to use the same protocol in module and its user. Change it only when you need it.