This commit is contained in:
John Estropia committed 2026-10-02 15:12:03 +09:00
1 parent 838637f29f
commit 10ac228867
8 files changed
+84 -40

No files matched your search

+36 -1
View File
@@ -33,7 +33,42 @@ import CoreStore
// MARK: - ConvenienceTests // MARK: - ConvenienceTests
class ConvenienceTests: BaseTestCase { class ConvenienceTests: BaseTestCase {
@objc
dynamic func test_ThatUserInfoLazyInitializer_CanReadAnotherKey() {
let keys = UnsafeMutableRawPointer.allocate(byteCount: 2, alignment: 1)
defer { keys.deallocate() }
let userInfo = UserInfo()
let value = userInfo[UnsafeRawPointer(keys), lazyInit: {
let nested = userInfo[
UnsafeRawPointer(keys.advanced(by: 1)),
lazyInit: { "nested" }
] as! String
return "outer \(nested)"
}]
XCTAssertEqual(value as? String, "outer nested")
}
func test_ThatUndoManagerIsReadyWhenUnsafeTransactionStartsOffMain() async {
let hasUndoManager = await Task.detached {
let parent = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
let transaction = UnsafeDataTransaction(
mainContext: parent,
queue: DispatchQueue(label: "com.corestore.tests.unsafeTransactionQueue"),
supportsUndo: true,
sourceIdentifier: nil
)
let context = transaction.context
return context.performAndWait { context.undoManager != nil }
}.value
XCTAssertTrue(hasUndoManager)
}
@objc @objc
@MainActor @MainActor
dynamic func test_ThatDataStacks_CanCreateFetchedResultsControllers() { dynamic func test_ThatDataStacks_CanCreateFetchedResultsControllers() {
@@ -52,21 +52,22 @@ extension Classic.ColorsDemo {
.toolbar { .toolbar {
ToolbarItemGroup(placement: .topBarLeading) { ToolbarItemGroup(placement: .topBarLeading) {
EditButton() EditButton()
Button("Clear") { .labelStyle(.iconOnly)
Button("Clear", systemImage: "trash") {
self.clearColors() self.clearColors()
} }
} }
ToolbarItemGroup(placement: .topBarTrailing) { ToolbarItemGroup(placement: .topBarTrailing) {
Button(self.filter.rawValue) { Button(self.filter.rawValue, systemImage: "line.3.horizontal.decrease.circle") {
self.changeFilter() self.changeFilter()
} }
Button("Shuffle") { Button("Shuffle", systemImage: "shuffle") {
self.shuffleColors() self.shuffleColors()
} }
Button("Add") { Button("Add", systemImage: "plus") {
self.addColor() self.addColor()
} }
@@ -55,7 +55,8 @@ extension Modern.ColorsDemo {
ToolbarItemGroup(placement: .topBarLeading) { ToolbarItemGroup(placement: .topBarLeading) {
EditButton() EditButton()
Button("Clear") { .labelStyle(.iconOnly)
Button("Clear", systemImage: "trash") {
self.clearColors() self.clearColors()
} }
@@ -63,15 +64,15 @@ extension Modern.ColorsDemo {
ToolbarItemGroup(placement: .topBarTrailing) { ToolbarItemGroup(placement: .topBarTrailing) {
Button(self.filter.rawValue) { Button(self.filter.rawValue, systemImage: "line.3.horizontal.decrease.circle") {
self.changeFilter() self.changeFilter()
} }
Button("Shuffle") { Button("Shuffle", systemImage: "shuffle") {
self.shuffleColors() self.shuffleColors()
} }
Button("Add") { Button("Add", systemImage: "plus") {
self.addColor() self.addColor()
} }
@@ -109,7 +109,12 @@ extension Modern.PlacemarksDemo {
return return
} }
let geocoded = await self.geocoder.geocode(place: place) let geocoded = await self.geocoder.geocode(place: place)
guard self.place?.persistentID() == place.persistentID() else { guard
!Task.isCancelled,
self.place?.persistentID() == place.persistentID(),
self.place?.$latitude == place.$latitude,
self.place?.$longitude == place.$longitude
else {
return return
} }
@@ -126,7 +131,7 @@ extension Modern.PlacemarksDemo {
.navigationTitle("Placemarks") .navigationTitle("Placemarks")
.toolbar { .toolbar {
Button("Random") { Button("Random", systemImage: "dice") {
self.demoSynchronousTransaction() self.demoSynchronousTransaction()
} }
@@ -195,7 +195,7 @@ extension Modern.PokedexDemo {
if let species = details.$species?.snapshot { if let species = details.$species?.snapshot {
self.fetchFormsIfNeeded( self.fetchFormsIfNeeded(
key: String(species.$id), key: pokedexEntry.$id,
detailsPersistentID: details.persistentID(), detailsPersistentID: details.persistentID(),
species: species species: species
) )
@@ -219,7 +219,6 @@ extension Modern.PokedexDemo {
self.detailTasks.removeValue(forKey: key) self.detailTasks.removeValue(forKey: key)
} }
await self.fetchSpecies( await self.fetchSpecies(
key: key,
detailsPersistentID: detailsPersistentID, detailsPersistentID: detailsPersistentID,
speciesURL: speciesURL speciesURL: speciesURL
) )
@@ -288,7 +287,6 @@ extension Modern.PokedexDemo {
} }
private func fetchSpecies( private func fetchSpecies(
key: String,
detailsPersistentID: PersistentID<Modern.PokedexDemo.Details>, detailsPersistentID: PersistentID<Modern.PokedexDemo.Details>,
speciesURL: URL speciesURL: URL
) async { ) async {
+11 -4
View File
@@ -554,11 +554,18 @@ public /*abstract*/ class BaseDataTransaction {
context.undoManager = nil context.undoManager = nil
} }
else if context.undoManager == nil { else if context.undoManager == nil {
let undoManager: UndoManager
Internals.mainActorImmediate { if Thread.isMainThread {
context.undoManager = UndoManager() undoManager = MainActor.assumeIsolated { UndoManager() }
} }
else {
undoManager = DispatchQueue.main.sync {
MainActor.assumeIsolated { UndoManager() }
}
}
context.undoManager = undoManager
} }
} }
+3 -4
View File
@@ -38,7 +38,7 @@ extension ListSnapshot {
public let sectionID: SectionID public let sectionID: SectionID
public let itemIDs: [NSManagedObjectID] public let itemIDs: [ItemID]
// MARK: RandomAccessCollection // MARK: RandomAccessCollection
@@ -79,7 +79,7 @@ extension ListSnapshot {
public subscript(position: Int) -> ObjectPublisher<O> { public subscript(position: Int) -> ObjectPublisher<O> {
let itemID = self.itemIDs[position] let itemID = self.itemIDs[position]
return self.context.objectPublisher(managedObjectID: itemID) return self.context.objectPublisher(managedObjectID: itemID.managedObjectID)
} }
public func index(_ i: Index, offsetBy distance: Int) -> Index { public func index(_ i: Index, offsetBy distance: Int) -> Index {
@@ -100,7 +100,7 @@ extension ListSnapshot {
public subscript(bounds: Range<Index>) -> ArraySlice<Element> { public subscript(bounds: Range<Index>) -> ArraySlice<Element> {
let itemIDs = self.itemIDs[bounds] let itemIDs = self.itemIDs[bounds]
return ArraySlice(itemIDs.map(self.context.objectPublisher(managedObjectID:))) return ArraySlice(itemIDs.map({ self.context.objectPublisher(managedObjectID: $0.managedObjectID) }))
} }
@@ -126,7 +126,6 @@ extension ListSnapshot {
} }
self.sectionID = sectionID self.sectionID = sectionID
self.itemIDs = listSnapshot.itemIDs(inSectionWithID: sectionID) self.itemIDs = listSnapshot.itemIDs(inSectionWithID: sectionID)
.map({ $0.managedObjectID })
self.context = context self.context = context
} }
} }
+16 -18
View File
@@ -38,7 +38,7 @@ import Foundation
``` ```
- Important: Do not use this class to store thread-sensitive data. - Important: Do not use this class to store thread-sensitive data.
*/ */
public final class UserInfo: Sendable { public final class UserInfo: @unchecked Sendable {
/** /**
Allows external libraries to store custom data. App code should rarely have a need for this. Allows external libraries to store custom data. App code should rarely have a need for this.
@@ -55,17 +55,15 @@ public final class UserInfo: Sendable {
get { get {
return self.data.withLock { (info: inout _) in self.lock.lock()
defer { self.lock.unlock() }
return info[key] return self.data[key]
}
} }
set { set {
return self.data.withLock { (info: inout _) in self.lock.lock()
defer { self.lock.unlock() }
info[key] = newValue self.data[key] = newValue
}
} }
} }
@@ -84,16 +82,15 @@ public final class UserInfo: Sendable {
*/ */
public subscript(key: UnsafeRawPointer, lazyInit closure: () -> any Sendable) -> any Sendable { public subscript(key: UnsafeRawPointer, lazyInit closure: () -> any Sendable) -> any Sendable {
return self.data.withLock { (info: inout _) in self.lock.lock()
defer { self.lock.unlock() }
if let value = info[key] { if let value = self.data[key] {
return value
}
let value = closure()
info[key] = value
return value return value
} }
let value = closure()
self.data[key] = value
return value
} }
@@ -104,5 +101,6 @@ public final class UserInfo: Sendable {
// MARK: Private // MARK: Private
private let data: Internals.Mutex<[UnsafeRawPointer: any Sendable]> = .init([:]) private var data: [UnsafeRawPointer: any Sendable] = [:]
private let lock = NSRecursiveLock()
} }