说说iOS中的MVC

本文深入探讨了iOS开发中的MVC模式,分析了Controller的职责与View、Model的交互,指出Controller的臃肿问题及解决方案。讨论了网络请求的归属,提出Model、ViewController与Store或ViewModel的优化方案,强调理解设计模式背后的意图并灵活应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

做过iOS开发的同学们对MVC肯定不陌生。这是苹果官方推荐的开发iOS应用程序的设计模式。

就让我们从一张图开始,谈谈MVC。

目录

MVC

iOS中的MVC

臃肿的Controller

网络请求放哪儿

为Contrller瘦身

MVC进化版

留下的思考

总结

MVC

media/image1.png

传统的MVC如上图所示。将不同的对象划分进入三个阵营Model、View、Controller。View负责绘图、接收用户的交互,并将交互传达给Controller。Controller则负责处理相对应的业务逻辑,并告诉Model更新数据。Model则负责更新数据,并将数据交给View或者Controller。在传统的MVC中C同时知晓V和M两者的状态和所有信息。View也知晓Model的信息,但Model是不知道其他两者的任何信息的。

这种设计在JavaWeb的开发中被广泛使用。因为View层的页面布局,响应,由js和css完成,jsp此时可以直接拿Model解析数据。在这种情况下,View和Controller的任务最重,Model更像是一个有数据查询功能的Entity。

iOS中的MVC

media/image2.png
iOS中的MVC和传统的MVC大同小异,小异的地方是对传统MVC的改进。

首先是View层。View依然负责着接收用户交互和图像的绘制,但是,View不再直接处理数据了。而是将数据相关的实现逻辑用协议的形式,交给其他对象(一般是ViewContorller),由这个对象告诉View,View应该如何展现。苹果的UITableView、UICollectionView等View的设计很好地印证了这一点。此时View变成了纯粹的”View”,一个”Data Independent”的View,复用性无疑是非常高的。

Model在iOS中也扮演着与Web开发中相同的角色。常常被开发者们误当成Entity使用。它负责持有数据,并且有少量的逻辑。在这样的设计下,Model其实是非常瘦的。

在iOS中,Model和View是相互分离的,既不相互拥有,也相互不交互,即时是以Blind的模式。当Model有所变化时,常常通过KVO或者Notification的方式通知Controller,而不是View。因此,即使有的时候View中会有Notification的监听者,但也绝对不会是Model发出的Notification的监听者。更何况是,我们在实际开发中,Controller有各种方式来告诉View,你应该显示什么,所以View与Model是可以绝对隔离开的。

最后是Controller。Controller在iOS中被命名为ViewController。从命名就可以看出Controller和View的关系非常密切,它直接拥有View,负责着View的创建,显示,隐藏等等。View的代理,数据源,用户交互的响应,也都由Controller负责。

Controller拥有ViewModel。知晓他们二者的一切情况,负责将Model的数据解释给View,负责根据View中的用户交互让Model处理数据并让View做出相对应的反馈。并且Controller也负责接收Model发出的Notification,在Model状态改变时,及时让View做出相对应的变化。

Controller在iOS开发中扮演了重量级的角色。在实际的开发过程中,有80%的时间是在ViewController上面做文章。这也直接导致了一些问题…

臃肿的Controller

在以上的MVC中,Model其实大多和业务相关,所以苹果的API设计并没有对这一层有所体现。Apple将诸如didReceiveMemoryWarning的方法都放在了ViewController里面,这让我们有种ViewController什么都可以干的直觉。
生命周期管理、依照View的需求将数据格式化,View的初始化及组织,各种Delegate,界面的跳转等等都在Controller中完成。

正是由于Controller扮演了太过重要的角色。导致Controller在编程过程中变得越来越臃肿。其中的代码动辄上千行。有些其他模块也需要的可以复用的代码只能通过复制黏贴的方式。一旦业务和需求有所修改,就得在混合了各种逻辑的Controller的一堆代码中上下翻找。这对像我一样喜欢偷懒的开发者来说就是噩梦。

网络请求放哪儿

在以上的MVC分类中,并没有涉及到网络请求。而网络请求几乎是每一个App必须的。那么,网络请求应该归类到哪边?稍微思考一下,觉得放在Model里会不错。但是网络请求是异步的,如果网络请求还没返回,Model的生命周期却结束了,那可不是一件好事。当然,肯定不会把请求放在View里…所以,最后网络请求还是放到了Controller中。在与我共事的许多有经验的iOS开发者都是这么选择的。但是,这又加剧了Controller的臃肿。

为Contrller瘦身

So,怎么样优化这一结构呢。首先想到的是,把网络请求先移出来。如果解决了Model的生命周期和网络请求的生命周期问题,网络请求相关的代码就可以放在Model里。前面把网络请求放在ViewController里,那么只要保持Model和ViewController的生命周期一致,就可以将网络请求移动到Model里了。

Bingo,让Model成为ViewController的一个Strong属性。

@interface ShopListController ()

@property (strong, nonatomic) ShopListModel *model;

@end

这个时候Model里可以尽情访问网络请求了。因为Model和ViewController生命周期一直,也不必再担心网络请求着陆点丢失的问题。

@interface ShopListModel : BaseModel

  • (void)fetchShopListData;

@end

那么数据的存储和格式化也自然而然地在Model中处理了。同时我们还可以把数据持久化,一些类似于验证用户输入等等与UI不相关的数据处理和计算,都可以放在Model里。

@interface ShopListModel : BaseModel

@property (strong, nonatomic) NSMutableArray *dataSource;

  • (void)fetchShopListData;

@end

最后我们在model中网络请求完成的回调里,处理好相关数据整理成包含UI直接可用对象的DataSource,并发送相对应的Notification来通知ViewController对View进行更新。

Model实现:

@implementation ShopListModel

  • (void)fetchShopListData

{

    ...

    ...

    [[NSNotificationCenter defaultCenter] postNotificationName:FETCH_SHOP_LIST_DATA_NOTIFICATION object:nil];

    ...

    ...

}

ViewController实现:

  • (void)viewDidLoad

    {

        [super viewDidLoad];
    
        [[NSNotificationCenter defaultCenter] addObserver:self
    
        selector:@selector(didReceiveModelNotification:)
    
        name:FETCH_SHOP_LIST_DATA_NOTIFICATION
    
        object:nil];
    

    }

  • (void)didReceiveModelNotification:(NSNotification *)notification

    {

        if ([notification.name isEqualToString:FETCH_SHOP_LIST_DATA_NOTIFICATION])
    
        {
    
            [self.tableView reloadData];
    
            return;
    
        }
    

    }

    此时我们得到了一个功能丰富的且易于复用的Model。如果别的界面也需要相同的数据,我们只需要将这个Model给另一个Controller使用,并注册相对应的Notification监听即可。

    MVC进化版

    其实我们做的这一系列事情,无非是不断地将代码分离。力求做到一种”原子性”。最终追求的目标就是更高的可维护性、可拓展性和复用性。以这种思维反观上面的MVC,Controller已经变得相对较瘦,但随之而来的是Model又胖了起来。

    于是就有人提出了MVCS的模式。SStore。将网络请求与数据持久化相关的操作,分到Store模块中去。

    借鉴微软的MVVM,应用到iOS中的ReactiveCocoa,考虑到View与Controller的紧密联系,将View和ViewController直接划分为View。开辟出一个ViewModel来解决上述问题。在MVVM里,你可以把输入验证,网络请求,数据持久化都放在ViewModel里,Model仍然是Model,不与View直接交互。

    其实以上的设计模式,都脱不开MVC的影子,只是在实践中不断借鉴,不断优化。在了解过MVVM设计模式后,这套解决方案还真颇有点MVVM的味道。

    留下的思考

    设计模式这么多,该如何选择?

    在实战中,不一定非要抓住某个模式不放,更重要的是理解模式背后的意义,并且灵活运用。比如说在MVVM中,ViewController中还剩下什么呢?生命周期,Delegate,界面跳转,Notification响应。有这么一种情况:

    有时候有几个TableView高度相似,想要复用之前ViewController中的代理。

    一般来说,我们复制黏贴相关代理方法到另一个ViewController中,并稍微修改一下不同的地方。

    以上情况其实可以抽象出一个Adapter,专门放TableView的代理,这样在ViewController中,只需要继承这个Adapter,配置需要修改的一些属性,而不需要一遍遍地复制黏贴代理代码。

    当然,这样做的后果,牺牲了少部分原有代理的灵活性——我们可以在ViewController中任意定义TableView,切我们的代码中又多出了一个Adapter类。但好处是,封装性更好了,复用性更好了。

    总结

    返璞归真,所有的设计模式,都是为了更好地解决问题存在的。每个系统有每个系统固有的复杂度,当设计模式将其细分到一定程度,所做的事情只不过是将复杂度的位置挪了挪。所以,我们要了解设计模式,更要在合理的地方,合理地利用设计模式。诸如App中的关于页面,信息量少,逻辑少。所有逻辑直接放在ViewController里,也不会超过100行代码,那就放吧。

    参考资料

    https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/MVC.html

    http://www.jianshu.com/p/b6367602dd72

    http://www.cnblogs.com/smileEvday/p/iOS_MVC.html

    http://draveness.me/mvx-model.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值