Search code examples
swifterror-handlingtry-catchthrow

Implementing a rethrowing function


I'm implementing a helper method for CoreData

public func doInMain(_ block: (NSManagedObjectContext) -> Void) {
    guard let context = mainQueueContext else { return }
    context.performAndWait({
        block(context)
    })
}

It works well and can be called like this:

CoreDataManager().doInMain { moc in
    // do your fetch request with managed object context
}

But it's very annoying when I want to try an error in doInMain block, because it can't be rethrowed by the calling function. Since the bloc is nonescaping, it should be done.

So I added:

public func doInMain(_ block: (NSManagedObjectContext) throws -> Void) rethrows {
    guard let context = mainQueueContext else { return }
    context.performAndWait({
        try block(context)
    })
}

But NSManagedObjectContext.performAndWait do not rethrows errors, so it won't compile.

It tryed this:

public func doInMain(_ block: (NSManagedObjectContext) throws -> Void) rethrows {
    guard let context = mainQueueContext else { return }
    var thrownError: Error?
    context.performAndWait({
        do { try block(context) }
        catch { thrownError = error }
    })
    if let error = thrownError {
        throw error
    }
}

But now the compiler says A function declared 'rethrows' may only throw if its parameter does

Am I screwed up? Is there a workaround?

Thanks


Solution

  • Not entirely ideal, but one option would be to just write two overloads – one that takes a closure that doesn't throw and itself doesn't throw, and another that takes a throwing closure and itself throws.

    For example:

    public func doInMain(_ block: (NSManagedObjectContext) -> Void) {
      guard let context = mainQueueContext else { return }
      context.performAndWait {
        block(context)
      }
    }
    
    public func doInMain(_ block: (NSManagedObjectContext) throws -> Void) throws {
      guard let context = mainQueueContext else { return }
      var thrownError: Error?
      context.performAndWait {
        do {
          try block(context)
        } catch {
          thrownError = error
        }
      }
      if let error = thrownError {
        throw error
      }
    }
    

    Bear in mind that if desired, you could always implement the non-throwing overload in terms of the throwing overload:

    public func doInMain(_ block: (NSManagedObjectContext) -> Void) {
      let fn = doInMain as ((NSManagedObjectContext) throws -> Void) throws -> Void
      try! fn(block)
    }
    

    You should be able to use both overloads in much the same way as a single rethrowing overload.