iOS关于Presenter的思考

不使用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还有一个需要注意的地方线程安全

在 替换时和调用时都必须注意同步,不要阴沟里翻船

你可能感兴趣的:(iOS关于Presenter的思考)