【问题标题】:How to properly handle NSFileHandle exceptions in Swift 2.0?如何在 Swift 2.0 中正确处理 NSFileHandle 异常?
【发布时间】:2016-04-29 14:26:58
【问题描述】:

首先,我是 iOS 和 Swift 的新手,具有 Android/Java 编程背景。所以对我来说,在尝试写入文件时捕获异常的想法是第二天性,以防空间不足、文件权限问题或文件可能发生的其他任何事情(并且根据我的经验已经发生) .我也明白,在 Swift 中,异常与 Android/Java 不同,所以这不是我在这里要问的。

我正在尝试使用 NSFileHandle 附加到文件,如下所示:

let fileHandle: NSFileHandle? = NSFileHandle(forUpdatingAtPath: filename)
if fileHandle == nil {
    //print message showing failure
} else {
    let fileString = "print me to file"
    let data = fileString.dataUsingEncoding(NSUTF8StringEncoding)
    fileHandle?.seekToEndOfFile() 
    fileHandle?.writeData(data!)
}

但是,seekToEndOfFile()writeData() 函数都表明它们会引发某种异常:

如果文件描述符已关闭或无效,如果接收方表示未连接的管道或套接字端点,如果文件系统上没有剩余空间,或者发生任何其他写入错误,则此方法引发异常。 - writeData() 的 Apple 文档

那么在 Swift 2.0 中处理这个问题的正确方法是什么?我已阅读链接 Error-Handling in Swift-Languagetry-catch exceptions in SwiftNSFileHandle writeData: exception handlingSwift 2.0 exception handlingHow to catch an exception in Swift,但他们都没有直接回答我的问题。我确实读过一些关于在 Swift 代码中使用 Objective-C 的内容,但是由于我是 iOS 新手,所以我不知道这种方法是什么,而且似乎在任何地方都找不到它。我还尝试了新的 Swift 2.0 do-catch 块,但他们没有认识到 NSFileHandle 方法会引发任何类型的错误,很可能是因为函数文档没有 throw 关键字。

我知道如果应用程序空间不足或其他原因,我可以让应用程序崩溃,但由于应用程序可能稍后会发布到应用程序商店,我不希望这样。那么我该如何以 Swift 2.0 的方式做到这一点呢?

编辑:目前这是一个只有 Swift 代码的项目,所以即使在 Objective-C 中似乎有一种方法可以做到这一点,但我不知道如何将两者结合起来。

【问题讨论】:

    标签: ios swift swift2 nsexception nsfilehandle


    【解决方案1】:

    第二种(可恢复的)解决方案是创建一个非常简单的 ObjectiveC++ 函数,该函数接受一个块并返回一个异常。

    创建一个名为:ExceptionCatcher.h 的文件并将其添加到您的桥接头中(如果您还没有,Xcode 会提示为您创建一个)

    //
    //  ExceptionCatcher.h
    //
    
    #import <Foundation/Foundation.h>
    
    NS_INLINE NSException * _Nullable tryBlock(void(^_Nonnull tryBlock)(void)) {
        @try {
            tryBlock();
        }
        @catch (NSException *exception) {
            return exception;
        }
        return nil;
    }
    

    使用这个助手非常简单,我已经修改了上面的代码来使用它。

    func appendString(string: String, filename: String) -> Bool {
        guard let fileHandle = NSFileHandle(forUpdatingAtPath: filename) else { return false }
        guard let data = string.dataUsingEncoding(NSUTF8StringEncoding) else { return false }
    
        // will cause seekToEndOfFile to throw an excpetion
        fileHandle.closeFile()
    
        let exception = tryBlock {
            fileHandle.seekToEndOfFile()
            fileHandle.writeData(data)
        }
        print("exception: \(exception)")
    
        return exception == nil
    }
    

    【讨论】:

    • 不幸的是我无法编译它。 Xcode(7.2 版)给了我错误Unknown type name 'NS_ASSUME_NONNULL_BEGIN',当然对于匹配的结束声明也是如此。据说这是在 Xcode 6.4 中修复的,因为我没有使用任何 pod 或任何东西,我不知道为什么会出现这个错误。有什么想法吗?
    • 很奇怪,我还没有看到这个问题。尝试创建一个新的 Swift 项目并使用那里的代码。如果可行,请找出两个项目文件之间的差异。
    • 更新代码以停止使用NS_ASSUME_NONNULL_BEGIN
    • 我对 Objective C 的经验非常少 - 现在是 Unknown type name 'NS_INLINE'Expected ';' after top level declarator。我在网上的任何地方都找不到这个错误的原因,所以我想知道这到底是语法错误还是编译错误。我的 .h 文件中没有其他内容,只有您拥有的内容。
    • 注意,这并不能捕获所有异常,即使它们在本质上看起来非常相似。即它会始终捕获某些 NSExceptions,而始终不会捕获其他异常。一直不明白为什么。
    【解决方案2】:

    这个可以在不使用Objective C代码的情况下实现,这里有一个完整的例子。

    class SomeClass: NSObject {
        static func appendString(string: String, filename: String) -> Bool {
            guard let fileHandle = NSFileHandle(forUpdatingAtPath: filename) else { return false }
            guard let data = string.dataUsingEncoding(NSUTF8StringEncoding) else { return false }
    
            // will cause seekToEndOfFile to throw an excpetion
            fileHandle.closeFile()
    
            SomeClass.startHandlingExceptions()
            fileHandle.seekToEndOfFile()
            fileHandle.writeData(data)
            SomeClass.stopHandlingExceptions()
    
            return true
        }
    
        static var existingHandler: (@convention(c) NSException -> Void)?
        static func startHandlingExceptions() {
            SomeClass.existingHandler = NSGetUncaughtExceptionHandler()
            NSSetUncaughtExceptionHandler({ exception in
                print("exception: \(exception))")
                SomeClass.existingHandler?(exception)
            })
        }
    
        static func stopHandlingExceptions() {
            NSSetUncaughtExceptionHandler(SomeClass.existingHandler)
            SomeClass.existingHandler = nil
        }
    }
    

    致电SomeClass.appendString("add me to file", filename:"/some/file/path.txt") 运行它。

    【讨论】:

    • 通过一些更改,这确实捕获了您所说的异常。我必须做出的改变包括 NSFileHandle - 虽然你已经编译了,但它总是在初始化 fileHandle 时触发守卫的 else 部分。它应该类似于guard let fileHandle: NSFileHandle? = NSFileHandle(forUpdatingAtPath: filename) else { return false },就像我上面的代码一样。但是,即使我将print 更改为NSLog,我也无法打印异常详细信息。你知道这是为什么吗?
    • 另外,您的代码似乎只捕获错误,打印它(如果我可以让它进入 NSLog,正如我所提到的),然后继续。如果我想知道处理程序是否在我的 Swift 代码中捕获了某些内容怎么办?有没有一种快速的方法可以做到这一点,还是肯定需要 Objective C 代码?
    • 我不知道你为什么必须更改保护语句,只要文件名是有效的文件路径就可以了。不幸的是,这段代码将无法从异常中恢复,它只是让您有机会在应用程序崩溃之前看到它。
    • 在这一点上,我建议创建一个小的 ObjC 类,它执行 @try/@catch 并将结果返回给 swift
    • 我也不知道为什么,我打印了文件名和所有内容,它是有效的,只是每次都从警卫语句中返回 false。不过谢谢你的建议。看起来对于我的特定目的,JAL 的答案更正确,因为我确实希望能够以某种方式警告用户有关异常。
    【解决方案3】:

    seekToEndOfFile()writeData() 没有被标记为throws(它们不会抛出NSError 可以被do-try-catch 块捕获的对象),这意味着在Swift 的当前状态下,他们提出的NSExceptions不能被“抓住”。

    如果你正在处理一个 Swift 项目,你可以创建一个 Objective-C 类来实现你的 NSFileHandle 方法来捕获 NSExceptions(就像在 this question 中一样),否则你就完了运气。

    【讨论】:

    • 回复@matt 的回答:我非常不同意writeData() 的具体情况。如果"if any ... writing error occurs",则可以引发NSException。也许用户的系统越狱,或者他们的设备上没有剩余空间。您应该如何考虑可能引发NSFileHandle 异常的所有可能情况?
    • 我已经编辑了这个问题,以表明确实,我一直在从事一个仅限 Swift 的项目。您能否提供一个示例或链接,说明如何将 Objective-C 类合并到项目中,以便我可以正确处理错误?您链接到的问题仅显示没有上下文的 Objective-C 代码。我总是可以查一下,但对于以后再看这个问题的人来说,它可能会有用。
    • 您需要使用bridging header