介绍 MCCS:一种全新的 iOS APP 构建方式

MCCS 是什么?

MCCS 是什么? …… 如果你没在任何地方听说过这个词,那就对了。因为这个词是本文作者,也就是颐和园创造出来的。

MCCS 是作者创造的一种新的 iOS APP 构建方式和设计模式。它是对 MVC 模式的扩展。其目的是为了解决 mvc 模式中控制器变得日益膨胀的问题。MCCS 会将复杂 UI 界面切分为多个更小的单元,并通过子控制器的方式对这些更小的单元进行管理。这里,主控制器主要负责管理这些子控制器,而这些小的 UI 单元(或视图)则由子控制器进行管理。

也就是说,原来一个大控制器中的代码,被分散到多个子控制器中去了。一个单独 UI 需要被细分到什么程度,完全取决于该 UI 的复杂程度。最简单的 UI 可能完全不需要切分,那么这个控制器可能只包含一个子控制器,甚至根本不需要子控制器就能解决(又回到 MVC 模式了)。而最复杂的 UI 可能被切分成数十个控制器(极端情况)。

今天,我们简单介绍一下 MCCS。

MCCS 架构

MCCS 的全称是 Model - Cell - Controller - SubController 架构。

第一个字母 M 表示 ‘Model’ 即模型。和 MVC 一样,MCCS 也是由模型进行驱动的。

第二个字母 C 表示 ‘Cell’ 即被分割后的小视图单元。在 MCCS 中,视图即 cell,一个大的视图将由若干个小的 cell 组成。

第三个字母 C 表示 ‘Controller’ 即控制器。MCCS 将一个 iPhone 屏幕分成了2 个部分,如下图所示:

介绍 MCCS:一种全新的 iOS APP 构建方式_第1张图片
  1. 固定大小的部分(红框),比如状态栏、导航栏、TabBar。这些元素是可选的。
  2. 滚动视图(蓝框)。这部分显示内容的 frame 是不固定的,根据模型进行展示。在 MCCS 中,这实际上是一个 CollectionView。

固定大小部分是由控制器自身负责管理的,而滚动视图部分是由子控制器管理的。主控制器负责管理这些子控制器的添加、删除和加载等等。

模型、cell、控制器、子控制器之间的关系如下图所示:

介绍 MCCS:一种全新的 iOS APP 构建方式_第2张图片

这里需要注意几点:

  1. Model 是作用到子控制器上,而不是控制器上的。也就是说子控制器自己管理了数据源。
  2. 控制器并不能直接管理 cell,cell 是由子控制器管理的。而控制器又管理了子控制器。
  3. 控制器和子控制器之间的关系是 1 对多的关系,一个控制器管理了多个子控制器。
  4. 子控制器和 cell 之间的关系是 1 对多的关系,一个子控制器管理了多个 cell。
  5. 模型可以作用于子控制器,当模型改变,子控制器会刷新 cell,但 cell 不直接受 Model 影响。cell 想要改变(编辑)模型的内容,需要通过子控制器进行。虽然从技术上可以让 cell 直接更新模型,但在 MCCS 中,这是不推荐的,并容易导致一些问题的出现。
  6. 模型和控制器之间可能存在关联,但这不是必须的,所以用虚线表示。控制器可以通过这种关联间接影响子控制器,从改变视图的显示。

后面我们会详细介绍。

cell 即视图

前面也说过,一个 app 的每一个 iPhone 屏幕界面,被分成两部分:一个是固定部分,比如导航栏和 TabBar;另外就是可滚动的部分,collectionView,而collectionView 是由 cell 构成的,因此 cell 指的就是 UICollectionViewCell。

切分 UI

在 MCCS 的第一个阶段,我们需要将 collectionView 中切分成一个个的 cell。由于 cell 是复用的,因此这里并不是简单地将 UI 画成一个一个的格子就行,而是将 cell 按照外观和功能的不同进行分类,使同一类的 cell 具有相同的功能和外观。

比如下面的例子:

介绍 MCCS:一种全新的 iOS APP 构建方式_第3张图片

整个 collectionView 中所包含的 cell 被分成了 4 种 cell。

  1. 最上面的轮播图显然可以划分为一类的 cell 。
  2. 第二排的 3 个 cell 划分为另一类。
  3. 第三排的向你推荐是一个类。
  4. 后面所有 cell 展示了商品列表,应该划分为一个类。

这样,这个 UI 需要我们去实现 4 个 UICollectionViewCell 子类。

实现 UICollectionViewCell

为了提高效率,在 MCCS 框架中,提供了一个 NibCollectionViewCell,你的 UICollectionViewCell 实际上只需要继承自这个 NibCollectionViewCell,就可以更容易被子控制器所使用。

以前面的例子为例,假设我们要实现第三排的 UICollectionViewCell,那么我们可以这样做:

  1. New File -> Cocoa Touch Class,Subclass of 选择 UICollectionViewCell,然后勾上 Also create the XIB file,类名不妨就叫做:MallMenusCell。

  2. 打开 MallMenusCell.h 文件,让 MallMenusCell 类继承 NibCollectionViewCell 类:

    #import 
    @interface MallMenusCell : NibCollectionViewCell
    
    

    同时定义一个属性:

     @property (strong, nonatomic) void(^menuClicked)(NSInteger index);
    

    这是一个 block(代码块)属性,将 cell 中的用户触摸事件暴露或委托给子控制器处理。

  3. 在故事板编辑器中,设计 MallMenusCell.xib 如下图所示:

    介绍 MCCS:一种全新的 iOS APP 构建方式_第4张图片

    画布中放入了 3 个按钮。你可以使用 StackView 把它们包裹起来。Cell 的大小你可以在 Size Inspector 中修改。

  4. 分别将 3 个按钮的 TouchUpInside 事件关联到源文件的 buttonClicked: 方法:

    - (IBAction)buttonClicked:(UIButton *)sender {
    if(_menuClicked){
        _menuClicked(sender.tag);
    }
    }
    

    在这里,我们直接调用了从子控制器中传递过来的 menuClicked 块。

    注意,我们为每个 button 设置了不同的 Tag 值,所以在 buttonClicked 方法中,我们将 Tag 值传递给了 menuClicked 块,以便子控制器识别这是哪一个按钮。

可以看到,一个 cell 的实现十分简单,主要的 UI 创建和布局工作都是在故事板编辑器中进行的,使得效率大大提高——不可否认,“画代码”要比“写代码”轻松得多,对于熟练的人来说,画一个这样的 cell 最多 20 分钟。同时,这也减轻了类文件中的代码,从而减少了程序员在以前无论如何都避免不了的一些错误。

设计好 cell 之后,就应该来看看子控制器了。

实现子控制器

子控制器与 cell 之间并不是简单的 1 对多的关系,实际工程中,更可能的是多对多关系。但无论如何,一个 cell 起码要在一个子控制器中得到调用。我们刚才设计了第一个 cell,现在就来实现调用和管理这个 cell 的子控制器。

在 MCCS 中,子控制器用 SubController 表示,你的子控制器必须继承这个类。继续以前面的例子为例,实现一个子控制器的过程大致如下:

  1. New File -> Cocoa Touch Class,Subclass of 选择 NSObject,类名叫做:MallMenusSC。

  2. 修改 MallMenusSC.h,让它继承 SubController:

    #import 
    #import 
    
    
    @interface MallMenusSC : SubController
    @end
    
    
  3. 覆盖 SubController 的 5 个方法:

    #import "MallMenusSC.h"
    #import "MallMenusCell.h"
    #import 
    
    // 1
    - (NSInteger)numberOfItems{
    
    return 1;
    }
    // 2
    - (CGSize)sizeForItemAtIndex:(NSInteger)index{
    return CGSizeMake(SCREEN_WIDTH, 95);
    }
    // 3
    -(UICollectionViewCell *)cellForItemAtIndex:(NSInteger)index{
    MallMenusCell* cell = [self.collectionContext 	dequeueReusableCellOfClass:MallMenusCell.class forSectionController:self 	atIndex:index];
    cell.menuClicked = ^(NSInteger index) {
        [self menuClicked:index];
    };
    return cell;
    }
    // 4 
    - (void)didUpdateToObject:(id)object{
    }
    // 5
    - (void)didSelectItemAtIndex:(NSInteger)index{
    
    }
    
    

    如果你之前用过 IGListKit,那么你会发现这 5 个方法很眼熟。没错, 这 5 个方法实际上来自于 IGListSectionType 协议。MCCS 在底层使用了一个 IGListCollectionView 来作为它的 collectionView,因此 SubController 其实是 IGListSectionController 的子类。

    1. numberOfItems 方法

      实现这个方法并返回一个整型,用于决定这个子控制器中将包含几个 cell。因为我们将所有的按钮都放到一个 cell 中去了所以,只需要一个 cell 就可以了。如果将一个按钮设计到一个 cell 中,那么我们需要 3个 cell 才能显示完这 3 个按钮,这这里需要返回 3.

    2. sizeForItemAtIndex 方法

      在这个方法里指定每个 cell 的大小。因为这个 cell 实际上占据了整个屏幕宽度,因此我们使用了 SCREEN_WIDTH 宏作为 cell 宽度,这个宏是包含在 头文件中的。95 是它的高度,这个高度不但是我们在 xib 里面指定的高度,也是 UI 标注图上的实际高度。

    3. cellForItemAtIndex 方法

      在这个方法中,实例化 cell。注意 cell 的实例化必须要调用 SubController 的 collectionContext 的 dequeueReusableCellOfxxx 方法,否则编译器会报错。在这里,我们分配了 cell 的块属性 menuClicked,这样当 cell 上的 3 颗按钮被点击,这里指定的代码块被执行。

    4. didUpdateToObject 方法

      这个方法永远不要实现。实际上,SubController 默认会对这个方法进行空实现。因此,正常的 SubController 不要去覆盖这个方法。

    5. didSelectItemAtIndex

      当 cell 被点击时,调用此方法,由于我们已经通过按钮的 TouchUpInside 事件进行了响应,所以这里不需要再实现了。

    这 5 个方法不是都必须要实现。除了前 3 个方法外,后面两个方法都可以不实现。在上面的代码中,直接删掉后两个方法即可。

将子控制器添加到控制器中

既然控制器原来的许多代码已经分散到子控制器中去运行了,那么控制器的代码是不是变得少很多呢?答案是:是。现在我们的控制器(ViewController) 可以写成这样:

  1. 首先是 .h 文件:

    #import 
    #import 
    
    @interface MallVC : BaseController
    
    @end
    	
    

    MCCS 提供了一个基本的 UIViewController,叫做 BaseController,你的控制器只需要继承它就可以了。

  2. 然后是 .m 文件:

    #import "MallVC.h"
    #import "MallMenusSC.h"
    
    @interface MallVC
    @property (strong, nonatomic) MallMenusSC* menuSC;// 菜单按钮
    @end
    
    @implementation MallVC
    
    - (void)viewDidLoad {
    	[super viewDidLoad];
    
    	[self addSC:self.menuSC]; 
    
    	[self.adapter reloadDataWithCompletion:nil];
    }
    -(MallMenusSC*)menuSC{
    if(!_menuSC){
        _menuSC = [MallMenusSC new];
    }
    return _menuSC;
    }
    @end
    
    

就这么多代码了。归结起来有 4 步:

  1. 首先声明要使用的子控制器属性,比如:
    @property (strong, nonatomic) MallMenusSC* menuSC;// 菜单按钮

  2. 然后以懒加载的方式初始化这个子控制器:

    -(MallMenusSC*)menuSC{
    if(!_menuSC){
        _menuSC = [MallMenusSC new];
    }
    return _menuSC;
    }
    
    
  3. 在 viewDidLoad 方法中调用 addSC 方法添加子控制器:

     [self addSC:self.menuSC]; 
    
  4. 调用 reloadDataWithCompletion:

     [self.adapter reloadDataWithCompletion:nil];
    

    注意,addSC 方法将子控制器添加到主控制器的 subControllers 数组中,每当 subControllers 数组有改变时,要想在屏幕上看到这种改变,必需调用 reloadDataWithCompletion 方法。如果你 addSC 之后运行 app,发现界面上没有任何改变,请检查一下你是否在 addSC 之后调用过 reloadDataWithCompletion 方法。

按照上面的模式,你可以在一个控制器中添加更多的子控制器。例如,如果你将我们之前划分的 4 种 cell 和对应的控制器都分别实现完了,那么 MallVC.m 中的代码可能会变成这个样子:


@interface MallVC (

// Section Controller
@property (strong, nonatomic) BannerItemsSC *bannerSC;// Banner
@property (strong, nonatomic) MallMenusSC* menuSC;// 菜单按钮
@property (strong, nonatomic) SectionTitleSC* recommendTitleSC;// 推荐商品的标题
@property (strong, nonatomic) RecommendGoodsSC* recommendSC;// 推荐商品

...

- (void)viewDidLoad {
    [super viewDidLoad];
        
    // 添加 SectionController
    [self addSC:self.bannerSC]; // banner
    [self addSC:self.menuSC]; // 菜单按钮
    [self addSC:self.recommendTitleSC];// 推荐商品标题
    [self addSC: self.recommendSC];// 推荐商品
    
    [self.adapter reloadDataWithCompletion:nil];
}

...

// MARK: - Lazy load
-(MallMenusSC*)menuSC{
        if(!_menuSC){
            _menuSC = [MallMenusSC new];
        }
        return _menuSC;
}
-(BannerItemsSC*)bannerSC{
    if(!_bannerSC){
        _bannerSC = [BannerItemsSC new];
    }
    return _bannerSC;
}
-(SectionTitleSC*)recommendTitleSC{
    if(!_recommendTitleSC){
        _recommendTitleSC = [SectionTitleSC new];
    }
    return _recommendTitleSC;
}
-(RecommendGoodsSC*)recommendSC{
    if(!_recommendSC){
        _recommendSC = [RecommendGoodsSC new];
    }
    return _ recommendSC;
}

结束

关于 MCCS 的概念今天就讲到这里。接下来一段时间,我会连续用一系列的文章来介绍 MCCS 的使用。感谢阅读。

你可能感兴趣的:(MCCS)