數(shù)據(jù)顯示和事件處理與controller解耦

5.8更新demo,歡迎拍磚????

問題描述

在開發(fā)應用時络凿,經(jīng)常遇到一個列表多種不同樣式的cell展示的情況,如圖:

WX20170426-182305@2x.png

多種cell就會造成cellForRowAtIndexPath(tableview)大量的if /else,如果再加上顯示數(shù)據(jù)和事件處理簡直是災難,而且不利于以后的擴展秦陋,難以維護蔓彩。

解決方案

1.定義一個協(xié)議

/**
 顯示數(shù)據(jù)協(xié)議
 */
@protocol BFDisplayProtocol <NSObject>
- (void)em_displayWithModel:(BFEventModel *)model;
@end

2.cell中實現(xiàn)BFDisplayProtocol協(xié)議


#pragma mark - BFDisplayProtocol

- (void)em_displayWithModel:(CircleItem *)model {
    self.titleLabel.text = model.circleName;
    self.distanceLabel.text = [NSString stringWithFormat:@"%ldm",model.distance];
}

此處cell無需將子view屬性暴露出來治笨。

3.在CollectionView/TableView代理中調(diào)用顯示數(shù)據(jù)方法

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    BFDCardNode *model = self.dataSources[indexPath.section];
    UICollectionViewCell<BFDisplayProtocol> *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kIdentifier forIndexPath:indexPath];
    [cell em_displayWithModel:model];
    return cell;
}

如此,產(chǎn)品經(jīng)理說列表再加一種cell赤嚼,你就只需要創(chuàng)建新的cell旷赖,然后實現(xiàn)BFDisplayProtocol協(xié)議就行了,甚至CollectionView/TableView代理都不需要修改更卒。這樣做的好處就是減少cell對controller的依賴等孵,將controller中的邏輯分散道每個cell中自己實現(xiàn),減少view對controller的耦合蹂空。最后代理方法cellForItemAtIndexPath看上去非常整潔舒服??俯萌。

現(xiàn)在問題來了??果录,2.中cell對model是有依賴的,也就是說有另一個列表也需要用到這個cell咐熙,而且model不同弱恒,就無法重用此cell了。現(xiàn)在要做的是解除cell對model的依賴棋恼,這時也可以用上面協(xié)議的方法實現(xiàn)返弹,就是為model的每一個屬性生成一個get方法的協(xié)議集合,然后所有的model實現(xiàn)這一個協(xié)議爪飘,在model中實現(xiàn)協(xié)議的方法返回數(shù)據(jù)义起。這種情況當model字段少時可以一試,但是當model屬性很多時师崎,就會出發(fā)大量的協(xié)議方法默终,而且有新的cell共用又要新建大量的共用協(xié)議。所以實現(xiàn)協(xié)議不能很好的解決cell對model的依賴問題抡诞。


問題描述

解決cell對model的依賴

解決方案

既然協(xié)議不能很好的解決該問題穷蛹,那么我們就曲線救國,有一種輕量的解決辦法昼汗,就是利用消息轉(zhuǎn)發(fā)實現(xiàn)肴熏。

1.定義一個model基類BFPropertyExchange

@interface BFPropertyExchange : NSObject
- (NSDictionary *)em_exchangeKeyFromPropertyName;
@end

2.model實現(xiàn)em_exchangeKeyFromPropertyName方法

- (NSDictionary *)em_exchangeKeyFromPropertyName {
    return @{@"name2":@"name",@"icon1":@"icon",@"iconUnselect1":@"iconUnselect"};
}

返回字典代表調(diào)用屬性與本地屬性的映射關(guān)系,cell的調(diào)用屬性是name2顷窒,此時傳入另一個modelA,但是modelA并沒有name2屬性蛙吏,則通過映射關(guān)系自動調(diào)用本地屬性name。

3.消息轉(zhuǎn)發(fā)(最重要的一步)

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return nil;
}

/**
 消息轉(zhuǎn)發(fā)
 
 @param aSelector 方法
 @return 調(diào)用方法的描述
 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    
    NSString *propertyName = NSStringFromSelector(aSelector);
    
    NSDictionary *propertyDic = [self em_exchangeKeyFromPropertyName];
    
    NSMethodSignature* (^doGetMethodSignature)(NSString *propertyName) = ^(NSString *propertyName){
    
        NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
        objc_setAssociatedObject(methodSignature, kPropertyNameKey, propertyName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        
        return  [NSMethodSignature signatureWithObjCTypes:"v@:"];
    };
    
    if ( [propertyDic.allKeys containsObject:propertyName] ) {
        
        NSString *targetPropertyName = [NSString stringWithFormat:@"em_%@",propertyName];
        if ( ![self respondsToSelector:NSSelectorFromString(targetPropertyName)] ) {
            // 如果沒有em_重寫屬性鞋吉,則用model原屬性替換
            targetPropertyName = [propertyDic objectForKey:propertyName];
        }
        
        return doGetMethodSignature(targetPropertyName);
    }
    
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    
    NSString *originalPropertyName = objc_getAssociatedObject(anInvocation.methodSignature, kPropertyNameKey);
    
    if ( originalPropertyName ) {
        anInvocation.selector = NSSelectorFromString(originalPropertyName);
        [anInvocation invokeWithTarget:self];
    }
    
}

此處走的是最后一步的完全消息轉(zhuǎn)發(fā)鸦做,不熟悉消息轉(zhuǎn)發(fā)的同學,我找了一個帖子可以看一下:消息轉(zhuǎn)發(fā)

4.cell中調(diào)用

  • 為了方便調(diào)用谓着,此處給model寫了一個類別泼诱。
@interface NSObject (PropertyExchange)

/**
 調(diào)用替換屬性 Invocation property
 */
@property (nonatomic, copy) id(^em_property)(NSString *propertyName);

@end

@implementation NSObject (PropertyExchange)

#pragma mark - Getter&&Setter

- (id(^)(NSString *))em_property {
    
    __weak typeof(self) weakSelf = self;
    id (^icp_block)(NSString *propertyName) = ^id (NSString *propertyName) {
        __strong typeof(self) strongSelf = weakSelf;
        
        SEL sel = NSSelectorFromString(propertyName);
        if ( !sel ) return nil;
        SuppressPerformSelectorLeakWarning(
                                           return [strongSelf performSelector:NSSelectorFromString(propertyName)];
                                           );
    };
    
    return icp_block;
}

@end
  • 在cell中調(diào)用

#pragma mark - BFDisplayProtocol

- (void)em_displayWithModel:(CircleItem *)model {
    self.titleLabel.text = model.em_property(@"name2");
    ......
}

梳理一下調(diào)用流程:調(diào)用model的name2屬性,通過em_exchangeKeyFromPropertyName方法返回屬性映射關(guān)系找到name赊锚,然后通過消息轉(zhuǎn)發(fā)調(diào)用name屬性治筒。

至此間接了完成cell對model的依賴,如果只是顯示屬性那么已經(jīng)可以重用了舷蒲。那么現(xiàn)在問題又來了??耸袜,如果cell中有事件處理操作,那么就無法重用了牲平?堤框??


問題描述

實現(xiàn)cell中事件處理解耦

解決方案

1.定義點擊事件的協(xié)議

/**
 點擊事件協(xié)議
 */
@protocol BFEventManagerProtocol <NSObject>

- (void)em_didSelectItemWithModel:(BFEventModel *)eventModel;

- (NSString *)em_eventManagerWithPropertName;

@end

2.定義基類BFEventManager并實現(xiàn)BFEventManagerProtocol協(xié)議,然后定義BFEventManager的子類蜈抓,在子類中實現(xiàn)em_didSelectItemWithModel方法启绰。

static const int BFGSpacEventTypeSectionSearch           = 1;// 搜索
static const int BFGSpacEventTypeSectionBack             = 2;// 返回

@interface BFGSpaceEventManager : BFEventManager

@end

@implementation BFGSpaceEventManager

- (void)em_didSelectItemWithModel:(BFEventModel *)eventModel {
    
    NSInteger eventType = eventModel.eventType;
    
    switch ( eventType ) {
        case BFGSpacEventTypeSectionSearch:
        {
            // 搜索
            [BFAnalyticsHelper event:@"GatherPlace_MorePlaceChoice_MoreNearby"];
            
            [[LKGlobalNavigationController sharedInstance] pushViewControllerWithUrLPattern:URL_GS_SEARCH_LIST];
            
        }
            break;
        case BFGSpacEventTypeSectionBack:
        {
            // 返回
            [BFAnalyticsHelper event:@"GatherPlace_Scan"];
          
            [[LKGlobalNavigationController sharedInstance] popPPViewController];
            
        }
            break;
        default:
            break;
    }
}

3.在controller初始化BFEventManager

- (BFEventManager *)eventManager {
    if( !_eventManager ) {
        _eventManager = [[BFGSpaceEventManager alloc] initWithTarget:self];
    }
    return _eventManager;
}

4.在cell中調(diào)用事件處理

- (void)em_displayWithModel:(CircleItem *)model {
    @weakify(self)
    [self.button addActionHandler:^(NSInteger tag) {
        @normalize(self)
        [self.eventManager em_didSelectItemWithModel:model];
    }];
    ......
}

以上中eventManager定義一個類別來獲取,通過runtime實現(xiàn)獲取eventManager沟使,代碼如下:

- (BFEventManager *)eventManager {
    
    BFEventManager *tempEventManager = objc_getAssociatedObject(self, kEventManagerKey);
    if ( !tempEventManager ) {
        
        UIViewController<BFEventManagerProtocol> *controller = (UIViewController<BFEventManagerProtocol> *)self.em_viewController;
        
        if ( [controller respondsToSelector:@selector(em_eventManagerWithPropertName)]) {
            
            NSString *propertyName = [controller em_eventManagerWithPropertName];
            
            tempEventManager =  [controller valueForKey:propertyName];
            
        } else {
        
            unsigned int propertCount = 0;
            objc_property_t *properts = class_copyPropertyList(controller.class, &propertCount);
            for (int i = 0; i < propertCount; i++) {
                objc_property_t property = properts[i];
                NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
//                NSString *property_Attributes = [NSString stringWithUTF8String:property_getAttributes(property)];

                id tempPropert = [controller valueForKey:propertyName];
                if ( tempPropert && [tempPropert isKindOfClass:[BFEventManager class]] ) {
                    tempEventManager =  tempPropert;
                    break;
                }
            }
            free(properts);
        }
        
        objc_setAssociatedObject(self, kEventManagerKey, tempEventManager, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    return tempEventManager;
}

現(xiàn)在將cell中的事件處理交由EventManager處理酬土,如果重用cell,只需傳入不同的eventType格带,然后在EventManager的子類中根據(jù)不同的eventType做相應的處理撤缴。這樣cell就可以完全重用了,而且頁面的事件做到了統(tǒng)一管理叽唱,相同的事件處理還可以重用屈呕。實際項目中還體會了統(tǒng)一管理的好處,就是當別人還去繁雜的頁面去尋找事件設置埋點時棺亭,而你卻只需要優(yōu)雅的打開EventManager設置埋點了虎眨。

以上就算是拋磚引玉吧,排版有點亂镶摘,代碼可以在這里找到嗽桩,如果覺得有幫助順便加個??,謝謝????凄敢。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碌冶,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子涝缝,更是在濱河造成了極大的恐慌扑庞,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拒逮,死亡現(xiàn)場離奇詭異罐氨,居然都是意外死亡,警方通過查閱死者的電腦和手機滩援,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進店門栅隐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人玩徊,你說我怎么就攤上這事租悄。” “怎么了佣赖?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵恰矩,是天一觀的道長记盒。 經(jīng)常有香客問我憎蛤,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任俩檬,我火速辦了婚禮萎胰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘棚辽。我一直安慰自己技竟,他們只是感情好,可當我...
    茶點故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布屈藐。 她就那樣靜靜地躺著榔组,像睡著了一般。 火紅的嫁衣襯著肌膚如雪联逻。 梳的紋絲不亂的頭發(fā)上搓扯,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天,我揣著相機與錄音包归,去河邊找鬼锨推。 笑死,一個胖子當著我的面吹牛公壤,可吹牛的內(nèi)容都是我干的换可。 我是一名探鬼主播,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼厦幅,長吁一口氣:“原來是場噩夢啊……” “哼沾鳄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起确憨,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤洞渔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后缚态,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體磁椒,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年玫芦,在試婚紗的時候發(fā)現(xiàn)自己被綠了浆熔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡桥帆,死狀恐怖医增,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情老虫,我是刑警寧澤叶骨,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站祈匙,受9級特大地震影響忽刽,放射性物質(zhì)發(fā)生泄漏天揖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一跪帝、第九天 我趴在偏房一處隱蔽的房頂上張望今膊。 院中可真熱鬧,春花似錦伞剑、人聲如沸斑唬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恕刘。三九已至,卻和暖如春抒倚,著一層夾襖步出監(jiān)牢的瞬間雪营,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工衡便, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留献起,地道東北人。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓镣陕,卻偏偏與公主長得像谴餐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子呆抑,可洞房花燭夜當晚...
    茶點故事閱讀 45,747評論 2 361

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理岂嗓,服務發(fā)現(xiàn),斷路器鹊碍,智...
    卡卡羅2017閱讀 134,714評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,312評論 25 707
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,166評論 30 470
  • 關(guān)鍵詞:同情題主:女問:冷大,你好耀销,我97年楼眷。因為爸媽離婚糾紛,爸爸自殺了熊尉。媽媽也在我很小的時候就已改嫁罐柳。所有人都...
    冷愛閱讀 528評論 0 0
  • 一 影片以二戰(zhàn)后期的日本神戶為大背景,通過哥哥清太和妹妹節(jié)子命運的變化狰住,折射出戰(zhàn)爭給日本帶來的傷害张吉。 由于空襲和海...
    阿彌陀佛加油鴨閱讀 377評論 0 1