如何避免UITableView重寫大量delegate以及n多if-else判斷和Block

Angelia_瀅發表於2018-06-06

前言

近來換工作,接手一專案,不知如何評價,有優點(一些新技術都要加上去,估計多半隻是前前同事拿來練手),缺點也格外明顯。最明顯的缺點是90%的程式碼堆積在UIViewController中,相信不用多說,各位大佬就秒懂。這樣的程式碼中更多的是if-else堆積,每個if-else判斷中 200~1000行程式碼不等,一個UIViewController中 均值 15個if-else判斷 , 先替自己默哀一分鐘

為了避免n多if-else看的暈頭轉向,針對使用頻率比較高和容易產生大量if-else的UITableView,用Adapter模式+ResonderChain+Strategy模式 來避免每次都要重寫UITableView的delegate和dataSource 和在點選cell以及點選cell中控制元件時造成n個if-else。(Adapter模式是之前一直用的寫法,因為我是個“懶人”,程式碼一行完成的不寫兩行,所以前期一直用Adapter模式。至於ResonderChain和Strategy模式 是參考了casa的文章一種基於ResponderChain的物件互動方式後與Adapter結合)。Example已經託管到全球最大男性交友網站Github點選cloneOrdown(也是最近才聽說這個別稱,好吧,已經被曾經虐我成渣渣的微軟收購),基於更好的理解這三種組合的綜合使用,程式碼中沒有按照傳統的MVC或MVVM來寫,希望大家能更好的理解這些模式和組合,因為任何一個都可以單獨用於不同的場景。

一、Adapter模式 適配UITableView、UITableViewDelegate、UITableViewDataSource

Adapter模式也是常用的設計模式之一,主要是在兩個類或協議之間起到橋樑的作用,具體的官方定義大家可以查閱其它資料。Adapter模式主要有三塊內容:AdapterAdapteeTarget,Adapter就是介面卡,起到中間橋接作用;Adaptee被適配者,Target是適配物件,Adapter的橋接作用就是將把Adaptee適配到Target。如果只是針對單一Adaptee類將其適配到Target稱為類物件適配,針對適配多個Adaptee以及其子類,稱之為物件介面卡。 之前有看《Objective-C程式設計之道》書中提到了該模式,但是中文翻譯版本估計是外行翻譯的,讀起來不太通順,建議看英文原版。

接下來簡單用圖示來說明AdapterUITableView以及其delegatedatasource 之間的關係

Adapter三者關係

簡單來講Adapter起到的作用就是將UITableDelegate和UITableView原來兩個不同的目標能和諧的在一起工作,這一步就是將原來我們可能會在View、自定義的TableView 或者像接手專案中ViewController中進行的設定都交給了Adapter來適配。在此關係中,UITableView就是Adaptee(被適配者),我們通過Adapter 將UITableViewDelegate(在此關係中是Target)兩者結合起來能夠順利地執行。

到此可以簡單理解Adapter將UITableViewDelegate和UITableView結合的這種做法,那麼如何給我們的Adaptee 即我們的UITableView都被適配上這一Target呢 ,就通過OC中的類目進行設定

- (void)setAdapter:(UITableViewAdapter *)adapter
{
    [self setDelegate:adapter];
    [self setDataSource:adapter];
    [adapter setView:self];
    [self reloadData];
}
複製程式碼

這樣,在專案中只要將建立的UITableView與Adapter關聯便可避免每次重寫大量代理方法。

任何一種模式的應用都有其侷限性,Adapter模式下有些方法還是不可避免的要重寫,如果複雜的頁面需要重寫的代理方法會增多,所以如果大家要用這種模式,Adapter中儘量考慮多種情況。

另外,Adapter模式下仍舊不可避免會宣告出一些Block以供外界呼叫,如何解耦呢?

二、基於ResponderChain傳遞點選事件 在此,解耦的出發點是考慮如何消除類與類之間的相互引用即可讓事件傳遞出去。iOS中繼承自UIResponse的控制元件會形成一棵響應樹,頂層是UIApplication。所有在這棵響應樹上的控制元件事件可以通過其中的一條線路傳遞到頂層。結合這一特性,我們便可將原來宣告的Block砍掉,換成響應事件傳遞。要想事件沿著整個Responder傳遞,我們同樣需要給UIResponse統一設定

- (void)rountEvent:(NSString *)eventName userInfo:(NSDictionary *)userInfo
{
    [[self nextResponder] rountEvent:eventName userInfo:userInfo];
}
複製程式碼

在原來需要傳遞Block或設定代理的地方新增rountEvent:(NSString *)eventName userInfo:(NSDictionary *)userInfo,比如在cell的點選事件中新增

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    
    [_tableView rountEvent:kCCellSeletedEventName userInfo:@{@"indexPath":indexPath}];
    
}
複製程式碼

這種傳遞模式可以應用到我們平常使用的其它模式或場景中。

三、Strategy模式避免if-else

策略模式通常把一個系列的演算法包裝到一系列的策略類裡面,這裡我們包裝的不是演算法,而是NSInvocation 提到NSInvocation研究過訊息轉發機制的同學應該對此並不陌生,為什麼選擇包裝NSInvocation,因為我們最終會迴歸到方法事件呼叫。如果沒有研究過訊息轉發機制,那麼大家對這行程式碼相比熟悉的不能再熟悉了

[button addTarget:self action:@selector(<#selector#>) forControlEvents:UIControlEventTouchUpInside]
複製程式碼

這行程式碼實際上最終會建立一NSInvocaion,明白了為何我們要包裝的是NSInvocation之後,接下來可以選擇根據不同的eventName建立不同的NSInvocation

- (NSDictionary *)strategyDictionary{
    
    NSDictionary *strategyDictionary = @{kCCellSeletedEventName:[self createInvocationWithSeletor:@selector(jumpToController:)]};
    return strategyDictionary;
    
}
複製程式碼

將點選Cell對應的NSInvocation放在一個字典中,在控制器處理事件時再根據不同的事件名稱取出對應的NSInvocation分別執行

- (void)rountEvent:(NSString *)eventName userInfo:(NSDictionary *)userInfo
{
    NSInvocation *invocation = [self strategyDictionary][eventName];
    [invocation setArgument:&userInfo atIndex:2];
    [invocation invoke];
}
複製程式碼

這樣在點選Cell的時候我們定義的方法就會執行了,如果事件處理增多,那麼可以在字典中增加相應的NSInvocation

至於建立NSInvocation,可以單獨宣告出去

- (NSInvocation *)createInvocationWithSeletor:(SEL)seletorname
{
    
    NSInvocation *invocaion = [NSInvocation invocationWithMethodSignature:[[self class] instanceMethodSignatureForSelector:seletorname]];
    [invocaion setTarget:self];
    [invocaion setSelector:seletorname];
    return invocaion;
    
}
複製程式碼

基於ResponserChain的互動方式還可以結合結合其它模式,大家可以去casa的部落格中看。

得益於這兩種模式,一種傳遞方式,改了接手專案很多冗餘的程式碼和大量的if-else,寫成博文,希望對大家有所幫助。

相關文章