美文网首页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