美文网首页v2panda的技术专题不明觉厉iOSiOS工作系列
不要把ViewController变成处理tableView的&

不要把ViewController变成处理tableView的&

作者: TEASON | 来源:发表于2015-12-14 17:23 被阅读10396次

请支持原创, 如需转载, 请注明出处@TEASON

之前的文章里我有说要写关于UITableView见解的两篇, 算是展开讨论吧, 这是第一篇, 第二篇下集链接在此.

说在前面:

最近有个MVVM模式非常火热, 相信它的出现是为了模块化iOS开发, 其实在我看来,它始终还是MVC模式, 只是一个变种罢了 .(当然有人用到了响应式编程的思路颠覆了常规 , 但我们今天把讨论点集中于代码的设计模式) .


与其专注于说明 MVVM 的来历,不如让我们看一个典型的 iOS 是如何构建的,并从那里了解MVVM

屏幕快照 2015-12-14 下午3.58.00.png

当然, 关于瘦身ViewController有很多方面 . 然而今天我们讲讲从Controller中分离TableView的表示逻辑 . 为什么引言MVVM设计模式, 也是阐述这个主要思想是相通的 . 就是把"逻辑部分"尽量移到Model层, 你可以认为它是一个中间层 , 所谓"逻辑部分"可以是各种delegate,网络请求,缓存,数据库,coredata等等等等 , 而controller正是用来组织串联他们 .使得整个程序走通 .

正文

我们很容易想到把 UITableViewDataSourceUITableViewDelegate 的代码提取出来放到一个单独的类.
但我发现还是有东西可以抽象出来 .
例如cell的生成, cell行高, 点击等等 .这里我还用了block的形式使得函数能够回调 . 如果你对block还不太了解先看这里 .
此外, 如果你也重度使用.xib生成Cell, 那和我封装的类会非常契合 .
记住我默认习惯用.xib前的文件名来定义cell的Identifier. 如果你想把它用于实战, 记得在xib设置cell的Identifier不要设错.


处理类XTTableDataDelegate.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

typedef void    (^TableViewCellConfigureBlock)(NSIndexPath *indexPath, id item, XTRootCustomCell *cell) ;
typedef CGFloat (^CellHeightBlock)(NSIndexPath *indexPath, id item) ;
typedef void    (^DidSelectCellBlock)(NSIndexPath *indexPath, id item) ;

@interface XTTableDataDelegate : NSObject <UITableViewDelegate,UITableViewDataSource>
//1
- (id)initWithItems:(NSArray *)anItems
     cellIdentifier:(NSString *)aCellIdentifier
 configureCellBlock:(TableViewCellConfigureBlock)aConfigureCellBlock
    cellHeightBlock:(CellHeightBlock)aHeightBlock
     didSelectBlock:(DidSelectCellBlock)didselectBlock ;
//2
- (void)handleTableViewDatasourceAndDelegate:(UITableView *)table ;
//3
- (id)itemAtIndexPath:(NSIndexPath *)indexPath ;

@end

注释: //1. 初始化方法: 传数据源, cellIdentifier, 三个block分别对应配置, 行高, 点击 .
//2. 将UITableViewDataSourceUITableViewDelegate设于XTTableDataDelegate
//3. 默认indexPath.row对应每个dataSource .相应返回item

此外, 为了更彻底, 有必要抽象出"根Cell" .但这样不利于扩展cell . 为了避开modelview的耦合. 所以使用category来做类的扩展 .

#import <UIKit/UIKit.h>

@interface UITableViewCell (Extension)

+ (void)registerTable:(UITableView *)table
        nibIdentifier:(NSString *)identifier ;

- (void)configure:(UITableViewCell *)cell
        customObj:(id)obj
        indexPath:(NSIndexPath *)indexPath ;

+ (CGFloat)getCellHeightWithCustomObj:(id)obj
                            indexPath:(NSIndexPath *)indexPath ;

@end```
故`UITableViewCell+Extension`, 通过类的扩展来实现新Cell .
注释: //1 .不解释. 
//2. 根据数据源配置并绘制cell 子类务必重写该方法
//3. 根据数据源计算cell的高度 子类可重写该方法, 若不写为默认值44.0

pragma mark - Public

  • (void)registerTable:(UITableView *)table
    nibIdentifier:(NSString *)identifier
    {
    [table registerNib:[self nibWithIdentifier:identifier] forCellReuseIdentifier:identifier] ;
    }

pragma mark --

pragma mark - Rewrite these func in SubClass !

  • (void)configure:(UITableViewCell *)cell
    customObj:(id)obj
    indexPath:(NSIndexPath *)indexPath
    {
    // Rewrite this func in SubClass !

}

  • (CGFloat)getCellHeightWithCustomObj:(id)obj
    indexPath:(NSIndexPath *)indexPath
    {
    // Rewrite this func in SubClass if necessary
    if (!obj) {
    return 0.0f ; // if obj is null .
    }
    return 44.0f ; // default cell height
    }

那么新cell类的实现如下: 实现两个新方法
  • (void)configure:(UITableViewCell *)cell
    customObj:(id)obj
    indexPath:(NSIndexPath *)indexPath
    {
    MyObj *myObj = (MyObj *)obj ;
    MyCell *mycell = (MyCell *)cell ;
    mycell.lbTitle.text = myObj.name ;
    mycell.lbHeight.text = [NSString stringWithFormat:@"my Height is : %@", @(myObj.height)] ;
    cell.backgroundColor = indexPath.row % 2 ? [UIColor greenColor] : [UIColor brownColor] ;
    }
  • (CGFloat)getCellHeightWithCustomObj:(id)obj
    indexPath:(NSIndexPath *)indexPath
    {
    return ((MyObj *)obj).height ;
    }

看下结果, 瘦身后的`controller`干净的不像实力派, 只剩下了这一个方法 .呵呵呵呵 .
  • (void)setupTableView
    {
    self.table.separatorStyle = 0 ;

    TableViewCellConfigureBlock configureCell = ^(NSIndexPath *indexPath, MyObj *obj, XTRootCustomCell *cell) {
    [cell configure:cell customObj:obj indexPath:indexPath] ;
    } ;

    CellHeightBlock heightBlock = ^CGFloat(NSIndexPath *indexPath, id item) {
    return [MyCell getCellHeightWithCustomObj:item indexPath:indexPath] ;
    } ;

    DidSelectCellBlock selectedBlock = ^(NSIndexPath *indexPath, id item) {
    NSLog(@"click row : %@",@(indexPath.row)) ;
    } ;

    self.tableHander = [[XTTableDataDelegate alloc] initWithItems:self.list
    cellIdentifier:MyCellIdentifier
    configureCellBlock:configureCell
    cellHeightBlock:heightBlock
    didSelectBlock:selectedBlock] ;

    [self.tableHander handleTableViewDatasourceAndDelegate:self.table] ;
    }

诸多`.m`文件太过于冗长,我就不贴到博客了, 博客主要是讲思路, 思路是王道 .
当然如果你想深入理解, 可以看源代码, 我传到了`github`,  [点我下载](https://github.com/Akateason/XTTableDatasourceDelegateSeparation) , 喜欢的话去那加个⭐️, 对开源者是莫大的鼓励 .
任何疑问或建议, 欢迎, 我会看你们的留言 .

> 
[介绍MVVM](http://www.objc.io/issue-13/mvvm.html)
[Lighter View Controllers](http://www.objc.io/issue-1/lighter-view-controllers.html)
[Table View Programming Guide](http://developer.apple.com/library/ios/#documentation/userexperience/conceptual/tableview_iphone/AboutTableViewsiPhone/AboutTableViewsiPhone.html)
[Cocoa Core Competencies: Controller Object](http://developer.apple.com/library/mac/#documentation/General/Conceptual/DevPedia-CocoaCore/ControllerObject.html)

相关文章

网友评论

  • 梁森的简书:浏览器左上角的X
    您工作中常用Xib吗?
    本篇博客主要是为了给VC瘦身吧? 将Tableview的一些代理方法中VC中抽离出来。 我觉得完全可以将Tableview绑定到已有的viewmodel中,并不用新建一个类,而且VC中的代码也是少了许多。
  • dd2f56d130c2:不错不错,收藏了。

    推荐下,分库分表中间件 Sharding-JDBC 源码解析 17 篇:http://t.cn/R0UfGFT


  • 君赏:我的应该更简单一些吧 https://github.com/josercc/ZHTableViewGroup
    梁森的简书:您的为什么更简单呢?
  • 52a49ce3c50a:看了一下源码,感觉通过这样的方式,想要实现像系统提供的 DataSource 和 delegate那样能应付多种复杂情况的话,需要把系统提供的方法全部写成 block,并且block 要传递系统传递的参数,返回系统需要返回的结果。demo 中没有展示多种 cell 的情况,我说一下我的实现思路:cell 的 identifier 不应该像 demo 中通过初始化传递进去,这样只能注册一种情况的 cell,有人想可以传数组、字典,但是这种方式很不灵活,我的实现是在controller 中注册好 cell,在 dataDelegate 中调一个 block 返回对应的 identifier;这样可以继续保持 dataDelegate 的干净,dataDelegate 可以继续复用。但是最终我还是决定使用系统的,因为 block 没自动提示😅
  • 游龙飞雪:不错,赞一个!比较深刻讲解了BLL层。 :+1:
  • d4713d91b2fc:如果是复杂的tableview,里面有多种cell,或者里面是group样式的tableview,并且sectionHeader或者sectionFooter有控件,是否要针对每种类型封装
  • coderChrisLee:很棒,看了你的思想,觉得自己写的就不叫代码了。
  • dcc7e0b17c02:可能是我看过的代码比较少,您这种封装是我目前看来最简洁的控制器了.受益匪浅,谢谢
  • 不可数的爱:根据楼主的自己又稍微改了下:https://github.com/lovemo/DevelopFramework :smile:
  • 七夜98:不错不错,这样写代码确实简洁不少 :blush:
  • smooth_lgh:可以,说的很清楚
  • 国王or乞丐:楼主好,我最近在做qq好友动态,看了你的文章,感觉挺实用的,但是又一个问题是,我如果用xib写的话,cell 上控件比较多,并且我是在cell 上又放了两个表,所以这样写的话可能会很麻烦,不知楼主是否可以提供一个比较不错的思路,感恩不尽,可以加我qq私聊1030554941:blush:
  • 不可数的爱:不错,开阔了下思路
  • 4f8260eb5945:datasource抛出去没什么问题,但是delegate也抛出去的类似didselect的事件你还得block或代理抛回给VC,违背了VM的意义,delegate主要做的是view的生成和页面跳转等逻辑,而并非数据处理。ps:个人觉得类似heightForCell等应该是datasource,而苹果却给了delegate
  • snackbaby:明明是左上角的x...我不小心发现了什么...
    游龙飞雪:@裂云野 跨平台—————— :grin: 我的QQ浏览器就是右上角
    TEASON:@裂云野 :eyes:
  • Ilovecoding822:太棒了,赞!
  • CNMD_LJ:多谢分享,我一直觉得viewController应该是V层而不是C层,MVC更应该是一直思想而非具体的模式,MVVM MVP等都是从MVC发展而来
  • Azen:最近也在做VC瘦身方面的一些事情,和楼楼想到一块儿去了~ 很棒!
    Azen:@Azen 有两个问题:
    1. 如何增强XTTableDataDelegate的通用性,比如:
    1.1 支持分组 以及 sectionHeaderView
    1.2 一个tableView里有多种样式的cell
    2. 涉及到下拉刷新,会出现一些小Bug...最近正在搞这两个问题...不知道楼楼是怎么解决的...
  • 鼻毛长长:我最后核心问题是方法多好维护还是方法少好维护
    不可数的爱:@洗洗的 :smile: 确实是,不过不要为了重构而重构就好
  • 8d2b8086c81a:extention和category
  • Dealloc:很受用.有个问题,如果一个tableView含有多个Cell类多个复用池,cellForRow方法需要改变
    779b68e64744:@TEASON 我又开了一下脑洞, 如果有多个section, 多种cell的话... :scream:
    TEASON:@Dealloc 是的,按需求改即可
  • tony关东升:MVC和MVVM难理解是M,它可以是处理业务逻辑,当然可以处理表示逻辑。
    游龙飞雪:@tony关东升 @TEASON 一直有个问题比较疑惑吧,Model里面可以定义很多属性,模型嘛,但是有了VM之后,很多模型属性可以直接写在VM里面,Model就显得那么冗余了(冗余不大准确,也想不出更好的表达)。
  • 齐达内:学习了
  • Tracy_ljs:很实用
  • 苦笑男神:学习了
  • Shelin:楼主代码思路挺棒的,有一个地方感觉就是楼主初始化XTTableDataDelegate的时候传入tableView的数据源数组,一般是网络请求成功后去值传递,如果初始化的时候传入,就意味着block的赋值和网络请求会放在一起,感觉分开去传入数据源好一些,谢谢...
    不可数的爱:@shelin https://github.com/lovemo/MVVMFramework :relieved: 改了改
    TEASON:@shelin 你的提议很有价值, 这个demo还是初步的, 写的时候把最主要的东西抽出来的, 没有结合请求, 当然我觉得结合父类Tableview的下拉刷新控件, 可以完美解决 , 不是特别麻烦的. 稍微改改就有了. :+1:
  • 仓佑伽措:-configure:customObj:indexPath: 和 + getCellHeightWithCustomObj:indexPath:
    这两个方法写在类中,就让Model和View绑定在一起了,我们的做法是写在Category里,比较灵活,可以提供多个configure方法,针对不同类型的Model进行数据展示,同时也增强Cell的移植性。
    52a49ce3c50a:@TEASON 这个configure方法缺少一个 delegate 参数
    52a49ce3c50a:@TEASON 我想问下,使用这个方法,cell 中如果有个 button 点击事件需要 controller 响应,这个事件要怎么传递到 controller?
    TEASON:@YunLo_NG 很nice,这个提议非常好, Category适合做类的扩展, 我已经对源码做出了修改, 感谢. :+1:
  • 8c44b81eef9b:能写出自己的习惯,钦佩
  • Sanchain:学习了,谢谢分享
  • 火星的蝈蝈:谢谢楼主,赞一个
  • Dealloc:不错的
  • 鋒芒毕露:看了下代码,楼主是实现了自定义xib情况下的,在平时项目中可能会遇到不使用xib加载,使用registerClass 加载的,这样的话是把这个封装在一起呢?还是分别使用两个类呢?
    TEASON:@鋒芒毕露 不客气
    鋒芒毕露:@TEASON 嗯,已经搞定了。谢啦。
    TEASON:@鋒芒毕露 是的,由于是我个人的库, 所以我个人习惯最多使用这个 . 但这个不是重点, 如果你有必要可以fork源代码再加一个方法配置其他形式生成cell
  • 华之曦:多多分享干货,
  • 小小猿:说的太棒了,要不是没有网,真是立马下载。:+1::+1:
  • GJCode:太棒了,不错
  • 曾樑::+1::+1:
    TEASON:@曾樑 :smile:

本文标题:不要把ViewController变成处理tableView的&

本文链接:https://www.haomeiwen.com/subject/piyehttx.html