TableView之協(xié)議托管

auu.space

TableView之協(xié)議托管

一直以來,UITableView在我們的程序開發(fā)中都扮演著一個重要的角色附井,隨著開發(fā)者的日益增多和需求的不斷變化讨越,就出現(xiàn)了各種各樣的寫法,今天我們來說一種很早以前就出現(xiàn)的方式羡忘,可以叫做協(xié)議托管(我是沒見過別處專業(yè)的叫法谎痢,暫且如是稱呼吧)。
其實這種模式早期是在Facebook開源的Three20就有了卷雕,不過這個庫最后不再更新节猿;隨后這個開發(fā)團隊的一個開發(fā)者做了又一套類似的庫Nimbus,文檔很全漫雕,用著也很方便滨嘱,但是更新了一段時間以后又停止了維護。當(dāng)然了浸间,這兩個庫也是包含了很多的內(nèi)容太雨,今天所說的也只是其中的一部分。
現(xiàn)在由于swift的大熱魁蒜,又出現(xiàn)一個很強勢的庫Eureka囊扳,有興趣的也可以去看一看。

一兜看、協(xié)議托管

看了上篇文章或者沒看的锥咸,都知道,對于一個UITableView來說细移,我們主要的時間都是花在其兩個協(xié)議上搏予,而且由于蘋果對協(xié)議方法的分散,讓我們在多個地方做了過多重復(fù)的工作弧轧,那么雪侥,第一步我們就來接管這兩個協(xié)議:

  1. 新建一個類AUUTableModel來接管UITableViewDataSource碗殷,用來處理TableView的數(shù)據(jù)源。
  2. 新建一個類AUUTableAction來接管UITableViewDelegate速缨,用來處理TableView的方法回調(diào)锌妻、事件控制等等。

此時我們的TableView可以這樣寫了:

- (UITableView *)tableView {
    if (!_tableView) {
        _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
        _tableView.dataSource = self.tableModel;
        _tableView.delegate = self.tableAction;
    }
    return _tableView;
}

tableModeltableAction里接收的都是一些協(xié)議方法鸟廓,因為幾乎所有的方法里都帶有UITableView的參數(shù)从祝,所以襟己,他們的關(guān)系如下圖:

image

二引谜、定制UITableViewCell

我們這里主要講的就是接管整個tableView的定制和處理,減少外部不斷判斷所出現(xiàn)的代碼浪費擎浴,那么我們就需要一個類來管理每個cell對應(yīng)的數(shù)據(jù)员咽,這里我們來用一個協(xié)議來做限定,讓使用者可以自由定制:

/**
 Cell對應(yīng)的Object需要實現(xiàn)的方法
 */
@protocol AUUCellObject <NSObject>

@required

/**
 當(dāng)前object對應(yīng)的cell的class
 */
- (Class)cellClass;

/**
 是否是可復(fù)用的
 */
- (BOOL)reuseable;

@end

現(xiàn)在我們接管每個cell的數(shù)據(jù)源贮预,但是我們還并不知道每個cell到底長啥樣贝室,那么我們再來用一個協(xié)議限定cell的一些屬性:

/**
 UITableViewCell 需要實現(xiàn)的協(xié)議方法
 */
@protocol AUUCell

@required

/**
 當(dāng)前cell的高度
 */
+ (CGFloat)heightWithObject:(id <AUUCellObject>)object;

/**
 每次顯示一個cell的時候都調(diào)用的方法,用于刷新cell上的數(shù)據(jù)
 */
- (BOOL)shouldUpdateWithObject:(id <AUUCellObject>)object;

@end

三仿吞、數(shù)據(jù)展示

現(xiàn)在TableView的協(xié)議我們也接管過來了滑频,cell和數(shù)據(jù)我們也先定好了,就可以來顯示數(shù)據(jù)了唤冈。
對于一個tableView是有多個分組的概念的(雖然我們有時也能看到只有一個分組的情況峡迷,但是這不也是分組么),在分組下管理的是這個分組的各個cell你虹,但是這里我們管理的是每個分組下的數(shù)據(jù)信息绘搞,下面我們就來定義一個分組的模型(暫時就設(shè)置這么簡單的一個屬性):

@interface AUUTableSection : NSObject

/**
 此分組下的cell object,就是每個cell所對應(yīng)的數(shù)據(jù)信息
 */
@property (nonatomic, strong) NSMutableArray <id <AUUCellObject>> *rows;

@end

然后在定義一個屬性來存所有的分組:

/**
 table中的分組
 */
@property (nonatomic, strong) NSMutableArray *sections;

數(shù)據(jù)現(xiàn)在有了傅物,那么就可以開始以前的一項重復(fù)的工作來顯示各條數(shù)據(jù)了夯辖,但是現(xiàn)在我們只需要在tableModel里寫一次即可:

// 設(shè)置分組的個數(shù)
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return self.sections.count;
}

// 設(shè)置每個分組下的行數(shù)
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [[[self.sections objectAtIndex:section] rows] count];
}

// 設(shè)置每行的cell視圖
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    // 獲取當(dāng)前tableView的indexPath這個位置對應(yīng)的數(shù)據(jù)信息
    id <AUUCellObject> object = [self objectForTable:tableView atIndexPath:indexPath];
    
    // 拼接復(fù)用的標(biāo)識,如果是可復(fù)用的cell董饰,直接用cell的類名來當(dāng)做標(biāo)識
    NSString *identifier = NSStringFromClass(object.cellClass);
    if (!object.reuseable) {
        // 如果不可復(fù)用蒿褂,就拼接上數(shù)據(jù)的hash值,來控制唯一性
        identifier = [identifier stringByAppendingFormat:@"%@", @([(NSObject *)object hash])];
    }
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
    if (!cell) {
        // 根據(jù)數(shù)據(jù)信息對應(yīng)的cell類型創(chuàng)建實例
        cell = [[object.cellClass alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
    }
    
    // 更新cell數(shù)據(jù)
    if ([cell conformsToProtocol:@protocol(AUUCell)] && [cell respondsToSelector:@selector(shouldUpdateWithObject:)]) {
        [(id <AUUCell>)cell shouldUpdateWithObject:object];
    }
    
    return cell;
}

四卒暂、高度控制

cell的視圖我們創(chuàng)建好了啄栓,但是高度怎么控制呢?高度設(shè)置是在tableViewdelegate里就是現(xiàn)在的tableAction介却,但是數(shù)據(jù)都在dataSource里就是現(xiàn)在的tableModel谴供,看看上面畫的關(guān)系圖,我們可以定義一個協(xié)議來要求tableModel給我指定的數(shù)據(jù):

/**
 AUUTableModel需要實現(xiàn)的方法
 */
@protocol AUUCellFactory

@required

/**
 獲取指定位置的object

 @param tableView 當(dāng)前的tableView
 @param indexPath 當(dāng)前的索引
 @return object
 */
- (id <AUUCellObject>)objectForTable:(UITableView *)tableView atIndexPath:(NSIndexPath *)indexPath;

@end

拿到了數(shù)據(jù)信息齿坷,那么高度的話就好說了:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    if ([tableView.dataSource conformsToProtocol:@protocol(AUUCellFactory)] &&
        [tableView.dataSource respondsToSelector:@selector(objectForTable:atIndexPath:)]) {
        // 拿到當(dāng)前位置的數(shù)據(jù)信息
        id <AUUCellObject> object = [(id <AUUCellFactory>)tableView.dataSource objectForTable:tableView atIndexPath:indexPath];
        if ([object.cellClass respondsToSelector:@selector(heightWithObject:)]) {
            // 獲取高度
            return [object.cellClass heightWithObject:object];
        }
    }
    return 44.0;
}

五桂肌、測試一下

實現(xiàn)部分的基類

因為我們上面做的對于cell和數(shù)據(jù)的控制都是通過協(xié)議來做的数焊,目的當(dāng)然很明確,就是為了能夠減少耦合崎场,所以佩耳,為了在項目里寫的方便,可以在實現(xiàn)的部分寫一個基類來簡化一些重復(fù)的工作谭跨,如:

  • BaseCell
@interface AUUBaseCell : UITableViewCell <AUUCell>
- (void)initialize;
@end

@implementation AUUBaseCell
+ (CGFloat)heightWithObject:(id<AUUCellObject>)object {
    return 44.0;
}
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
        [self initialize];
    }
    return self;
}
- (void)initialize { }
- (BOOL)shouldUpdateWithObject:(id<AUUCellObject>)object {
    return YES;
}
@end

  • BaseObject
@interface AUUBaseObject : NSObject <AUUCellObject>
+ (instancetype)objectWithClass:(Class)cls;
+ (instancetype)object;
@end

@interface AUUBaseObject()
@property (nonatomic) Class cls;
@end
@implementation AUUBaseObject
+ (instancetype)object {
    return nil;
}
+ (instancetype)objectWithClass:(Class)cls {
    AUUBaseObject *object = [[self alloc] init];
    object.cls = cls;
    return object;
}
- (Class)cellClass {
    return self.cls;
}
- (BOOL)reuseable {
    return YES;
}
@end

舉例的cell

下面在實現(xiàn)各個cell的時候干厚,就可以如下寫了:

  • .h
@interface AUUTextCell : AUUBaseCell
@end

@interface AUUTextObject : AUUBaseObject
+ (instancetype)objectWithText:(NSString *)text;
@end
  • .m
@interface AUUTextObject ()
@property (copy, nonatomic) NSString *text;
@end

@implementation AUUTextObject

+ (instancetype)objectWithText:(NSString *)text {
    AUUTextObject *object = [super objectWithClass:[AUUTextCell class]];
    object.text = text;
    return object;
}

@end

@interface AUUTextCell()
@property (nonatomic, strong) UILabel *label;
@end

@implementation AUUTextCell

+ (CGFloat)heightWithObject:(id<AUUCellObject>)object {
    return  44;
}

- (BOOL)shouldUpdateWithObject:(AUUTextObject *)object {
    self.label.text = object.text;
    return YES;
}

- (void)initialize {
    self.label = [[UILabel alloc] initWithFrame:CGRectMake(20, 0, [UIScreen mainScreen].bounds.size.width - 40, 44)];
    self.label.font = [UIFont systemFontOfSize:14];
    self.label.textColor = [UIColor redColor];
    [self addSubview:self.label];
}

@end

這里呢就先寫這一個,更多的可以看一下測試代碼螃宙。

組裝數(shù)據(jù)到頁面

在一開始的時候我們就創(chuàng)建了tableModel下面我們就來添加數(shù)據(jù):

[self.tableModel appendObject:[AUUTextObject objectWithText:@"title"]];
[self.tableModel appendObject:[AUUInputObject object]];
[self.tableModel appendObject:[AUUImageObject object]];
[self.tableModel appendObject:[AUUImageScrollerObject object]];

好了蛮瞄,我們添加了4個celltableView里,效果如下:

image

至此谆扎,一個tableView已經(jīng)完美展示挂捅。

六、事件響應(yīng)

上面的操作都只是出于看靜態(tài)頁面的狀態(tài)堂湖,我們需要的是可以交互啊闲先,就像最原始的寫法一樣:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

我們在里面收到tableView的調(diào)用以后就去做一些事情,但是現(xiàn)在我們的目的是接管這些協(xié)議方法无蜂,不給外面使用的機會伺糠。
為了解決這個問題,就該強化我們的tableAction了斥季。

響應(yīng)對應(yīng)緩存

我們來創(chuàng)建一個緩存類_AUUActionCache训桶,用來緩存每一行設(shè)定的響應(yīng)方法,里面包含如下幾個屬性:

@property (weak, nonatomic) id target;
@property (nonatomic) SEL sel;
@property (copy, nonatomic) AUUCellSelectedAction action;

提供外部注冊事件的方法

為了適應(yīng)不同的需求泻肯,我們提供一下兩種回調(diào)設(shè)置方式:

  • 單一對象的事件監(jiān)聽渊迁,只監(jiān)聽這一個數(shù)據(jù)對應(yīng)的cell的點擊事件
// 通過block回調(diào)
- (id<AUUCellObject>)attachObject:(id<AUUCellObject>)object action:(AUUCellSelectedAction)action {
    if (object) {
        [self.objectActions setObject:[_AUUActionCache cacheWithAction:action] forKey:[self keyForObject:object]];
    }
    return object;
}

// 通過方法調(diào)用來回調(diào)
- (id <AUUCellObject>)attachObject:(id<AUUCellObject>)object target:(id)target action:(SEL)action {
    if (object) {
        [self.objectActions setObject:[_AUUActionCache cacheWithTarget:target selector:action] forKey:[self keyForObject:object]];
    }
    return object;
}
  • 某一類對象的監(jiān)聽,監(jiān)聽這一類及其子類的點擊事件
// 通過block回調(diào)
- (void)attachClass:(Class)cls action:(AUUCellSelectedAction)action {
    [self.classActions setObject:[_AUUActionCache cacheWithAction:action] forKey:(id <NSCopying>)cls];
}

// 通過方法調(diào)用來回調(diào)
- (void)attachClass:(Class)cls target:(id)target action:(SEL)action {
    [self.classActions setObject:[_AUUActionCache cacheWithTarget:target selector:action] forKey:(id <NSCopying>)cls];
}

事件響應(yīng)

在上面我們添加了AUUCellFactory這么一個協(xié)議灶挟,那么數(shù)據(jù)肯定不在話下了琉朽,既然能拿到數(shù)據(jù),那就能找到其對應(yīng)的回調(diào)操作稚铣,如下:

- (void)performActionForObject:(id <AUUCellObject>)object {
    if (!object) {
        return;
    }
    
    _AUUActionCache *action = [self.objectActions objectForKey:[self keyForObject:object]];
    if (!action) {
        Class keyClass = [(NSObject *)object class];
        
        for (Class class in self.classActions.allKeys) {
            if ([keyClass isKindOfClass:class] || [keyClass isSubclassOfClass:class]) {
                // 需要做處理箱叁,比如  A<-B<-C
                // 我添加了A、B的監(jiān)聽惕医,當(dāng)我拿C class來取響應(yīng)者的時候耕漱,到底是取誰?
                // 所以這里只是簡單的寫了一下
                action = [self.classActions objectForKey:(id<NSCopying>)class];
            }
        }
    }
    
    if (!action) {
        return;
    }
    
    if (action.action) {
        action.action(object);
    }
    if (action.target && action.sel && [action.target respondsToSelector:action.sel]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [action.target performSelector:action.sel withObject:object];
#pragma clang diagnostic pop
    }
}

七抬伺、再測試一下

響應(yīng)事件添加了螟够,我們再來看一下實現(xiàn),跟第一次測試也沒多大的卻別,跑起來試一下妓笙,在控制臺就會出現(xiàn)想要的log結(jié)果了若河。

[self.tableAction attachClass:[AUUTextObject class] action:^(AUUTextObject *object) {
    NSLog(@"tap : %@", object.text);
}];
    
[self.tableAction attachClass:[AUUImageObject class] target:self action:@selector(imageAction:)];
    
[self.tableModel appendObject:[AUUTextObject objectWithText:@"title"]];
[self.tableModel appendObject:[AUUInputObject object]];
[self.tableModel appendObject:[self.tableAction attachObject:[AUUImageObject object] action:^(id<AUUCellObject> object) {
    NSLog(@"tap 你點擊了這個圖片cell");
}]];
[self.tableModel appendObject:[AUUImageScrollerObject objectWithDelegate:self]];

八、更多的操作

這里我們的測試例子寞宫,只是很簡單的寫了一下這個操作過程萧福,不過對于一般不過于復(fù)雜的項目來說,貌似這就夠用了啊辈赋。

非要自己實現(xiàn)一些協(xié)議

當(dāng)然鲫忍,tableView的協(xié)議方法有很多,就算自己再封裝一個庫也不會做到剛好符合每一個人的需求钥屈,而且對于蘋果公司來說悟民,隨著技術(shù)的不斷更新和進步,也肯定會有更多的API開放出來焕蹄,于是逾雄,作為應(yīng)用層的一次封裝阀溶,提供給使用者更多的選擇還是很有必要的腻脏。
在這里,我推薦去看一看NimbusNITableAction下對于Forward Invocations的一些實現(xiàn)银锻。

Cell的自更新

響應(yīng)式設(shè)置永品,就像是集合了ReactiveCocoa的操作一樣,每當(dāng)有數(shù)據(jù)變動击纬,都能及時的將變動后的結(jié)果展示出來鼎姐,這里的這種寫法當(dāng)然也行啊,就看你怎么封裝了更振。
不過炕桨,我推薦去看個現(xiàn)成的內(nèi)容Eureka,里面對于數(shù)據(jù)和cell的封裝更緊密肯腕。

九献宫、總結(jié)

這種方式把tableView協(xié)議里的操作進行了打散,重點的工作就是前期的tableModeltableAction的定制实撒,在到應(yīng)用的時候姊途,是把各種類型的判斷使用數(shù)據(jù)緩存來做了區(qū)分,這樣在添加一個cell的時候就能省去更多的時間和判斷邏輯知态。
不信捷兰,可以增加一些增刪的測試操作試試,如果用原始的寫法來做的話负敏,是不是有種要哭的感覺贡茅?

測試代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子顶考,更是在濱河造成了極大的恐慌彤叉,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件村怪,死亡現(xiàn)場離奇詭異秽浇,居然都是意外死亡,警方通過查閱死者的電腦和手機甚负,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門柬焕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人梭域,你說我怎么就攤上這事斑举。” “怎么了病涨?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵富玷,是天一觀的道長。 經(jīng)常有香客問我既穆,道長赎懦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任幻工,我火速辦了婚禮励两,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘囊颅。我一直安慰自己当悔,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布踢代。 她就那樣靜靜地躺著盲憎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪胳挎。 梳的紋絲不亂的頭發(fā)上饼疙,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機與錄音串远,去河邊找鬼宏多。 笑死院水,一個胖子當(dāng)著我的面吹牛泵琳,可吹牛的內(nèi)容都是我干的佃扼。 我是一名探鬼主播正卧,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼迅皇,長吁一口氣:“原來是場噩夢啊……” “哼榴徐!你這毒婦竟也來了技掏?” 一聲冷哼從身側(cè)響起署恍,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎却妨,沒想到半個月后饵逐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡彪标,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年倍权,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捞烟。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡薄声,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出题画,到底是詐尸還是另有隱情默辨,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布苍息,位于F島的核電站缩幸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏竞思。R本人自食惡果不足惜表谊,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望衙四。 院中可真熱鬧铃肯,春花似錦、人聲如沸传蹈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惦界。三九已至,卻和暖如春咙冗,著一層夾襖步出監(jiān)牢的瞬間沾歪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工雾消, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留灾搏,地道東北人。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓立润,卻偏偏與公主長得像狂窑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子桑腮,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,884評論 2 354

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

  • 概述在iOS開發(fā)中UITableView可以說是使用最廣泛的控件泉哈,我們平時使用的軟件中到處都可以看到它的影子,類似...
    liudhkk閱讀 9,043評論 3 38
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件丛晦、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,103評論 4 62
  • 獨上古寺奕纫,虔誠問佛,能否為你我求得一支上上簽烫沙。佛不語匹层,笑著看我。我不明锌蓄,跪地祈佛又固。 我接著拜佛,問佛何為緣煤率。佛說緣...
    紙才閱讀 195評論 0 0
  • 你喜歡你的身體嗎蝶糯?當(dāng)這句話從腦子里冒出來的時候我剛完成了keep的訓(xùn)練洋只,癱在沙發(fā)上捏著肚子上的肉肉發(fā)呆。隨即我在心...
    張喜樂閱讀 290評論 0 2
  • 前天,看了一個報告妒茬,90后的也逐漸成為被“結(jié)婚”的年紀了担锤。 看到這里,長長的嘆了一口氣乍钻。 原來不知不覺肛循,我們已經(jīng)長...
    晨光花開閱讀 327評論 2 2