Objective c 为什么';t@try…@catch与-[NSFileHandle writeData]一起工作?
我有一个类似于tee实用程序的方法。它接收到一个通知,表示已在管道上读取数据,然后将该数据写入一个或多个管道(连接到从属应用程序)。如果一个下级应用程序崩溃,那么这个管道就会断开,我自然会得到一个异常,然后在@try…@catch块中处理 这在大多数情况下都有效。让我感到困惑的是,偶尔,异常会导致应用程序完全崩溃,同时出现一个未捕获的异常,并指向writeData行。我一直无法弄清楚它崩溃时的模式是什么,但为什么它永远不会被捕获?(注意,这不是在调试器内部执行。) 代码如下:Objective c 为什么';t@try…@catch与-[NSFileHandle writeData]一起工作?,objective-c,cocoa,Objective C,Cocoa,我有一个类似于tee实用程序的方法。它接收到一个通知,表示已在管道上读取数据,然后将该数据写入一个或多个管道(连接到从属应用程序)。如果一个下级应用程序崩溃,那么这个管道就会断开,我自然会得到一个异常,然后在@try…@catch块中处理 这在大多数情况下都有效。让我感到困惑的是,偶尔,异常会导致应用程序完全崩溃,同时出现一个未捕获的异常,并指向writeData行。我一直无法弄清楚它崩溃时的模式是什么,但为什么它永远不会被捕获?(注意,这不是在调试器内部执行。) 代码如下: //in setu
//in setup:
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tee:) name:NSFileHandleReadCompletionNotification object:fileHandle];
-(void)tee:(NSNotification *)notification
{
// NSLog(@"Got read for tee ");
NSData *readData = notification.userInfo[NSFileHandleNotificationDataItem];
totalDataRead += readData.length;
// NSLog(@"Total Data Read %ld",totalDataRead);
NSArray *pipes = [teeBranches objectForKey:notification.object];
if (readData.length) {
for (NSPipe *pipe in pipes {
@try {
[[pipe fileHandleForWriting] writeData:readData];
}
@catch (NSException *exception) {
NSLog(@"download write fileHandleForWriting fail: %@", exception.reason);
if (!_download.isCanceled) {
[_download rescheduleOnMain];
NSLog(@"Rescheduling");
}
return;
}
@finally {
}
}
}
我应该提到,我已经在我的AppDelegate>appDidFinishLaunching中设置了一个信号处理程序:
signal(SIGPIPE, &signalHandler);
signal(SIGABRT, &signalHandler );
void signalHandler(int signal)
{
NSLog(@"Got signal %d",signal);
}
无论应用程序崩溃还是信号被捕获,这都会执行。
下面是一个示例崩溃回溯:
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Application Specific Information:
*** Terminating app due to uncaught exception 'NSFileHandleOperationException', reason: '*** -[NSConcreteFileHandle writeData:]: Broken pipe'
abort() called
terminating with uncaught exception of type NSException
Application Specific Backtrace 1:
0 CoreFoundation 0x00007fff838cbbec __exceptionPreprocess + 172
1 libobjc.A.dylib 0x00007fff90e046de objc_exception_throw + 43
2 CoreFoundation 0x00007fff838cba9d +[NSException raise:format:] + 205
3 Foundation 0x00007fff90a2be3c __34-[NSConcreteFileHandle writeData:]_block_invoke + 81
4 Foundation 0x00007fff90c53c17 __49-[_NSDispatchData enumerateByteRangesUsingBlock:]_block_invoke + 32
5 libdispatch.dylib 0x00007fff90fdfb76 _dispatch_client_callout3 + 9
6 libdispatch.dylib 0x00007fff90fdfafa _dispatch_data_apply + 110
7 libdispatch.dylib 0x00007fff90fe9e73 dispatch_data_apply + 31
8 Foundation 0x00007fff90c53bf0 -[_NSDispatchData enumerateByteRangesUsingBlock:] + 83
9 Foundation 0x00007fff90a2bde0 -[NSConcreteFileHandle writeData:] + 150
10 myApp 0x000000010926473e -[MTTaskChain tee:] + 2030
11 CoreFoundation 0x00007fff838880dc __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 12
12 CoreFoundation 0x00007fff83779634 _CFXNotificationPost + 3140
13 Foundation 0x00007fff909bb9b1 -[NSNotificationCenter postNotificationName:object:userInfo:] + 66
14 Foundation 0x00007fff90aaf8e6 _performFileHandleSource + 1622
15 CoreFoundation 0x00007fff837e9ae1 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
16 CoreFoundation 0x00007fff837dbd3c __CFRunLoopDoSources0 + 476
17 CoreFoundation 0x00007fff837db29f __CFRunLoopRun + 927
18 CoreFoundation 0x00007fff837dacb8 CFRunLoopRunSpecific + 296
19 HIToolbox 0x00007fff90664dbf RunCurrentEventLoopInMode + 235
20 HIToolbox 0x00007fff90664b3a ReceiveNextEventCommon + 431
21 HIToolbox 0x00007fff9066497b _BlockUntilNextEventMatchingListInModeWithFilter + 71
22 AppKit 0x00007fff8acf5cf5 _DPSNextEvent + 1000
23 AppKit 0x00007fff8acf5480 -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 194
24 AppKit 0x00007fff8ace9433 -[NSApplication run] + 594
25 AppKit 0x00007fff8acd4834 NSApplicationMain + 1832
26 myApp 0x00000001091b16a2 main + 34
27 myApp 0x00000001091ab864 start + 52
我知道这并不能回答为什么异常捕获看起来被破坏了,但我希望这是一个解决这个问题的有用答案 我在尝试读取/写入封装在
NSFileHandle
中的套接字时遇到了类似的问题。我通过直接使用fileDescriptor
测试管道的可用性来解决这个问题
- (BOOL)socketIsValid
{
return (write([fh fileDescriptor], NULL, 0) == 0);
}
然后,在尝试调用
writeData:
之前,我使用该方法进行了测试,因此,Crashlytics的好心人能够在这里帮助我。引用他们的话:
故事是这样的:
- 由于子进程崩溃,管道死亡。下一次读/写操作将导致故障
- 发生该写操作时,会导致SIGPIPE(不是运行时异常)
- 如果该SIGPIPE被屏蔽/忽略,NSFileHandle将检查errno并创建一个运行时异常,并引发该异常
- 一个比tee:method更深的函数已将此写入封装在@try/@catch中(通过在
上设置断点来证明)\uucxa\u begin\u catch
- 该函数是“\u dispatch\u client\u callout”,它调用objc\u terminate,从而有效地终止 过程
-(void)tee:(NSNotification *)notification {
NSData *readData = notification.userInfo[NSFileHandleNotificationDataItem];
totalDataRead += readData.length;
// NSLog(@"Total Data Read %ld",totalDataRead);
NSArray *pipes = [teeBranches objectForKey:notification.object];
if (readData.length) {
for (NSPipe *pipe in pipes ) {
NSInteger numTries = 3;
size_t bytesLeft = readData.length;
while (bytesLeft > 0 && numTries > 0 ) {
ssize_t amountSent= write ([[pipe fileHandleForWriting] fileDescriptor], [readData bytes]+readData.length-bytesLeft, bytesLeft);
if (amountSent < 0) {
NSLog(@"write fail; tried %lu bytes; error: %zd", bytesLeft, amountSent);
break;
} else {
bytesLeft = bytesLeft- amountSent;
if (bytesLeft > 0) {
NSLog(@"pipe full, retrying; tried %lu bytes; wrote %zd", (unsigned long)[readData length], amountSent);
sleep(1); //probably too long, but this is quite rare
numTries--;
}
}
}
if (bytesLeft >0) {
if (numTries == 0) {
NSLog(@"Write Fail4: couldn't write to pipe after three tries; giving up");
}
[self rescheduleOnMain];
}
}
}
}
-(void)tee:(NSNotification*)通知{
NSData*readData=notification.userInfo[NSFileHandleNotificationDataItem];
totalDataRead+=readData.length;
//NSLog(@“总数据读取%ld”,总数据读取);
NSArray*pipes=[teebracks-objectForKey:notification.object];
if(readData.length){
用于(NSPipe*管道中的管道){
NSInteger numTries=3;
size\u t byteslight=readData.length;
while(bytesleet>0&&numTries>0){
ssize_t amountSent=write([[pipe fileHandleForWriting]fileDescriptor],[readData bytes]+readData.length-byteslight,byteslight);
如果(发送的数量<0){
NSLog(@“写入失败;尝试了%lu个字节;错误:%zd”,字节左,已发送的数量);
打破
}否则{
bytesLeft=bytesLeft-发送的数量;
如果(字节左>0){
NSLog(@“管道已满,正在重试;尝试了%lu字节;写入了%zd”,(无符号长)[readData length],amountSent);
睡眠(1);//可能太长了,但这种情况很少见
numTries--;
}
}
}
如果(字节左>0){
如果(numTries==0){
NSLog(@“写入失败4:尝试三次后无法写入管道;放弃”);
}
[自我重新安排];
}
}
}
}
这不是一个异常,它是一个C信号。那么为什么它会说“由于未捕获的异常'NSFileHandleOperationException'”而终止应用程序?我很好奇:@catch块中的断点在这次崩溃中没有被命中?我怀疑“rescheduleOnMain”是在主线程崩溃后不能做的事情。这是正确的。如果在调试器下运行(在catch的第一行上有一个断点),并且发生管道故障,则调试器将在写入时捕获一个SIGPIPE;当我继续时,我得到了“get signal 13”,然后是堆栈转储,在writeData行和“libc++abi.dylib:以NSException类型的未捕获异常终止”,然后我再次点击continue,它将我带到一个系统问题报告。在最后一部分,抓住一个信号管并继续下去是很标准的。你们有没有找到解决这个问题的办法?你能捕获这个异常吗?我试过了,上面的写测试结果很好。我认为问题是,书写本身会导致管道失效,而管道已经失效。@麦克沃思好吧,那么问题可能是管道在写入操作过程中下降了吗?还有一个解决方法是将级别降低到低于NSFileHandle
;如果你想知道其他的事情,我希望你发帖子,因为我想知道:)我遇到了同样的问题,但我发现EPIPE实际上是被处理的,并被做成了一个objc异常。