理解 iOS 的 MVC 架构

MVC 架构在 web 开发、软件开发和 App 开发中都是推荐的一种模式,它能让程序结构清晰、松耦合,其思想也非常的 instinct。本文就结合 iOS App 的开发来谈谈对 MVC 架构的理解。

iOS 对 MVC 的支持是默认的,它提供了一系列的机制保证我们可以让程序更好的 MVC 化。首先借用一下斯坦福 cs193p 《Developing Applications for iOS》中这张经典的图片来说明,然后结合我写的一个 Demo 做解释。

理解 iOS 的 MVC 架构_第1张图片

Controller 是总指挥,当它需要数据的时候就告诉 Model,你帮我获取某某数据;当它需要 UI 展示和更新的时候就告诉 View,你帮我生成一个 UI 显示某某数据。Controller 可以直接发送消息给 Model 和 View,对于 nib (xib 或 storyboard)方式创建的 view,使用 outlet 来联系 View。

Model 可不可以给 Controller 发送消息呢?一个场景是数据改变了,需要通知 Controller,UI 需要 update 了。可以,通过 radio 机制,也就是图片中的 Notification & KVO。iOS 中有一个 NotificationCenter,Model 在这里注册一个广播,它可以选择自己在什么时候发送这个广播,广播中带有消息。而 Controller 中要注册一个 Observer,也就是一个听众,随时关注着 Model 的广播,当收听到 Model 发送的广播后,可以接收消息,也可以做出相应的动作。当然,除了 KVO,也可以直接把 Model 的某些操作放在 Controller 中做,这样就不需要这么复杂了,但这会破坏 MVC 清晰的分工,破坏其松耦合。我Demo中先是用了后一种方法,后来改成了 KVO 方式。

View 可不可以向 Controller 发送消息呢?一个场景是用户对 View 产生了 event, 如 touch。可以,对于 nib 是用 IBAction,对于 code 是用 addTarget,这个消息是当某某事件发生的时候告诉 Contoller 你可以做出某些相应的动作了。

还有一种场景是对于某些 View,一般会有一些相应的操作,iOS 自动提供了这样的消息,允许我们重写其方法。比如对于 table view 就有didSelectRowAtIndexPath,我们可以实现这个方法,当用户选择某一个 cell 的时候,就会调用这个里面定义的一些操作,这个定义就是在 Controller 中实现的。这就是图片中的 delegate,也是一种设计模式,其方法前面通常有 will, should, did。对于 delegate 我以后应该会专门介绍自己的理解。

此外还有一种场景:当我的 UI 需要数据的时候怎么办呢?比如 table view 中的 cell 就是用来展示数据的,table view 应该放几个 cell,每个 cell 里面是什么?从图中可以看到,我们一般是严禁 Model 和 View 之间的联系的,因为我们希望 data 和 representation 是分离的,所以不能直接跟 Model 要数据啊。iOS 提供了 datasource 这种机制来让 View 向 Controller 要数据。

为了练习 MVC,我写了一个小 demo,放在 github,地址是:https://github.com/zippera/MVCDemo 。

功能很简单,2个 scene,一个展示新闻列表,一个展示新闻详情,这里用到的是网友提供的 Startup News 的 API。

理解 iOS 的 MVC 架构_第2张图片

理解 iOS 的 MVC 架构_第3张图片

用到了一个第三方库,AFNetwork,异步访问网络,并自动把 json 转换为 dict。当然也可以用 iOS 自带的 request,我这里顺便练习了一下 AFNetwork。

程序的内容有点多,不方便在这里展开一一来谈,但还是想就前面提到的几种 communication 用代码做一下注解。

先看 KVO:

+ (void)SNPosts:(NSString *)url
{
    AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
    [manager GET:url parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSLog(@"success");        if (responseObject) {            [[NSNotificationCenter defaultCenter]postNotificationName:@"dataLoaded" object:self userInfo:[NSDictionary dictionaryWithObject:responseObject forKey:@"json"]];
        }
        
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"fails");
    }];
}

注意NSNotificationCenter这里。前面我异步访问一个网址,如果访问成功,并返回了数据,即if (responseObject)成立,我就需要告诉 Controller 该更新 UI 了。这句是注册一个广播,广播的频道是dataLoaded,发送者是这个 Model 类,要传递的参数就是访问网址返回的数据。

相应地,在 Controller 中注册了一个听众,收听dataLoaded频道,当收到消息时,做出动作updateUI:,即更新 UI。

[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(updateUI:) name:@"dataLoaded" object:nil];

- (void)updateUI:(NSNotification *)notification
{
    posts = [notification.userInfo objectForKey:@"json"];
    [self.tableView reloadData];
}

哎呀,篇幅太大,其他不说了,再说一个 KVC(Key-Value-Coding) 相关的吧。AFNetwork 获取 json 数据后返回的默认是 NSCFDictionary,怎么把这个类型跟 Model 这个类型对应起来呢?笨的办法是对应元素分别赋值,简单的方式如下:

- (id)initWithDictionary:(NSMutableDictionary *)json
{    // json -> DemoPost
    if(self = [super init])
    {
        [self setValuesForKeysWithDictionary:json];
    }    return  self;
}


前提是 json 的 key 跟 DemoPost 这个 Model 的 property 的名字都是相同的。

MVC 是相对的,只能说程序很 MVC,不很 MVC,希望随着我的学习和理解,能让这个程序更加 MVC 化。



你可能感兴趣的:(理解 iOS 的 MVC 架构)