异常处理--Exception Handling
Objective-C语言具有类似于Java和c++的异常处理语法。通过对NSException、NSError或自定义类使用此语法,您可以向程序中添加健壮的错误处理。
异常处理
异常是中断程序正常执行流的特殊情况。产生异常(异常通常被称为引发或抛出)的原因有很多,硬件和软件都有。示例包括算术错误,如除以0、下溢或溢出,调用未定义的指令(如试图调用未实现的方法),以及试图越界访问集合元素。
Objective-C异常支持包含四个编译器指令:@try, @catch, @throw,和@finally:
可能抛出异常的代码包含在@try{}块中。
@catch{}块包含@try{}块中抛出的异常的异常处理逻辑。您可以使用多个@catch{}块来捕获不同类型的异常。
你使用@throw指令来抛出一个异常,它本质上是一个Objective-C对象。通常使用NSException对象,但不需要这样做。
@finally{}块包含无论是否抛出异常都必须执行的代码。
这个例子描述了一个简单的异常处理算法:
Cup *cup = [[Cup alloc] init];
@try {
[cup fill];
}
@catch (NSException *exception) {
NSLog(@"main: Caught %@: %@", [exception name], [exception reason]);
}
@finally {
[cup release];
}
下面的列表描述了编号的代码行:
1.捕获最特定的异常类型。
2.捕获更通用的异常类型。
3.执行必须始终执行的任何清理处理,无论是否抛出异常。
抛出异常
要抛出异常,必须用适当的信息实例化对象,例如异常名称和抛出异常的原因。
NSException *exception = [NSException exceptionWithName: @"HotTeaException"
reason: @"The tea is too hot"
userInfo: nil];
@throw exception;
重要:在许多环境中,异常的使用相当普遍。例如,您可能会抛出一个异常来表示例程不能正常执行——例如当文件丢失或数据不能正确解析时。在Objective-C中,异常是资源密集型的。对于一般的流控制,不应该使用异常,或者仅仅表示错误。相反,您应该使用方法或函数的返回值来指示错误已经发生,并在错误对象中提供有关问题的信息。
在@catch{}块中,您可以使用@throw指令重新抛出捕获的异常,而无需提供参数。在这种情况下,省略参数有助于提高代码的可读性。
您不限于抛出NSException对象。你可以将任何Objective-C对象作为异常对象抛出。NSException类提供了有助于异常处理的方法,但是如果您愿意,也可以实现自己的方法。您还可以子类化NSException来实现特殊类型的异常,例如文件系统异常或通信异常。
Threading--线程
Objective-C在应用中支持多线程。因此,两个线程可以尝试同时修改同一个对象,这种情况可能会在程序中导致严重的问题。为了防止一段代码同时被多个线程执行,Objective-C提供了@synchronized()指令。
@synchronized()指令锁定一段代码供单个线程使用。其他线程被阻塞,直到该线程退出受保护的代码,也就是说,当执行继续通过@synchronized()块中的最后一条语句时。
@synchronized()指令只接受任何Objective-C对象的参数,包括self。这个对象被称为互斥信号量或互斥锁。它允许一个线程锁定一段代码,以防止其他线程使用它。您应该使用单独的信号量来保护程序的不同关键部分。在应用程序成为多线程之前创建所有互斥对象是最安全的,以避免竞争条件。
清单11-1显示了使用self作为互斥锁来同步对当前对象的实例方法的访问的代码。您可以使用类似的方法来同步关联类的类方法,使用类对象而不是self。当然,在后一种情况下,一次只允许一个线程执行类方法,因为只有一个类对象是所有调用者共享的。
清单
- (void)criticalMethod
{
@synchronized(self) {
//关键代码。
...
}
}
清单11-2展示了一种通用方法。在执行关键流程之前,代码从Account类获取一个信号量,并使用它锁定关键部分。Account类可以在其initialize方法中创建信号量。
清单11-2使用自定义信号量锁定方法
Account *account = [Account accountFromString:[accountField stringValue]];
// 得到信号量
id accountSemaphore = [Account semaphore];
@synchronized(accountSemaphore) {
// 关键代码。
...
}
Objective-C同步特性支持递归和可重入代码。线程可以递归地多次使用单个信号量;其他线程被阻止使用它,直到线程释放用它获得的所有锁;也就是说,每个@synchronized()块都会正常退出或通过异常退出。
当@synchronized()块中的代码抛出异常时,Objective-C运行时捕获异常,释放信号量(以便其他线程可以执行受保护的代码),并将异常重新抛出到下一个异常处理程序。
异常和Cocoa框架
Cocoa中的异常由NSException类的对象表示,NSException类是Foundation框架的一部分。该类的方法允许您创建异常对象,引发(抛出)异常,并获取与异常相关的调用返回地址。NSException对象的属性如下:
name——用于唯一标识异常的短字符串。名称是必需的。
reason ——一个较长的字符串,包含异常的“可读”原因。原因是必须的。
一个可选的字典(userInfo),用于向异常处理程序提供特定于应用程序的数据。例如,如果方法的返回值引发异常,您可以通过userInfo将返回值传递给异常处理程序。
您可以在异常对象中提取信息,并在适当的情况下在警报对话框中显示给用户,也可以使用NSError对象。
处理异常
Objective-C程序可用的异常处理机制是处理异常情况的有效方法。它们将这些条件的检测和处理解耦,并自动将异常从检测点传播到处理点。因此,您的代码可以更清晰、更容易正确地编写和维护。
通知----Notifications
通知封装有关事件的信息,例如获得焦点的窗口或网络连接关闭。需要知道事件的对象(例如,需要知道窗口何时关闭的文件)在事件发生时向通知中心注册它希望得到通知。当事件发生时,通知被发布到通知中心,通知中心将立即将通知广播给所有注册的对象。可选地,通知在通知队列中排队,通知队列在延迟指定的通知后将通知发送到通知中心,并根据您指定的某些指定条件合并类似的通知。
通知及其基本原理
在对象之间传递信息的标准方法是消息传递——一个对象调用另一个对象的方法。然而,消息传递要求发送消息的对象知道接收者是谁,以及它响应什么消息。有时,两个对象的这种紧密耦合是不可取的——最显著的原因是它会将两个本来独立的子系统连接在一起。对于这些情况,引入了广播模型:对象发布通知,通过NSNotificationCenter对象或简单的通知中心将通知分派给适当的观察者。
NSNotification对象(称为通知)包含名称、对象和可选字典。名称是标识通知的标记。对象是通知的海报想要发送给该通知的观察者的任何对象——通常是发布通知本身的对象。字典可能包含有关事件的附加信息。
通知名称:通知名称通常定义为常量字符串变量——例如,NSWindowDidBecomeMainNotification。通常字符串的值与变量名相似。但是,您不应该在代码中使用字符串值,您应该始终使用变量的名称
任何对象都可以发布通知。其他对象可以将自己注册为通知中心的观察员,以便在发布通知时接收通知。通知中心负责向注册观察员广播通知(如有)。发布通知的对象、通知中包含的对象和通知的观察者都可以是不同的对象或相同的对象。发布通知的对象不需要知道关于观察者的任何信息。另一方面,如果提供的话,观察者至少需要知道通知名称和字典的键。
通知和代理
使用通知系统与使用委托类似,但有以下不同之处:
1.任何数量的对象都可以接收通知,而不仅仅是委托对象。这就排除了返回值的可能性。
2.对象可以从通知中心接收您喜欢的任何消息,而不仅仅是预定义的委托方法。
3. 发布通知的对象甚至不需要知道观察者的存在。
通知中心
通知中心管理通知的发送和接收。它将符合特定标准的通知通知所有观察者。通知信息封装在NSNotification对象中。客户端对象在通知中心将自己注册为其他对象发布的特定通知的观察者。当事件发生时,对象将适当的通知发送到通知中心。通知中心向每个注册的观察者发送一条消息,将通知作为唯一的参数传递。发布对象和观察对象可能是相同的。
NSNotificationCenter
每个进程都有一个默认的通知中心,您可以使用NSNotificationCenter +defaultCenter类方法访问该中心。此通知中心在单个进程中处理通知。对于同一机器上的进程之间的通信,请使用分布式通知中心。
通知中心同步地向观察者发送通知。换句话说,当发布通知时,直到所有观察者都收到并处理了通知,控件才返回到海报。要异步发送通知,请使用通知队列,该队列在通知队列中进行了描述。
Notification Queues--通知队列
NSNotificationQueue对象,或者简单地说,通知队列,充当通知中心(NSNotificationCenter的实例)的缓冲区。NSNotificationQueue类为Foundation Kit的通知机制提供了两个重要特性:通知的合并和异步发布。
Notification Queue Basics--通知队列基础
使用NSNotificationCenter的postNotification:方法及其变体,您可以将通知发布到通知中心。但是,方法的调用是同步的:在post对象可以恢复其执行线程之前,它必须等待通知中心将通知分派给所有观察者并返回。另一方面,通知队列通常以先进先出(FIFO)的顺序维护通知(NSNotification的实例)。当通知上升到队列的前端时,队列将其发送到通知中心,该中心随后将通知分派给所有注册为观察者的对象。
每个线程都有一个默认通知队列,该队列与流程的默认通知中心相关联。您可以创建自己的通知队列,每个中心和线程有多个队列。
异步发布通知
使用NSNotificationQueue的enqueueNotification:postingStyle:和enqueueNotification:postingStyle:coalesceMask:forModes: methods,您可以通过将通知放入队列中,将其异步地发送到当前线程。将通知放入队列后,这些方法立即返回调用对象。
注意:当进入通知队列的线程在通知队列将通知发布到其通知中心之前终止时,通知不会发布。
根据排队方法中指定的发布样式和运行循环模式,清空通知队列并发布其通知。mode参数指定清空队列的运行循环模式。例如,如果指定NSModalPanelRunLoopMode,则仅在运行循环处于该模式时才会发出通知。如果运行循环当前不在此模式中,则通知将等待下一次输入该模式。
向通知队列发送消息的方式有三种:NSPostASAP、NSPostWhenIdle和NSPostNow。
NSPostASAP--尽快--发布通知
假设当前运行循环模式与所请求的模式匹配,那么当运行循环的当前迭代完成时,以NSPostASAP样式排队的任何通知都将被发布到通知中心。(如果请求模式和当前模式不同,则在输入请求模式时发出通知。)因为run循环可以在每次迭代中执行多个callout,所以在当前callout退出并将控件返回到run循环时,通知可能会被传递,也可能不会被传递。其他标注可能首先发生,例如计时器或源触发或其他异步通知被传递。
对于昂贵的资源(如显示服务器),通常使用NSPostASAP发布样式。当许多客户端在run循环的callout期间使用窗口缓冲区时,在每次绘制操作之后将缓冲区刷新到显示服务器是非常昂贵的。在这种情况下,每个抽签…方法将某些通知(如“FlushTheServer”)加入队列,并将指定的名称和对象合并,并使用NSPostASAP的发布样式。因此,这些通知中只有一个在运行循环的末尾发出,并且只刷新一次窗口缓冲区。
NSPostWhenIdle--发帖的时候空闲
只有在运行循环处于等待状态时,才使用NSPostWhenIdle样式发布队列中的通知。在这种状态下,运行循环的输入通道中没有任何内容,无论是计时器还是其他异步事件。这是一个典型的例子,当用户输入文本时,程序会以字节的形式显示文本的大小,并使用NSPostWhenIdle样式进行排队。在用户键入的每个字符之后更新文本字段的大小是非常昂贵的(也不是非常有用),特别是在用户快速键入的情况下。在本例中,程序将通知(如“ChangeTheDisplayedSize”)放入队列,并打开合并功能,在每个字符键入后空闲时将其发送样式设置为nspostwhen。当用户停止输入时,当运行循环进入等待状态并更新显示时,队列中的单个“ChangeTheDisplayedSize”通知(由于合并)将被发布。请注意,即将退出的运行循环(在所有输入通道都已过期时发生)没有处于等待状态,因此不会发布通知。
立即发布-----NSPostNow
使用NSPostNow排队的通知在合并到通知中心后立即发布。当不需要异步调用行为时,可以使用NSPostNow对通知进行队列(或者使用postNotification:)。对于许多编程情况,同步行为不仅是允许的,而且是可取的:您希望通知中心在调度之后返回,这样您就可以确保观察到的对象已经接收并处理了通知。当然,你应该使用enqueueNotification…使用NSPostNow而不是使用postNotification:当队列中有类似的通知需要通过合并删除时。
合并的通知
在某些情况下,如果给定的事件至少发生一次,您可能希望发布通知,但是即使该事件发生多次,您也希望发布的通知不超过一个。例如,在接收离散数据包中的数据的应用程序中,在接收到数据包时,您可能希望发出通知,以表示需要处理数据。但是,如果在给定的时间段内有多个数据包到达,则不希望发布多个通知。此外,发布这些通知的对象可能无法知道是否有更多的包到来,以及是否在循环中调用了post方法。
在某些情况下,可以简单地设置布尔标志(无论是对象的实例变量还是全局变量)来表示事件已经发生,并在清除标志之前禁止发布进一步的通知。但是,如果这是不可能的,在这种情况下,您不能直接使用NSNotificationCenter,因为它的行为是同步的——在返回之前发布通知,因此没有机会“忽略”重复的通知;此外,NSNotificationCenter实例无法知道是否有更多通知。
因此,您可以将通知添加到指定用于合并的适当选项的NSNotificationQueue实例中,而不是将通知发布到通知中心。合并是从队列通知中删除通知的过程,这些通知在某些方面与前面排队的通知类似。您可以通过在enqueueNotification的第三个参数中指定一个或多个以下常量来指示相似性的标准:postingStyle:coalesceMask:forModes:方法。
您可以使用NSNotificationCoalescingOnName和NSNotificationCoalescingOnSender常量执行位或操作,以使用通知名称和通知对象指定合并。下面的示例说明如何使用队列来确保在给定的事件循环周期中,名为MyNotificationName的所有通知都合并到单个通知中。
// MyNotificationName定义为全局变量
NSString *MyNotificationName = @"MyNotification";
id object = <#The object associated with the notification#>;
NSNotification *myNotification =
[NSNotification notificationWithName:MyNotificationName object:object]
[[NSNotificationQueue defaultQueue]
enqueueNotification:myNotification
postingStyle:NSPostWhenIdle
coalesceMask:NSNotificationCoalescingOnName
forModes:nil];
注册通知
您可以在自己的应用程序或其他应用程序中注册通知。若要注销通知(必须在释放对象时完成),。
注册本地通知
通过调用通知中心方法addObserver:selector:name:object:,指定观察者,通知中心应该发送给观察者的消息,它想接收的通知的名称,以及关于哪个对象,您注册一个对象来接收通知。您不需要同时指定名称和对象。如果只指定一个对象,观察者将接收到包含该对象的所有通知。如果您仅指定通知名称,则观察者将在每次发布该通知时接收该通知,而不考虑与之关联的对象。
观察者可以注册为同一通知接收多个消息。在这种情况下,观察者将接收它注册为接收通知的所有消息,但是无法确定它接收这些消息的顺序。
如果您稍后决定一个观察者不再需要接收通知(例如,如果观察者正在回收),您可以使用removeObserver:或removeObserver:name:object:方法从通知中心的观察者列表中删除该观察者。
通常,您向流程的默认通知中心注册对象。使用defaultCenter类方法获取默认对象。
作为使用通知中心接收通知的一个示例,假设您希望在窗口变为main时执行操作(例如,如果您正在为inspector面板实现控制器)。您可以将客户机对象注册为观察者,如下面的示例所示:
注册本地通知
通过调用通知中心方法addObserver:selector:name:object:,指定观察者,通知中心应该发送给观察者的消息,它想接收的通知的名称,以及关于哪个对象,您注册一个对象来接收通知。您不需要同时指定名称和对象。如果只指定一个对象,观察者将接收到包含该对象的所有通知。如果您仅指定通知名称,则观察者将在每次发布该通知时接收该通知,而不考虑与之关联的对象。
观察者可以注册为同一通知接收多个消息。在这种情况下,观察者将接收它注册为接收通知的所有消息,但是无法确定它接收这些消息的顺序。
如果您稍后决定一个观察者不再需要接收通知(例如,如果观察者正在回收),您可以使用removeObserver:或removeObserver:name:object:方法从通知中心的观察者列表中删除该观察者。
通常,您向流程的默认通知中心注册对象。使用defaultCenter类方法获取默认对象。
作为使用通知中心接收通知的一个示例,假设您希望在窗口变为main时执行操作(例如,如果您正在为inspector面板实现控制器)。您可以将客户机对象注册为观察者,如下面的示例所示:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(aWindowBecameMain:)
name:NSWindowDidBecomeMainNotification object:nil];
通过传递nil作为要观察的对象,当任何对象发布NSWindowDidBecomeMainNotification通知时,将通知客户机对象(self)。
当窗口成为主窗口时,它会向通知中心发布一个NSWindowDidBecomeMainNotification。通知中心通过调用在addObserver:selector:name:object:的selector参数中指定的方法来通知所有对通知感兴趣的观察者。在我们的示例observer中,选择器是aWindowBecameMain:。aWindowBecameMain:方法可能有以下实现:
- (void)aWindowBecameMain:(NSNotification *)notification {
NSWindow *theWindow = [notification object];
MyDocument = (MyDocument *)[[theWindow windowController] document];
// Retrieve information about the document and update the panel.
}
取消注册一个观察者
在释放正在观察通知的对象之前,它必须通知通知中心停止向其发送通知。否则,下一个通知将被发送到不存在的对象,程序将崩溃。您可以发送以下消息,以完全删除作为本地通知观察者的对象,而不管该对象注册了多少个对象和通知:
[[NSNotificationCenter defaultCenter] removeObserver:self];
分布式通知发送的观察者:
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
使用更具体的removeObserver…方法,该方法指定通知名称和被观察对象,以选择性地取消特定通知的对象注册。
发布一个通知
您可以在自己的应用程序中发布通知,或者将通知提供给其他应用程序。
当地发布通知
您可以使用notificationWithName:object:或notificationWithName:object:userInfo:创建通知对象。然后使用postNotification: instance方法将通知对象发布到通知中心。NSNotification对象是不可变的,因此一旦创建,它们就不能被修改。
但是,您通常不会直接创建自己的通知。NSNotificationCenter类的postNotificationName:object:和postNotificationName:object:userInfo:方法允许您在不先创建通知的情况下方便地发布通知。
在每种情况下,您通常将通知发布到流程的默认通知中心。使用defaultCenter类方法获取默认对象。
作为使用通知中心发布通知的示例,请考虑注册本地通知的示例。您有一个可以对文本执行许多转换的程序(例如,将RTF转换为ASCII)。转换由一类对象(转换器)处理,这些对象可以在程序执行期间添加或删除。您的程序可能有其他对象希望在添加或删除转换器时得到通知,但是转换器对象不需要知道这些对象是谁或它们做什么。因此,您声明两个通知,“ConverterAdded”和“converterremove”,在给定事件发生时发布这两个通知。
当用户安装或移除转换器时,它将向通知中心发送以下消息之一:
[[NSNotificationCenter defaultCenter]
postNotificationName:@"ConverterAdded" object:self];
或者
[[NSNotificationCenter defaultCenter]
postNotificationName:@"ConverterRemoved" object:self];
然后通知中心确定哪些对象(如果有的话)对这些通知感兴趣并通知它们。
如果观察者对其他对象感兴趣(除了通知名称和被观察对象),那么将它们放在通知的可选字典中,或者使用postNotificationName:object:userInfo:。
分布式发布通知
发布分布式通知与发布本地通知非常相似。您可以手动创建NSNotification对象并使用postNotification post:或者使用NSDistributedNotificationCenter便利方法之一。唯一的区别是通知对象必须是字符串对象,可选的user-info字典只能包含属性列表对象,如NSString和NSNumber。
给定通知的观察者可能处于挂起状态,不能立即处理通知。如果发布通知的对象希望确保所有观察者立即收到通知(例如,如果通知是服务器即将关闭的警告),它可以调用postNotificationName:object:userInfo: deliverimmediate: with deliverimmediate:YES。通知中心发送通知,就像观察员已经在nsnotificationsuspensionbehavior或deliverorimmediately中注册过一样(在注册分布式通知中有进一步的描述)。然而,不能保证交付。例如,接收通知的进程可能太忙,无法处理和接受排队的通知。在这种情况下,通知将被删除。
网友评论