When marking the ViewController and the function with @MainActor, the assertion to check that the UI is updated on main thread fails.
How do I guarantee that a function is run on Main Thread when using @MainActor?
Example code:
import UIKit
@MainActor
class ViewController: UIViewController {
let updateObject = UpdateObject()
override func viewDidLoad() {
super.viewDidLoad()
updateObject.fetchSomeData { [weak self] _ in
self?.updateSomeUI()
}
}
@MainActor
func updateSomeUI() {
assert(Thread.isMainThread) // Assertion failed!
}
}
class UpdateObject {
func fetchSomeData(completion: @escaping (_ success: Bool) -> Void) {
DispatchQueue.global().async {
completion(true)
}
}
}
Even changing DispatchQueue.global().async to Task.detached does not work.
Tested with Xcode 13.2.1 and Xcode 13.3 RC
Concurrency
RSS for tagConcurrency is the notion of multiple things happening at the same time.
Posts under Concurrency tag
163 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
I have enabled runtime concurrency warnings to check for future problems concerning concurrency: Build Setting / Other Swift Flags:
-Xfrontend -warn-concurrency -Xfrontend -enable-actor-data-race-checks
When trying to call the async form of PHPhotoLibrary.shared().performChanges{} I get the following runtime warning: warning: data race detected: @MainActor function at ... was not called on the main thread in the line containing performChanges.
My sample code inside a default Xcode multi platform app template is as follows:
import SwiftUI
import Photos
@MainActor
class FotoChanger{
func addFotos() async throws{
await PHPhotoLibrary.requestAuthorization(for: .addOnly)
try! await PHPhotoLibrary.shared().performChanges{
let data = NSDataAsset(name: "Swift")!.data
let creationRequest = PHAssetCreationRequest.forAsset()
creationRequest.addResource(with: .photo, data: data, options: PHAssetResourceCreationOptions())
}
}
}
struct ContentView: View {
var body: some View {
ProgressView()
.task{
try! await FotoChanger().addFotos()
}
}
}
You would have to have a Swift data asset inside the asset catalog to run the above code, but the error can even be recreated if the data is invalid.
But what am I doing wrong? I have not found a way to run perform changes, the block or whatever causes the error on the main thread.
PS: This is only test code to show the problem, don't mind the forced unwraps.
How does one add Codable conformance to a class that needs to be isolated to the MainActor?
For example, the following code gives compiler errors:
@MainActor final class MyClass: Codable {
var value: Int
enum CodingKeys: String, CodingKey {
case value
}
init(from decoder: Decoder) throws { // <-- Compiler error: Initializer 'init(from:)' isolated to global actor 'MainActor' can not satisfy corresponding requirement from protocol 'Decodable'
let data = try decoder.container(keyedBy: CodingKeys.self)
self.value = try data.decode(Int.self, forKey: .value)
}
func encode(to encoder: Encoder) throws { // <-- Compiler error: Instance method 'encode(to:)' isolated to global actor 'MainActor' can not satisfy corresponding requirement from protocol 'Encodable'
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(value, forKey: .value)
}
}
I'm definitely struggling to get my head around actors and @MainActor at the moment!