How to properly handle NSFileHandle exceptions in Swift 2.0?

mirage picture mirage · Jan 22, 2016 · Viewed 7.1k times · Source

First of all, I am new to iOS and Swift and come from a background of Android/Java programming. So to me the idea of catching an exception from an attempt to write to a file is second nature, in case of lack of space, file permissions problems, or whatever else can possibly happen to a file (and has happened, in my experience). I also understand that in Swift, exceptions are different from Android/Java ones, so that's not what I'm asking about here.

I am attempting to append to a file using NSFileHandle, like so:

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!)
}

However, both the seekToEndOfFile(), and writeData() functions indicate that they throw some kind of exception:

This method raises an exception if the file descriptor is closed or is not valid, if the receiver represents an unconnected pipe or socket endpoint, if no free space is left on the file system, or if any other writing error occurs. - Apple Documentation for writeData()

So what is the proper way to handle this in Swift 2.0? I've read the links Error-Handling in Swift-Language, try-catch exceptions in Swift, NSFileHandle writeData: exception handling, Swift 2.0 exception handling, and How to catch an exception in Swift, but none of them have a direct answer to my question. I did read something about using objective-C in Swift code, but since I am new to iOS, I don't know what this method is and can't seem to find it anywhere. I also tried the new Swift 2.0 do-catch blocks, but they don't recognize that any type of error is being thrown for NSFileHandle methods, most likely since the function documentation has no throw keyword.

I am aware that I could just let the app crash if it runs out of space or whatever, but since the app will possibly be released to the app store later, I don't want that. So how do I do this the Swift 2.0 way?

EDIT: This currently is a project with only Swift code, so even though it seems there is a way to do this in Objective-C, I have no idea how to blend the two.

Answer

Casey picture Casey · Jan 25, 2016

a second (recoverable) solution would be to create a very simple ObjectiveC++ function that takes a block and returns an exception.

create a file entitled: ExceptionCatcher.h and add import it in your bridging header (Xcode will prompt to create one for you if you don't have one already)

//
//  ExceptionCatcher.h
//

#import <Foundation/Foundation.h>

NS_INLINE NSException * _Nullable tryBlock(void(^_Nonnull tryBlock)(void)) {
    @try {
        tryBlock();
    }
    @catch (NSException *exception) {
        return exception;
    }
    return nil;
}

Using this helper is quite simple, I have adapted my code from above to use it.

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
}