Dependency with main actor-isolated initializer requirements like FeedbackGeneratorClient from isowords #22
-
Hello, I was trying to extract public struct FeedbackGeneratorClient: Sendable {
public internal(set) var prepare: @Sendable () async -> Void
public internal(set) var generate: @Sendable () async -> Void
public init(
prepare: @escaping @Sendable () async -> Void,
generate: @escaping @Sendable () async -> Void
) {
self.prepare = prepare
self.generate = generate
}
}
extension DependencyValues {
public var feedbackGeneratorClient: FeedbackGeneratorClient {
get { self[FeedbackGeneratorClient.self] }
set { self[FeedbackGeneratorClient.self] = newValue }
}
} And the live implementation looks like this: extension FeedbackGeneratorClient: DependencyKey {
public static let liveValue = {
let selectionFeedbackGenerator = UISelectionFeedbackGenerator() // 🔴 Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context
return Self(
prepare: { await selectionFeedbackGenerator.prepare() },
generate: { await selectionFeedbackGenerator.selectionChanged() }
)
}()
} However if you have Maybe something like a |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 10 replies
-
Hey @ph1ps, you're right, it is indeed really annoying to wrap main actor stuff with the protocol requirement getting in the way. public final class MainActorIsolated<Value: AnyObject>: @unchecked Sendable {
@MainActor
public private(set) lazy var value: Value = initialValue()
private let initialValue: @MainActor @Sendable () -> Value
public init(_ initialValue: @MainActor @Sendable @escaping () -> Value) {
self.initialValue = initialValue
}
@MainActor
public func withValue<T: Sendable>(
_ operation: @MainActor (inout Value) throws -> T
) rethrows -> T {
return try operation(&value)
}
@MainActor
public subscript<Subject: Sendable>(dynamicMember keyPath: KeyPath<Value, Subject>) -> Subject {
self.value[keyPath: keyPath]
}
} Because the value is only lazily created in a I was planning to add this utility to |
Beta Was this translation helpful? Give feedback.
-
Hello all. Been working on a dependency with the same issue: needing to initialize resources on the main actor. Unfortunately, the quoted suggestion is now warning for me on Xcode 15.3 / Swift 5.10 / @MainActor
public final class MainActorIsolated<Value>: Sendable {
public lazy var value: Value = initialValue() // ⚠️ Non-sendable type '@MainActor () -> Value' in asynchronous access to main actor-isolated property 'initialValue' cannot cross actor boundary
private let initialValue: @MainActor () -> Value
nonisolated public init(initialValue: @MainActor @escaping () -> Value) {
self.initialValue = initialValue // ⚠️ Main actor-isolated property 'initialValue' can not be mutated from a non-isolated context; this is an error in Swift 6
}
} Was playing around, trying to come up with a solution to this problem. What about something like this? Gives me no warnings. struct LocationClient: Sendable {
var requestLocation: @Sendable () async -> Void
// etc
}
extension LocationClient: DependencyKey {
public static var liveValue: Self {
Self(
requestLocation: { @MainActor in
Self.manager.requestLocation()
}
// etc
)
}
@MainActor
private static let manager = {
let manager = CLLocationManager()
// configure resources if necessary
return manager
}()
} Not sure if there's a way to extract the main-actor-static isolation into a wrapper type that doesn't warn; could save needing to annotate every endpoint closure Got the idea to store resources as statics on the client from isowords' Game Center client. |
Beta Was this translation helpful? Give feedback.
Hey @ph1ps, you're right, it is indeed really annoying to wrap main actor stuff with the protocol requirement getting in the way.
I have a
MainActorIsolated
wrapper that I'm toying with to tackle these issues: