Skip to content

Commit

Permalink
Require main actor isolation in store collection (#3333)
Browse files Browse the repository at this point in the history
* Require main actor isolation in store collection

* `preconditionIsolated` is not available in iOS <14

* Update Sources/ComposableArchitecture/Observation/IdentifiedArray+Observation.swift
  • Loading branch information
stephencelis committed Sep 4, 2024
1 parent 5e3f420 commit bb34f69
Showing 1 changed file with 33 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@
private let store: Store<IdentifiedArray<ID, State>, IdentifiedAction<ID, Action>>
private let data: IdentifiedArray<ID, State>

#if swift(<5.10)
@MainActor(unsafe)
#else
@preconcurrency@MainActor
#endif
fileprivate init(_ store: Store<IdentifiedArray<ID, State>, IdentifiedAction<ID, Action>>) {
self.store = store
self.data = store.withState { $0 }
Expand All @@ -98,21 +103,35 @@
public var startIndex: Int { self.data.startIndex }
public var endIndex: Int { self.data.endIndex }
public subscript(position: Int) -> Store<State, Action> {
guard self.data.indices.contains(position)
else {
return Store()
}
let id = self.data.ids[position]
var element = self.data[position]
return self.store.scope(
id: self.store.id(state: \.[id:id]!, action: \.[id:id]),
state: ToState {
element = $0[id: id] ?? element
return element
},
action: { .element(id: id, action: $0) },
isInvalid: { !$0.ids.contains(id) }
precondition(
Thread.isMainThread,
#"""
Store collections must be interacted with on the main actor.
When passing a scoped store to a 'ForEach' in a lazy view (for example, 'LazyVStack'), it \
must be eagerly transformed into a collection to avoid access off the main actor:
Array(store.scope(state: \.elements, action: \.elements))
"""#
)
return MainActor._assumeIsolated { [uncheckedSelf = UncheckedSendable(self)] in
let `self` = uncheckedSelf.wrappedValue
guard self.data.indices.contains(position)
else {
return Store()
}
let id = self.data.ids[position]
var element = self.data[position]
return self.store.scope(
id: self.store.id(state: \.[id:id]!, action: \.[id:id]),
state: ToState {
element = $0[id: id] ?? element
return element
},
action: { .element(id: id, action: $0) },
isInvalid: { !$0.ids.contains(id) }
)
}
}
}
#endif

0 comments on commit bb34f69

Please sign in to comment.