21、使用MVC设计应用程序(上)

使用MVC设计应用程序(上)

1. 学习阶段与MVC概述

对于新的Mac和iPhone程序员来说,学习过程通常有一个固定的周期。早期主要是熟悉Objective - C和一些基本的内存管理,这只是学会使用工具。接下来的步骤是弄清楚如何组织类并让它们相互通信。

当新的Cocoa程序员到了这个阶段,他们常问的问题是:“如何在类之间发送数据?” 实际上并没有神奇的 sendDataToClass 方法,而是要使用Model - View - Controller(MVC),这不是一个类,而是一种思维方式。

MVC认为存在三种对象:
- 模型对象 :保存原始数据。
- 视图对象 :用户可以看到并与之交互。
- 控制器对象 :使模型和视图对象保持同步。

可以把MVC想象成科幻作品中的“原力”,它没有实体形式,但贯穿于Cocoa的每一部分,是Mac和iPhone程序员构建类的指导原则。

为了理解MVC如何工作,我们将创建一个照片画廊应用程序,这个项目会涉及以下几个重要的主题:
- Core Data :Cocoa的持久化框架,用于在应用程序启动之间保存和加载数据。
- ImageKit :Mac OS X中一个强大的框架,提供照片网格显示、缩略图缩放、旋转、照片编辑等高端功能。
- 窗口控制器 :管理单个窗口内的所有活动,通常每个窗口都有自己的XIB文件。
- 视图控制器 :管理视图内的活动,在许多Mac应用程序中使用,在iPhone、iPad和iPod touch的UIKit中更是无处不在。

2. 项目特点

这个应用程序的结构与其他示例有很大不同,更接近“真实”的Cocoa应用程序。其关键特点是应用程序的不同部分被分隔开,这样在不破坏整个项目的情况下更改某些部分变得更加容易,关键部分是自给自足的,可以自由移动。

3. 窗口控制器

早期的示例使用通用的应用程序委托类来管理应用程序的核心部分,但在大型应用程序中,将所有内容放在一个类中会导致混乱。解决方法之一是使用窗口控制器,即 NSWindowController 的实例。

窗口控制器看似复杂,实际上很通用,通常会对基类进行子类化,并为自定义窗口添加特定的属性和方法。以下是一些重要的方法:
- - (id) initWithWindowNibName: (NSString *)windowNibName; :创建窗口控制器并加载指定名称的XIB文件内容。
- - (void) loadWindow; :初始化窗口并加载XIB内容,可以重写此方法进行自定义初始化。
- - (NSWindow *) window; :获取窗口控制器对应的 NSWindow 实例。
- - (IBAction) showWindow: (id)sender; :将窗口显示在屏幕上。

大多数Cocoa应用程序的窗口都有自己的XIB文件和关联的窗口控制器,窗口控制器在Interface Builder中被称为“File’s Owner”。

以下是一个自定义窗口控制器的示例:

@interface MyCustomWindowController : NSWindowController
@property (retain) IBOutlet NSSlider*    slider;
@property (retain) IBOutlet NSTextField* textField; 
- (IBAction) sliderDidChange:(id)sender;
- (IBAction) textDidChange:(id)sender;
@end

创建窗口控制器并显示窗口的代码通常如下:

MyWindowController* controller;
controller = [[MyWindowController alloc] initWithWindowNibName:@"Window"];
[controller showWindow:self];

当窗口控制器被释放时,它也会释放XIB中的其他项,这大大简化了每个窗口的维护。

4. 视图控制器

视图可能包含多个子视图,变得非常复杂,管理起来更困难且占用更多内存。将视图拆分成各自的XIB文件,不仅使用更方便,而且只有在需要时才会加载到内存中。视图XIB文件有对应的视图控制器,即 NSViewController 的实例。

视图控制器的工作原理与窗口控制器类似,但更精细。一个窗口可能由一个窗口控制器和一个引用多个视图XIB文件及其视图控制器的窗口XIB文件组成。通过这些基本构建块,可以构建任何复杂程度的应用程序。

视图控制器通常会对 NSViewController 进行子类化,并添加自己的属性和方法。以下是一些关键方法:
- - (id)initWithNibName:(NSString *)name bundle:(NSBundle *)bundle; :创建视图控制器并加载指定名称的XIB文件内容。
- - (void) loadView; :初始化视图并加载XIB内容,可以重写此方法进行自定义初始化。
- - (NSView *) view; :获取视图控制器对应的视图实例。
- - (void)setTitle:(NSString *)title; :为视图控制器设置标题。
- - (NSString *)title; :获取视图控制器的标题。
- - (void)setRepresentedObject:(id)object; :设置视图所描述的对象。
- - (id)representedObject; :获取视图所描述的对象。

视图控制器在Mac上比较流行,在Mac OS X 10.5 Leopard中正式引入。在iPhone和iPad的UIKit中,视图控制器是必不可少的。

5. Core Data概述

Core Data是Cocoa的持久化框架,大多数Cocoa应用程序使用它来存储数据。它是一个高度可扩展、灵活的框架,即使在简单的应用程序中也很有用。它还提供更改跟踪功能,能够轻松撤销或重做对数据所做的更改。

Core Data提供了几种内置的存储类型:XML、二进制和SQL,所有这些存储类型都根据你提供的设计来存储数据。你可以定义要存储的数据类型及其之间的关系,框架会处理具体细节。SQL存储选项基于SQLite开源库,具有强大的扩展能力。你还可以通过对 NSAtomicStore 类进行子类化来提供自己的存储类型。

以下是Core Data中几个重要的类:
| 类名 | 描述 |
| ---- | ---- |
| NSManagedObjectModel | 描述应用程序要存储的不同类型的数据,可以使用Xcode的图形建模工具创建。 |
| NSEntityDescription | 描述单一类型的数据,属于 NSManagedObjectModel ,比类更抽象,只描述数据。 |
| NSManagedObject | Core Data的基础数据类,每个托管对象都与一个 NSEntityDescription 实例相关联。 |
| NSManagedObjectContext | 管理 NSManagedObject 实例,负责跟踪更改、从磁盘加载数据以及处理撤销、重做和保存操作。 |
| NSPersistentStoreCoordinator | 设置和管理持久存储,应用程序可以同时打开多个持久存储。 |

6. 创建项目文件

接下来我们开始搭建项目,具体步骤如下:
1. 打开Xcode,创建一个名为“Gallery”的新Cocoa应用程序项目。
2. 确保勾选“Use Core Data for Storage”选项。

7. 创建实体

创建项目后,需要创建实体来描述应用程序的数据:
1. 打开Xcode侧边栏中的“Models”组,点击“Gallery_DataModel.xcdatamodel”打开模型编辑器。
2. 在编辑器的左上角面板中列出了实体,点击实体表底部的小加号按钮创建一个新的实体实例。
3. 双击“Entity”编辑名称,输入“Photo”。
4. 重复上述步骤,添加另一个实体并命名为“Album”。
5. 对于每个实体,在实体表的“Class”列中,双击“Photo”的“Class”条目,输入类名“CBPhoto”;将“Album”的类名设置为“CBAlbum”。

创建实体的流程可以用以下mermaid流程图表示:

graph TD;
    A[打开模型编辑器] --> B[创建新实体实例];
    B --> C[命名为Photo];
    B --> D[命名为Album];
    C --> E[设置类名为CBPhoto];
    D --> F[设置类名为CBAlbum];
8. 添加属性和关系

创建实体后,需要为实体添加属性和关系:
- 为Photo实体添加属性
1. 选择左侧表中的“Photo”实体。
2. 点击右侧属性表中的加号按钮,从弹出菜单中选择“Add Attribute”。
3. 在名称字段中输入“filePath”,取消勾选“Optional”,从类型下拉菜单中选择“String”。
4. 重复上述步骤,添加另一个属性“uniqueID”,设置相同的参数。
5. 添加一个非可选属性“orderIndex”,类型为“Int 32”。
- 为Photo实体添加关系
1. 点击属性表下方的加号按钮,从弹出菜单中选择“Add Relationship”。
2. 将关系命名为“album”,取消勾选“Optional”,从“Destination”弹出菜单中选择“Album”。
- 为Album实体添加属性和关系
1. 选择左侧表中的“Album”实体。
2. 添加一个非可选属性“title”,类型为“String”,在“Default Value”字段中输入“New Album”。
3. 添加一个关系“photos”,勾选“Optional”,从“Destination”弹出菜单中选择“Photo”,从“Inverse”下拉菜单中选择“album”。
4. 勾选“To - Many Relationship”,表示每个相册可以有多个照片。
5. 从“Delete Rule”弹出菜单中选择“Cascade”,表示删除相册时会同时删除所有照片。

完成后,确保模型的设置与要求一致,因为Core Data将使用这些信息将应用程序数据保存到磁盘。最后,保存模型文件。

添加属性和关系的步骤可以总结如下:
1. 选择实体。
2. 添加属性或关系。
3. 配置属性或关系的参数。

通过以上步骤,我们完成了项目的初步搭建,包括创建项目文件、实体、属性和关系。在下一部分中,我们将继续完成项目,包括更新应用程序委托等内容。

使用MVC设计应用程序(下)

9. 更新应用程序委托

完成实体和属性关系的设置后,接下来需要更新应用程序委托。具体操作如下:
1. 打开Xcode侧边栏中的“Classes”组,点击“Gallery_AppDelegate.m”文件。
2. 在 -applicationSupportDirectory: 方法的实现中,将返回文件路径的代码:

return [basePath stringByAppendingPathComponent:@"Gallery"];

替换为:

return [basePath stringByAppendingPathComponent:@"CocoaBookGallery"];

这样做是为了避免因“gallery”这个名称过于通用,导致数据被其他应用程序覆盖。
3. 在 -applicationSupportDirectory: 方法实现的上方添加几个换行符和注释,以表明内置方法的起始位置:

// all of these methods are provided by the template.
- (NSString *)applicationSupportDirectory {
  1. 将第一个内置方法上方的所有内容替换为以下代码:
#import "Gallery_AppDelegate.h"
#import "CBMainWindow.h"
#import "CBBrowserView.h"
#import "CBAlbum.h"
#import "CBPhoto.h"
@implementation Gallery_AppDelegate
@synthesize mainWindow;
@synthesize mainWindowController;
@synthesize selectedAlbum;
@synthesize window;
@synthesize managedObjectModel;
@synthesize persistentStoreCoordinator;
@synthesize managedObjectContext;
- (IBAction) newAlbum: (id)sender {
    [CBAlbum albumInDefaultContext];
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:
                                    (NSApplication *)sender {
    return YES;
}

其中, -newAlbum: 动作方法将在后续章节中用于菜单项; -applicationShouldTerminateAfterLastWindowClosed: 方法返回 YES ,表示当主窗口关闭时应用程序将退出。

  1. 切换到“Gallery_AppDelegate.h”头文件,移除花括号和其中的所有实例变量定义,仅保留 @property 声明。添加注释以指出哪些属性是由模板提供的。
  2. 在文件中添加以下属性、类声明和动作方法,确保它们都在模板提供的属性之上(前两行在 @interface Gallery_AppDelegate : NSObject 行之上,后三行在其之下):
@class CBMainWindow;
@class CBAlbum;
@interface Gallery_AppDelegate : NSObject
@property (retain) CBMainWindow* mainWindowController;
@property (retain) CBAlbum*      selectedAlbum;
- (IBAction) newAlbum: (id)sender;

完成后,文件应与以下示例代码匹配:

#import <Cocoa/Cocoa.h>
@class CBMainWindow;
@class CBAlbum;
@interface Gallery_AppDelegate : NSObject
@property (retain) CBMainWindow* mainWindowController;
@property (retain) CBAlbum*      selectedAlbum;
- (IBAction) newAlbum: (id)sender;
// provided by template.
@property (nonatomic, retain) IBOutlet NSWindow *window;
@property (nonatomic, retain, readonly)
    NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (nonatomic, retain, readonly)
    NSManagedObjectModel *managedObjectModel;
@property (nonatomic, retain, readonly)
    NSManagedObjectContext *managedObjectContext;
- (IBAction)saveAction:sender;
@end

更新应用程序委托的步骤可以用以下表格总结:
| 步骤 | 操作 | 文件 |
| ---- | ---- | ---- |
| 1 | 替换文件路径代码 | Gallery_AppDelegate.m |
| 2 | 添加注释 | Gallery_AppDelegate.m |
| 3 | 替换上方代码 | Gallery_AppDelegate.m |
| 4 | 移除实例变量定义 | Gallery_AppDelegate.h |
| 5 | 添加注释 | Gallery_AppDelegate.h |
| 6 | 添加属性、类声明和动作方法 | Gallery_AppDelegate.h |

10. 总结

通过以上一系列步骤,我们完成了一个基于MVC架构的照片画廊应用程序的初步搭建。从理解MVC的基本概念,到创建项目文件、实体、属性和关系,再到更新应用程序委托,每一步都为构建一个完整的应用程序奠定了基础。

在这个过程中,我们学习了如何使用窗口控制器和视图控制器来管理窗口和视图的活动,以及如何利用Core Data进行数据的持久化存储。以下是整个项目搭建过程的mermaid流程图:

graph LR;
    A[理解MVC概念] --> B[创建项目文件];
    B --> C[创建实体];
    C --> D[添加属性和关系];
    D --> E[更新应用程序委托];

通过合理运用MVC架构和相关技术,我们可以构建出结构清晰、易于维护的应用程序。在后续的开发中,我们可以进一步完善这个应用程序,添加更多的功能,如照片显示、编辑等。同时,我们也可以将这些知识应用到其他项目中,提高开发效率和代码质量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值