Сбой пользовательского URLProtocol Swift 3 при преобразовании ошибки в NSError

У меня есть довольно большой объем кода Swift 3 для Mac OS 10.11 и выше (с использованием Xcode 8.2.1). Существует ряд процессов, среди которых приложение с графическим интерфейсом и фоновая служба. Оба они используют пользовательский URLProtocol, который реализован в рамках (импортируется как приложением, так и службой). Иногда протокол может генерировать экземпляры enum, которые соответствуют Error, которые он перехватывает и обрабатывает соответствующим образом (обычно с помощью URLProtocolClient для передачи их в URLSession, пытающийся выполнить запрос).

  • Когда ошибки нет, и приложение, и служба работают нормально.
  • Когда возникает есть ошибка, приложение работает нормально (ну, как и ожидалось).
  • При возникновении ошибки служба аварийно завершает работу.

Блуждание по отладчику показало, что этот сбой происходит, когда Error автоматически преобразуется во время выполнения в NSError. Я явно добавил это приведение в свой код, и, конечно же, я получаю тот же сбой, теперь в этой строке.

Я видел Swift 3.1: сбой, когда пользовательская ошибка преобразуется в NSError для доступа к свойству домена, но здесь это не применяется:

  • Решение — расширение Error до CustomNSErrorLocalizedError на всякий случай) и реализация соответствующих свойств — не помогло.
  • Сбой происходит после получения domain; насколько я могу судить, для меня это не было проблемой.
  • Возможно актуально: это было на iOS, а не на Mac.

Уже попробовав единственное перечисленное решение этого вопроса, я немного растерялся. Я отлаживал это часами, но ничего не добился, за исключением того, что это, кажется, происходит где-то глубоко внутри dyld.

Код:

enum CrowbarProtocolError: Error {
    case FailedToCreateSocket;
    case PathTooLong;
    case NonSocketFile;
    case SocketNotFound;
...
    case UnrecognizedError(errno: Int32);
}
extension CrowbarProtocolError: LocalizedError {
    public var errorDescription: String? {
        return "Some localized description"
    }
}
extension CrowbarProtocolError: CustomNSError {
    public static var errorDomain: String {
        return "Some Domain Name"
    }
    public var errorCode: Int {
        return 204 //Should be your custom error code.
    }
    public var errorUserInfo: [String: Any] {
        return ["WTF": "Swift?"];
    }
}
...
open class CrowbarUrlProtocol: URLProtocol  {
...
    override open func startLoading() {
        do {
            let sockHandle = try CrowbarUrlProtocol.openSocket();
            let req = try buildRequestData();
            sockHandle.write(req);
            NotificationCenter.default.addObserver(
                self, 
                selector: #selector(self.readCompleted), 
                name: .NSFileHandleReadToEndOfFileCompletion, 
                object: nil);
            sockHandle.readToEndOfFileInBackgroundAndNotify();
        } catch let err {
            Log.warn("CrowbarUrlProtocol request failed with error \(err)");
            // -- In the background service, THIS LINE CRASHES! --
            let err2 = err as NSError;
            Log.warn("As NSError: \(err2)");
            // -- Without the explicit cast, it crashes on this line --
            self.client?.urlProtocol(self, didFailWithError: err);
        }
    }
...
}

У меня есть одна идея для решения этой проблемы: просто делать все (или, насколько это возможно), используя NSErrors, на том основании, что если никогда не будет необходимости конвертировать Error в NSError, то что бы ни вызывало этот сбой, не произойдет. Не знаю, получится ли, но попробовать стоит...


person CBHacking    schedule 09.05.2017    source источник


Ответы (1)


Хорошо, насколько я могу судить, это просто ошибка в среде выполнения Swift, но я нашел обходной путь: просто используйте NSError для всего, что связано с кодом Obj-C, а не Swift Errors (чтобы избежать неявного приведения). Поскольку я уже реализовал CustomNSError, было легко просто создать функцию toNSError() в моем перечислении Error и использовать ее для строк self.client?.urlProtocol(self, didFailWithError: err).

enum CrowbarProtocolError: Error, CustomNSError {
    case FailedToCreateSocket;
    case PathTooLong;
...
    public func asNSError() -> NSError {
        return NSError(domain: CrowbarProtocolError.errorDomain,
                    code: self.errorCode,
                    userInfo: self.errorUserInfo);
    }
}
...
open class CrowbarUrlProtocol: URLProtocol  {
...
    override open func startLoading() {
        do {
            let sockHandle = try CrowbarUrlProtocol.openSocket();
            let req = try buildRequestData();
            sockHandle.write(req);
            NotificationCenter.default.addObserver(
                self, 
                selector: #selector(self.readCompleted), 
                name: .NSFileHandleReadToEndOfFileCompletion, 
                object: nil);
            sockHandle.readToEndOfFileInBackgroundAndNotify();
        } catch let err as CrowbarProtocolError {
            Log.warn("CrowbarUrlProtocol request failed with error \(err)");
            self.client?.urlProtocol(self, didFailWithError: err.asNSError());
        }
        catch let err {
            Log.warn("CrowbarUrlProtocol caught non-CrowbarProtocol Error \(err)");
            // This would probably crash the service, but shouldn't ever happen
            self.client?.urlProtocol(self, didFailWithError: err);
        }
    }
...
}
person CBHacking    schedule 09.05.2017