This commit is contained in:
John Estropia
2026-07-24 11:51:36 +09:00
parent 1ea9ad7c4a
commit 13093d72d4
51 changed files with 1083 additions and 243 deletions
+5 -1
View File
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
5F035E4F300F726700E98F8F /* WithMainActorImmediate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F035E4E300F725800E98F8F /* WithMainActorImmediate.swift */; };
B531EFE724EA762D005F247D /* Menu.PlaceholderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFE624EA762D005F247D /* Menu.PlaceholderView.swift */; };
B531EFE924EB5A53005F247D /* Modern.PokedexDemo.PokedexEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFE824EB5A52005F247D /* Modern.PokedexDemo.PokedexEntry.swift */; };
B531EFEB24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift in Sources */ = {isa = PBXBuildFile; fileRef = B531EFEA24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift */; };
@@ -38,7 +39,7 @@
B5A3916024E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3915F24E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift */; };
B5A3916224E697BA00E7E8BD /* Modern.PlacemarksDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3916124E697BA00E7E8BD /* Modern.PlacemarksDemo.MainView.swift */; };
B5A3916524E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3916424E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift */; };
B5A3916B24E698F900E7E8BD /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3916724E698F900E7E8BD /* CoreStore.framework */; };
B5A3916B24E698F900E7E8BD /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3916724E698F900E7E8BD /* CoreStore.framework */; platformFilter = ios; };
B5A3916C24E698F900E7E8BD /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3916724E698F900E7E8BD /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
B5A3917524E6990200E7E8BD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3917424E6990200E7E8BD /* MapKit.framework */; };
B5A3917724E6990700E7E8BD /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5A3917624E6990700E7E8BD /* CoreLocation.framework */; };
@@ -115,6 +116,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
5F035E4E300F725800E98F8F /* WithMainActorImmediate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WithMainActorImmediate.swift; sourceTree = "<group>"; };
B531EFE624EA762D005F247D /* Menu.PlaceholderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Menu.PlaceholderView.swift; sourceTree = "<group>"; };
B531EFE824EB5A52005F247D /* Modern.PokedexDemo.PokedexEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.PokedexEntry.swift; sourceTree = "<group>"; };
B531EFEA24EB5ECD005F247D /* Modern.PokedexDemo.Service.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.Service.swift; sourceTree = "<group>"; };
@@ -366,6 +368,7 @@
B5A3917A24E6A75F00E7E8BD /* Helpers */ = {
isa = PBXGroup;
children = (
5F035E4E300F725800E98F8F /* WithMainActorImmediate.swift */,
B5A3917F24E787D900E7E8BD /* InstructionsView.swift */,
B5E32C8F24FA41F9003F46AD /* ImageDownloader.swift */,
B5A3915424E6857F00E7E8BD /* Menu */,
@@ -685,6 +688,7 @@
B5A54401250487C7000DC5E3 /* Advanced.EvolutionDemo.ListView.swift in Sources */,
B5A543FF250487B1000DC5E3 /* Advanced.EvolutionDemo.MainView.swift in Sources */,
B5D6F209250E14AA00DF5D2F /* Advanced.EvolutionDemo.Migrator.swift in Sources */,
5F035E4F300F726700E98F8F /* WithMainActorImmediate.swift in Sources */,
B5C18F3325138700001BEFB3 /* Advanced.EvolutionDemo.ProgressView.swift in Sources */,
B5D6F1F8250E07FD00DF5D2F /* Advanced.EvolutionDemo.V1.swift in Sources */,
B5D6F210250E1E3200DF5D2F /* Advanced.EvolutionDemo.V1.xcdatamodeld in Sources */,
@@ -3,9 +3,23 @@
LastUpgradeVersion = "1600"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
parallelizeBuildables = "NO"
buildImplicitDependencies = "NO">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
BuildableName = "CoreStore.framework"
BlueprintName = "CoreStore iOS"
ReferencedContainer = "container:../CoreStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
@@ -48,7 +48,7 @@ extension Classic.ColorsDemo {
/**
Sample 3: We can end monitoring updates anytime. `removeObserver()` was called here for illustration purposes only. `ObjectMonitor`s safely remove deallocated observers automatically.
*/
deinit {
isolated deinit {
self.palette.removeObserver(self)
}
@@ -87,13 +87,16 @@ extension Classic.ColorsDemo {
// MARK: ObjectObserver
func objectMonitor(
nonisolated func objectMonitor(
_ monitor: ObjectMonitor<Classic.ColorsDemo.Palette>,
didUpdateObject object: Classic.ColorsDemo.Palette,
didUpdateObject object: sending Classic.ColorsDemo.Palette,
changedPersistentKeys: Set<KeyPathString>
) {
self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys)
withMainActorImmediate {
self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys)
}
}
@@ -248,10 +251,11 @@ extension Classic.ColorsDemo {
private dynamic func hueSliderValueDidChange(_ sender: UISlider) {
let value = sender.value
let persistentID = self.palette.object?.persistentID()
Classic.ColorsDemo.dataStack.perform(
asynchronous: { [weak self] (transaction) in
asynchronous: { (transaction) in
let palette = transaction.edit(self?.palette.object)
let palette = transaction.edit(persistentID)
palette?.hue = value
},
completion: { _ in }
@@ -262,10 +266,11 @@ extension Classic.ColorsDemo {
private dynamic func saturationSliderValueDidChange(_ sender: UISlider) {
let value = sender.value
let persistentID = self.palette.object?.persistentID()
Classic.ColorsDemo.dataStack.perform(
asynchronous: { [weak self] (transaction) in
asynchronous: { (transaction) in
let palette = transaction.edit(self?.palette.object)
let palette = transaction.edit(persistentID)
palette?.saturation = value
},
completion: { _ in }
@@ -276,10 +281,11 @@ extension Classic.ColorsDemo {
private dynamic func brightnessSliderValueDidChange(_ sender: UISlider) {
let value = sender.value
let persistentID = self.palette.object?.persistentID()
Classic.ColorsDemo.dataStack.perform(
asynchronous: { [weak self] (transaction) in
asynchronous: { (transaction) in
let palette = transaction.edit(self?.palette.object)
let palette = transaction.edit(persistentID)
palette?.brightness = value
},
completion: { _ in }
@@ -11,6 +11,7 @@ extension Modern.ColorsDemo {
// MARK: - Modern.ColorsDemo.MainView
@MainActor
struct MainView<ListView: View, DetailView: View>: View {
// MARK: Internal
@@ -11,6 +11,7 @@ extension Modern.ColorsDemo.SwiftUI {
// MARK: - Modern.ColorsDemo.SwiftUI.DetailView
@MainActor
struct DetailView: View {
/**
@@ -33,6 +34,7 @@ extension Modern.ColorsDemo.SwiftUI {
init(_ palette: ObjectPublisher<Modern.ColorsDemo.Palette>) {
let persistentID = palette.persistentID()
self._palette = .init(palette)
self._hue = Binding(
get: { palette.hue ?? 0 },
@@ -41,7 +43,7 @@ extension Modern.ColorsDemo.SwiftUI {
Modern.ColorsDemo.dataStack.perform(
asynchronous: { (transaction) in
let palette = palette.asEditable(in: transaction)
let palette = persistentID.asEditable(in: transaction)
palette?.hue = percentage
},
completion: { _ in }
@@ -55,7 +57,7 @@ extension Modern.ColorsDemo.SwiftUI {
Modern.ColorsDemo.dataStack.perform(
asynchronous: { (transaction) in
let palette = palette.asEditable(in: transaction)
let palette = persistentID.asEditable(in: transaction)
palette?.saturation = percentage
},
completion: { _ in }
@@ -69,7 +71,7 @@ extension Modern.ColorsDemo.SwiftUI {
Modern.ColorsDemo.dataStack.perform(
asynchronous: { (transaction) in
let palette = palette.asEditable(in: transaction)
let palette = persistentID.asEditable(in: transaction)
palette?.brightness = percentage
},
completion: { _ in }
@@ -11,6 +11,7 @@ extension Modern.ColorsDemo.SwiftUI {
// MARK: - Modern.ColorsDemo.SwiftUI.ItemView
@MainActor
struct ItemView: View {
/**
@@ -11,6 +11,7 @@ extension Modern.ColorsDemo.SwiftUI {
// MARK: - Modern.ColorsDemo.SwiftUI.ListView
@MainActor
struct ListView: View {
/**
@@ -81,7 +82,7 @@ extension Modern.ColorsDemo.SwiftUI {
Modern.ColorsDemo.dataStack.perform(
asynchronous: { transaction in
transaction.delete(objectIDs: objectIDsToDelete)
transaction.delete(persistentIDs: objectIDsToDelete)
},
completion: { _ in }
)
@@ -92,7 +92,7 @@ extension Modern.ColorsDemo.UIKit {
_ monitor: ObjectMonitor<Modern.ColorsDemo.Palette>,
didUpdateObject object: sending Modern.ColorsDemo.Palette,
changedPersistentKeys: Set<KeyPathString>,
sourceIdentifier: Any?
sourceIdentifier: (any Sendable)?
) {
MainActor.assumeIsolated {
@@ -253,10 +253,11 @@ extension Modern.ColorsDemo.UIKit {
private dynamic func hueSliderValueDidChange(_ sender: UISlider) {
let value = sender.value
let paletteID = self.palette.object?.persistentID()
Modern.ColorsDemo.dataStack.perform(
asynchronous: { [weak self] (transaction) in
asynchronous: { transaction in
let palette = transaction.edit(self?.palette.object)
let palette = transaction.edit(paletteID)
palette?.hue = value
},
completion: { _ in }
@@ -268,10 +269,11 @@ extension Modern.ColorsDemo.UIKit {
private dynamic func saturationSliderValueDidChange(_ sender: UISlider) {
let value = sender.value
let paletteID = self.palette.object?.persistentID()
Modern.ColorsDemo.dataStack.perform(
asynchronous: { [weak self] (transaction) in
asynchronous: { transaction in
let palette = transaction.edit(self?.palette.object)
let palette = transaction.edit(paletteID)
palette?.saturation = value
},
completion: { _ in }
@@ -283,10 +285,11 @@ extension Modern.ColorsDemo.UIKit {
private dynamic func brightnessSliderValueDidChange(_ sender: UISlider) {
let value = sender.value
let paletteID = self.palette.object?.persistentID()
Modern.ColorsDemo.dataStack.perform(
asynchronous: { [weak self] (transaction) in
asynchronous: { transaction in
let palette = transaction.edit(self?.palette.object)
let palette = transaction.edit(paletteID)
palette?.brightness = value
},
completion: { _ in }
@@ -294,5 +297,3 @@ extension Modern.ColorsDemo.UIKit {
}
}
}
@@ -81,7 +81,7 @@ extension Modern.ColorsDemo.UIKit {
self.dataStack.perform(
asynchronous: { (transaction) in
transaction.delete(objectIDs: [itemID])
transaction.delete(itemID)
},
sourceIdentifier: Modern.ColorsDemo.TransactionSource.delete,
completion: { _ in }
@@ -14,6 +14,7 @@ extension Modern.PlacemarksDemo {
// MARK: - Modern.PlacemarksDemo.MainView
@MainActor
struct MainView: View {
/**
@@ -21,10 +22,11 @@ extension Modern.PlacemarksDemo {
*/
private func demoAsynchronousTransaction(coordinate: CLLocationCoordinate2D) {
let persistentID = self.$place?.persistentID()
Modern.PlacemarksDemo.dataStack.perform(
asynchronous: { (transaction) in
let place = self.$place?.asEditable(in: transaction)
let place = persistentID?.asEditable(in: transaction)
place?.annotation = .init(coordinate: coordinate)
},
completion: { _ in }
@@ -107,7 +109,7 @@ extension Modern.PlacemarksDemo {
return
}
let geocoded = await self.geocoder.geocode(place: place)
guard self.place?.objectID() == place.objectID() else {
guard self.place?.persistentID() == place.persistentID() else {
return
}
@@ -34,7 +34,7 @@ extension Modern.PokedexDemo {
// MARK: ImportableObject
typealias ImportSource = Dictionary<String, Any>
typealias ImportSource = Dictionary<String, any Sendable>
// MARK: ImportableUniqueObject
@@ -11,6 +11,7 @@ extension Modern.PokedexDemo {
// MARK: - Modern.PokedexDemo.MainView
@MainActor
struct MainView<ListView: View>: View {
// MARK: Internal
@@ -36,7 +36,7 @@ extension Modern.PokedexDemo {
// MARK: ImportableObject
typealias ImportSource = (index: Int, json: Dictionary<String, Any>)
typealias ImportSource = (index: Int, json: Dictionary<String, any Sendable>)
func didInsert(from source: ImportSource, in transaction: BaseDataTransaction) throws {
@@ -21,7 +21,7 @@ extension Modern.PokedexDemo {
/**
Sample 1: Importing a list of JSON data into `ImportableUniqueObject`s whose `ImportSource` are tuples
*/
private static func importPokedexEntries(from data: Data) async throws {
private static func importPokedexEntries(from data: Data) async throws(Modern.PokedexDemo.Service.Error) {
do {
@@ -43,7 +43,7 @@ extension Modern.PokedexDemo {
}
catch {
throw self.mapError(error)
throw self.mapError(.init(error))
}
}
@@ -51,31 +51,31 @@ extension Modern.PokedexDemo {
Sample 2: Importing a single JSON data into an `ImportableUniqueObject` whose `ImportSource` is a JSON `Dictionary`
*/
private static func importSpecies(
for detailsObjectID: NSManagedObjectID,
for detailsPersistentID: DynamicObjectID<Modern.PokedexDemo.Details>,
from data: Data
) async throws -> ObjectSnapshot<Modern.PokedexDemo.Species> {
let speciesObjectID = try await Modern.PokedexDemo.dataStack.async.perform { transaction -> NSManagedObjectID in
let speciesPersistentID = try await Modern.PokedexDemo.dataStack.async.perform { transaction in
let json: Dictionary<String, Any> = try self.parseJSON(
try JSONSerialization.jsonObject(with: data, options: [])
)
guard
let species = try transaction.importUniqueObject(
Into<Modern.PokedexDemo.Species>(),
source: json
let json: Dictionary<String, Any> = try self.parseJSON(
try JSONSerialization.jsonObject(with: data, options: [])
)
else {
guard
let species = try transaction.importUniqueObject(
Into<Modern.PokedexDemo.Species>(),
source: .init(json: json)
)
else {
throw Modern.PokedexDemo.Service.Error.unexpected
}
transaction
.edit(Into<Modern.PokedexDemo.Details>(), detailsObjectID)?
.edit(Into<Modern.PokedexDemo.Details>(), detailsPersistentID)?
.species = species
return species.objectID()
return species.persistentID()
}
guard
let species: Modern.PokedexDemo.Species = Modern.PokedexDemo.dataStack.fetchExisting(speciesObjectID),
let species: Modern.PokedexDemo.Species = Modern.PokedexDemo.dataStack.fetchExisting(speciesPersistentID),
let snapshot = species.asSnapshot()
else {
@@ -88,7 +88,7 @@ extension Modern.PokedexDemo {
Sample 3: Importing a list of JSON data into `ImportableUniqueObject`s whose `ImportSource` are JSON `Dictionary`s
*/
private static func importForms(
for detailsObjectID: NSManagedObjectID,
for detailsPersistentID: DynamicObjectID<Modern.PokedexDemo.Details>,
from dataArray: [Data]
) async throws {
@@ -110,13 +110,13 @@ extension Modern.PokedexDemo {
throw Modern.PokedexDemo.Service.Error.unexpected
}
transaction
.edit(Into<Modern.PokedexDemo.Details>(), detailsObjectID)?
.edit(Into<Modern.PokedexDemo.Details>(), detailsPersistentID)?
.forms = forms
}
}
catch {
throw self.mapError(error)
throw self.mapError(.init(error))
}
}
@@ -195,8 +195,8 @@ extension Modern.PokedexDemo {
if let species = details.$species?.snapshot {
self.fetchFormsIfNeeded(
key: species.$id,
detailsObjectID: details.objectID(),
key: String(species.$id),
detailsPersistentID: details.persistentID(),
species: species
)
return
@@ -207,7 +207,7 @@ extension Modern.PokedexDemo {
return
}
let speciesURL = pokedexEntry.$speciesURL
let detailsObjectID = details.objectID()
let detailsPersistentID = details.persistentID()
self.detailTasks[key] = Task { [weak self] in
guard let self else {
@@ -220,7 +220,7 @@ extension Modern.PokedexDemo {
}
await self.fetchSpecies(
key: key,
detailsObjectID: detailsObjectID,
detailsPersistentID: detailsPersistentID,
speciesURL: speciesURL
)
}
@@ -289,7 +289,7 @@ extension Modern.PokedexDemo {
private func fetchSpecies(
key: String,
detailsObjectID: NSManagedObjectID,
detailsPersistentID: DynamicObjectID<Modern.PokedexDemo.Details>,
speciesURL: URL
) async {
@@ -299,7 +299,7 @@ extension Modern.PokedexDemo {
try Task.checkCancellation()
let species = try await Self.importSpecies(
for: detailsObjectID,
for: detailsPersistentID,
from: data
)
guard species.$details?.snapshot?.$forms.isEmpty == true else {
@@ -307,7 +307,7 @@ extension Modern.PokedexDemo {
return
}
await self.fetchForms(
detailsObjectID: detailsObjectID,
detailsPersistentID: detailsPersistentID,
formsURLs: species.$formsURLs
)
}
@@ -331,7 +331,7 @@ extension Modern.PokedexDemo {
private func fetchFormsIfNeeded(
key: String,
detailsObjectID: NSManagedObjectID,
detailsPersistentID: DynamicObjectID<Modern.PokedexDemo.Details>,
species: ObjectSnapshot<Modern.PokedexDemo.Species>
) {
@@ -356,14 +356,14 @@ extension Modern.PokedexDemo {
self.detailTasks.removeValue(forKey: key)
}
await self.fetchForms(
detailsObjectID: detailsObjectID,
detailsPersistentID: detailsPersistentID,
formsURLs: formsURLs
)
}
}
private func fetchForms(
detailsObjectID: NSManagedObjectID,
detailsPersistentID: DynamicObjectID<Modern.PokedexDemo.Details>,
formsURLs: [URL]
) async {
@@ -378,7 +378,7 @@ extension Modern.PokedexDemo {
dataArray.append(data)
}
try await Self.importForms(
for: detailsObjectID,
for: detailsPersistentID,
from: dataArray
)
}
@@ -408,7 +408,7 @@ extension Modern.PokedexDemo {
case networkError(URLError)
case parseError(expected: Any.Type, actual: Any.Type, file: String)
case saveError(CoreStoreError)
case otherError(Swift.Error)
case otherError(Swift::Error)
case unexpected
}
}
@@ -65,7 +65,10 @@ extension Modern.PokedexDemo {
// MARK: ImportableObject
typealias ImportSource = Dictionary<String, Any>
struct ImportSource: @unchecked Sendable {
let json: Dictionary<String, Any>
}
// MARK: ImportableUniqueObject
@@ -82,14 +85,14 @@ extension Modern.PokedexDemo {
static func uniqueID(from source: ImportSource, in transaction: BaseDataTransaction) throws -> UniqueIDType? {
let json = source
let json = source.json
return try Modern.PokedexDemo.Service.parseJSON(json["id"])
}
func update(from source: ImportSource, in transaction: BaseDataTransaction) throws {
typealias Service = Modern.PokedexDemo.Service
let json = source
let json = source.json
self.name = try Service.parseJSON(json["name"])
self.weight = try Service.parseJSON(json["weight"])
@@ -50,7 +50,7 @@ extension Modern.PokedexDemo.UIKit {
fatalError()
}
deinit {
isolated deinit {
self.listPublisher.removeObserver(self)
}
@@ -94,13 +94,25 @@ extension Modern.PokedexDemo.UIKit {
private func startObservingList() {
self.listPublisher.addObserver(self) { (listPublisher) in
self.dataSource.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
self.listPublisher.addObserver(
self,
notifyInitial: false,
{ [weak self] (listPublisher) in
let snapshot = listPublisher.snapshot
withMainActorImmediate {
guard let self else {
return
}
self.dataSource.apply(
snapshot,
animatingDifferences: true
)
}
}
)
self.dataSource.apply(
self.listPublisher.snapshot,
animatingDifferences: false
+57 -39
View File
@@ -13,52 +13,70 @@ extension Menu {
// MARK: - Menu.MainView
struct MainView: View {
@State
private var selection: Menu.Route?
@Environment(\.horizontalSizeClass)
private var horizontalSizeClass
// MARK: View
@ViewBuilder
var body: some View {
NavigationSplitView(
sidebar: {
List(selection: self.$selection) {
ForEach(Menu.Section.allCases, id: \.self) { section in
Section(section.rawValue) {
ForEach(section.routes) { route in
Menu.ItemView(
title: route.title,
subtitle: route.subtitle,
isEnabled: route.isEnabled
)
.tag(route as Menu.Route?)
.disabled(!route.isEnabled)
}
}
}
}
.navigationTitle("CoreStore Demos")
.listStyle(.sidebar)
},
detail: {
if let selection = self.selection {
selection.destination
}
else {
if self.horizontalSizeClass == .compact {
NavigationStack {
self.menuList
}
}
else {
NavigationSplitView(
sidebar: {
self.menuList
},
detail: {
Menu.PlaceholderView()
}
)
}
}
// MARK: Private
@ViewBuilder
private var menuList: some View {
List {
ForEach(Menu.Section.allCases, id: \.self) { section in
SwiftUI.Section(
content: {
ForEach(section.routes) { route in
NavigationLink(
destination: {
route.destination
},
label: {
Menu.ItemView(
title: route.title,
subtitle: route.subtitle,
isEnabled: route.isEnabled
)
}
)
.disabled(!route.isEnabled)
}
},
header: {
Text(section.rawValue)
}
)
}
)
}
.navigationTitle("CoreStore Demos")
.listStyle(.sidebar)
}
}
}
@@ -0,0 +1,31 @@
//
// WithMainActorImmediate.swift
// Demo
//
// Created by John Estropia on 2026/07/21.
//
import Foundation
// MARK: - withMainActorImmediate
func withMainActorImmediate(
_ task: @MainActor @Sendable @escaping () -> Void
) {
if #available(iOS 26.0, *) {
Task.immediate(operation: task)
}
else if Thread.isMainThread {
MainActor.assumeIsolated {
task()
}
}
else {
Task.init(operation: task)
}
}