30、文档应用开发与异常调试全解析

文档应用开发与异常调试全解析

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 |

【SCI复现】基于纳什博弈的多微网主体电热双层共享策略研究(Matlab代码实现)内容概要:本文围绕“基于纳什博弈的多微网主体电热双层共享策略研究”展开,结合Matlab代码实现,复现了SCI级别的科研成果。研究聚焦于多个微网主体之间的能源共享问题,引入纳什博弈理论构建双层优化模型,上层为各微网间的非合作博弈策略,下层为各微网内部电热联合优化调度,实现能源高效利用经济性目标的平衡。文中详细阐述了模型构建、博弈均衡求解、约束处理及算法实现过程,并通过Matlab编程进行仿真验证,展示了多微网在电热耦合条件下的运行特性和共享效益。; 适合人群:具备一定电力系统、优化理论和博弈论基础知识的研究生、科研人员及从事能源互联网、微电网优化等相关领域的工程师。; 使用场景及目标:① 学习如何将纳什博弈应用于多主体能源系统优化;② 掌握双层优化模型的建模求解方法;③ 复现SCI论文中的仿真案例,提升科研实践能力;④ 为微电网集群协同调度、能源共享机制设计提供技术参考。; 阅读建议:建议读者结合Matlab代码逐行理解模型实现细节,重点关注博弈均衡的求解过程双层结构的迭代逻辑,同时可尝试修改参数或扩展模型以适应不同应用场景,深化对多主体协同优化机制的理解。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值