Как правильно обрабатывать исключения 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, попробуйте перехватить исключения в 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, я понятия не имею, как их смешать.


person mirage    schedule 22.01.2016    source источник


Ответы (3)


вторым (исправимым) решением было бы создание очень простой функции 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
}
person Casey    schedule 25.01.2016
comment
К сожалению, я не могу заставить это скомпилировать. Xcode (версия 7.2) дает мне ошибку Unknown type name 'NS_ASSUME_NONNULL_BEGIN' и, конечно же, то же самое для соответствующего конечного объявления. Предположительно, это было исправлено в Xcode 6.4 из того, что я читаю, и, поскольку я не использую какие-либо модули или что-то еще, я понятия не имею, почему у меня эта ошибка. Есть идеи? - person mirage; 26.01.2016
comment
очень странно, я не видел эту тему. попробуйте создать новый проект Swift и использовать там код. если это работает, найдите разницу между двумя вашими файлами проекта. - person Casey; 26.01.2016
comment
обновлен код, чтобы прекратить использование NS_ASSUME_NONNULL_BEGIN - person Casey; 26.01.2016
comment
У меня очень мало опыта работы с задачей C — теперь она говорит Unknown type name 'NS_INLINE' и Expected ';' after top level declarator. Я не могу найти причину этой ошибки нигде в Интернете, поэтому мне интересно, является ли это синтаксической ошибкой или ошибкой компиляции с моей стороны. В моем файле .h нет ничего, кроме того, что есть у вас. - person mirage; 26.01.2016
comment
Вот оно. Работает очень хорошо, спасибо. Я бы дал вам +1, но у меня недостаточно представителей. Принято, однако. - person mirage; 26.01.2016
comment
Это спасло мой день. Большое спасибо за очень красиво реализованное решение досадной проблемы! - person devios1; 02.02.2016
comment
Обратите внимание, что это не перехватывает все исключения, даже если они кажутся очень похожими по своей природе. т. е. он будет последовательно перехватывать определенные NSExceptions, но так же последовательно НЕ перехватывать другие. Никогда не понимал, почему. - person David James; 28.07.2016

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

person Casey    schedule 23.01.2016
comment
С некоторыми изменениями это действительно улавливает исключение, как вы сказали. Изменение, которое я должен был сделать, включает в себя NSFileHandle - хотя то, что у вас есть, компилируется, оно всегда запускает часть защиты else при инициализации fileHandle. Это должно быть что-то вроде guard let fileHandle: NSFileHandle? = NSFileHandle(forUpdatingAtPath: filename) else { return false }, как в моем коде выше. Однако я не могу распечатать сведения об исключении, даже если я изменю print на NSLog. Вы знаете, почему это так? - person mirage; 25.01.2016
comment
Кроме того, кажется, что ваш код только улавливает ошибку, печатает ее (если я могу заставить ее перейти в NSLog, как я уже упоминал), а затем движется дальше. Что, если бы я хотел знать, поймал ли обработчик что-то в моем коде Swift? Есть ли быстрый способ сделать это или для этого определенно нужен код Objective C? - person mirage; 25.01.2016
comment
я не уверен, почему вам пришлось изменить оператор защиты, все должно быть в порядке, если имя файла является допустимым путем к файлу. этот код, к сожалению, не сможет восстановиться после исключения, он просто дает вам возможность увидеть его до сбоя приложения. - person Casey; 26.01.2016
comment
на этом этапе я бы порекомендовал создать небольшой класс ObjC, который выполняет @try/@catch и возвращает результат обратно в swift - person Casey; 26.01.2016
comment
Я тоже не уверен, почему, я распечатал имя файла и все остальное, и оно было действительным, оно просто каждый раз возвращало false из оператора защиты. Но спасибо за ваше предложение. Похоже, что для моей конкретной цели ответ JAL более правильный, так как я хочу иметь возможность как-то предупредить пользователя об исключении. - person mirage; 26.01.2016
comment
см. альтернативное решение, которое я разместил, должно быть именно то, что вам нужно. - person Casey; 26.01.2016
comment
Интересное решение, когда это было добавлено в Swift? - person JAL; 26.01.2016
comment
насколько я знаю, он всегда был там, связан с существующей функцией C - person Casey; 26.01.2016

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

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

person JAL    schedule 22.01.2016
comment
Ответ @matt: я с уважением не согласен в конкретном случае writeData(). NSException может возникнуть, если если возникает... ошибка записи. Возможно, у пользователя взломана система или на его устройстве не осталось места. Как вы должны учитывать каждый возможный сценарий, который может вызвать исключение с NSFileHandle? - person JAL; 23.01.2016
comment
Я отредактировал вопрос, чтобы показать, что я действительно работаю над проектом только для Swift. Не могли бы вы привести пример или ссылку на то, как я могу включить класс Objective-C в проект, чтобы я мог правильно обрабатывать ошибки? Вопрос, на который вы ссылаетесь, просто показывает код Objective-C без контекста. Я всегда мог бы посмотреть его, но для тех, кто посмотрит на этот вопрос позже, это может быть полезно. - person mirage; 23.01.2016
comment
Вам нужно будет использовать связующий заголовок . - person JAL; 23.01.2016