2

I'm working on an App that still supports iOS 13 and need to fetch some data with CoreData.

This is how I would normally do it

context.perform({
  let results = try context.fetch(request)
})

Now with Xcode 13 and async/await being available back to iOS 13 I get a compiler error

'perform(schedule:_:)' is only available in iOS 15.0 or newer

Jumping to definition shows the following new API in CoreData

@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
extension NSManagedObjectContext {

    public func performAndWait<T>(_ block: () throws -> T) rethrows -> T

    public func perform<T>(schedule: NSManagedObjectContext.ScheduledTaskType = .immediate, _ block: @escaping () throws -> T) async rethrows -> T
}

When commenting out the code in the Block, it instead jumps to the old API in CoreData/NSManagedObjectContext

/* asynchronously performs the block on the context's queue.  Encapsulates an autorelease pool and a call to processPendingChanges */
@available(iOS 5.0, *)
open func perform(_ block: @escaping () -> Void)

Why does the compiler select the new variant of perform and how can I force it to use the older non-async version?

Edit: Here is a minimal sample project that demonstrates the issue: https://github.com/iv-mexx/CoreDataRepro

MeXx
  • 3,357
  • 24
  • 39
  • Available async/await feature, not methods implemented in the future SDK's. If you want you can implement your's own async perform method – Cy-4AH Jan 19 '22 at 16:10
  • I've tried this and I can't reproduce the error, even in an `async` function. Can you edit the question to add more detail about where this code appears? It's probably very relevant to the problem you're having. – Tom Harrington Jan 19 '22 at 21:38
  • @TomHarrington I've edited the question and added a link to a new sample project that replicates the issue. – MeXx Jan 19 '22 at 22:10
  • @Cy-4AH I want to use the old non-async method which is available since iOS 5.0 but the compiler wrongly (?) selects the new method which is only available since iOS 15.0 – MeXx Jan 19 '22 at 22:11
  • There is also error: `Call can throw, but it is not marked with 'try' and the error is not handled`. Original perform is from `Objective-C` and doesn't have `throws/rethrows`. You just need handle `throws` in the passed block – Cy-4AH Jan 20 '22 at 08:45
  • @Cy-4AH the original method I want to use does not throw, only the iOS 15 method rethrows. – MeXx Jan 20 '22 at 08:48
  • @MeXx Then you just need handle all `try`'s – Cy-4AH Jan 20 '22 at 08:49
  • @Cy-4AH unfortunately no. If I handle it, it still does not compile for iOS < 15 which is what I'm actually trying to do! – MeXx Jan 20 '22 at 08:49
  • 1
    I have just replaced `try` with `try?` and all become fine – Cy-4AH Jan 20 '22 at 08:50
  • @Cy-4AH wow I've tried this and it works for me as well! I dont understand why, but thanks! – MeXx Jan 20 '22 at 08:55

1 Answers1

0

Thanks to @Cy-4AH I've figured it out!

The problem was, that I was doing everything, including the context.perform in one big do / catch block

do {
    ...
    context.perform({
      let results = try context.fetch(request)
    })
    ...
} catch { 
    ...
}

The new extension method for perform is now marked as rethrows whereas the old one was not, so the fact there was a throwing method in the perform block meant the compiler selected the rethrowing perform which is only available on iOS >= 15.

@Cy-4AH suggested to use try? instead of try which works because the error is caught right there, not forcing the rethrowing method to be used.

Another solution is to move the do/catch inside the perform:

context.perform({
  do {
    let results = try context.fetch(request)
  } catch { 
    ...
  }
})
MeXx
  • 3,357
  • 24
  • 39