Как правильно обрабатывать исключения NSFileHandle в Swift 2.0?

Прежде всего, я новичок в 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() указывают на то, что они генерируют какое-то исключение:

Этот метод вызывает исключение, если дескриптор файла закрыт или недействителен, если приемник представляет собой не связанную трубку или конечную точку сокета, если в файловой системе не остается свободного места или возникает какая-либо другая ошибка записи. - Документация Apple для writeData()

Итак, каков правильный способ справиться с этим в Swift 2.0? Я прочитал ссылки Обработка ошибок в Swift-Language, исключения try-catch в Swift, NSFileHandle writeData: обработка исключений, Обработка исключений Swift 2.0 и Как поймать исключение в Swift, но ни один из них не имеет прямого ответа на мой вопрос. Я прочитал что-то об использовании objective-C в Swift-коде, но поскольку я новичок в iOS, я не знаю, что этот метод и не может найти его где-нибудь. Я также попробовал новые Swift 2.0 do-catch, но они не признают, что для методов NSFileHandle генерируется какой-либо тип ошибки, скорее всего, поскольку в документации по функциям нет ключевого слова throw.

Я знаю, что я мог просто позволить краху приложения, если у него закончилось свободное пространство или что-то еще, но поскольку приложение, возможно, позже будет выпущено в магазин приложений, я этого не хочу. Итак, как мне это сделать Swift 2.0?

EDIT: В настоящее время это проект с только кодом Swift, поэтому, хотя кажется, что есть способ сделать это в Objective-C, я понятия не имею, как смешать эти два.

Ответы

Ответ 1

вторым (восстанавливаемым) решением было бы создание очень простой функции ObjectiveС++, которая берет блок и возвращает исключение.

создайте файл под названием: 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
}

Ответ 2

seekToEndOfFile() и writeData() не помечены как throws (они не бросают объект NSError, который может быть захвачен блоком do-try-catch), что означает в текущем состоянии Swift, созданный ими NSException не может быть "пойман".

Если вы работаете над проектом Swift, вы можете создать класс Objective-C, который реализует ваши методы NSFileHandle, которые ловит NSException (например, этот вопрос), но в противном случае вам не повезло.

Ответ 3

Это может быть достигнуто без использования кода 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"), чтобы запустить его.