iOS關(guān)于Presenter的思考

不使用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)用時都必須注意同步杈笔,不要陰溝里翻船

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市糕非,隨后出現(xiàn)的幾起案子蒙具,更是在濱河造成了極大的恐慌,老刑警劉巖朽肥,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件禁筏,死亡現(xiàn)場離奇詭異,居然都是意外死亡衡招,警方通過查閱死者的電腦和手機篱昔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來始腾,“玉大人州刽,你說我怎么就攤上這事±思” “怎么了穗椅?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長山林。 經(jīng)常有香客問我房待,道長邢羔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任桑孩,我火速辦了婚禮拜鹤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘流椒。我一直安慰自己敏簿,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布宣虾。 她就那樣靜靜地躺著惯裕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绣硝。 梳的紋絲不亂的頭發(fā)上蜻势,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音鹉胖,去河邊找鬼握玛。 笑死,一個胖子當(dāng)著我的面吹牛甫菠,可吹牛的內(nèi)容都是我干的挠铲。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼寂诱,長吁一口氣:“原來是場噩夢啊……” “哼拂苹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起痰洒,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤瓢棒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后带迟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體音羞,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年仓犬,在試婚紗的時候發(fā)現(xiàn)自己被綠了嗅绰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡搀继,死狀恐怖窘面,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情叽躯,我是刑警寧澤财边,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站点骑,受9級特大地震影響酣难,放射性物質(zhì)發(fā)生泄漏谍夭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一憨募、第九天 我趴在偏房一處隱蔽的房頂上張望紧索。 院中可真熱鬧,春花似錦菜谣、人聲如沸珠漂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽媳危。三九已至,卻和暖如春冈敛,著一層夾襖步出監(jiān)牢的瞬間待笑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工抓谴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留滋觉,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓齐邦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親第租。 傳聞我的和親對象是個殘疾皇子措拇,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容