Правильный способ распространения исключений в блоке отсрочки - PullRequest
0 голосов
/ 15 мая 2018

В следующем примере функция usingTemporaryDirectory() создает и удаляет временный каталог, вызывая переданную функцию body() между ними. Если исключение выдается createTemporaryDirectory() или переданной функцией body(), оно передается вызывающей стороне. Но исключение, выданное removeDirectory(), не может быть передано вызывающей стороне, поскольку ни одно исключение не может выйти из блока defer.

import Foundation

func createTemporaryDirectory() throws -> URL { ... }

func removeDirectory(_ url: URL) throws { ... }

func usingTemporaryDirectory(body: (URL) throws -> ()) throws {
    let tempDir = try createTemporaryDirectory()

    defer {
        // Errors thrown from here are not handled.
        try removeDirectory(tempDir)
    }

    try body(tempDir)
}

Как правильно обрабатывать такое исключение? Я вижу два варианта:

  1. Просто перехватите исключение и зарегистрируйте сообщение.
  2. Перехватите исключение, зарегистрируйте сообщение и прервите процесс.

Я не хочу использовать опцию 1, потому что это может привести в этом примере к произвольному количеству накопившихся временных каталогов. И я также не хочу использовать вариант 2, потому что это помешало бы кадрам внешнего стека завершить свою работу по очистке (например, если было создано несколько временных каталогов, по крайней мере, следует попытаться удалить их все).

Java имеет функцию под названием исключенные исключения . В этом случае исключение, выданное в блоке defer, может быть добавлено как исключенное исключенное к исключению, выданному body(), если таковое имеется. Есть ли у Swift аналогичная функция?

1 Ответ

0 голосов
/ 02 июня 2018

В этом случае исключение, выброшенное в блоке отсрочки, может быть добавлено как исключенное исключение к исключению, выданному body(), если таковое имеется. Есть ли у Swift аналогичная функция?

Не то, что я знаю - вы могли бы, однако, построить нечто подобное. Во-первых, давайте определим тип Error, который может хранить несколько основных ошибок:

struct ErrorCollection : Error {
  private var errors: [Error] = []
  init() {}

  init<S : Sequence>(_ sequence: S) where S.Element == Error {
    for error in sequence {
      append(error)
    }
  }

  mutating func append(_ error: Error) {
    switch error {
    case let x as ErrorCollection: // ensure we flatten out any nested error collections.
      errors.append(contentsOf: x.errors)
    case let x:
      errors.append(x)
    }
  }
}

extension ErrorCollection : RandomAccessCollection {
  typealias Index = Int
  typealias Element = Error

  var startIndex: Index { return errors.startIndex }
  var endIndex: Index { return errors.endIndex }

  func index(_ i: Index, offsetBy n: Index) -> Index {
    return errors.index(i, offsetBy: n)
  }

  subscript(index: Index) -> Element { return errors[index] }
}

Затем мы можем определить тип Result<T>, который мы можем использовать для оценки замыкания, сохраняя возвращаемое значение в случае успеха, в противном случае выдается ошибка. Затем мы можем добавить метод then(_:), чтобы позволить нам перехватывать любые дополнительные ошибки, которые могут привести к тому, что значение успеха будет признано недействительным:

enum Result<T> {
  case success(T)
  case failure(Error)

  init(_ body: () throws -> T) {
    do {
      self = .success(try body())
    } catch {
      self = .failure(error)
    }
  }

  func then(_ body: () throws -> Void) -> Result {
    do {
      try body()
      return self
    } catch let nextError {
      switch self {
      case .success: // invalidate the success value and store the error.
        return .failure(nextError)
      case .failure(let error): // concatenate the errors.
        return .failure(ErrorCollection([error, nextError]))
      }
    }
  }

  func materialize() throws -> T {
    switch self {
    case .success(let value):
      return value
    case .failure(let error):
      throw error
    }
  }
}

Затем вы можете использовать это так:

func usingTemporaryDirectory<R>(body: (URL) throws -> R) throws -> R {
  let tempDir = try createTemporaryDirectory()

  return try Result { try body(tempDir) }
              .then { try removeDirectory(tempDir) }
              .materialize()
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...