文档应用开发与异常调试全解析
1. 为图形用户界面添加混合颜色
要将混合颜色样本添加到文档窗口,首先需在
CMDocument.h
文件中为每个样本添加输出口。新的输出口会连接到
CMColorBlendView
实例。在文件顶部添加
@class
声明,告知编译器
CMColorBlendView
是类名,这样编译器无需导入类头文件就能处理指向该类实例的指针。使用前向声明而非
#import
可提升编译速度,减少头文件依赖。在实现文件中调用类方法时,则需导入头文件。
#import <Cocoa/Cocoa.h>
@class CMColorBlendView;
@interface CMDocument : NSPersistentDocument
@property (weak) IBOutlet CMColorBlendView *multiplyBlendView;
@property (weak) IBOutlet CMColorBlendView *screenBlendView;
@property (weak) IBOutlet CMColorBlendView *overlayBlendView;
@property (weak) IBOutlet CMColorBlendView *darkenBlendView;
@property (weak) IBOutlet CMColorBlendView *lightenBlendView;
@property (weak) IBOutlet CMColorBlendView *colorDodgeBlendView;
@property (weak) IBOutlet CMColorBlendView *colorBurnBlendView;
@property (weak) IBOutlet CMColorBlendView *softLightBlendView;
@property (weak) IBOutlet CMColorBlendView *hardLightBlendView;
@property (weak) IBOutlet CMColorBlendView *differenceBlendView;
@property (weak) IBOutlet CMColorBlendView *exclusionBlendView;
@property (weak) IBOutlet CMColorBlendView *hueBlendView;
@property (weak) IBOutlet CMColorBlendView *saturationBlendView;
@property (weak) IBOutlet CMColorBlendView *colorBlendView;
@property (weak) IBOutlet CMColorBlendView *luminosityBlendView;
@end
接着回到
CMDocument.xib
,将窗口大小调整为约 350×500,保留顶部的两个颜色选择器。从对象库中找到自定义视图(实际是
NSView
实例)拖入窗口,使用身份检查器将其类改为
CMColorBlendView
,再用大小检查器将其调整为约 90×50。为显示视图代表的混合模式,从对象库中拖入标签到
CMColorBlendView
下方,使用控制手柄使标签与上方视图宽度相同,用属性检查器将标签文本居中。
选择
CMColorBlendView
和标签,按 ⌘D 复制并向右排列,多次操作后选择所有
ColorBlendView
和标签复制并排列在下方,直到有五行三个标签,然后设置所有标签标题。
布局完成后进行连接操作,打开连接检查器,选择文件所有者对象(文档类的代理),将每个输出口的圆圈拖到窗口中对应的
CMColorBlendView
上。
回到
CMDocument.m
,添加代码配置每个
CMColorBlendView
的混合模式并手动配置绑定,使颜色选择改变时视图更新。
#import "CMColorBlendView.h"
- (void)windowControllerDidLoadNib:(NSWindowController *)windowController
{
[super windowControllerDidLoadNib:windowController];
if (isNew) {
id newObj = [_objectController newObject];
[newObj setValue:[NSColor redColor] forKey:@"color1"];
[newObj setValue:[NSColor yellowColor] forKey:@"color2"];
[_objectController addObject:newObj];
}
_multiplyBlendView.blendMode = kCGBlendModeMultiply;
_screenBlendView.blendMode = kCGBlendModeScreen;
_overlayBlendView.blendMode = kCGBlendModeOverlay;
_darkenBlendView.blendMode = kCGBlendModeDarken;
_lightenBlendView.blendMode = kCGBlendModeLighten;
_colorDodgeBlendView.blendMode = kCGBlendModeColorDodge;
_colorBurnBlendView.blendMode = kCGBlendModeColorBurn;
_softLightBlendView.blendMode = kCGBlendModeSoftLight;
_hardLightBlendView.blendMode = kCGBlendModeHardLight;
_differenceBlendView.blendMode = kCGBlendModeDifference;
_exclusionBlendView.blendMode = kCGBlendModeExclusion;
_hueBlendView.blendMode = kCGBlendModeHue;
_saturationBlendView.blendMode = kCGBlendModeSaturation;
_colorBlendView.blendMode = kCGBlendModeColor;
_luminosityBlendView.blendMode = kCGBlendModeLuminosity;
NSArray *allBlendViews =
@[_multiplyBlendView, _screenBlendView, _overlayBlendView,
_darkenBlendView, _lightenBlendView, _colorDodgeBlendView,
_colorBurnBlendView, _softLightBlendView, _hardLightBlendView,
_differenceBlendView, _exclusionBlendView, _hueBlendView,
_saturationBlendView, _colorBlendView, _luminosityBlendView];
for (CMColorBlendView *cbv in allBlendViews) {
[cbv bind:@"color1"
toObject:_objectController
withKeyPath:@"selection.color1"
options:nil];
[cbv bind:@"color2"
toObject:_objectController
withKeyPath:@"selection.color2"
options:nil];
}
}
代码第一部分逐个设置视图的混合模式,第二部分遍历所有
ColorBlendView
数组进行绑定配置。
保存工作并运行程序,点击颜色选择器可看到 15 种混合颜色随之更新。程序运行后可使用标准菜单项创建、保存、关闭文档等,Cocoa 的文档架构会处理相关细节,开发者也可进行功能增强。
2. 撤销与重做功能
在 Cocoa 中,
NSUndoManager
处理撤销/重做支持。在
ColorMix
应用中,改变颜色等操作可通过编辑菜单撤销和重做,且这些操作是文档特定的。在 Core Data 应用中,基本的撤销/重做功能通常会自动处理,托管对象上下文能检测对象编辑并将反向操作添加到“撤销栈”。
撤销栈的基本原理是每次编辑操作时创建反向操作表示,撤销时从撤销栈弹出最近项执行,同时将其反向操作添加到“重做栈”。Cocoa 实现撤销项时使用目标对象、方法选择器和参数隐式构建,触发撤销命令时直接调用方法。
例如,在有可设置名称的类中,若要使设置名称操作可撤销,可添加如下代码:
- (void)setName:(NSString *)newName {
if (![newName isEqual:name]) {
NSUndoManager *undoManager = ...
[undoManager registerUndoWithTarget:self
selector:@selector(setName:)
object:name];
[undoManager setActionName:@"Name Change"];
name = newName;
}
}
在实际应用中,撤销管理器来源取决于是否有 Core Data 和
NSDocument
支持。Core Data 应用会自动实现类似上述代码的功能,检测到模型对象编辑时设置撤销操作。
3. 异常处理机制
在编程中,事情可能不会按计划进行,Cocoa 提供了处理异常和错误的机制。异常是特殊对象,用于在程序某部分告知另一部分出现问题,创建并抛出异常的代码示例如下:
// imagine we're in a method that has a parameter "index", whose
// value must not be negative.
if (index < 0) {
[NSException raise:NSRangeException format:
@"I can't take all this negativity! (index == %d)", index];
}
使用
NSException
类的类方法创建并抛出异常,第一个参数是异常名称,用于分类异常;第二个参数是异常原因的格式化字符串。
当抛出异常时,程序正常流程中断,会在调用栈中寻找异常处理程序。异常处理程序由
@try
、
@catch
和可选的
@finally
块组成。
@try {
if (index < 0) {
[NSException raise:NSRangeException format:
@"I can't take all this negativity! (index == %d)", index];
}
}
@catch (NSException *e) {
NSLog(@"Encountered exception %@ with reason %@",
[e name],
[e reason]);
}
@finally {
// we don't have anything to do here, really.
}
在 Cocoa 中,异常用于表示程序运行时不应出现的严重问题,通常是代码存在 bug。与其他语言不同,Cocoa 对异常使用较为谨慎,一般仅用于报告可能由代码错误导致的意外结果。默认情况下,Cocoa 应用有顶级异常处理程序,输出异常信息后尝试继续运行,但可能使应用处于未定义或不一致状态,部分应用会安装自定义顶级异常处理程序。
为演示 Cocoa 程序员可能遇到的异常,在 Xcode 中创建名为 “ExceptionCity” 的新 Cocoa 应用,类前缀为 “EC”。
ECAppDelegate.m
文件内容如下:
#import "ECAppDelegate.h"
@implementation ECAppDelegate
- (void)invalidArgumentException_unrecognizedSelector {
NSArray *nameComponents = @[@"Thurston",
@"Howell",
[NSNumber numberWithInt:3]];
NSInteger nameComponentLength = 0;
for (NSString *component in nameComponents) {
nameComponentLength += [component length];
}
NSLog(@"Total length of all name components: %ld",
(long)nameComponentLength);
}
- (void)invalidArgumentException_insertNil {
NSMutableArray *array = [NSMutableArray array];
id object1 = @"hello";
[array addObject:object1];
id object2 = nil;
[array addObject:object2];
NSLog(@"inserted all the objects I could!");
}
- (void)rangeException {
NSArray *array = @[@"one", @"two", @"three"];
NSUInteger indexOfTwo = [array indexOfObject:@"two"];
NSLog(@"found indexed item %@", array[indexOfTwo]);
NSUInteger indexOfFive = [array indexOfObject:@"five"];
NSLog(@"found indexed item %@", array[indexOfFive]);
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[self invalidArgumentException_unrecognizedSelector];
[self invalidArgumentException_insertNil];
[self rangeException];
NSRunAlertPanel(@"Success", @"Hooray, you fixed everything!",
nil, nil, nil);
}
@end
运行代码时,由于未处理异常,程序会跳过部分操作并输出异常信息。此时可使用 Xcode 调试器定位问题。
4. Xcode 调试器使用
大多数开发者熟悉调试器概念,它可在程序运行时检查状态诊断问题。以下是调试器的关键概念:
-
断点
:可通过源代码行号或方法、函数名指定程序暂停位置,所有断点显示在项目窗口左侧的断点导航器中。程序在断点暂停时,可检查 CPU 寄存器和相关变量,这些信息显示在项目窗口底部的调试区域的表格视图中。
-
调用栈
:显示任意时刻正在运行的嵌套方法和函数列表,在 Xcode 的调试导航器的表格视图中显示。程序暂停时,当前方法或函数在栈顶,调用它的方法在下方,可选择栈帧切换焦点查看对应时刻的 CPU 寄存器和变量。
-
调试操作按钮
:可对当前高亮代码行进行操作,如单步执行、进入方法、跳出方法、继续或停止程序。
-
lldb 命令行界面
:可执行上述功能,还能执行任意代码并查看结果,可像在源代码中一样调用 C 函数和 Objective-C 方法,使用程序变量作为消息接收者和参数。
在 Xcode 中,通过选择视图 ➤ 导航器 ➤ 显示断点导航器或按 ⌘6 打开断点导航器,点击底部左侧的 + 符号创建新断点,选择“添加异常断点”,将异常弹出框设置为 Objective-C,断点弹出框设置为 On Throw,点击完成。
重启 “ExceptionCity” 应用,当遇到问题代码时程序会在异常抛出处暂停。此时可查看调用栈和调试区域信息,若要查看异常信息,需点击
objc_exception_throw
函数栈帧。
通过以上内容,我们学习了文档应用开发、异常处理和调试器使用等知识,这些技能对于 Cocoa 应用开发至关重要。
文档应用开发与异常调试全解析
5. 异常处理示例分析
下面我们详细分析
ExceptionCity
应用中几个会引发异常的方法。
5.1
invalidArgumentException_unrecognizedSelector
方法
- (void)invalidArgumentException_unrecognizedSelector {
NSArray *nameComponents = @[@"Thurston",
@"Howell",
[NSNumber numberWithInt:3]];
NSInteger nameComponentLength = 0;
for (NSString *component in nameComponents) {
nameComponentLength += [component length];
}
NSLog(@"Total length of all name components: %ld",
(long)nameComponentLength);
}
在这个方法中,数组
nameComponents
包含了一个
NSNumber
对象。当使用
for
循环遍历数组并尝试调用
length
方法时,由于
NSNumber
没有
length
方法,就会引发异常。这体现了动态类型语言中类型不匹配可能导致的问题。
可以通过以下方式修正这个问题:
- (void)invalidArgumentException_unrecognizedSelector {
NSArray *nameComponents = @[@"Thurston",
@"Howell",
[NSNumber numberWithInt:3]];
NSInteger nameComponentLength = 0;
for (id component in nameComponents) {
if ([component isKindOfClass:[NSString class]]) {
nameComponentLength += [component length];
}
}
NSLog(@"Total length of all name components: %ld",
(long)nameComponentLength);
}
通过检查对象类型,避免了对非
NSString
对象调用
length
方法。
5.2
invalidArgumentException_insertNil
方法
- (void)invalidArgumentException_insertNil {
NSMutableArray *array = [NSMutableArray array];
id object1 = @"hello";
[array addObject:object1];
id object2 = nil;
[array addObject:object2];
NSLog(@"inserted all the objects I could!");
}
在这个方法中,尝试向可变数组
array
中添加
nil
对象,这会引发异常。因为
NSMutableArray
不允许添加
nil
对象。
修正方法是在添加对象前检查是否为
nil
:
- (void)invalidArgumentException_insertNil {
NSMutableArray *array = [NSMutableArray array];
id object1 = @"hello";
[array addObject:object1];
id object2 = nil;
if (object2 != nil) {
[array addObject:object2];
}
NSLog(@"inserted all the non - nil objects!");
}
5.3
rangeException
方法
- (void)rangeException {
NSArray *array = @[@"one", @"two", @"three"];
NSUInteger indexOfTwo = [array indexOfObject:@"two"];
NSLog(@"found indexed item %@", array[indexOfTwo]);
NSUInteger indexOfFive = [array indexOfObject:@"five"];
NSLog(@"found indexed item %@", array[indexOfFive]);
}
在这个方法中,当查找
@"five"
在数组中的索引时,由于数组中不存在该元素,
indexOfObject:
方法会返回
NSNotFound
。而直接使用
NSNotFound
作为索引访问数组会引发越界异常。
修正方法是在使用索引前检查是否为
NSNotFound
:
- (void)rangeException {
NSArray *array = @[@"one", @"two", @"three"];
NSUInteger indexOfTwo = [array indexOfObject:@"two"];
if (indexOfTwo != NSNotFound) {
NSLog(@"found indexed item %@", array[indexOfTwo]);
}
NSUInteger indexOfFive = [array indexOfObject:@"five"];
if (indexOfFive != NSNotFound) {
NSLog(@"found indexed item %@", array[indexOfFive]);
}
}
6. 异常处理与调试流程总结
为了更清晰地展示异常处理和调试的流程,下面用 mermaid 流程图表示:
graph TD;
A[编写代码] --> B[运行程序];
B --> C{是否抛出异常};
C -- 是 --> D[查看控制台异常信息];
D --> E[使用 Xcode 调试器设置异常断点];
E --> F[重新运行程序在断点处暂停];
F --> G[分析调用栈和变量];
G --> H[定位问题代码];
H --> I[修复代码];
I --> B;
C -- 否 --> J[程序正常运行];
7. 总结
通过以上内容,我们深入学习了文档应用开发中的多个重要方面:
-
GUI 配置
:学会了如何在代码中配置 GUI,包括添加混合颜色样本到文档窗口,设置视图的混合模式和绑定颜色选择器。
-
撤销与重做
:了解了
NSUndoManager
的工作原理和撤销栈的机制,以及在 Core Data 应用中撤销/重做功能的自动实现。
-
异常处理
:掌握了异常的创建、抛出和捕获,以及 Cocoa 中异常的使用场景和限制。
-
调试技巧
:学会了使用 Xcode 调试器设置断点、检查调用栈和变量,从而定位和解决异常问题。
这些知识和技能对于开发高质量的 Cocoa 应用至关重要,能够帮助开发者更好地应对开发过程中遇到的各种问题,提高程序的稳定性和可靠性。
以下是一个简单的表格总结不同异常类型及解决方法:
| 异常类型 | 引发原因 | 解决方法 |
| ---- | ---- | ---- |
|
NSInvalidArgumentException
(
unrecognizedSelector
) | 调用对象不存在的方法 | 检查对象类型,避免对不支持的对象调用方法 |
|
NSInvalidArgumentException
(
insertNil
) | 向不允许添加
nil
的集合添加
nil
对象 | 在添加对象前检查是否为
nil
|
|
NSRangeException
| 数组越界访问 | 使用索引前检查是否为
NSNotFound
|
超级会员免费看
5万+

被折叠的 条评论
为什么被折叠?



