In Xcode 16, I get this error, related to Swift 6.
Capture of ‘timer’ with non-sendable type ‘Timer’ in a
@Sendable
closure; this is an error in the Swift 6 language mode
How can I make this code Swift 6 compliant?
func updateBuyAmountWithJsValue(url: String, delaySeconds: Double, maxTries: Int, js: String) {
var tries = 0
Timer.scheduledTimer(withTimeInterval: delaySeconds, repeats: true) { timer in
tries += 1
if (self.webView.url?.absoluteString != url || tries > maxTries) {
timer.invalidate()
return
}
self.webView.evaluateJavaScript(js) { (result, error) in
if let error = error {
print("Error executing JavaScript: (error)")
} else if let value = result as? String {
if value.contains(".") || (tries==maxTries && value.count < 10) {
self.updateBuyProgress("AMOUNT*" + value)
timer.invalidate()
}
}
}
}
}
2
Answers
You can wrap the closure inside a task that is isolated to the global – MainActor to avoid this warning:
However, there is still a warning/error here, and IMO, it’s quite impossible to resolve. Because the closure is marked with
@Sendable
, but the Timer itself did not conform toSendable
, unless you explicitly override the extensionSendable
outside ofFoudation
.Since
evaluateJavaScript
also supportsasync await
. I could prefer to refactor the code:The immediate problem is that the timer handler closure is not actor-isolated. We can solve that by adding
MainActor.assumeIsolated {…}
(not, generally,Task { @MainActor in …}
).There is a more subtle problem that is only evidenced when you change the “Strict concurrency checking” build setting to “Complete”, namely that the
Timer
parameter of thescheduledTimer
closure is “task isolated” and this non-Sendable
type cannot be updated from an actor-isolated context.You can solve both of these problems by moving the
Timer
reference to an actor-isolated property in conjunction withassumeIsolated
:There are other refinements I might suggest, but this illustrates the basic idea.
Alternatively, if you are willing to adopt Swift concurrency, it is simpler:
Or you could use an
AsyncTimerSequence
, but the idea would be the same: Namely, retire the closure-based API and thereby eliminate all the issues those introduce in Swift concurrency (especially with Swift 6 and/or “strict concurrency checking”).