不使用MVC的理由
MVP的理解
在幾年前還寫著ASP.Net 享受著拖控件帶來的便捷灯荧,幾分鐘便能能做出一個網(wǎng)頁纹蝴,但隨便業(yè)務(wù)的發(fā)展對需求的變更海铆,界面越來越復(fù)雜迹恐,以至于在Page中產(chǎn)生大量無用的歷史代碼,維護困難卧斟,加班殴边,績效上不去,工資更上不去珍语;
后來ASP.Net MVC的出現(xiàn), UI可以和Controller分離锤岸,M負責(zé)處理業(yè)務(wù)員,一個Razor可以無痛替換板乙,一段時間里覺得自己碼力大漲是偷;
隨后轉(zhuǎn)到iOS平臺發(fā)展,深刻替換到App store過審之難募逞,往往業(yè)務(wù)長期落后于需求蛋铆;在編碼階段需要更快更好的完成,在MVC模型中,M中可能同時處理VC的請求凡辱;業(yè)務(wù)往往在C中變得無法維護戒职,需要開發(fā)者有良好的代碼追求,才能抽離出測試單元透乾;
再后來Android也需要參與開發(fā)洪燥,在Android的接觸到MVP這種設(shè)計模式,同時體會也對全OO語言的DI乳乌,AOP這種大殺器捧韵;
MVP一個重要的體會就是,交互是單向的汉操,也就是沒有MVC中各種依賴關(guān)系再来,而Android憑借依賴翻轉(zhuǎn)可以根據(jù)業(yè)務(wù)需求自動創(chuàng)建IPresenter,而IPresenter都是一個個的業(yè)務(wù)接口,不關(guān)心輸出磷瘤,只輸出芒篷,在TDD開發(fā)中,無疑能更快更準確的找到潛在的BUG
當(dāng)然這里有還一個MVVM模式采缚,這里需要一個UI響應(yīng)的框架针炉,考慮到手下開發(fā)水平參差不齊。在沒有吃透如ReactCocoas或者RxJava這種類庫下扳抽,牟然上MVVM反而會寫出更加不可維護的代碼
Presenter的創(chuàng)建
在iOS中篡帕,IPresenter使用Protocol作為定義殖侵,創(chuàng)建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;
}
這樣我們就能根據(jù)不同的業(yè)務(wù)環(huán)境“new”出一個不同的Presenter
但是隨之而來的一個問題拢军,如果業(yè)務(wù)環(huán)境也是動態(tài)變化的,createPresenterWithProtocol
這個方法就會在多個地方調(diào)用怔鳖。
一個有追求的調(diào)庫仔不能忍
NSProxy
既然需要"幫"controller動態(tài)切換presenter, 第一時間就應(yīng)該想到的是代理模式
OC里面就自帶一個NSProxy幫我們轉(zhuǎn)發(fā)消息
關(guān)于NSProxy objc與鴨子對象
就像平時實現(xiàn)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];
}
監(jiān)聽配置茉唉,動態(tài)切換Presenter
Proxy已經(jīng)準備好了,下面到切換
監(jiān)聽的對象的方式隨便败砂,這里使用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這個來說可以是可有可無的赌渣,但是作為一個有追求的調(diào)庫仔,new 這種粗活自動化來做好了昌犹,也不必關(guān)心EVODynmaicPresenter的子類(比如某些能AOP的DynmaicPresenter
注入的時機在viewDidLoad就好了,再來一發(fā)swizzle
需要注意的是NSProxy沒有+(void)load
和+(void)initialize
在DynamicPresenter.m中創(chuàng)建一個DynamicPresenterInject
的內(nèi)部類
DynamicPresenterInject
這個類幫我們實現(xiàn)替換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創(chuàng)建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調(diào)用P的時候铸敏,都會經(jīng)過Proxy的forwardingTargetForSelector
。
在這里我記錄的調(diào)用過程悟泵,crash的時候迅速找到crash的流程然后打包JSPath
LOG_INFO(self.protocolName.UTF8String, @"%@",NSStringFromSelector(selector));
不足
DynamicProxy
還有一個需要注意的地方線程安全
在 替換時和調(diào)用時都必須注意同步杈笔,不要陰溝里翻船