不使用MVC的理由
MVP的理解
在几年前还写着ASP.Net 享受着拖控件带来的便捷,几分钟便能能做出一个网页,但随便业务的发展对需求的变更,界面越来越复杂,以至于在Page中产生大量无用的历史代码,维护困难,加班,绩效上不去,工资更上不去;
后来ASP.Net MVC的出现, UI可以和Controller分离,M负责处理业务员,一个Razor可以无痛替换,一段时间里觉得自己码力大涨;
随后转到iOS平台发展,深刻替换到App store过审之难,往往业务长期落后于需求;在编码阶段需要更快更好的完成,在MVC模型中,M中可能同时处理VC的请求;业务往往在C中变得无法维护,需要开发者有良好的代码追求,才能抽离出测试单元;
再后来Android也需要参与开发,在Android的接触到MVP这种设计模式,同时体会也对全OO语言的DI,AOP这种大杀器;
MVP一个重要的体会就是,交互是单向的,也就是没有MVC中各种依赖关系,而Android凭借依赖翻转可以根据业务需求自动创建IPresenter,而IPresenter都是一个个的业务接口,不关心输出,只输出,在TDD开发中,无疑能更快更准确的找到潜在的BUG
当然这里有还一个MVVM模式,这里需要一个UI响应的框架,考虑到手下开发水平参差不齐。在没有吃透如ReactCocoas或者RxJava这种类库下,牟然上MVVM反而会写出更加不可维护的代码
Presenter的创建
在iOS中,IPresenter使用Protocol作为定义,创建P首先想到的是抽象工厂;在ASP.Net 中有一种'约定大于配置'的说法,所以不难写出如下代码
+ (id)createPresenterWithProtocol:(Protocol *)protocol
NSString *protocolName = NSStringFromProtocol(protocol);
NSMutableString *temp = [protocolName stringByReplacingOccurrencesOfString:@"Delegate" withString:@""].mutableCopy;
if (infix) {
NSMutableString *temp2 = temp.mutableCopy;
[temp2 insertString:infix atIndex:3];
NSString *presenterClassName = temp2.copy;
presneterClass = NSClassFromString(presenterClassName);
}
if(!presneterClass) {
//try common
NSString *presenterClassName = temp.copy;
presneterClass = NSClassFromString(presenterClassName);
}
id presenter = [[presneterClass alloc] init];
if (!presenter) {
assert(presenter!=nil);
}
return presenter;
}
这样我们就能根据不同的业务环境“new”出一个不同的Presenter
但是随之而来的一个问题,如果业务环境也是动态变化的,createPresenterWithProtocol
这个方法就会在多个地方调用。
一个有追求的调库仔不能忍
NSProxy
既然需要"帮"controller动态切换presenter, 第一时间就应该想到的是代理模式
OC里面就自带一个NSProxy帮我们转发消息
关于NSProxy objc与鸭子对象
就像平时实现WeakProxy防止leak一样
把实际干活的target包起来(这里取一个霸气的DynamicPresenter)
@implementation DynamicPresenter
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
- (id)forwardingTargetForSelector:(SEL)selector {
return self.target;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [self.target respondsToSelector:aSelector];
}
- (BOOL)isEqual:(id)object {
return [self.target isEqual:object];
}
- (NSUInteger)hash {
return [self.target hash];
}
- (Class)superclass {
return [self.target superclass];
}
- (Class)class {
return [self.target class];
}
- (BOOL)isKindOfClass:(Class)aClass {
return [self.target isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
return [self.target isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return [self.target conformsToProtocol:aProtocol];
}
- (BOOL)isProxy {
return YES;
}
- (NSString *)description {
return [self.target description];
}
- (NSString *)debugDescription {
return [self.target debugDescription];
}
监听配置,动态切换Presenter
Proxy已经准备好了,下面到切换
监听的对象的方式随便,这里使用NotifyCenter
.....
- (instancetype)initWithTarget:(id)target {
_target = target;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(productNameChange) name:kEVOProductInfo object:nil];
return self;
}
......
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
注入DI
DI这个来说可以是可有可无的,但是作为一个有追求的调库仔,new 这种粗活自动化来做好了,也不必关心EVODynmaicPresenter的子类(比如某些能AOP的DynmaicPresenter
注入的时机在viewDidLoad就好了,再来一发swizzle
需要注意的是NSProxy没有+(void)load
和+(void)initialize
在DynamicPresenter.m中创建一个DynamicPresenterInject
的内部类
DynamicPresenterInject
这个类帮我们实现替换viewDidLoad
@interface PresenterInject : NSObject
@end
@implementation PresenterInject
+ (void)load {
Class currentClass = [UIViewController class];
[self swizzleVideDidLoadMethodForClass:currentClass];
}
static inline void _swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
static inline BOOL _addMethod(Class theClass, SEL selector, Method method) {
return class_addMethod(theClass, selector, method_getImplementation(method), method_getTypeEncoding(method));
}
+ (void)swizzleVideDidLoadMethodForClass:(Class)theClass {
Method afResumeMethod = class_getInstanceMethod(self, @selector(_viewDidLoad));
if (_addMethod(theClass, @selector(_viewDidLoad), afResumeMethod)) {
_swizzleSelector(theClass, @selector(viewDidLoad), @selector(_viewDidLoad));
}
}
_viewDidLoad
_viewDidLoad
要做的工作很简单,遍历ivar,找到DynamicPresenter
,解析DynamicPresenter
的Protocol,再通过Protocol创建Presenter
- (void)evo_viewDidLoad {
if ([self isKindOfClass:[UINavigationController class]]
|| [self isKindOfClass:[RDVTabBarController class]]) {
return;
}
unsigned int outCount = 0;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (unsigned int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
const char * name = ivar_getName(ivar);
const char * type = ivar_getTypeEncoding(ivar);
NSString *ocTypeString = [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
NSString *ocNameString = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
if ([ocTypeString containsString:[NSString stringWithFormat:@"%@",[EVODynamicPresenter class]]]) {
Rx* rx = [NSRegularExpression rx:@"<.+>"];
NSString *matchProtocolName = [[[rx firstMatch:ocTypeString] stringByReplacingOccurrencesOfString:@"<" withString:@""] stringByReplacingOccurrencesOfString:@">" withString:@""];
Protocol *protocol = objc_getProtocol(matchProtocolName.UTF8String);
id actualyPresenter = [EVOPresenterProvider createPresenterWithProtocol:protocol];
EVODynamicPresenter *p = [[EVODynamicPresenter alloc] initWithTarget:actualyPresenter];
p.protocolName = matchProtocolName;
[self setValue:p forKeyPath:ocNameString];
break;
}
}
free(ivars);
}
AOP
花了这么大力气弄个Proxy,最意想不到的地方就是顺便完成了AOP功能。
在V调用P的时候,都会经过Proxy的forwardingTargetForSelector
。
在这里我记录的调用过程,crash的时候迅速找到crash的流程然后打包JSPath
LOG_INFO(self.protocolName.UTF8String, @"%@",NSStringFromSelector(selector));
不足
DynamicProxy
还有一个需要注意的地方线程安全
在 替换时和调用时都必须注意同步,不要阴沟里翻船