Skip to content

Latest commit

 

History

History
238 lines (193 loc) · 9.56 KB

ModuleAdapter.md

File metadata and controls

238 lines (193 loc) · 9.56 KB

模块适配器

如果你不想让模块的调用者和模块都使用同一个 protocol,可以用模块适配彻底把两个模块解耦。此时即便模块间有相互依赖的情况,也可以让每个模块各自单独编译。

Provided protocolRequired protocol

你可以为同一个 router 注册多个 protocol。模块本身提供的接口是provided protocol,模块的调用者使用的接口是required protocol

在 UML 的组件图中,就很明确地表现出了这两者的概念。下图中的半圆就是Required Interface,框外的圆圈就是Provided Interface

组件图

那么如何实施Required InterfaceProvided Interface?在我的这篇文章iOS VIPER架构实践(二):VIPER详解与实现里有详细讲解过,应该由 App Context 在一个 adapter 里进行接口适配,从而使得调用者可以继续在内部使用Required Interface,adapter 负责把Required Interface和修改后的Provided Interface进行适配。

这时候,调用者中的required protocol就相当于是在声明自己所依赖的模块。

Provided模块添加Required Interface

用 category、extension、proxy 类为模块添加required protocol,工作全部由模块的使用和装配者 App Context 完成。

例如,某个界面A需要展示一个登陆界面,而且这个登陆界面可以显示一段自定义的提示语。

调用者模块示例:

protocol ModuleARequiredLoginViewInput {
  var message: String? { get set } //显示在登陆界面上的自定义提示语
}
//Module A中调用Login模块
Router.perform(
    to RoutableView<ModuleARequiredLoginViewInput>(),
    path: .presentModally(from: self)
    configuring { (config, _) in
        config.prepareDestination = { destination in
            destination.message = "请登录查看笔记详情"
        }
    })
Objective-C示例
@protocol ModuleARequiredLoginViewInput <ZIKViewRoutable>
@property (nonatomic, copy) NSString *message;
@end

//Module A 中调用 Login 模块
[ZIKRouterToView(ModuleARequiredLoginViewInput)
	          performPath:ZIKViewRoutePath.presentModallyFrom(self)
	          configuring:^(ZIKViewRouteConfiguration *config) {
	              //配置目的界面
	              config.prepareDestination = ^(id<ModuleARequiredLoginViewInput> destination) {
	                  destination.message = @"请登录查看笔记详情";
	              };
	          }];

ZIKViewAdapterZIKServiceAdapter专门负责为其他 router 添加 protocol。

在宿主 App Context 中让登陆模块支持ModuleARequiredLoginViewInput

//登陆界面提供的接口
protocol ProvidedLoginViewInput {
   var notifyString: String? { get set }
}
//由App Context 实现,让登陆界面支持 ModuleARequiredLoginViewInput
class LoginViewAdapter: ZIKViewRouteAdapter {
    override class func registerRoutableDestination() {
        //如果可以获取到 router 类,可以直接为 router 添加 ModuleARequiredLoginViewInput
        LoginViewRouter.register(RoutableView<ModuleARequiredLoginViewInput>())
        //如果不能得到对应模块的 router,可以注册 adapter
        register(adapter: RoutableView<ModuleARequiredLoginViewInput>(), forAdaptee: RoutableView<ProvidedLoginViewInput>())
    }
}

extension LoginViewController: ModuleARequiredLoginViewInput {
    var message: String? {
        get {
            return notifyString
        }
        set {
            notifyString = newValue
        }
    }
}
Objective-C示例
//Login Module Provided Interface
@protocol ProvidedLoginViewInput <NSObject>
@property (nonatomic, copy) NSString *notifyString;
@end
//LoginViewAdapter.h,ZIKViewRouteAdapter 的子类
@interface LoginViewAdapter : ZIKViewRouteAdapter
@end

//LoginViewAdapter.m
@implementation LoginViewAdapter

+ (void)registerRoutableDestination {
	//如果可以获取到 router 类,可以直接为 router 添加 ModuleARequiredLoginViewInput
	[LoginViewRouter registerViewProtocol:ZIKRoutable(ModuleARequiredLoginViewInput)];
	//如果不能得到对应模块的 router,可以注册 adapter
	[self registerDestinationAdapter:ZIKRoutable(ModuleARequiredLoginViewInput) forAdaptee:ZIKRoutable(ProvidedLoginViewInput)];
}

@end

//用Objective-C的 category、Swift 的 extension 进行接口适配
@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

用中介者转发接口

如果不能直接为模块添加required protocol,比如 protocol 里的一些 delegate 需要兼容:

protocol ModuleARequiredLoginViewDelegate {
    func didFinishLogin() -> Void
}
protocol ModuleARequiredLoginViewInput {
  var message: String? { get set }
  var delegate: ModuleARequiredLoginViewDelegate { get set }
}
Objective-C示例
@protocol ModuleARequiredLoginViewDelegate <NSObject>
- (void)didFinishLogin;
@end

@protocol ModuleARequiredLoginViewInput <ZIKViewRoutable>
@property (nonatomic, copy) NSString *message;
@property (nonatomic, weak) id<ModuleARequiredLoginViewDelegate> delegate;
@end

而模块里的 delegate 接口不一样:

protocol ProvidedLoginViewDelegate {
    func didLogin() -> Void
}
protocol ProvidedLoginViewInput {
  var notifyString: String? { get set }
  var delegate: ProvidedLoginViewDelegate { get set }
}
Objective-C示例
@protocol ProvidedLoginViewDelegate <NSObject>
- (void)didLogin;
@end

@protocol ProvidedLoginViewInput <NSObject>
@property (nonatomic, copy) NSString *notifyString;
@property (nonatomic, weak) id<ProvidedLoginViewDelegate> delegate;
@end

相同方法有不同参数类型时,可以用一个新的 router 代替真正的 router,在新的 router 里插入一个中介者,负责转发接口:

class ModuleAReqiredLoginViewRouter: ZIKViewRouter {
   override class func registerRoutableDestination() {
       registerView(/* proxy 类*/);
       register(RoutableView<ModuleARequiredLoginViewInput>())
   }
   override func destination(with configuration: ZIKViewRouteConfiguration) -> ModuleARequiredLoginViewInput? {
       let realDestination: ProvidedLoginViewInput = LoginViewRouter.makeDestination()
       //proxy 负责把 ModuleARequiredLoginViewInput 转发为 ProvidedLoginViewInput
       let proxy: ModuleARequiredLoginViewInput = ProxyForDestination(realDestination)
       return proxy
   }
}
Objective-C示例
@implementation ModuleARequiredLoginViewRouter
+ (void)registerRoutableDestination {
	//注册 ModuleARequiredLoginViewInput,和新的ModuleARequiredLoginViewRouter 配对,而不是目的模块中的 LoginViewRouter
	[self registerView:/* proxy 类*/];
	[self registerViewProtocol:ZIKRoutable(ModuleARequiredLoginViewInput)];
}
- (id)destinationWithConfiguration:(ZIKViewRouteConfiguration *)configuration {
   //用 LoginViewRouter 获取真正的 destination
   id<ProvidedLoginViewInput> realDestination = [LoginViewRouter makeDestination];
    //proxy 负责把 ModuleARequiredLoginViewInput 转发为 ProvidedLoginViewInput
    id<ModuleARequiredLoginViewInput> proxy = ProxyForDestination(realDestination);
    return mediator;
}
@end

对于普通类,proxy 可以用 NSProxy 来实现。对于 UIKit 中的那些复杂的 UI 类,可以用子类,然后在子类中重写方法,进行模块适配。

模块化

区分了required protocolprovided protocol后,就可以实现真正的模块化。在调用者声明了所需要的required protocol后,被调用模块就可以随时被替换成另一个相同功能的模块。

参考 demo 中的ZIKLoginModule示例模块,登录模块依赖于一个弹窗模块,而这个弹窗模块在ZIKRouterDemoZIKRouterDemo-macOS中是不同的,而在切换弹窗模块时,登录模块中的代码不需要做任何改变。

什么时候应该使用 adapter

一般来说,并不需要立即把所有的 protocol 都分离为required protocolprovided protocol。调用模块和目的模块可以暂时共用 protocol,或者只是简单地改个名字,让required protocol作为provided protocol的子集,在第一次需要替换模块的时候再用 category、extension、proxy、subclass 等技术进行接口适配。

接口适配也不能滥用,因为成本比较高。对于模块间耦合的处理,有这么几条建议:

  • 如果是特定功能模块间的互相依赖,直接引用类即可
  • 如果是依赖某些简单的通用模块(例如日志模块),可以在模块的接口上把依赖交给外部来设置
  • 大部分需要解耦的模块都是需要重用的业务模块,如果你的模块不需要重用,直接引用对应类即可
  • 只有在你的业务模块的确允许使用者使用不同的依赖模块时,才进行多个接口间的适配。例如需要跨平台的模块,例如登录模块允许不同的 app 使用不同的登陆 service 模块

通过required protocolprovided protocol,就可以实现模块间的完全解耦。