In Swift 6, stricter concurrency rules can lead to challenges when making SwiftUI views conform to Equatable. Specifically, the == operator required for Equatable must be nonisolated, which means it cannot access @MainActor-isolated properties. This creates an error when trying to compare views with such properties:
Error Example:
struct MyView: View, Equatable {
let title: String
let count: Int
static func ==(lhs: MyView, rhs: MyView) -> Bool {
// Accessing `title` here would trigger an error due to actor isolation.
return lhs.count == rhs.count
}
var body: some View {
Text(title)
}
}
Error Message:
Main actor-isolated operator function '==' cannot be used to satisfy nonisolated protocol requirement; this is an error in the Swift 6 language mode.
Any suggestions?
Thanks
FB: FB15753655 (SwiftUI View cannot conform custom Equatable protocol in Swift 6.)
Concurrency
RSS for tagConcurrency is the notion of multiple things happening at the same time.
Posts under Concurrency tag
115 Posts
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I’m trying to create a property wrapper that that can manage shared state across any context, which can get notified if changes happen from somewhere else.
I'm using mutex, and getting and setting values works great. However, I can't find a way to create an observer pattern that the property wrappers can use.
The problem is that I can’t trigger a notification from a different thread/context, and have that notification get called on the correct thread of the parent object that the property wrapper is used within.
I would like the property wrapper to work from anywhere: a SwiftUI view, an actor, or from a class that is created in the background. The notification preferably would get called synchronously if triggered from the same thread or actor, or otherwise asynchronously. I don’t have to worry about race conditions from the notification because the state only needs to reach eventuall consistency.
Here's the simplified pseudo code of what I'm trying to accomplish:
// A single source of truth storage container.
final class MemoryShared<Value>: Sendable {
let state = Mutex<Value>(0)
func withLock(_ action: (inout Value) -> Void) {
state.withLock(action)
notifyObservers()
}
func get() -> Value
func notifyObservers()
func addObserver()
}
// Some shared state used across the app
static let globalCount = MemoryShared<Int>(0)
// A property wrapper to access the shared state and receive changes
@propertyWrapper
struct SharedState<Value> {
public var wrappedValue: T {
get { state.get() }
nonmutating set { // Can't set directly }
}
var publisher: Publisher {}
init(state: MemoryShared) {
// ...
}
}
// I'd like to use it in multiple places:
@Observable
class MyObservable {
@SharedState(globalCount)
var count: Int
}
actor MyBackgroundActor {
@SharedState(globalCount)
var count: Int
}
@MainActor
struct MyView: View {
@SharedState(globalCount)
var count: Int
}
What I’ve Tried
All of the examples below are using the property wrapper within a @MainActor class. However the same issue happens no matter what context I use the wrapper in: The notification callback is never called on the context the property wrapper was created with.
I’ve tried using @isolated(any) to capture the context of the wrapper and save it to be called within the state in with unchecked sendable, which doesn’t work:
final class MemoryShared<Value: Sendable>: Sendable {
// Stores the callback for later.
public func subscribe(callback: @escaping @isolated(any) (Value) -> Void) -> Subscription
}
@propertyWrapper
struct SharedState<Value> {
init(state: MemoryShared<Value>) {
MainActor.assertIsolated() // Works!
state.subscribe {
MainActor.assertIsolated() // Fails
self.publisher.send()
}
}
}
I’ve tried capturing the isolation within a task with AsyncStream. This actually compiles with no sendable issues, but still fails:
@propertyWrapper
struct SharedState<Value> {
init(isolation: isolated (any Actor)? = #isolation, state: MemoryShared<Value>) {
let (taskStream, continuation) = AsyncStream<Value>.makeStream()
// The shared state sends new values to the continuation.
subscription = state.subscribe(continuation: continuation)
MainActor.assertIsolated() // Works!
let task = Task {
_ = isolation
for await value in taskStream {
_ = isolation
MainActor.assertIsolated() // Fails
}
}
}
}
I’ve tried using multiple combine subjects and publishers:
final class MemoryShared<Value: Sendable>: Sendable {
let subject: PassthroughSubject<T, Never> // ...
var publisher: Publisher {} // ...
}
@propertyWrapper
final class SharedState<Value> {
var localSubject: Subject
init(state: MemoryShared<Value>) {
MainActor.assertIsolated() // Works!
handle = localSubject.sink {
MainActor.assertIsolated() // Fails
}
stateHandle = state.publisher.subscribe(localSubject)
}
}
I’ve also tried:
Using NotificationCenter
Making the property wrapper a class
Using NSKeyValueObserving
Using a box class that is stored within the wrapper.
Using @_inheritActorContext.
All of these don’t work, because the event is never called from the thread the property wrapper resides in.
Is it possible at all to create an observation system that notifies the observer from the same context as where the observer was created?
Any help would be greatly appreciated!
In my app, I've been using ModelActors in SwiftData, and using actors with a custom executor in Core Data to create concurrency safe services.
I have multiple actor services that relate to different data model components or features, each that have their own internally managed state (DocumentService, ImportService, etc).
The problem I've ran into, is that I need to be able to use multiple of these services within another service, and those services need to share the same context. Swift 6 doesn't allow passing contexts across actors.
The specific problem I have is that I need a master service that makes multiple unrelated changes without saving them to the main context until approved by the user.
I've tried to find a solution in SwiftData and Core Data, but both have the same problem which is contexts are not sendable. Read the comments in the code to see the issue:
/// This actor does multiple things without saving, until committed in SwiftData.
@ModelActor
actor DatabaseHelper {
func commitChange() throws {
try modelContext.save()
}
func makeChanges() async throws {
// Do unrelated expensive tasks on the child context...
// Next, use our item service
let service = ItemService(modelContainer: SwiftDataStack.shared.container)
let id = try await service.expensiveBackgroundTask(saveChanges: false)
// Now that we've used the service, we need to access something the service created.
// However, because the service created its own context and it was never saved, we can't access it.
let itemFromService = context.fetch(id) // fails
// We need to be able to access changes made from the service within this service,
/// so instead I tried to create the service by passing the current service context, however that results in:
// ERROR: Sending 'self.modelContext' risks causing data races
let serviceFromContext = ItemService(context: modelContext)
// Swift Data doesn't let you create child contexts, so the same context must be used in order to change data without saving.
}
}
@ModelActor
actor ItemService {
init(context: ModelContext) {
modelContainer = SwiftDataStack.shared.container
modelExecutor = DefaultSerialModelExecutor(modelContext: context)
}
func expensiveBackgroundTask(saveChanges: Bool = true) async throws -> PersistentIdentifier? {
// Do something expensive...
return nil
}
}
Core Data has the same problem:
/// This actor does multiple things without saving, until committed in Core Data.
actor CoreDataHelper {
let parentContext: NSManagedObjectContext
let context: NSManagedObjectContext
/// In Core Data, I can create a child context from a background context.
/// This lets you modify the context and save it without updating the main context.
init(progress: Progress = Progress()) {
parentContext = CoreDataStack.shared.newBackgroundContext()
let childContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
childContext.parent = parentContext
self.context = childContext
}
/// To commit changes, save the parent context pushing them to the main context.
func commitChange() async throws {
// ERROR: Sending 'self.parentContext' risks causing data races
try await parentContext.perform {
try self.parentContext.save()
}
}
func makeChanges() async throws {
// Do unrelated expensive tasks on the child context...
// As with the Swift Data example, I am unable to create a service that uses the current actors context from here.
// ERROR: Sending 'self.context' risks causing data races
let service = ItemService(context: self.context)
}
}
Am I going about this wrong, or is there a solution to fix these errors?
Some services are very large and have their own internal state. So it would be very difficult to merge all of them into a single service. I also am using Core Data, and SwiftData extensively so I need a solution for both.
I seem to have trapped myself into a corner trying to make everything concurrency save, so any help would be appreciated!
Hello, I was hoping to clarify my understanding of the use of for await with an AsyncStream. My use case is, I'd like to yield async closures to the stream's continuation, with the idea that, when I use for await with the stream to process and execute the closures, it would only continue on to the following closure once the current closure has been run to completion.
At a high level, I am trying to implement in-order execution of async closures in the context of re-entrancy. An example of asynchronous work I want to execute is a network call that should write to a database:
func syncWithRemote() async -> Void {
let data = await fetchDataFromNetwork()
await writeToLocalDatabase(data)
}
For the sake of example, I'll call the intended manager of closure submission SingleOperationRunner.
where, at a use site such as this, my desired outcome is that call 1 of syncWithRemote() is always completed before call 2 of it:
let singleOperationRunner = SingleOperationRunner(priority: nil)
singleOperationRunner.run {
syncWithRemote()
}
singleOperationRunner.run {
syncWithRemote()
}
My sketch implementation looks like this:
public final class SingleOperationRunner {
private let continuation: AsyncStream<() async -> Void>.Continuation
public init(priority: TaskPriority?) {
let (stream, continuation) = AsyncStream.makeStream(of: (() async -> Void).self)
self.continuation = continuation
Task.detached(priority: priority) {
// Will this loop only continue when the `await operation()` completes?
for await operation in stream {
await operation()
}
}
}
public func run(operation: @escaping () async -> Void) {
continuation.yield(operation)
}
deinit {
continuation.finish()
}
}
The resources I've found are https://developer.apple.com/videos/play/wwdc2022-110351/?time=1445 and https://forums.swift.org/t/swift-async-func-to-run-sequentially/60939/2 but do not think I have fully put the pieces together, so would appreciate any help!
Consider this simple miniature of my iOS Share Extension:
import SwiftUI
import Photos
class ShareViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let itemProviders = (extensionContext?.inputItems.first as? NSExtensionItem)?.attachments {
let hostingView = UIHostingController(rootView: ShareView(extensionContext: extensionContext, itemProviders: itemProviders))
hostingView.view.frame = view.frame
view.addSubview(hostingView.view)
}
}
}
struct ShareView: View {
var extensionContext: NSExtensionContext?
var itemProviders: [NSItemProvider]
var body: some View {
VStack{}
.task{
await extractItems()
}
}
func extractItems() async {
guard let itemProvider = itemProviders.first else { return }
guard itemProvider.hasItemConformingToTypeIdentifier(UTType.url.identifier) else { return }
do {
guard let url = try await itemProvider.loadItem(forTypeIdentifier: UTType.url.identifier) as? URL else { return }
try await downloadAndSaveMedia(reelURL: url.absoluteString)
extensionContext?.completeRequest(returningItems: [])
}
catch {}
}
}
On the line 34
guard let url = try await itemProvider.loadItem
...
I get these warnings:
Passing argument of non-sendable type '[AnyHashable : Any]?' outside of main actor-isolated context may introduce data races; this is an error in the Swift 6 language mode
1.1. Generic enum 'Optional' does not conform to the 'Sendable' protocol (Swift.Optional)
Passing argument of non-sendable type 'NSItemProvider' outside of main actor-isolated context may introduce data races; this is an error in the Swift 6 language mode
2.2. Class 'NSItemProvider' does not conform to the 'Sendable' protocol (Foundation.NSItemProvider)
How to fix them in Xcode 16?
Please provide a solution which works, and not the one which might (meaning you run the same code in Xcode, add your solution and see no warnings).
I tried
Decorating everything with @MainActors
Using @MainActor in the .task
@preconcurrency import
Decorating everything with @preconcurrency
Playing around with nonisolated
I'm trying to rewrite a Swift code to Swift 6 language mode and am stuck with this problem. How do I safely pass the bestAttemptContent and contentHandler to the Task? This is from the UNNotificationServiceExtension subclass.
final class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
var customNotificationTask: Task<Void, Error>?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
guard let bestAttemptContent = bestAttemptContent else {
invokeContentHandler(with: request.content)
return
}
do {
let notificationModel = try PushNotificationUserInfo(data: request.content.userInfo)
guard let templatedImageUrl = notificationModel.templatedImageUrlString,
let imageUrl = imageUrl(from: templatedImageUrl) else {
invokeContentHandler(with: bestAttemptContent)
return
}
setupCustomNotificationTask(
imageUrl: imageUrl,
bestAttemptContent: bestAttemptContent,
contentHandler: contentHandler
)
} catch {
invokeContentHandler(with: bestAttemptContent)
}
}
// More code
private func downloadImageTask(
imageUrl: URL,
bestAttemptContent: UNMutableNotificationContent,
contentHandler: @escaping (UNNotificationContent) -> Void
) {
self.customNotificationTask = Task {
let (location, _) = try await URLSession.shared.download(from: imageUrl)
let desiredLocation = URL(fileURLWithPath: "\(location.path)\(imageUrl.lastPathComponent)")
try FileManager.default.moveItem(at: location, to: desiredLocation)
let attachment = try UNNotificationAttachment(identifier: imageUrl.absoluteString, url: desiredLocation, options: nil)
bestAttemptContent.attachments = [attachment]
contentHandler(bestAttemptContent)
}
}
}
I tried using the MainActor.run {}, but it just moved the error to that run function.
The UNNotificationRequest is not sendable, and I don't think I can make it so.
Wrap the setupCustomNotification in a Task will move the errors to the didReceive method.
It seems like the consuming keyword will help here, but it leads to a compilation error, even with the latest Xcode (16.2).
Any pointers?
Hello ladies and gentlemen, I'm writing a simple renderer on the main actor using Metal and Swift 6. I am at the stage now where I want to create a render pipeline state using asynchronous API:
@MainActor
class Renderer {
let opaqueMeshRPS: MTLRenderPipelineState
init(/*...*/) async throws {
let descriptor = MTLRenderPipelineDescriptor()
// ...
opaqueMeshRPS = try await device.makeRenderPipelineState(descriptor: descriptor)
}
}
I get a compilation error if try to use the asynchronous version of the makeRenderPipelineState method:
Non-sendable type 'any MTLRenderPipelineState' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary
Which is understandable, since MTLRenderPipelineState is not Sendable. But it looks like no matter where or how I try to access this method, I just can't do it - you have this API, but you can't use it, you can only use the synchronous versions.
Am I missing something or is Metal just not usable with Swift 6 right now?
Hello,
When attempting to assign the UNNotificationResponse to a Published property on the main thread inside UNUserNotificationCenterDelegate's method
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async
both Task { @MainActor in } and await MainActor.run are throwing a NSInternalInconsistencyException: 'Call must be made on main thread'.
I thought both of them were essentially doing the same thing, i.e. call their closure on the main thread. So why is this exception thrown? Is my understanding of the MainActor still incorrect, or is this a bug?
Thank you
Note: Task { await MainActor.run { ... } } and DispatchQueue.main.async don't throw any exception.
Considering below dummy codes:
@MainActor var globalNumber = 0
@MainActor
func increase(_ number: inout Int) async {
// some async code excluded
number += 1
}
class Dummy: @unchecked Sendable {
@MainActor var number: Int {
get { globalNumber }
set { globalNumber = newValue }
}
@MainActor
func change() async {
await increase(&number) //Actor-isolated property 'number' cannot be passed 'inout' to 'async' function call
}
}
I'm not really trying to make an increasing function like that, this is just an example to make everything happen. As for why number is a computed property, this is to trigger the actor-isolated condition (otherwise, if the property is stored and is a value type, this condition will not be triggered).
Under these conditions, in function change(), I got the error: Actor-isolated property 'number' cannot be passed 'inout' to 'async' function call.
My question is: Why Actor-isolated property cannot be passed 'inout' to 'async' function call? What is the purpose of this design? If this were allowed, what problems might it cause?
Are there any plans to add RBI support (the sending keyword) to the OSAllocatedLock interface? So it could be used with non-sendable objects without surrendering to the unchecked API
I've got a problem with compatibility with Swift6 in iOS app that I have no idea how to sort it out.
That is an extract from my main app file
@MainActor
@main struct LangpadApp: App {
...
@State private var notificationDataProvider = NotificationDataProvider()
@UIApplicationDelegateAdaptor(NotificationServiceDelegate.self) var notificationServiceDelegate
var body: some Scene {
WindowGroup {
TabView(selection: $tabSelection) {
...
}
.onChange(of: notificationDataProvider.dateId) { oldValue, newValue in
if !notificationDataProvider.dateId.isEmpty {
tabSelection = 4
}
}
}
}
init() {
notificationServiceDelegate.notificationDataProvider = notificationDataProvider
}
}
and the following code shows other classes
@MainActor
final class NotificationServiceDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate
{
var notificationDataProvider: NotificationDataProvider?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -&gt; Bool {
UNUserNotificationCenter.current().delegate = self
return true
}
func setDateId(dateId: String) {
if let notificationDataProvider = notificationDataProvider {
notificationDataProvider.dateId = dateId
}
}
nonisolated func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
// After user pressed notification
let content = response.notification.request.content
if let dateId = content.userInfo["dateId"] as? String {
await MainActor.run {
setDateId(dateId: dateId)
}
}
}
nonisolated func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -&gt; UNNotificationPresentationOptions {
// Before notification is to be shown
return [.sound, .badge, .banner, .list]
}
}
@Observable
final public class NotificationDataProvider : Sendable {
public var dateId = ""
}
I have set Strict Concurrency Checking to 'Complete.' The issue I'm facing is related to the delegate class method, which is invoked after the user presses the notification.
Current state causes crash after pressing notification. If I remove "nonisolated" keyword it works fine but I get the following warning
Non-sendable type 'UNNotificationResponse' in parameter of the protocol requirement satisfied by main actor-isolated instance method 'userNotificationCenter(_:didReceive:)' cannot cross actor boundary; this is an error in the Swift 6 language mode
I have no idea how to make it Swift6 compatible. Does anyone have any clues?
In the header for UIViewController, the method dismissViewControllerAnimated is declared like this:
- (void)dismissViewControllerAnimated: (BOOL)flag completion: (void (^ __nullable)(void))completion NS_SWIFT_DISABLE_ASYNC API_AVAILABLE(ios(5.0));
NS_SWIFT_DISABLE_ASYNC means that there's no async version exposed like there would normally be of a method that exposes a completion handler. Why is this? And is it unwise / unsafe for me to make my own async version of it using a continuation?
My use case is that I want a method that will sequentially dismiss all view controllers presented by a root view controller. So I could have this extension on UIViewController:
extension UIViewController {
func dismissAsync(animated: Bool) async {
await withCheckedContinuation { continuation in
self.dismiss(animated: animated) {
continuation.resume()
}
}
}
func dismissPresentedViewControllers() async {
while self.topPresentedViewController != self {
await self.topPresentedViewController.dismissAsync(animated: true)
}
}
var topPresentedViewController: UIViewController {
var result = self
while result.presentedViewController != nil {
result = result.presentedViewController!
}
return result
}
I found a similar problem here https://developer.apple.com/forums/thread/764777 and I could solve my problem by wrapping the call to requestAutomaticPassPresentationSuppression in a call to DispatchQueue.global().async.
But my question is if this is really how things should work. Even with strict concurrency warnings in Swift 6 I don't get any warnings. Just a runtime crash.
How are we supposed to find these problems? Couldn't the compiler assist with a warning/error.
Why does the compiler make the assumptions it does about the method that is declared like this:
@available(iOS 9.0, *)
open class func requestAutomaticPassPresentationSuppression(responseHandler: @escaping (PKAutomaticPassPresentationSuppressionResult) -> Void) -> PKSuppressionRequestToken
Now that we have migrated to Swift 6 our code base contains a bunch of unknown places where it will crash as above.
I'm calling a method with the context as parameter, within the context's perform block – is this really not legal in Swift 6?
actor MyActor {
func bar(context: NSManagedObjectContext) { /* some code */ }
func foo(context: NSManagedObjectContext) {
context.performAndWait {
self.bar(context: context)
// WARN: Sending 'context' risks causing data races; this is an error in the Swift 6 language mode
// 'self'-isolated 'context' is captured by a actor-isolated closure. actor-isolated uses in closure may race against later nonisolated uses
// Access can happen concurrently
}
}
}
The warning appears when I call a method with a context parameter, within the performAndWait-block.
Background: In my app I have methods that takes in API data, and I need to call the same methods from multiple places with the same context to store it, and I do not want to copy paste the code and have hundreds of lines of duplicate code.
Is there a well-known "this is how you should do it" for situations like this?
This is related to a previous post I made, but it's a bit flimsy and got no response: https://developer.apple.com/forums/thread/770605
I'm getting the following error in my SwiftUI code:
"Main actor-isolated property 'avatarImage' can not be referenced from a Sendable closure"
I don't understand how to fix it.
This happens in the following code:
You can copy-paste this into an empty project and make sure to have Swift 6 enabled under the Build Settings > Swift Language Version
import PhotosUI
import SwiftUI
public struct ContentView: View {
@State private var avatarItem: PhotosPickerItem?
@State private var avatarImage: Image?
@State private var avatarData: Data?
public var body: some View {
VStack(spacing: 30) {
VStack(alignment: .center) {
PhotosPicker(selection: $avatarItem, matching: .images) {
if let avatarImage {
avatarImage
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.foregroundColor(.gray)
.background(.white)
.clipShape(Circle())
.opacity(0.75)
.overlay {
Image(systemName: "pencil")
.font(.title)
.shadow(radius: 5)
}
} else {
Image(systemName: "person.circle.fill")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100, height: 100)
.foregroundColor(.gray)
.background(.white)
.clipShape(Circle())
.opacity(0.75)
.overlay {
Image(systemName: "pencil")
.font(.title)
.shadow(radius: 5)
}
}
}
}
}
.onChange(of: avatarItem) {
Task {
if let data = try? await avatarItem?.loadTransferable(
type: Data.self
) {
if let processed = processImage(data: data) {
avatarImage = processed.image
avatarData = processed.data
} else {
}
}
}
}
}
private func processImage(data: Data) -> (image: Image?, data: Data?)? {
guard let uiImage = UIImage(data: data)?.preparingForDisplay() else {
return nil
}
// Check original size
let sizeInMB = Double(data.count) / (1024 * 1024)
// If image is larger than 1MB, compress it
if sizeInMB > 1.0 {
guard let compressedData = uiImage.compress() else { return nil }
return (Image(uiImage: uiImage), compressedData)
}
return (Image(uiImage: uiImage), data)
}
}
#Preview {
ContentView()
}
public extension UIImage {
func compress(to maxSizeInMB: Double = 1.0) -> Data? {
let maxSizeInBytes = Int(
maxSizeInMB * 1024 * 1024
) // Convert MB to bytes
var compression: CGFloat = 1.0
let step: CGFloat = 0.1
var imageData = jpegData(compressionQuality: compression)
while (imageData?.count ?? 0) > maxSizeInBytes, compression > 0 {
compression -= step
imageData = jpegData(compressionQuality: compression)
}
return imageData
}
}
I am adopting Swift Concurrency in my network extension app to use Swift 6 protections.
In the UI app I ended up with most of the app marked as MainActor, so that pieces of my app can keep seamless access to each other and at the same time have thread safe access.
When it comes to my network extension, does it make sense to also mark most of the code as MainActor for the purposes of thread safety and seamless access of most classes to each other? I have doubts, because MainActor sounds like it should be a UI think, but network extension has no UI
Of course any long or blocking operations would not be MainActor
Hi everyone,
I believe this should be a simple and expected default behavior in a real-world app, but I’m unable to make it work:
1. I have a View (a screen/page in this case) that calls an endpoint using async/await.
2. If the endpoint hasn’t finished, but I navigate forward to a DetailView, I want the endpoint to continue fetching data (i.e., inside the @StateObject ViewModel that the View owns). This way, when I go back, the View will have refreshed with the fetched data once it completes.
3. If the endpoint hasn’t finished and I navigate back to the previous screen, I want it to be canceled, and the @StateObject ViewModel should be deinitialized.
I can achieve 1 and 3 using the .task modifier, since it automatically cancels the asynchronous task when the view disappears:
view
.task { await vm.getData() }
I can achieve 1 and 2 using a structured Task in the View (or in the ViewModel, its the same behavior), for example:
.onFirstAppearOnly {
Task { away vm.getData() }
}
onFirstAppearOnly is a custom modifier that I have for calling onAppear only once in view lifecycle. Just to clarify, dont think that part is important for the purpose of the example
But the question is: How can I achieve all three behaviors? Is this really such an unusual requirement?
My minimum deployment target is iOS 15, and I’m using NavigationView + NavigationLink. However, I have also tried using NavigationStack + NavigationPath and still couldn’t get it to work.
Any help would be much appreciated.
Thank you, folks!
Hi all, we try migrate project to Swift 6
Project use AVPlayer in MainActor
Selection audio and subtitiles not work
Task { @MainActor in let group = try await item.asset.loadMediaSelectionGroup(for: AVMediaCharacteristic.audible)
get error: Non-sendable type 'AVMediaSelectionGroup?' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary
and second example
`if #available(iOS 15.0, *) {
player?.currentItem?.asset.loadMediaSelectionGroup(for: AVMediaCharacteristic.audible, completionHandler: { group, error in
if error != nil {
return
}
if let groupWrp = group {
DispatchQueue.main.async {
self.setupAudio(groupWrp, audio: audioLang)
}
}
})
}`
get error: Sending 'groupWrp' risks causing data races
I've got a watch app, still with storyboard, WKInterfaceController and WatchConnectivity.
After updating it for swift 6 concurrency I thought I'd keep it for a little while without swift 6 concurrency dynamic runtime check.
So I added -disable-dynamic-actor-isolation in OTHER_SWIFT_FLAGS, but it doesn't seem to have an effect for the Apple Watch target. Without manually marking callbacks where needed with @Sendable in dynamic checks seem to be in place.
swiftc invocation is as (includes -disable-dynamic-actor-isolation):
swiftc -module-name GeoCameraWatchApp -Onone -enforce-exclusivity\=checked ... GeoCameraWatchApp.SwiftFileList -DDEBUG -enable-bridging-pch -disable-dynamic-actor-isolation -D DEBUG -enable-experimental-feature DebugDescriptionMacro -sdk /Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS11.2.sdk -target arm64_32-apple-watchos7.0 -g -module-cache-path /Users/stand/Library/Developer/Xcode/DerivedData/ModuleCache.noindex -Xfrontend -serialize-debugging-options -enable-testing -index-store-path /Users/stand/Library/Developer/Xcode/DerivedData/speedo-almhjmryctkitceaufvkvhkkfvdw/Index.noindex/DataStore -enable-experimental-feature OpaqueTypeErasure -Xcc -D_LIBCPP_HARDENING_MODE\=_LIBCPP_HARDENING_MODE_DEBUG -swift-version 6
...
-disable-dynamic-actor-isolation flag seems to be working for the iOS targets, I believe.
The flag is described here
Am I missing something? Should the flag work for both iOS and Apple Watch targets?
decidePolicyFor delegate method:
import WebKit
@objc extension DocumentationVC
{
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)
Being called just alright in swift 5 minimal concurrency.
Raising concurrency to complete with swift 5 or swift 6. Changing the code to avoid warnings:
@preconcurrency import WebKit
@objc extension DocumentationVC
{
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
The delegate method is not being called. Changing back to swift 5 concurrency minimal - it is called.
Looking at WKNavigationDelegate:
WK_SWIFT_UI_ACTOR
@protocol WKNavigationDelegate <NSObject>
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(WK_SWIFT_UI_ACTOR void (^)(WKNavigationActionPolicy))decisionHandler WK_SWIFT_ASYNC(3);
Changing the delegate method to:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void) {
And it is called across swift 5 concurrency minimal to complete to swift 6.
I thought, the meaning of @preconcurrency import WebKit was to keep the delegate without @MainActor before the (WKNavigationActionPolicy) still matching regardless the swift concurrency mode?
My point is - this can introduce hidden breaking changes? I didn't see this documented anyhow at: https://www.swift.org/migration/documentation/migrationguide/.
decidePolicyFor is an optional method - so if signature 'mismatches' - there will be no warning on not-implementing the delegate method.
How do we catch or diagnose irregularities like this? Is it something @preconcurrency import WebKit should be ensuring and it is not?
Is this delegate mismatch a bug on swift side or something we should be taking care of while migrating? If it is on us, how do we diagnose these potential mismatches?