Concurrency

RSS for tag

Concurrency is the notion of multiple things happening at the same time.

Posts under Concurrency tag

102 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

WWDC 2015 video on GCD missing (again)
In a thread titled “Avoid Dispatch Global Concurrent Queues” [1], Quinn links to a video from WWDC 2015 Session 718, “Building Responsive and Efficient Apps with GCD”. However, this video is not available from the Apple Developer Videos site; only a half dozen or so videos from 2015 are available. This same issue of the missing video came up about five years ago, when Quinn stated that the video had been mistakenly removed but had been restored. Now it’s gone again. :sad_face: Could this video be restored again, or at least its transcript? While I understand that Apple is focused on Swift concurrency, I need to maintain some Objective-C code that uses GCD, and in tracking down some performance issues, I would like to better understand the tradeoffs in the existing code and make improvements where I can. I don’t have the resources to reimplement the code in Swift right now. (More generally, why can't Apple just leave all these videos online indefinitely, for historical purposes at least? Couldn't the ones deemed “old and misleading” just be tagged with a banner like the legacy documentation has?) [1] I like to think of these valuable threads as “Quinn Technical Notes”; I have a page in my Notes app that holds links to the ones I’ve found.
5
1
796
Sep ’23
Instruments - Swift Concurrency not working
Hello, I am trying to debug Swift Concurrency codes by using Swift Concurrency Instruments. But in our project which has been maintained for a long time, Swift Concurrency Instruments seems not working. Task { print("async code") await someAsyncFunction() } When I run Swift Concurrency Instruments with the above codes in our project, I could check that the print works well by checking stdout/stderr Instruments, but there are no records on Swift Concurrency Instruments. But in the small simple sample project, I checked that Swift Concurrency Instruments works well. Are there any settings that I need to do for the legacy project to debug Swift Concurrency?
10
0
1.3k
Mar ’24
URLSessionDownloadDelegate never called
My request call site: let headers = coordinator.requestHeaders(path: headersPath) var request = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: defaultTimeoutInterval) request.httpMethod = HTTPRequestMethod.get.rawValue request.allHTTPHeaderFields = headers let delegate = SessionDelegate(priority: priority, progressHandler: progressHandler) let session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: .main) let (localUrl, response) = try await session.download(for: request) let data = try Data(contentsOf: localUrl) SessionDelegate: public class SessionDelegate: NSObject, URLSessionTaskDelegate, URLSessionDownloadDelegate { public typealias ProgressHandler = ((_ progress: Double) -> Void) public var metrics: URLSessionTaskMetrics? private let priority: TaskPriority private let progressHandler: ProgressHandler? //MARK: - Lifecycle init(priority: TaskPriority, progressHandler: ProgressHandler? = nil) { self.priority = priority self.progressHandler = progressHandler } //MARK: - URLSessionTaskDelegate public func urlSession(_ session: URLSession, didCreateTask task: URLSessionTask) { task.priority = priority.rawValue } public func urlSession(_ session: URLSession, task: URLSessionTask, didFinishCollecting metrics: URLSessionTaskMetrics) { self.metrics = metrics } public func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) { if let progressHandler { let progress = Progress(totalUnitCount: totalBytesExpectedToSend) progress.completedUnitCount = totalBytesSent progressHandler(progress.fractionCompleted) } } //MARK: - URLSessionDownloadDelegate public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { } public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { if let progressHandler { let progress = Progress(totalUnitCount: totalBytesExpectedToWrite) progress.completedUnitCount = totalBytesWritten progressHandler(progress.fractionCompleted) } } } For some reason I get delegate callbacks from URLSessionDelegate and URLSessionTaskDelegate but never from URLSessionDownloadDelegate. I've tried assigning the delegate to the task or the session (or both), but behavior remains the same. Not sure if related but when I try using background session configuration I crash on let (localUrl, response) = try await session.download(for: request) with the error Completion handler blocks are not supported in background sessions. Use a delegate instead. even though I use the async version of the download task, and assigning delegate to the session, it might point to the reason why none of the delegate functions is getting called (maybe?), but I have no clue why Xcode thinks I'm using completion handler.
4
1
981
Oct ’23
SwiftUI Commands and StrictConcurrency Warnings Issue
I have enabled “StrictConcurrency” warnings in my project that uses SwiftUI. I have a Commands struct. It has a Button, whose action is calling an async method via Task{}. This builds without warnings within Views, but not Commands. There the compiler reports “Main actor-isolated property 'body' cannot be used to satisfy nonisolated protocol requirement”. Looking at SwiftUI: In View, body is declared @MainActor: @ViewBuilder @MainActor var body: Self.Body { get } In Commands, body is not declared @MainActor: @CommandsBuilder var body: Self.Body { get } So the common practice of making a Button action asynchronous: Button { Task { await model.load() } } label:{ Text("Async Button") } will succeed without warnings in Views, but not in Commands. Is this intentional? I've filed FB13212559. Thank you.
2
0
675
Sep ’23
fatal error: Index out of range...
class CalculatorViewModel : NSObject, ObservableObject, Identifiable { var id = UUID() @Published var output = "Disconnected" @Published var connected = false @Published var databasePath = String() enum itemType : Int{ case angle = 1 case degree = 2 case grip1 = 3 case grip2 = 4 } struct test_Array: Identifiable { var id = UUID() var time: String var swingNum : Int var dataSeqInSwing: Int var timeStampInSeq: Double var itemType: Int var value: Double } @Published var testDBdata = [test_Array] () ..... private var centralManager: CBCentralManager? func connectCalculator() { output = "Connecting..." centralQueue = DispatchQueue(label: "test.discovery") centralManager = CBCentralManager(delegate: self, queue: centralQueue) } .... func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { ..... if characteristic.uuid == outputCharUUID, var data = characteristic.value { ..... DispatchQueue.main.async() { for index in tupleSets { strItemSets = index.components(separatedBy: ",") ....... var itemN = 1 for indexStr in strItemSets { self.golfDBdata[numDB].dataSeqInSwing = countItem. <= Error self.golfDBdata[numDB].itemType = itemN self.golfDBdata[numDB].value = Double(strItemSets[itemN]) ?? 0.0
5
0
696
Oct ’23
Swift Concurrency and synchronous, nonisolated infrastructure?
I've been working with Swift Concurrency a good bit for about 6 months now and keep bumping into where I need to bridge synchronously between it and other nonisolated system frameworks. I'm curious if there are recommended patterns for this type of interaction. Consider this scenario: I have a @MainActor isolated class that I'm using in my UI and would like to make it Codable. As soon as you add that requirement, the init(from decoder: Decoder) and encode(to encoder Encoder) must each be nonisolated, synchronous calls, which as far as I can tell means that I need an inner lock (NSLock or similar) to guard access to the state of the class. Despite the claim made elsewhere in the forums it is not as simple as adding the async keyword to these methods because it changes their signature. While I agree it is the right thing to encode/decode in the background off the main thread, adding locks every time I need to bridge feels contrary to the spirit of Swift Concurrency. What am I missing?
1
0
553
Oct ’23
new swift concurrency model with core data willSave()
anyone know how to use the new swift concurrency model (ie. await context.perform and context.perform) core data willSave()? for example. how would I ensure this is thread safe? changing self.managedObjectContext!.performAndWait to await self.managedObjectContext!.perform means I have to change willSave() to async which then means it never gets called because its not overriding the correct method anymore. override public func willSave() { self.managedObjectContext!.performAndWait { if self.modifiedDate == nil || Date().timeIntervalSince(self.modifiedDate!) >= 1 { self.modifiedDate = Date() } } super.willSave() }
0
0
562
Oct ’23
Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
Can't understand why this compiles extension HomeReducer { @MainActor @Observable final class State: BaseState { var isPresentingSubscription = false } } class BaseState {} but this gives an error Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context extension HomeReducer { @MainActor @Observable final class State { var isPresentingSubscription = false } } extension DependencyValues { var homeView: @Sendable () -> HomeView { HomeKey().value } private struct HomeKey: DependencyKey { typealias Value = @Sendable () -> HomeView let liveValue: Value = { HomeView(store: Store(initialState: HomeReducer.State(), reducer: HomeReducer())) } let testValue: Value = { fatalError("testValue not implemented") } var previewValue: Value = { fatalError("previewValue not implemented") } } }
5
0
4.9k
Nov ’23
Please help with: Trailing closure passed to parameter of type 'EKEvent?' that does not accept a closure
I use this function to read calendar headers in a array of strings: `func getCalendarHeaders(ofType: EKEntityType, alerter: Alerter) async -> [String] { var headers: [String] = [] do { if try await requestAuthorizationFor(ofType) { let calendars = try await fetchCalendars(ofType: ofType) for calendar in calendars { headers.append(calendar.title) } } } catch let error { DispatchQueue.main.async { alerter.alert = ErrorManager.shared.showAlert(error: error) } } return headers } In my view model / or view I try to init with: Task { self.eventCalendarHeaders = await CalendarManager.shared.getCalendarHeaders(ofType: EKEntityType.event, alerter: alerter) } Why I get this error? Trailing closure passed to parameter of type 'EKEvent?' that does not accept a closure
1
0
441
Nov ’23
Why aren't changes to @Published variables automatically published on the main thread?
Given that SwiftUI and modern programming idioms promote asynchronous activity, and observing a data model and reacting to changes, I wonder why it's so cumbersome in Swift at this point. Like many, I have run up against the problem where you perform an asynchronous task (like fetching data from the network) and store the result in a published variable in an observed object. This would appear to be an extremely common scenario at this point, and indeed it's exactly the one posed in question after question you find online about this resulting error: Publishing changes from background threads is not allowed Then why is it done? Why aren't the changes simply published on the main thread automatically? Because it isn't, people suggest a bunch of workarounds, like making the enclosing object a MainActor. This just creates a cascade of errors in my application; but also (and I may not be interpreting the documentation correctly) I don't want the owning object to do everything on the main thread. So the go-to workaround appears to be wrapping every potentially problematic setting of a variable in a call to DispatchQueue.main. Talk about tedious and error-prone. Not to mention unmaintainable, since I or some future maintainer may be calling a function a level or two or three above where a published variable is actually set. And what if you decide to publish a variable that wasn't before, and now you have to run around checking every potential change to it? Is this not a mess?
8
0
1.6k
Jan ’24
Concurrency Resources
Swift Concurrency Resources: DevForums tags: Concurrency The Swift Programming Language Concurrency > Concurrency documentation WWDC 2022 Session 110351 Eliminate data races using Swift Concurrency — This ‘sailing on the sea of concurrency’ talk is a great introduction to the fundamentals. WWDC 2021 Session 10134 Explore structured concurrency in Swift — The table that starts rolling out at around 25:45 is really helpful. Dispatch Resources: DevForums tags: Dispatch Dispatch documentation — Note that the Swift API and C API, while generally aligned, are different in many details. Make sure you select the right language at the top of the page. Dispatch man pages — While the standard Dispatch documentation is good, you can still find some great tidbits in the man pages. See Reading UNIX Manual Pages. Start by reading dispatch in section 3. WWDC 2015 Session 718 Building Responsive and Efficient Apps with GCD [1] WWDC 2017 Session 706 Modernizing Grand Central Dispatch Usage [1] Avoid Dispatch Global Concurrent Queues DevForums post Share and Enjoy — Quinn “The Eskimo!” @ Developer Technical Support @ Apple let myEmail = "eskimo" + "1" + "@" + "apple.com" [1] These videos may or may not be available from Apple. If not, the URL should help you locate other sources of this info.
0
0
655
Nov ’23
How to safely access Core data NSManagedObject attributes from a SwiftUI view using swift concurrency model?
How do I safely access Core data NSManagedObject attributes from SwiftUI view using the swift concurrency model. My understanding is we need to enclose any access to NSManageObject attributes in await context.perform {}. But if I use it anywhere in a view be that in the body(), or init() or .task() modifier I get the following warnings or errors: for .task{} modifier or if within any Task {}: Passing argument of non-sendable type 'NSManagedObjectContext.ScheduledTaskType' outside of main actor-isolated context may introduce data races this happens even if I create a new context solely for this access. if within body() or init(): 'await' in a function that does not support concurrency but we cant set body or init() to async an example of the code is something along the lines of: var attributeString: String = "" let context = PersistentStore.shared.persistentContainer.newBackgroundContext() await context.perform { attributeString = nsmanagedObject.attribute! }
2
1
958
Dec ’23
How to prevent TSAN from reporting data-races that are intentional and considered safe?
I'm having a hard time relying on TSAN to detect problems due to its rightful insistence on reporting data-races (I know, stick with me). Picture the following implementation of a lazily-allocated property in an Obj-C class: @interface MyClass { id _myLazyValue; // starts as nil as all other Obj-C ivars } @end @implementation MyClass - (id)myLazyValue { if (_myLazyValue == nil) { @synchronized(self) { if (_myLazyValue == nil) { _myLazyValue = &lt;expensive computation&gt; } } } return _myLazyValue; } @end The first line in the method is reading a pointer-sized chunk of memory outside of the protection provided by the @synchronized(...) statement. That same value may be written by a different thread within the execution of the @synchronized block. This is what TSAN complains about, but I need it not to. The code above ensures the ivar is written by at most one thread. The read is unguarded, but it is impossible for any thread to read a non-nil value back that is invalid, uninitialized or unretained. Why go through this trouble? Such a lazily-allocated property usually locks on @synchronized once, until (at most) one thread does any work. Other threads may be temporarily waiting on the same lock but again only while the value is being initialized. The cost of allocation and initialization is guaranteed to be paid once: multiple threads cannot initialize the value multiple times (that’s the reason for the second _myLazyValue == nil check within the scope of the @synchronized block). Subsequent accesses of the initialized property skip locking altogether, which is exactly the performance we want from a lazily-allocated, immutable property that still guarantees thread-safe access. Assuming there isn't a big embarrassing hole in my logic, is there a way to decorate specific portions of our sources (akin to #pragma statements that disable certain warnings) so that you can mark any read/write access to a specific value as "safe"? Is the most granular tool for this purpose the __attribute__((no_sanitize("thread")))? Ideally one would want to ask TSAN to ignore only specific read/writes, rather than the entire body of a function. Thank you!
8
0
923
Jan ’24
A random crash on DateFormatter
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread 0 libicucore.A.dylib 0x18c500918 icu::SimpleDateFormat::subFormat(icu::UnicodeString&amp;, char16_t, int, UDisplayContext, int, char16_t, icu::FieldPositionHandler&amp;, icu::Calendar&amp;, UErrorCode&amp;) const + 532 1 libicucore.A.dylib 0x18c500520 icu::SimpleDateFormat::format(icu::Calendar&amp;, icu::UnicodeString&amp;, icu::FieldPositionHandler&amp;, UErrorCode&amp;) const + 520 2 libicucore.A.dylib 0x18c5002f8 icu::SimpleDateFormat::format(icu::Calendar&amp;, icu::UnicodeString&amp;, icu::FieldPosition&amp;) const + 84 3 libicucore.A.dylib 0x18c42c4ac icu::DateFormat::format(double, icu::UnicodeString&amp;, icu::FieldPosition&amp;) const + 176 4 libicucore.A.dylib 0x18c535c40 udat_format + 352 5 CoreFoundation 0x189349a14 __cficu_udat_format + 68 6 CoreFoundation 0x189349898 CFDateFormatterCreateStringWithAbsoluteTime + 192 7 Foundation 0x18a432bf0 -[NSDateFormatter stringForObjectValue:] + 316 8 ShiningUtiliesKit 0x10522a130 SULog.log(:logDegree:file:method:line:) + 488 9 ShiningUtiliesKit 0x105229f3c SULog.debug(:file:method:line:) + 48 10 Calibration 0x1029f56bc closure #1 in Device.setDeviceData(:) + 487 (DeviceObject.swift:293) 11 Calibration 0x1029f3de0 specialized autoreleasepool(invoking:) + 23 (:0) [inlined] 12 Calibration 0x1029f3de0 Device.setDeviceData(_:) + 23 (DeviceObject.swift:291) [inlined] 13 Calibration 0x1029f3de0 closure #1 in closure #1 in variable initialization expression of Device.callback + 403 (DeviceObject.swift:221) 14 Calibration 0x1029f3548 thunk for @escaping @callee_guaranteed () -&gt; () + 27 (:0) 15 libdispatch.dylib 0x18907dcb8 _dispatch_call_block_and_release + 32 16 libdispatch.dylib 0x18907f910 _dispatch_client_callout + 20 17 libdispatch.dylib 0x18908dfa8 _dispatch_main_queue_drain + 984 18 libdispatch.dylib 0x18908dbc0 _dispatch_main_queue_callback_4CF + 44 19 CoreFoundation 0x18934b15c CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE + 16 20 CoreFoundation 0x189308a80 __CFRunLoopRun + 1996 21 CoreFoundation 0x189307c5c CFRunLoopRunSpecific + 608 22 HIToolbox 0x193884448 RunCurrentEventLoopInMode + 292 23 HIToolbox 0x193884284 ReceiveNextEventCommon + 648 24 HIToolbox 0x193883fdc _BlockUntilNextEventMatchingListInModeWithFilter + 76 25 AppKit 0x18cae2c54 _DPSNextEvent + 660 26 AppKit 0x18d2b8ebc -[NSApplication(NSEventRouting) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 716 27 AppKit 0x18cad6100 -[NSApplication run] + 476 28 AppKit 0x18caad3cc NSApplicationMain + 880 29 SwiftUI 0x1b3c14f80 0x1b3b3b000 + 892800 30 SwiftUI 0x1b44a530c 0x1b3b3b000 + 9872140 31 SwiftUI 0x1b48eb4f0 0x1b3b3b000 + 14353648 32 Calibration 0x1029fa1a0 static IntraoralScanMiniProgramApp.$main() + 51 (IntraoralScanMiniProgramApp.swift:14) [inlined] 33 Calibration 0x1029fa1a0 main + 63 (DeviceObject.swift:0) 34 dyld 0x188eb10e0 start + 2360 My mac APP has a crash about DateFormatter. mac version is 14.1.1. public class SULog { private let dateFormatter = { let dateFormatter = DateFormatter() dateFormatter.dateFormte = "yyy-MM-dd HH:mm:ss_sss" return dateFormatter } private var lock = NSLock() public func debug(_ message: Any...) { log(message) } public func log(_ message: Any...) { lock.lock() let date = Date() let dateString = dateFormatter.string(from:date) lock.unlock() print(dateString) } } var log:SULog! class AppDelegate: NSObject { func applicationWillFinishLaunching(_ notification: Notification) { log = SULog() } } public class Test { func test() { autoreleasepool { //.......other code log.debug("test") // crashed //........ other code } } } Any thought on this crash? Thanks
1
0
534
Dec ’23
The need to use Task inside the declaration block of an asynchronous function
I am following the iOS App Dev Tutorials on Adopting Swift concurrency and Persisting data. On Adopting Swift concurrency, it is mentioned that we can call an asynchronous function using the await keyword. Because the asynchronous function can suspend execution, we can use await only in asynchronous contexts, such as inside the body of another asynchronous function. Adopting Swift concurrency, it is also mentioned that we can create a new asynchronous context with a Task to call asynchronous functions from a synchronous context. Based on this information, I concluded that the main added value of a Task is to enable calling an asynchronous function in a synchronous context. On Persisting data, a Task object is created inside the declaration block of an asynchronous function: func load() async throws { let task = Task<[DailyScrum], Error> { let fileURL = try Self.fileURL() guard let data = try? Data(contentsOf: fileURL) else { return [] } let dailyScrums = try JSONDecoder().decode([DailyScrum].self, from: data) return dailyScrums } let scrums = try await task.value self.scrums = scrums } I didn't understand why it was necessary to create task. Based on what I learned from Adopting Swift concurrency, the context of load() is already asynchronous. Therefore, we can simply call asynchronous functions without the need to create a Task object. When I tried to run the code by removing the task, the app still worked the same, or at least that was my perception. I believe that there is another reason to use Task in this example other than just enabling calling asynchronous functions. However, this was not explicitly explained in the tutorials. Could you please explain why we use Task here? What is the added value and how it would improve the behavior of this app? Please keep in mind that I am a complete beginner and had no idea about what concurrency was two weeks ago. In case deleting task would not change anything, could you please confirm that my initial understanding was correct?
1
0
488
Dec ’23
StoreKit sample project from Apple fails concurrency checks
I am trying to use code from Apple's sample project on StoreKit (Implementing a store in your app using the StoreKit API). However, even if I don't make any changes to this sample project except enable "complete" concurrency checking in the build settings, I get warnings like Capture of 'self' with non-sendable type 'Store' in a '@Sendable' closure. How worried should I be about these warnings and how can I fix them?
1
0
558
Dec ’23
POSIX read/write thread safety: is sharing socket descriptors between threads on macOS correct?
Hi, Despite the following code works great on Windows and Linux (well, there is an OS layer stripped from the code), it hangs on macOS (pseudocode first): create non-blocking socketpair(AF_UNIX, SOCK_STREAM, ...); + a couple of fcntl(fd, ..., flags | O_NONBLOCK) spawn 128 pairs of threads (might be as little as 32, but will need several iterations to reproduce). Of course, there is the errno check to ensure there are no errors but EWOULDBLOCK / EAGAIN readers read a byte 10000 times: for (...) { while (read(fd[1]...) < 1) select(...); r++;} writers write a byte 10000 times: for (...) { while (write(fd[0]...) < 1) select(...); w++;} Join writers; Join readers; On Linux/Windows with the iterations number really cranked up, I'm getting a socket buffer overflow, so ::write returns EWOULDBLOCK, then I'm waiting on a socket until it's ready, continue, and after joining both sets of threads I see that bytes-read is equal to bytes-written, everything fine. However, on macOS I quickly end up in a strange lock when writers are waiting on ::select(...., &write_fds, ...) and readers on the corresponding ::select(..., &read_fds, ...); I have really no idea how that could happen except that the read/write is not thread-safe. However, it looks like POSIX docs and manpages state that it is (at least, reentrant). Could anyone point me in the right direction? Detailed code below: std::atomic<int> bytes_written(0); std::atomic<int> bytes_read(0); static constexpr int k_packets = 10000; static constexpr int k_threads = 32; std::vector<std::thread> writers; std::vector<std::thread> readers; writers.reserve(k_threads); readers.reserve(k_threads); for (int i = 0; i < k_threads; ++i) { writers.emplace_back([fd_write = fd[1], &bytes_written]() { char data = 'x'; for (int i = 0; i < k_packets; ++i) { while (::write(fd_write, &data, 1) < 1) { fd_set writefds; FD_ZERO(&writefds); FD_SET(fd_write, &writefds); assert(errno == EAGAIN || errno == EWOULDBLOCK); int retval = ::select(fd_write + 1, nullptr, &writefds, nullptr, nullptr); if (retval < 1) assert(errno == EAGAIN || errno == EWOULDBLOCK); } ++bytes_written; } }); readers.emplace_back([fd_read = fd[0], &bytes_read]() { char data; for (int i = 0; i < k_packets; ++i) { while (::read(fd_read, &data, 1) < 1) { fd_set readfds; FD_ZERO(&readfds); FD_SET(fd_read, &readfds); assert(errno == EAGAIN || errno == EWOULDBLOCK); int retval = ::select(fd_read + 1, &readfds, nullptr, nullptr, nullptr); if (retval < 1) assert(errno == EAGAIN || errno == EWOULDBLOCK); } ++bytes_read; } }); } for (auto& t : writers) t.join(); for (auto& t : readers) t.join(); assert(bytes_written == bytes_read);
0
0
456
Dec ’23
Migrating @MainActor ViewModel to @Observable causing error
I get this error while migrating from ObservableObject to @Observable. Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context My original code: struct SomeView: View { @StateObject private var viewModel = ViewModel() } After migration: @MainActor @Observable class BaseViewModel { } @MainActor class ViewModel: BaseViewModel { } struct SomeView: View { @State private var viewModel = ViewModel() } As discussed here. It seems like @StateObject is adding @MainActor compliance to my View under the hood because it's wrappedValue and projectedValue properties are marked as @MainActor, while on @State they are not. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) @frozen @propertyWrapper public struct StateObject&lt;ObjectType&gt; : DynamicProperty where ObjectType : ObservableObject { ... @MainActor public var wrappedValue: ObjectType { get } .... @MainActor public var projectedValue: ObservedObject&lt;ObjectType&gt;.Wrapper { get } } One solution for this is to mark my View explicitly as @MainActor struct ViewModel: View but it have it side effects, for example code like: Button(action: resendButtonAction) { Text(resendButtonAttributedTitle()) } Will result a warning Converting function value of type '@MainActor () -&gt; ()' to '() -&gt; Void' loses global actor 'MainActor' While could be easily solved by using instead Button(action: { resendButtonAction() } ) { Text(resendButtonAttributedTitle()) } I still feel like marking the whole View explicitly as @MainActor is not a good practice. Adding fake @StateObject property to my view also do the trick, but it's a hack (the same for @FetchRequest). Can anyone think of a more robust solution for this?
1
1
1.2k
May ’24
SwiftUI.View Compiler Errors when Property Wrapper is Annotated with MainActor
Hi! I'm seeing some confusing behavior with a propertyWrapper that tries to constrain its wrappedValue to MainActor. I'm using this in a SwiftUI.View… but I'm seeing some confusing behavior when I try to add that component to my graph. There seems to be some specific problem when body is defined in an extension. I start with a simple property wrapper: @propertyWrapper struct Wrapper<T> { @MainActor var wrappedValue: T } I then try a simple App with a View that uses a Wrapper: @main struct MainActorDemoApp: App { var body: some Scene { WindowGroup { ContentView() } } } struct ContentView: View { @Wrapper var value = "Hello, world!" var body: some View { Text(self.value) } } This code compiles with no problems for me. For style… I might choose to define the body property of my MainActorDemoApp with an extension: @main struct MainActorDemoApp: App { // var body: some Scene { // WindowGroup { // ContentView() // } // } } extension MainActorDemoApp { var body: some Scene { WindowGroup { ContentView() // Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context } } } struct ContentView: View { @Wrapper var value = "Hello, world!" var body: some View { Text(self.value) } } Explicitly marking my body as a MainActor fixes the compiler error: @main struct MainActorDemoApp: App { // var body: some Scene { // WindowGroup { // ContentView() // } // } } extension MainActorDemoApp { @MainActor var body: some Scene { WindowGroup { ContentView() } } } struct ContentView: View { @Wrapper var value = "Hello, world!" var body: some View { Text(self.value) } } So I guess the question is… why? Why would code that breaks when my body is in an extension not break when my body is in my original struct definition? Is this intended behavior? I'm on Xcode Version 15.2 (15C500b) and Swift 5.9.2 (swiftlang-5.9.2.2.56 clang-1500.1.0.2.5). It's unclear to me what is "wrong" about the code that broke… any ideas?
3
0
571
Jan ’24