Compare commits

...

31 Commits

Author SHA1 Message Date
John Estropia
bf10f4668c revert Objc method changes 2021-09-22 15:03:46 +09:00
John Estropia
a1a04aaf8a tag refetch methods with source identifiers 2021-09-15 19:38:59 +09:00
John Estropia
4ddfa95140 added mechanism to track transaction sources 2021-09-15 14:45:13 +09:00
John Estropia
45215c7a18 WIP 2021-08-25 20:13:14 +09:00
John Estropia
d2f1656fdd test copy-on-write for ListSnapshot 2021-08-25 09:37:20 +09:00
John Estropia
5fbb1ab09d Merge pull request #421 from xquezme/develop
Fix compound indexes for dynamic models
2021-07-20 09:26:17 +09:00
John Estropia
d0cc01877e Merge pull request #433 from tackgyu/unset-library-type
Unset library type in Swift package manifests
2021-07-19 13:35:39 +09:00
tackgyu
a434628249 Unset library type in Swift package manifests 2021-07-19 12:52:51 +09:00
John Estropia
9cc616f720 fix compiler error on Xcode 13 2021-06-19 15:49:45 +09:00
John Estropia
4534bc06f5 Add utility for DiffableDataSources to create an empty snapshot for custom list construction 2021-06-16 23:28:46 +09:00
John Estropia
798d30bbd9 allow ListPublisher and ObjectPublisher combine Publishers to cancel subscription from any thread 2021-06-13 14:36:22 +09:00
John Estropia
7938aa2447 Added fast index-based ListSnapshot mutators 2021-06-12 11:06:43 +09:00
John Estropia
dda69389eb Fix SPM issues 2021-04-20 18:30:14 +09:00
John Estropia
b20be10a19 update Readme banner image 2021-04-11 11:39:43 +09:00
John Estropia
0ee8fbabd5 version bump, update resources 2021-04-11 11:26:02 +09:00
John Estropia
1f562b25a7 Updated README 2021-04-11 11:03:17 +09:00
Pimenov Sergey
6a2394052c fix compound indexes for dynamic models 2021-04-04 20:12:40 -07:00
John Estropia
593c0510d3 Allow placeholder Views in ObjectReader when an object becomes nil 2021-03-13 11:25:27 +09:00
John Estropia
338e4ddc9f Fix sig abort error 2021-03-07 17:02:09 +09:00
John Estropia
bfb1df3c40 added Publishers for addStorage functions 2021-03-07 16:08:19 +09:00
John Estropia
003bf897e2 Cleanup unnecessary #available branches 2021-03-07 12:01:08 +09:00
John Estropia
0b127956d3 Removed ObservableObject implementations of ListPublisher/ObjectPublisher in favor of LiveList/LiveObject and ".reactive" publishers 2021-03-07 10:38:53 +09:00
John Estropia
098e560fcc Merge branch 'develop' of github.com:JohnEstropia/CoreStore into develop 2021-03-07 10:17:34 +09:00
John Estropia
7dbd3777ec added sugar syntax for ForEach that removes requirement for the "id:" argument 2021-03-07 10:17:20 +09:00
John Estropia
447d8e5880 fix compile error 2021-03-03 16:59:20 +09:00
John Estropia
1f97225efa SwiftUI and Combine utilities appledocs. Bump up to 8.0 and iOS 11.0/macOS 10.13 2021-03-01 09:14:41 +09:00
John Estropia
d13b0cfabb update unit tests 2021-02-21 10:59:55 +09:00
John Estropia
d7b852fca4 SwiftUI utilities done (for now) 2021-02-21 10:56:27 +09:00
John Estropia
f2efe175e5 deprecate misleading API for sectionIndexTransformers 2021-02-21 10:17:58 +09:00
John Estropia
8f3a6638d8 Delete long-deprecated CoreStore namespace 2021-02-21 10:16:08 +09:00
John Estropia
f7471f56a4 Prototyping SwiftUI utilities 2021-02-16 09:12:36 +09:00
118 changed files with 6024 additions and 3377 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "CoreStore"
s.version = "7.3.1"
s.swift_version = "5.3"
s.version = "8.0.1"
s.swift_version = "5.4"
s.license = "MIT"
s.homepage = "https://github.com/JohnEstropia/CoreStore"
s.documentation_url = "https://JohnEstropia.github.io/CoreStore"
@@ -9,10 +9,10 @@ Pod::Spec.new do |s|
s.author = { "John Rommel Estropia" => "rommel.estropia@gmail.com" }
s.source = { :git => "https://github.com/JohnEstropia/CoreStore.git", :tag => s.version.to_s }
s.ios.deployment_target = "10.0"
s.osx.deployment_target = "10.12"
s.watchos.deployment_target = "3.0"
s.tvos.deployment_target = "10.0"
s.ios.deployment_target = "11.0"
s.osx.deployment_target = "10.13"
s.watchos.deployment_target = "4.0"
s.tvos.deployment_target = "11.0"
s.source_files = "Sources", "Sources/**/*.{swift,h,m}"
s.public_header_files = "Sources/**/*.h"

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -191,16 +191,8 @@
XCTAssertEqualObjects([[sqliteStorage class] storeType], [CSSQLiteStore storeType]);
XCTAssertEqualObjects([[sqliteStorage class] storeType], NSSQLiteStoreType);
XCTAssertNil(sqliteStorage.configuration);
NSDictionary *storeOptions;
if (@available(iOS 11.0, macOS 10.13, tvOS 11.0, *)) {
storeOptions = @{ NSSQLitePragmasOption: @{ @"journal_mode": @"WAL" },
NSBinaryStoreInsecureDecodingCompatibilityOption: @YES };
}
else {
storeOptions = @{ NSSQLitePragmasOption: @{ @"journal_mode": @"WAL" }};
}
NSDictionary *storeOptions = @{ NSSQLitePragmasOption: @{ @"journal_mode": @"WAL" },
NSBinaryStoreInsecureDecodingCompatibilityOption: @YES };
XCTAssertEqualObjects(sqliteStorage.storeOptions, storeOptions);
XCTAssertNil(sqliteError);
}

View File

@@ -23,13 +23,15 @@
// SOFTWARE.
//
import CoreData
import XCTest
@testable
import CoreStore
// MARK: - ConvenienceTests
@available(macOS 10.12, *)
class ConvenienceTests: BaseTestCase {
@objc

View File

@@ -197,7 +197,7 @@ class DynamicModelTests: BaseTestDataTestCase {
modelVersion: "V1",
entities: [
Entity<Animal>("Animal"),
Entity<Dog>("Dog"),
Entity<Dog>("Dog", indexes: [[\Dog.$nickname, \Dog.$age]]),
Entity<Person>("Person")
],
versionLock: [

View File

@@ -23,6 +23,7 @@
// SOFTWARE.
//
import CoreData
import XCTest
@testable

View File

@@ -23,6 +23,7 @@
// SOFTWARE.
//
import CoreData
import XCTest
@testable

View File

@@ -23,6 +23,7 @@
// SOFTWARE.
//
import CoreData
import XCTest
@testable

View File

@@ -23,6 +23,7 @@
// SOFTWARE.
//
import CoreData
import XCTest
@testable
@@ -31,7 +32,6 @@ import CoreStore
// MARK: - ListObserverTests
@available(macOS 10.12, *)
class ListObserverTests: BaseTestDataTestCase {
@objc
@@ -551,7 +551,6 @@ class ListObserverTests: BaseTestDataTestCase {
// MARK: TestListObserver
@available(macOS 10.12, *)
class TestListObserver: ListSectionObserver {
// MARK: ListObserver

View File

@@ -31,7 +31,6 @@ import CoreStore
// MARK: - ObjectObserverTests
@available(macOS 10.12, *)
class ObjectObserverTests: BaseTestDataTestCase {
@objc
@@ -203,7 +202,6 @@ class ObjectObserverTests: BaseTestDataTestCase {
// MARK: TestObjectObserver
@available(macOS 10.12, *)
class TestObjectObserver: ObjectObserver {
typealias ObjectEntityType = TestEntity1

View File

@@ -31,7 +31,6 @@ import CoreStore
// MARK: - ObjectPublisherTests
@available(macOS 10.12, *)
class ObjectPublisherTests: BaseTestDataTestCase {
@objc

View File

@@ -23,6 +23,7 @@
// SOFTWARE.
//
import CoreData
import XCTest
@testable

View File

@@ -23,6 +23,7 @@
// SOFTWARE.
//
import CoreData
import XCTest
@testable

View File

@@ -23,6 +23,7 @@
// SOFTWARE.
//
import CoreData
import XCTest
@testable
@@ -31,7 +32,6 @@ import CoreStore
//MARK: - SectionByTests
@available(macOS 10.12, *)
final class SectionByTests: XCTestCase {
@objc
@@ -41,11 +41,14 @@ final class SectionByTests: XCTestCase {
let sectionBy = SectionBy<NSManagedObject>("key")
XCTAssertEqual(sectionBy.sectionKeyPath, "key")
XCTAssertEqual(sectionBy.sectionIndexTransformer("key"), "key")
XCTAssertNil(sectionBy.sectionIndexTransformer("key"))
}
do {
let sectionBy = SectionBy<NSManagedObject>("key") { $0.flatMap { "\($0):suffix" } }
let sectionBy = SectionBy<NSManagedObject>(
"key",
sectionIndexTransformer: { $0.flatMap { "\($0):suffix" } }
)
XCTAssertEqual(sectionBy.sectionKeyPath, "key")
XCTAssertEqual(sectionBy.sectionIndexTransformer("key"), "key:suffix")
XCTAssertNil(sectionBy.sectionIndexTransformer(nil))

View File

@@ -23,6 +23,7 @@
// SOFTWARE.
//
import CoreData
import XCTest
@testable

View File

@@ -23,6 +23,9 @@
// SOFTWARE.
//
import CoreData
import XCTest
@testable
import CoreStore

View File

@@ -23,6 +23,7 @@
// SOFTWARE.
//
import CoreData
import XCTest
@testable
@@ -83,21 +84,11 @@ final class StorageInterfaceTests: XCTestCase {
let store = SQLiteStore()
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
XCTAssertNil(store.configuration)
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, *) {
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
)
}
else {
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary
)
}
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
)
XCTAssertEqual(store.fileURL, SQLiteStore.defaultFileURL)
XCTAssertTrue(store.migrationMappingProviders.isEmpty)
@@ -123,21 +114,11 @@ final class StorageInterfaceTests: XCTestCase {
)
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
XCTAssertEqual(store.configuration, "config1")
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, *) {
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
)
}
else {
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary
)
}
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
)
XCTAssertEqual(store.fileURL, fileURL)
XCTAssertEqual(store.migrationMappingProviders as! [XcodeSchemaMappingProvider], [mappingProvider])
@@ -160,21 +141,11 @@ final class StorageInterfaceTests: XCTestCase {
)
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
XCTAssertEqual(store.configuration, "config1")
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, *) {
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
)
}
else {
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary
)
}
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
)
XCTAssertEqual(store.fileURL.deletingLastPathComponent(), SQLiteStore.defaultRootDirectory)
XCTAssertEqual(store.fileURL.lastPathComponent, fileName)
@@ -209,21 +180,11 @@ final class StorageInterfaceTests: XCTestCase {
let store = SQLiteStore.legacy()
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
XCTAssertNil(store.configuration)
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, *) {
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
)
}
else {
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary
)
}
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
)
XCTAssertEqual(store.fileURL, SQLiteStore.legacyDefaultFileURL)
XCTAssertTrue(store.migrationMappingProviders.isEmpty)
@@ -246,21 +207,11 @@ final class StorageInterfaceTests: XCTestCase {
)
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
XCTAssertEqual(store.configuration, "config1")
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, *) {
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
)
}
else {
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary
)
}
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"],
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
)
XCTAssertEqual(store.fileURL.deletingLastPathComponent(), SQLiteStore.legacyDefaultRootDirectory)
XCTAssertEqual(store.fileURL.lastPathComponent, fileName)

View File

@@ -383,8 +383,6 @@ final class TransactionTests: BaseTestCase {
}
}
@available(macOS 10.12, *)
@objc
dynamic func test_ThatSynchronousTransactions_CanCommitWithoutWaitingForMerges() {

View File

@@ -23,6 +23,7 @@
// SOFTWARE.
//
import CoreData
import XCTest
@testable

View File

@@ -23,6 +23,7 @@
// SOFTWARE.
//
import CoreData
import XCTest
@testable

View File

@@ -15,9 +15,9 @@
B54D2F7C251196B6004BEC7D /* Advanced.EvolutionDemo.V2.FromV1MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54D2F7B251196B6004BEC7D /* Advanced.EvolutionDemo.V2.FromV1MigrationPolicy.swift */; };
B54D2F7E25119DCE004BEC7D /* Advanced.EvolutionDemo.V1.FromV2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = B54D2F7D25119DCE004BEC7D /* Advanced.EvolutionDemo.V1.FromV2.xcmappingmodel */; };
B54D2F852511B70B004BEC7D /* Advanced.EvolutionDemo.CreaturesDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54D2F842511B70B004BEC7D /* Advanced.EvolutionDemo.CreaturesDataSource.swift */; };
B566C8E824F9D406001134A1 /* Modern.PokedexDemo.ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8E724F9D406001134A1 /* Modern.PokedexDemo.ListView.swift */; };
B566C8EA24F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8E924F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift */; };
B566C8EC24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8EB24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift */; };
B566C8E824F9D406001134A1 /* Modern.PokedexDemo.UIKit.ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8E724F9D406001134A1 /* Modern.PokedexDemo.UIKit.ListView.swift */; };
B566C8EA24F9D412001134A1 /* Modern.PokedexDemo.UIKit.ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8E924F9D412001134A1 /* Modern.PokedexDemo.UIKit.ListViewController.swift */; };
B566C8EC24F9D694001134A1 /* Modern.PokedexDemo.UIKit.ItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8EB24F9D694001134A1 /* Modern.PokedexDemo.UIKit.ItemCell.swift */; };
B566C8EE24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566C8ED24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift */; };
B5931B4225124756007DA58E /* Advanced.EvolutionDemo.V1.FromV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5931B4125124756007DA58E /* Advanced.EvolutionDemo.V1.FromV2.swift */; };
B5931B442512480A007DA58E /* Advanced.EvolutionDemo.V2.FromV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5931B432512480A007DA58E /* Advanced.EvolutionDemo.V2.FromV1.swift */; };
@@ -68,7 +68,7 @@
B5A391A024E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A3919F24E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift */; };
B5A391A224E8F01F00E7E8BD /* ⭐Modern.ColorsDemo.UIKit.ListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A124E8F01F00E7E8BD /* ⭐Modern.ColorsDemo.UIKit.ListViewController.swift */; };
B5A391A424E8F04300E7E8BD /* Modern.ColorsDemo.UIKit.ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A324E8F04300E7E8BD /* Modern.ColorsDemo.UIKit.ListView.swift */; };
B5A391A624E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A524E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift */; };
B5A391A624E8F4EA00E7E8BD /* Modern.ColorsDemo.MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A524E8F4EA00E7E8BD /* Modern.ColorsDemo.MainView.swift */; };
B5A391AA24E9104300E7E8BD /* Modern.ColorsDemo.UIKit.ItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391A924E9104300E7E8BD /* Modern.ColorsDemo.UIKit.ItemCell.swift */; };
B5A391AC24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391AB24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift */; };
B5A391AE24E9150F00E7E8BD /* ⭐Modern.ColorsDemo.UIKit.DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A391AD24E9150F00E7E8BD /* ⭐Modern.ColorsDemo.UIKit.DetailViewController.swift */; };
@@ -97,6 +97,7 @@
B5A5440B25049492000DC5E3 /* Advanced.EvolutionDemo.V3.Creature.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A5440A25049492000DC5E3 /* Advanced.EvolutionDemo.V3.Creature.swift */; };
B5A5440D2504949C000DC5E3 /* Advanced.EvolutionDemo.V4.Creature.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A5440C2504949C000DC5E3 /* Advanced.EvolutionDemo.V4.Creature.swift */; };
B5C18F3325138700001BEFB3 /* Advanced.EvolutionDemo.ProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C18F3225138700001BEFB3 /* Advanced.EvolutionDemo.ProgressView.swift */; };
B5C795B625D8EE2B00BDACC1 /* Modern.PokedexDemo.UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C795B525D8EE2B00BDACC1 /* Modern.PokedexDemo.UIKit.swift */; };
B5D6F1F8250E07FD00DF5D2F /* Advanced.EvolutionDemo.V1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D6F1F7250E07FD00DF5D2F /* Advanced.EvolutionDemo.V1.swift */; };
B5D6F1FC250E0B1700DF5D2F /* Advanced.EvolutionDemo.V2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D6F1FB250E0B1700DF5D2F /* Advanced.EvolutionDemo.V2.swift */; };
B5D6F1FE250E0B3F00DF5D2F /* Advanced.EvolutionDemo.GeologicalPeriod.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D6F1FD250E0B3F00DF5D2F /* Advanced.EvolutionDemo.GeologicalPeriod.swift */; };
@@ -133,9 +134,9 @@
B54D2F7B251196B6004BEC7D /* Advanced.EvolutionDemo.V2.FromV1MigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V2.FromV1MigrationPolicy.swift; sourceTree = "<group>"; };
B54D2F7D25119DCE004BEC7D /* Advanced.EvolutionDemo.V1.FromV2.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = Advanced.EvolutionDemo.V1.FromV2.xcmappingmodel; sourceTree = "<group>"; };
B54D2F842511B70B004BEC7D /* Advanced.EvolutionDemo.CreaturesDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.CreaturesDataSource.swift; sourceTree = "<group>"; };
B566C8E724F9D406001134A1 /* Modern.PokedexDemo.ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.ListView.swift; sourceTree = "<group>"; };
B566C8E924F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.ListViewController.swift; sourceTree = "<group>"; };
B566C8EB24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.ItemCell.swift; sourceTree = "<group>"; };
B566C8E724F9D406001134A1 /* Modern.PokedexDemo.UIKit.ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.UIKit.ListView.swift; sourceTree = "<group>"; };
B566C8E924F9D412001134A1 /* Modern.PokedexDemo.UIKit.ListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.UIKit.ListViewController.swift; sourceTree = "<group>"; };
B566C8EB24F9D694001134A1 /* Modern.PokedexDemo.UIKit.ItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.UIKit.ItemCell.swift; sourceTree = "<group>"; };
B566C8ED24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.Details.swift; sourceTree = "<group>"; };
B5931B4125124756007DA58E /* Advanced.EvolutionDemo.V1.FromV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V1.FromV2.swift; sourceTree = "<group>"; };
B5931B432512480A007DA58E /* Advanced.EvolutionDemo.V2.FromV1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V2.FromV1.swift; sourceTree = "<group>"; };
@@ -185,7 +186,7 @@
B5A3919F24E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.UIKit.swift; sourceTree = "<group>"; };
B5A391A124E8F01F00E7E8BD /* ⭐Modern.ColorsDemo.UIKit.ListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐Modern.ColorsDemo.UIKit.ListViewController.swift"; sourceTree = "<group>"; };
B5A391A324E8F04300E7E8BD /* Modern.ColorsDemo.UIKit.ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.UIKit.ListView.swift; sourceTree = "<group>"; };
B5A391A524E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐️Modern.ColorsDemo.MainView.swift"; sourceTree = "<group>"; };
B5A391A524E8F4EA00E7E8BD /* Modern.ColorsDemo.MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.MainView.swift; sourceTree = "<group>"; };
B5A391A924E9104300E7E8BD /* Modern.ColorsDemo.UIKit.ItemCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.UIKit.ItemCell.swift; sourceTree = "<group>"; };
B5A391AB24E9143B00E7E8BD /* Modern.ColorsDemo.UIKit.DetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.ColorsDemo.UIKit.DetailView.swift; sourceTree = "<group>"; };
B5A391AD24E9150F00E7E8BD /* ⭐Modern.ColorsDemo.UIKit.DetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "⭐Modern.ColorsDemo.UIKit.DetailViewController.swift"; sourceTree = "<group>"; };
@@ -214,6 +215,7 @@
B5A5440A25049492000DC5E3 /* Advanced.EvolutionDemo.V3.Creature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V3.Creature.swift; sourceTree = "<group>"; };
B5A5440C2504949C000DC5E3 /* Advanced.EvolutionDemo.V4.Creature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V4.Creature.swift; sourceTree = "<group>"; };
B5C18F3225138700001BEFB3 /* Advanced.EvolutionDemo.ProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.ProgressView.swift; sourceTree = "<group>"; };
B5C795B525D8EE2B00BDACC1 /* Modern.PokedexDemo.UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Modern.PokedexDemo.UIKit.swift; sourceTree = "<group>"; };
B5D6F1F7250E07FD00DF5D2F /* Advanced.EvolutionDemo.V1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V1.swift; sourceTree = "<group>"; };
B5D6F1FB250E0B1700DF5D2F /* Advanced.EvolutionDemo.V2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.V2.swift; sourceTree = "<group>"; };
B5D6F1FD250E0B3F00DF5D2F /* Advanced.EvolutionDemo.GeologicalPeriod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Advanced.EvolutionDemo.GeologicalPeriod.swift; sourceTree = "<group>"; };
@@ -412,7 +414,7 @@
children = (
B5A3918E24E7E06500E7E8BD /* Modern.ColorsDemo.swift */,
B5A3919724E7E67000E7E8BD /* Modern.ColorsDemo.Filter.swift */,
B5A391A524E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift */,
B5A391A524E8F4EA00E7E8BD /* Modern.ColorsDemo.MainView.swift */,
B5A3919C24E8EE9000E7E8BD /* ⭐UIKit */,
B5A3919B24E8EE8100E7E8BD /* ⭐SwiftUI */,
B5A3919024E7E0B000E7E8BD /* Models */,
@@ -458,9 +460,7 @@
B5A391B024E96AF600E7E8BD /* Modern.PokedexDemo.swift */,
B531EFEA24EB5ECD005F247D /* ⭐Modern.PokedexDemo.Service.swift */,
B531EFEC24EB7453005F247D /* Modern.PokedexDemo.MainView.swift */,
B566C8E724F9D406001134A1 /* Modern.PokedexDemo.ListView.swift */,
B566C8E924F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift */,
B566C8EB24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift */,
B5C795B425D8EE1A00BDACC1 /* ⭐UIKit */,
B5A391B224E96B7400E7E8BD /* ⭐Models */,
);
path = "⭐PokedexDemo";
@@ -549,6 +549,17 @@
name = Models;
sourceTree = "<group>";
};
B5C795B425D8EE1A00BDACC1 /* ⭐UIKit */ = {
isa = PBXGroup;
children = (
B5C795B525D8EE2B00BDACC1 /* Modern.PokedexDemo.UIKit.swift */,
B566C8E724F9D406001134A1 /* Modern.PokedexDemo.UIKit.ListView.swift */,
B566C8E924F9D412001134A1 /* Modern.PokedexDemo.UIKit.ListViewController.swift */,
B566C8EB24F9D694001134A1 /* Modern.PokedexDemo.UIKit.ItemCell.swift */,
);
name = "⭐UIKit";
sourceTree = "<group>";
};
B5D6F1F9250E08D200DF5D2F /* V1 */ = {
isa = PBXGroup;
children = (
@@ -704,6 +715,7 @@
B5A543ED24FB84BE000DC5E3 /* Classic.ColorsDemo.MainView.swift in Sources */,
B5A543DD24FB78F9000DC5E3 /* Classic.ColorsDemo.Palette.swift in Sources */,
B5A3919824E7E67000E7E8BD /* Modern.ColorsDemo.Filter.swift in Sources */,
B5C795B625D8EE2B00BDACC1 /* Modern.PokedexDemo.UIKit.swift in Sources */,
B5A3919224E7E0C600E7E8BD /* Modern.ColorsDemo.Palette.swift in Sources */,
B5A3919E24E8EEB600E7E8BD /* Modern.ColorsDemo.SwiftUI.swift in Sources */,
B5A391A024E8F00A00E7E8BD /* Modern.ColorsDemo.UIKit.swift in Sources */,
@@ -711,16 +723,16 @@
B5A3916024E6925900E7E8BD /* Modern.PlacemarksDemo.MapView.swift in Sources */,
B5A3916524E698C700E7E8BD /* Modern.PlacemarksDemo.Place.swift in Sources */,
B566C8EE24FA1EA3001134A1 /* Modern.PokedexDemo.Details.swift in Sources */,
B566C8EC24F9D694001134A1 /* Modern.PokedexDemo.ItemCell.swift in Sources */,
B566C8E824F9D406001134A1 /* Modern.PokedexDemo.ListView.swift in Sources */,
B566C8EA24F9D412001134A1 /* Modern.PokedexDemo.ListViewController.swift in Sources */,
B566C8EC24F9D694001134A1 /* Modern.PokedexDemo.UIKit.ItemCell.swift in Sources */,
B566C8E824F9D406001134A1 /* Modern.PokedexDemo.UIKit.ListView.swift in Sources */,
B566C8EA24F9D412001134A1 /* Modern.PokedexDemo.UIKit.ListViewController.swift in Sources */,
B531EFED24EB7453005F247D /* Modern.PokedexDemo.MainView.swift in Sources */,
B5A3918C24E7B44B00E7E8BD /* Modern.TimeZonesDemo.ItemView.swift in Sources */,
B5A3918A24E7AD1800E7E8BD /* Modern.TimeZonesDemo.ListView.swift in Sources */,
B5A3918624E7A54A00E7E8BD /* Modern.TimeZonesDemo.TimeZone.swift in Sources */,
B5A543EB24FB84AF000DC5E3 /* ⭐Classic.ColorsDemo.DetailViewController.swift in Sources */,
B5A543F124FB84DD000DC5E3 /* ⭐Classic.ColorsDemo.ListViewController.swift in Sources */,
B5A391A624E8F4EA00E7E8BD /* ⭐️Modern.ColorsDemo.MainView.swift in Sources */,
B5A391A624E8F4EA00E7E8BD /* Modern.ColorsDemo.MainView.swift in Sources */,
B5A3916224E697BA00E7E8BD /* ⭐Modern.PlacemarksDemo.MainView.swift in Sources */,
B5A391B424E96C0A00E7E8BD /* ⭐Modern.PokedexDemo.Form.swift in Sources */,
B531EFE924EB5A53005F247D /* ⭐Modern.PokedexDemo.PokedexEntry.swift in Sources */,

View File

@@ -20,8 +20,8 @@ struct InstructionsView: View {
var body: some View {
ZStack(alignment: .center) {
Color.white
.cornerRadius(10)
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(Color.white)
.shadow(color: Color(.sRGB, white: 0.5, opacity: 0.3), radius: 2, x: 1, y: 1)
VStack(alignment: .leading, spacing: 3) {
ForEach(self.rows, id: \.header) { row in

View File

@@ -73,7 +73,9 @@ extension Menu {
title: "Pokedex API",
subtitle: "Importing JSON data from external source",
destination: {
Modern.PokedexDemo.MainView()
Modern.PokedexDemo.MainView(
listView: Modern.PokedexDemo.UIKit.ListView.init
)
}
)
}

View File

@@ -45,17 +45,16 @@ extension Advanced.EvolutionDemo {
.padding(.horizontal)
GeometryReader { geometry in
ZStack(alignment: .leading) {
Color.gray
.opacity(0.2)
RoundedRectangle(cornerRadius: 4, style: .continuous)
.fill(Color.gray.opacity(0.2))
.frame(width: geometry.size.width, height: 8)
.cornerRadius(4.0)
Color.blue
RoundedRectangle(cornerRadius: 4, style: .continuous)
.fill(Color.blue)
.frame(
width: geometry.size.width
* self.progressObserver.fractionCompleted,
height: 8
)
.cornerRadius(4.0)
}
}
.fixedSize(horizontal: false, vertical: true)

View File

@@ -1,12 +1,15 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19F101" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17709" systemVersion="20C5048l" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Palette" representedClassName="Classic_ColorsDemo_Palette" syncable="YES">
<attribute name="brightness" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="colorGroup" optional="YES" transient="YES" attributeType="String"/>
<attribute name="hue" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<attribute name="saturation" attributeType="Float" defaultValueString="0.0" usesScalarValueType="YES"/>
<fetchedProperty name="testFetchProperty" optional="YES">
<fetchRequest name="fetchedPropertyFetchRequest" entity="Palette"/>
</fetchedProperty>
</entity>
<elements>
<element name="Palette" positionX="-63" positionY="-18" width="128" height="103"/>
<element name="Palette" positionX="-63" positionY="-18" width="128" height="110"/>
</elements>
</model>

View File

@@ -13,12 +13,6 @@ extension Modern.ColorsDemo {
struct MainView<ListView: View, DetailView: View>: View {
/**
Sample 1: Setting a sectioned `ListPublisher` declared as an `@ObservedObject`
*/
@ObservedObject
private var listPublisher: ListPublisher<Modern.ColorsDemo.Palette>
// MARK: Internal
init(
@@ -30,38 +24,24 @@ extension Modern.ColorsDemo {
self.listView = listView
self.detailView = detailView
self.listPublisher = Modern.ColorsDemo.palettesPublisher
self._filter = Binding(
get: { Modern.ColorsDemo.filter },
set: { Modern.ColorsDemo.filter = $0 }
)
}
// MARK: View
var body: some View {
let detailView: AnyView
if let selectedPalette = self.selectedPalette {
detailView = AnyView(
self.detailView(selectedPalette)
)
}
else {
detailView = AnyView(EmptyView())
}
let listPublisher = self.listPublisher
return VStack(spacing: 0) {
self.listView(listPublisher, { self.selectedPalette = $0 })
self.listView(self.$palettes, { self.selectedPalette = $0 })
.navigationBarTitle(
Text("Colors (\(listPublisher.snapshot.numberOfItems) objects)")
Text("Colors (\(self.palettes.count) objects)")
)
.frame(minHeight: 0, maxHeight: .infinity)
detailView
.edgesIgnoringSafeArea(.all)
.frame(minHeight: 0, maxHeight: .infinity)
self.selectedPalette.map {
self.detailView($0)
.edgesIgnoringSafeArea(.all)
.frame(minHeight: 0, maxHeight: .infinity)
}
}
.navigationBarItems(
leading: HStack {
@@ -91,6 +71,9 @@ extension Modern.ColorsDemo {
// MARK: Private
@ListState(Modern.ColorsDemo.palettesPublisher)
private var palettes: ListSnapshot
private let listView: (
_ listPublisher: ListPublisher<Modern.ColorsDemo.Palette>,
_ onPaletteTapped: @escaping (ObjectPublisher<Modern.ColorsDemo.Palette>) -> Void
@@ -103,12 +86,13 @@ extension Modern.ColorsDemo {
@State
private var selectedPalette: ObjectPublisher<Modern.ColorsDemo.Palette>?
@Binding
private var filter: Modern.ColorsDemo.Filter
@State
private var filter: Modern.ColorsDemo.Filter = Modern.ColorsDemo.filter
private func changeFilter() {
Modern.ColorsDemo.filter = Modern.ColorsDemo.filter.next()
self.filter = Modern.ColorsDemo.filter
}
private func clearColors() {
@@ -118,6 +102,7 @@ extension Modern.ColorsDemo {
try transaction.deleteAll(From<Modern.ColorsDemo.Palette>())
},
sourceIdentifier: TransactionSource.clear,
completion: { _ in }
)
}
@@ -129,6 +114,7 @@ extension Modern.ColorsDemo {
_ = transaction.create(Into<Modern.ColorsDemo.Palette>())
},
sourceIdentifier: TransactionSource.add,
completion: { _ in }
)
}
@@ -143,6 +129,7 @@ extension Modern.ColorsDemo {
palette.setRandomHue()
}
},
sourceIdentifier: TransactionSource.shuffle,
completion: { _ in }
)
}

View File

@@ -30,14 +30,19 @@ extension Modern.ColorsDemo.UIKit {
return UIViewControllerType(self.palette)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Self.Context) {}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Self.Context) {
uiViewController.palette = Modern.ColorsDemo.dataStack.monitorObject(
self.palette.object!
)
}
static func dismantleUIViewController(_ uiViewController: UIViewControllerType, coordinator: Void) {}
// MARK: Private
private let palette: ObjectPublisher<Modern.ColorsDemo.Palette>
private var palette: ObjectPublisher<Modern.ColorsDemo.Palette>
}
}

View File

@@ -2,6 +2,7 @@
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import SwiftUI
import CoreStore
// MARK: - Modern
@@ -45,7 +46,10 @@ extension Modern {
static let palettesPublisher: ListPublisher<Modern.ColorsDemo.Palette> = Modern.ColorsDemo.dataStack.publishList(
From<Modern.ColorsDemo.Palette>()
.sectionBy(\.$colorGroup)
.sectionBy(
\.$colorGroup,
sectionIndexTransformer: { $0?.first?.uppercased() }
)
.where(Modern.ColorsDemo.filter.whereClause())
.orderBy(.ascending(\.$hue))
)
@@ -56,11 +60,27 @@ extension Modern {
try! Modern.ColorsDemo.palettesPublisher.refetch(
From<Modern.ColorsDemo.Palette>()
.sectionBy(\.$colorGroup)
.sectionBy(
\.$colorGroup,
sectionIndexTransformer: { $0?.first?.uppercased() }
)
.where(self.filter.whereClause())
.orderBy(.ascending(\.$hue))
.orderBy(.ascending(\.$hue)),
sourceIdentifier: TransactionSource.refetch
)
}
}
// MARK: - TransactionSource
enum TransactionSource {
case add
case delete
case shuffle
case clear
case refetch
}
}
}

View File

@@ -15,10 +15,10 @@ extension Modern.ColorsDemo.SwiftUI {
struct DetailView: View {
/**
Sample 1: Setting an `ObjectPublisher` declared as an `@ObservedObject`
Sample 1: Using a `ObjectState` to observe object changes. Note that the `ObjectSnapshot` is always `Optional`
*/
@ObservedObject
private var palette: ObjectPublisher<Modern.ColorsDemo.Palette>
@ObjectState
private var palette: ObjectSnapshot<Modern.ColorsDemo.Palette>?
/**
Sample 2: Setting properties that can be binded to controls (`Slider` in this case) by creating custom `@Binding` instances that updates the store when the values change.
@@ -34,7 +34,7 @@ extension Modern.ColorsDemo.SwiftUI {
init(_ palette: ObjectPublisher<Modern.ColorsDemo.Palette>) {
self.palette = palette
self._palette = .init(palette)
self._hue = Binding(
get: { palette.hue ?? 0 },
set: { percentage in
@@ -84,56 +84,50 @@ extension Modern.ColorsDemo.SwiftUI {
var body: some View {
guard let snapshot = self.palette.snapshot else {
if let palette = self.palette {
return AnyView(EmptyView())
}
return AnyView(
GeometryReader { geometry in
ZStack(alignment: .bottom) {
Color(snapshot.$color)
ZStack {
Color.white
.cornerRadius(10)
.shadow(color: Color(.sRGB, white: 0.5, opacity: 0.3), radius: 2, x: 1, y: 1)
VStack(alignment: .leading, spacing: 10) {
HStack {
Text("H: \(Int(snapshot.$hue * 359))°")
.frame(width: 80)
Slider(
value: self.$hue,
in: 0 ... 1,
step: 1 / 359
)
}
HStack {
Text("S: \(Int(snapshot.$saturation * 100))%")
.frame(width: 80)
Slider(
value: self.$saturation,
in: 0 ... 1,
step: 1 / 100
)
}
HStack {
Text("B: \(Int(snapshot.$brightness * 100))%")
.frame(width: 80)
Slider(
value: self.$brightness,
in: 0 ... 1,
step: 1 / 100
)
}
ZStack(alignment: .center) {
Color(palette.$color)
ZStack {
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(Color.white)
.shadow(color: Color(.sRGB, white: 0.5, opacity: 0.3), radius: 2, x: 1, y: 1)
VStack(alignment: .leading, spacing: 10) {
HStack {
Text("H: \(Int(palette.$hue * 359))°")
.frame(width: 80)
Slider(
value: self.$hue,
in: 0 ... 1,
step: 1 / 359
)
}
HStack {
Text("S: \(Int(palette.$saturation * 100))%")
.frame(width: 80)
Slider(
value: self.$saturation,
in: 0 ... 1,
step: 1 / 100
)
}
HStack {
Text("B: \(Int(palette.$brightness * 100))%")
.frame(width: 80)
Slider(
value: self.$brightness,
in: 0 ... 1,
step: 1 / 100
)
}
.foregroundColor(Color(.sRGB, white: 0, opacity: 0.8))
.padding()
}
.fixedSize(horizontal: false, vertical: true)
.foregroundColor(Color(.sRGB, white: 0, opacity: 0.8))
.padding()
.padding(geometry.safeAreaInsets)
}
.fixedSize(horizontal: false, vertical: true)
.padding()
}
)
}
}
}
}

View File

@@ -14,36 +14,34 @@ extension Modern.ColorsDemo.SwiftUI {
struct ItemView: View {
/**
Sample 1: Setting an `ObjectPublisher` declared as an `@ObservedObject`
Sample 1: Using a `ObjectState` to observe object changes. Note that the `ObjectSnapshot` is always `Optional`
*/
@ObservedObject
private var palette: ObjectPublisher<Modern.ColorsDemo.Palette>
// MARK: Internal
@ObjectState
private var palette: ObjectSnapshot<Modern.ColorsDemo.Palette>?
/**
Sample 2: Initializing a `ObjectState` from an existing `ObjectPublisher`
*/
internal init(_ palette: ObjectPublisher<Modern.ColorsDemo.Palette>) {
self.palette = palette
self._palette = .init(palette)
}
// MARK: View
/**
Sample 3: Readding values directly from the `ObjectSnapshot`
*/
var body: some View {
guard let palette = self.palette.snapshot else {
if let palette = self.palette {
return AnyView(EmptyView())
}
return AnyView(
Color(palette.$color).overlay(
Text(palette.$colorText)
.foregroundColor(palette.$brightness > 0.6 ? .black : .white)
.padding(),
alignment: .leading
)
)
.animation(.default)
}
}
}
}

View File

@@ -5,7 +5,6 @@
import CoreStore
import SwiftUI
// MARK: - Modern.ColorsDemo.SwiftUI
extension Modern.ColorsDemo.SwiftUI {
@@ -15,53 +14,58 @@ extension Modern.ColorsDemo.SwiftUI {
struct ListView: View {
/**
Sample 1: Setting a sectioned `ListPublisher` declared as an `@ObservedObject`
Sample 1: Using a `ListState` to observe list changes
*/
@ObservedObject
private var listPublisher: ListPublisher<Modern.ColorsDemo.Palette>
@ListState
private var palettes: ListSnapshot<Modern.ColorsDemo.Palette>
/**
Sample 2: Assigning sections and items of the `ListPublisher` to corresponding `View`s
Sample 2: Initializing a `ListState` from an existing `ListPublisher`
*/
var body: some View {
let listSnapshot = self.listPublisher.snapshot
return List {
ForEach(listSnapshot.sectionIDs, id: \.self) { (sectionID) in
Section(header: Text(sectionID)) {
ForEach(listSnapshot.items(inSectionWithID: sectionID), id: \.self) { palette in
init(
listPublisher: ListPublisher<Modern.ColorsDemo.Palette>,
onPaletteTapped: @escaping (ObjectPublisher<Modern.ColorsDemo.Palette>) -> Void
) {
self._palettes = .init(listPublisher)
self.onPaletteTapped = onPaletteTapped
}
/**
Sample 3: Assigning sections and items of the `ListSnapshot` to corresponding `View`s by using the correct `ForEach` overloads.
*/
var body: some View {
List {
ForEach(sectionIn: self.palettes) { section in
Section(header: Text(section.sectionID)) {
ForEach(objectIn: section) { palette in
Button(
action: {
self.onPaletteTapped(palette)
},
label: {
Modern.ColorsDemo.SwiftUI.ItemView(palette)
}
)
.listRowInsets(.init())
}
.onDelete { itemIndices in
self.deleteColors(at: itemIndices, in: sectionID)
self.deleteColors(at: itemIndices, in: section.sectionID)
}
}
}
GeometryReader { geometry in
Spacer(minLength: geometry.safeAreaInsets.bottom)
}
}
.animation(.default)
.listStyle(PlainListStyle())
}
// MARK: Internal
init(
listPublisher: ListPublisher<Modern.ColorsDemo.Palette>,
onPaletteTapped: @escaping (ObjectPublisher<Modern.ColorsDemo.Palette>) -> Void
) {
self.listPublisher = listPublisher
self.onPaletteTapped = onPaletteTapped
.edgesIgnoringSafeArea([])
}
@@ -71,7 +75,7 @@ extension Modern.ColorsDemo.SwiftUI {
private func deleteColors(at indices: IndexSet, in sectionID: String) {
let objectIDsToDelete = self.listPublisher.snapshot.itemIDs(
let objectIDsToDelete = self.palettes.itemIDs(
inSectionWithID: sectionID,
atIndices: indices
)

View File

@@ -40,6 +40,15 @@ extension Modern.ColorsDemo.UIKit {
/**
Sample 3: We can end monitoring updates anytime. `removeObserver()` was called here for illustration purposes only. `ObjectMonitor`s safely remove deallocated observers automatically.
*/
var palette: ObjectMonitor<Modern.ColorsDemo.Palette> {
didSet {
oldValue.removeObserver(self)
self.startMonitoringObject()
}
}
deinit {
self.palette.removeObserver(self)
@@ -82,7 +91,8 @@ extension Modern.ColorsDemo.UIKit {
func objectMonitor(
_ monitor: ObjectMonitor<Modern.ColorsDemo.Palette>,
didUpdateObject object: Modern.ColorsDemo.Palette,
changedPersistentKeys: Set<KeyPathString>
changedPersistentKeys: Set<KeyPathString>,
sourceIdentifier: Any?
) {
self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys)
@@ -183,9 +193,8 @@ extension Modern.ColorsDemo.UIKit {
equalTo: view.safeAreaLayoutGuide.leadingAnchor,
constant: 10
),
containerView.bottomAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.bottomAnchor,
constant: -10
containerView.centerYAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.centerYAnchor
),
containerView.trailingAnchor.constraint(
equalTo: view.safeAreaLayoutGuide.trailingAnchor,
@@ -223,8 +232,6 @@ extension Modern.ColorsDemo.UIKit {
// MARK: Private
private let palette: ObjectMonitor<Modern.ColorsDemo.Palette>
@available(*, unavailable)
required init?(coder: NSCoder) {

View File

@@ -15,9 +15,9 @@ extension Modern.ColorsDemo.UIKit {
final class ListViewController: UITableViewController {
/**
Sample 1: Setting up a `DiffableDataSource.TableViewAdapter` that will manage tableView snapshot updates automatically. We can use the built-in `DiffableDataSource.TableViewAdapter` type directly, but in our case we want to enabled `UITableView` cell deletions so we create a custom subclass `DeletionEnabledDataSource` (see declaration below).
Sample 1: Setting up a `DiffableDataSource.TableViewAdapter` that will manage tableView snapshot updates automatically. We can use the built-in `DiffableDataSource.TableViewAdapter` type directly, but in our case we want to enabled `UITableView` cell deletions so we create a custom subclass `CustomDataSource` (see declaration below).
*/
private lazy var dataSource: DiffableDataSource.TableViewAdapter<Modern.ColorsDemo.Palette> = DeletionEnabledDataSource(
private lazy var dataSource: DiffableDataSource.TableViewAdapter<Modern.ColorsDemo.Palette> = CustomDataSource(
tableView: self.tableView,
dataStack: Modern.ColorsDemo.dataStack,
cellProvider: { (tableView, indexPath, palette) in
@@ -32,21 +32,26 @@ extension Modern.ColorsDemo.UIKit {
)
/**
Sample 2: Once the views are created, we can start binding `ListPublisher` updates to the `DiffableDataSource`. We typically call this at the end of `viewDidLoad`. Note that the `addObserver`'s closure argument will only be called on the succeeding updates, so to immediately display the current values, we need to call `dataSource.apply()` once.
Sample 2: Once the views are created, we can start binding `ListPublisher` updates to the `DiffableDataSource`. We typically call this at the end of `viewDidLoad`. Note that the `addObserver`'s closure argument will only be called on the succeeding updates, so to immediately display the current values, we need to call `dataSource.apply()` once. This example inspects the optional `transactionSource` to determine the source of the update which is helpful for debugging or for fine-tuning animations.
*/
private func startObservingList() {
self.listPublisher.addObserver(self) { (listPublisher) in
let dataSource = self.dataSource
self.listPublisher.addObserver(self, notifyInitial: true) { (listPublisher, transactionSource) in
self.dataSource.apply(
listPublisher.snapshot,
animatingDifferences: true
)
switch transactionSource as? Modern.ColorsDemo.TransactionSource {
case .add,
.delete,
.shuffle,
.clear:
dataSource.apply(listPublisher.snapshot, animatingDifferences: true)
case nil,
.refetch:
dataSource.apply(listPublisher.snapshot, animatingDifferences: false)
}
}
self.dataSource.apply(
listPublisher.snapshot,
animatingDifferences: false
)
}
/**
@@ -58,9 +63,11 @@ extension Modern.ColorsDemo.UIKit {
}
/**
Sample 4: This is the custom `DiffableDataSource.TableViewAdapter` subclass we wrote that enabled swipe-to-delete gestures on the `UITableView`.
Sample 4: This is the custom `DiffableDataSource.TableViewAdapter` subclass we wrote that enabled swipe-to-delete gestures and section index titles on the `UITableView`.
*/
final class DeletionEnabledDataSource: DiffableDataSource.TableViewAdapter<Modern.ColorsDemo.Palette> {
final class CustomDataSource: DiffableDataSource.TableViewAdapter<Modern.ColorsDemo.Palette> {
// MARK: UITableViewDataSource
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
@@ -76,6 +83,7 @@ extension Modern.ColorsDemo.UIKit {
transaction.delete(objectIDs: [itemID])
},
sourceIdentifier: Modern.ColorsDemo.TransactionSource.delete,
completion: { _ in }
)
@@ -83,6 +91,16 @@ extension Modern.ColorsDemo.UIKit {
break
}
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return self.sectionIndexTitlesForAllSections().compactMap({ $0 })
}
override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
return index
}
}

View File

@@ -25,7 +25,7 @@ extension Modern.PlacemarksDemo {
Modern.PlacemarksDemo.dataStack.perform(
asynchronous: { (transaction) in
let place = self.place.asEditable(in: transaction)
let place = self.$place?.asEditable(in: transaction)
place?.annotation = .init(coordinate: coordinate)
},
completion: { _ in }
@@ -42,7 +42,7 @@ extension Modern.PlacemarksDemo {
_ = try? Modern.PlacemarksDemo.dataStack.perform(
synchronous: { (transaction) in
let place = self.place.asEditable(in: transaction)
let place = self.$place?.asEditable(in: transaction)
place?.setRandomLocation()
}
)
@@ -71,22 +71,25 @@ extension Modern.PlacemarksDemo {
// MARK: Internal
@ObservedObject
var place: ObjectPublisher<Modern.PlacemarksDemo.Place>
@ObjectState(Modern.PlacemarksDemo.placePublisher)
var place: ObjectSnapshot<Modern.PlacemarksDemo.Place>?
init() {
self.place = Modern.PlacemarksDemo.placePublisher
self.sinkCancellable = self.place.sink(
self.sinkCancellable = self.$place?.reactive.snapshot().sink(
receiveCompletion: { _ in
// Deleted, do nothing
},
receiveValue: { [self] (snapshot) in
guard let snapshot = snapshot else {
return
}
self.geocoder.geocode(place: snapshot) { (title, subtitle) in
guard self.place.snapshot == snapshot else {
guard self.place == snapshot else {
return
}
@@ -104,22 +107,29 @@ extension Modern.PlacemarksDemo {
// MARK: View
var body: some View {
Modern.PlacemarksDemo.MapView(
place: self.place.snapshot,
onTap: { coordinate in
Group {
if let place = self.place {
self.demoAsynchronousTransaction(coordinate: coordinate)
Modern.PlacemarksDemo.MapView(
place: place,
onTap: { coordinate in
self.demoAsynchronousTransaction(coordinate: coordinate)
}
)
.overlay(
InstructionsView(
("Random", "Sets random coordinate"),
("Tap", "Sets to tapped coordinate")
)
.padding(.leading, 10)
.padding(.bottom, 40),
alignment: .bottomLeading
)
}
)
.overlay(
InstructionsView(
("Random", "Sets random coordinate"),
("Tap", "Sets to tapped coordinate")
)
.padding(.leading, 10)
.padding(.bottom, 40),
alignment: .bottomLeading
)
}
.navigationBarTitle("Placemarks")
.navigationBarItems(
trailing: Button("Random") {
@@ -132,7 +142,7 @@ extension Modern.PlacemarksDemo {
// MARK: Private
private var sinkCancellable: AnyCancellable? = nil
private var sinkCancellable: AnyCancellable?
private let geocoder = Modern.PlacemarksDemo.Geocoder()
}
}

View File

@@ -12,30 +12,28 @@ extension Modern.PokedexDemo {
// MARK: - Modern.PokedexDemo.MainView
struct MainView: View {
struct MainView<ListView: View>: View {
// MARK: Internal
init() {
self.pokedexEntries = Modern.PokedexDemo.pokedexEntries
init(
listView: @escaping () -> ListView
) {
self.listView = listView
}
// MARK: View
var body: some View {
let pokedexEntries = self.pokedexEntries.snapshot
return ZStack {
ZStack {
Modern.PokedexDemo.ListView(
service: self.service,
listPublisher: self.pokedexEntries
)
self.listView()
.frame(minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.vertical)
if pokedexEntries.isEmpty {
if self.pokedexEntries.isEmpty {
VStack(alignment: .center, spacing: 30) {
Text("This demo needs to make a network connection to download Pokedex entries")
@@ -64,11 +62,17 @@ extension Modern.PokedexDemo {
// MARK: Private
@ObservedObject
private var pokedexEntries: ListPublisher<Modern.PokedexDemo.PokedexEntry>
@ListState(
From<Modern.PokedexDemo.PokedexEntry>()
.orderBy(.ascending(\.$index)),
in: Modern.PokedexDemo.dataStack
)
private var pokedexEntries
@ObservedObject
private var service: Modern.PokedexDemo.Service = .init()
private let listView: () -> ListView
}
}
@@ -82,7 +86,9 @@ struct _Demo_Modern_PokedexDemo_MainView_Preview: PreviewProvider {
static var previews: some View {
Modern.PokedexDemo.MainView()
Modern.PokedexDemo.MainView(
listView: Modern.PokedexDemo.UIKit.ListView.init
)
}
}

View File

@@ -7,9 +7,9 @@ import CoreStore
import UIKit
// MARK: - Modern.PokedexDemo
// MARK: - Modern.PokedexDemo.UIKit
extension Modern.PokedexDemo {
extension Modern.PokedexDemo.UIKit {
// MARK: - Modern.PokedexDemo.ItemCell
@@ -17,7 +17,7 @@ extension Modern.PokedexDemo {
// MARK: Internal
static let reuseIdentifier: String = NSStringFromClass(Modern.PokedexDemo.ItemCell.self)
static let reuseIdentifier: String = NSStringFromClass(Modern.PokedexDemo.UIKit.ItemCell.self)
func setPokedexEntry(
_ pokedexEntry: Modern.PokedexDemo.PokedexEntry,
@@ -61,6 +61,7 @@ extension Modern.PokedexDemo {
contentView.backgroundColor = UIColor.placeholderText.withAlphaComponent(0.1)
contentView.layer.cornerRadius = 10
contentView.layer.cornerCurve = .continuous
contentView.layer.masksToBounds = true
}

View File

@@ -5,9 +5,9 @@
import CoreStore
import SwiftUI
// MARK: - Modern.PokedexDemo
// MARK: - Modern.PokedexDemo.UIKit
extension Modern.PokedexDemo {
extension Modern.PokedexDemo.UIKit {
// MARK: - Modern.PokedexDemo.ListView
@@ -15,19 +15,20 @@ extension Modern.PokedexDemo {
// MARK: Internal
init(
service: Modern.PokedexDemo.Service,
listPublisher: ListPublisher<Modern.PokedexDemo.PokedexEntry>
) {
init() {
self.service = service
self.listPublisher = listPublisher
self.service = Modern.PokedexDemo.Service.init()
self.listPublisher = Modern.PokedexDemo.dataStack
.publishList(
From<Modern.PokedexDemo.PokedexEntry>()
.orderBy(.ascending(\.$index))
)
}
// MARK: UIViewControllerRepresentable
typealias UIViewControllerType = Modern.PokedexDemo.ListViewController
typealias UIViewControllerType = Modern.PokedexDemo.UIKit.ListViewController
func makeUIViewController(context: Self.Context) -> UIViewControllerType {
@@ -53,7 +54,7 @@ extension Modern.PokedexDemo {
#if DEBUG
struct _Demo_Modern_PokedexDemo_ListView_Preview: PreviewProvider {
struct _Demo_Modern_PokedexDemo_UIKit_ListView_Preview: PreviewProvider {
// MARK: PreviewProvider
@@ -62,10 +63,7 @@ struct _Demo_Modern_PokedexDemo_ListView_Preview: PreviewProvider {
let service = Modern.PokedexDemo.Service()
service.fetchPokedexEntries()
return Modern.PokedexDemo.ListView(
service: service,
listPublisher: Modern.PokedexDemo.pokedexEntries
)
return Modern.PokedexDemo.UIKit.ListView()
}
}

View File

@@ -6,9 +6,9 @@ import CoreStore
import UIKit
// MARK: - Modern.PokedexDemo
// MARK: - Modern.PokedexDemo.UIKit
extension Modern.PokedexDemo {
extension Modern.PokedexDemo.UIKit {
// MARK: - Modern.PokedexDemo.ListViewController
@@ -65,8 +65,8 @@ extension Modern.PokedexDemo {
self.collectionView.backgroundColor = UIColor.systemBackground
self.collectionView.register(
Modern.PokedexDemo.ItemCell.self,
forCellWithReuseIdentifier: Modern.PokedexDemo.ItemCell.reuseIdentifier
Modern.PokedexDemo.UIKit.ItemCell.self,
forCellWithReuseIdentifier: Modern.PokedexDemo.UIKit.ItemCell.reuseIdentifier
)
self.startObservingList()
@@ -84,9 +84,9 @@ extension Modern.PokedexDemo {
cellProvider: { (collectionView, indexPath, pokedexEntry) in
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: Modern.PokedexDemo.ItemCell.reuseIdentifier,
withReuseIdentifier: Modern.PokedexDemo.UIKit.ItemCell.reuseIdentifier,
for: indexPath
) as! Modern.PokedexDemo.ItemCell
) as! Modern.PokedexDemo.UIKit.ItemCell
cell.setPokedexEntry(pokedexEntry, service: self.service)
return cell
}

View File

@@ -0,0 +1,13 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
// MARK: - Modern.PokedexDemo
extension Modern.PokedexDemo {
// MARK: - UIKit
enum UIKit {}
}

View File

@@ -29,10 +29,10 @@ import PackageDescription
let package = Package(
name: "CoreStore",
platforms: [
.macOS(.v10_12), .iOS(.v10), .tvOS(.v10), .watchOS(.v3)
.macOS(.v10_13), .iOS(.v11), .tvOS(.v11), .watchOS(.v4)
],
products: [
.library(name: "CoreStore", type: .static, targets: ["CoreStore"])
.library(name: "CoreStore", targets: ["CoreStore"])
],
dependencies: [],
targets: [

598
README.md
View File

@@ -1,5 +1,5 @@
<p align="center">
<img alt="CoreStore" src="https://cloud.githubusercontent.com/assets/3029684/13373932/84daee2a-ddb8-11e5-99db-fdf415620102.png" width=614 />
<img alt="CoreStore" src="https://github.com/JohnEstropia/CoreStore/raw/develop/CoreStore.png" width=614 />
<br />
<br />
Unleashing the real power of Core Data with the elegance and safety of Swift
@@ -19,42 +19,17 @@ Unleashing the real power of Core Data with the elegance and safety of Swift
<br />
</p>
* **Swift 5.3:** iOS 10+ / macOS 10.12+ / watchOS 3.0+ / tvOS 10.0+
* Previously supported Swift versions: [Swift 3.2](https://github.com/JohnEstropia/CoreStore/tree/4.2.3), [Swift 4.2](https://github.com/JohnEstropia/CoreStore/tree/6.2.1), [Swift 5.0](https://github.com/JohnEstropia/CoreStore/tree/6.3.2), [Swift 5.1](https://github.com/JohnEstropia/CoreStore/tree/7.0.4)
* **Swift 5.4:** iOS 11+ / macOS 10.13+ / watchOS 4.0+ / tvOS 11.0+
* Previously supported Swift versions: [Swift 3.2](https://github.com/JohnEstropia/CoreStore/tree/4.2.3), [Swift 4.2](https://github.com/JohnEstropia/CoreStore/tree/6.2.1), [Swift 5.0](https://github.com/JohnEstropia/CoreStore/tree/6.3.2), [Swift 5.1](https://github.com/JohnEstropia/CoreStore/tree/7.0.4), [Swift 5.3](https://github.com/JohnEstropia/CoreStore/tree/7.3.1)
Upgrading from CoreStore 6.x (swift 5.0) to 7.x (Swift 5.1)? Check out the [🆕 features](#features) and make sure to read the [Change logs](https://github.com/JohnEstropia/CoreStore/releases).
Upgrading from previous CoreStore versions? Check out the [🆕 features](#features) and make sure to read the [Change logs](https://github.com/JohnEstropia/CoreStore/releases).
CoreStore is now part of the [Swift Source Compatibility projects](https://swift.org/source-compatibility/#current-list-of-projects).
## Why use CoreStore?
CoreStore was (and is) heavily shaped by real-world needs of developing data-dependent apps. It enforces safe and convenient Core Data usage while letting you take advantage of the industry's encouraged best practices.
### Features
- **Backwards-portable DiffableDataSources implementation!** `UITableViews` and `UICollectionViews` now have a new ally: `ListPublisher`s provide diffable snapshots that make reloading animations very easy and very safe. Say goodbye to `UITableViews` and `UICollectionViews` reload errors!
- **💎Tight design around Swifts code elegance and type safety.** CoreStore fully utilizes Swift's community-driven language features.
- **🚦Safer concurrency architecture.** CoreStore makes it hard to fall into common concurrency mistakes. The main `NSManagedObjectContext` is strictly read-only, while all updates are done through serial *transactions*. *(See [Saving and processing transactions](#saving-and-processing-transactions))*
- **🔍Clean fetching and querying API.** Fetching objects is easy, but querying for raw aggregates (`min`, `max`, etc.) and raw property values is now just as convenient. *(See [Fetching and querying](#fetching-and-querying))*
- **🔭Type-safe, easy to configure observers.** You don't have to deal with the burden of setting up `NSFetchedResultsController`s and KVO. As an added bonus, list and object observable types all support multiple observers. This means you can have multiple view controllers efficiently share a single resource! *(See [Observing changes and notifications](#observing-changes-and-notifications))*
- **📥Efficient importing utilities.** Map your entities once with their corresponding import source (JSON for example), and importing from *transactions* becomes elegant. Uniquing is also done with an efficient find-and-replace algorithm. *(See [Importing data](#importing-data))*
- **🗑Say goodbye to *.xcdatamodeld* files!** While CoreStore supports `NSManagedObject`s, it offers `CoreStoreObject` whose subclasses can declare type-safe properties all in Swift code without the need to maintain separate resource files for the models. As bonus, these special properties support custom types, and can be used to create type-safe keypaths and queries. *(See [Type-safe `CoreStoreObject`s](#type-safe-corestoreobjects))*
- **🔗Progressive migrations.** No need to think how to migrate from all previous model versions to your latest model. Just tell the `DataStack` the sequence of version strings (`MigrationChain`s) and CoreStore will automatically use progressive migrations when needed. *(See [Migrations](#migrations))*
- **Easier custom migrations.** Say goodbye to *.xcmappingmodel* files; CoreStore can now infer entity mappings when possible, while still allowing an easy way to write custom mappings. *(See [Migrations](#migrations))*
- **📝Plug-in your own logging framework.** Although a default logger is built-in, all logging, asserting, and error reporting can be funneled to `CoreStoreLogger` protocol implementations. *(See [Logging and error reporting](#logging-and-error-reporting))*
- **⛓Heavy support for multiple persistent stores per data stack.** CoreStore lets you manage separate stores in a single `DataStack`, just the way *.xcdatamodeld* configurations are designed to. CoreStore will also manage one stack by default, but you can create and manage as many as you need. *(See [Setting up](#setting-up))*
- **🎯Free to name entities and their class names independently.** CoreStore gets around a restriction with other Core Data wrappers where the entity name should be the same as the `NSManagedObject` subclass name. CoreStore loads entity-to-class mappings from the managed object model file, so you can assign independent names for the entities and their class names.
- **📙Full Documentation.** No magic here; all public classes, functions, properties, etc. have detailed *Apple Docs*. This *README* also introduces a lot of concepts and explains a lot of CoreStore's behavior.
- **Informative (and pretty) logs.** All CoreStore and Core Data-related types now have very informative and pretty print outputs! *(See [Logging and error reporting](#logging-and-error-reporting))*
- **🎗Objective-C support!** Is your project transitioning from Objective-C to Swift but still can't quite fully convert some huge classes to Swift yet? CoreStore adjusts to the ever-increasing Swift adoption. While still written in pure Swift, all CoreStore types have their corresponding Objective-C-visible "bridging classes". *(See [Objective-C support](#objective-c-support))*
- **🛡More extensive Unit Tests.** Extending CoreStore is safe without having to worry about breaking old behavior.
*Have ideas that may benefit other Core Data users? [Feature Request](https://github.com/JohnEstropia/CoreStore/issues)s are welcome!*
CoreStore is part of the [Swift Source Compatibility projects](https://swift.org/source-compatibility/#current-list-of-projects).
## Contents
- [TL;DR (a.k.a. sample codes)](#tldr-aka-sample-codes)
- [Why use CoreStore?](#why-use-corestore)
- [Architecture](#architecture)
- CoreStore Tutorials (All of these have demos in the **Demo** app project!)
- [Setting up](#setting-up)
@@ -94,13 +69,28 @@ CoreStore was (and is) heavily shaped by real-world needs of developing data-dep
- [Observe detailed list changes](#observe-detailed-list-changes)
- [Objective-C support](#objective-c-support)
- [Type-safe `CoreStoreObject`s](#type-safe-corestoreobjects)
- 🆕[New `@Field` Property Wrapper syntax](#new-field-property-wrapper-syntax)
- 🆕[`@Field.Stored` ](#fieldstored)
- 🆕[`@Field.Virtual` ](#fieldvirtual)
- 🆕[`@Field.Coded` ](#fieldcoded)
- 🆕[`@Field.Relationship` ](#fieldrelationship)
- 🆕[`@Field` usage notes](#field-usage-notes)
- [New `@Field` Property Wrapper syntax](#new-field-property-wrapper-syntax)
- [`@Field.Stored` ](#fieldstored)
- [`@Field.Virtual` ](#fieldvirtual)
- [`@Field.Coded` ](#fieldcoded)
- [`@Field.Relationship` ](#fieldrelationship)
- [`@Field` usage notes](#field-usage-notes)
- [`VersionLock`s](#versionlocks)
- [Reactive Programming](#reactive-programming)
- [RxSwift](#rxswift)
- 🆕[Combine](#combine)
- 🆕[`DataStack.reactive`](#datastackreactive)
- 🆕[`ListPublisher.reactive`](#listpublisherreactive)
- 🆕[`ObjectPublisher.reactive`](#objectpublisherreactive)
- 🆕[SwiftUI Utilities](#swiftui-utilities)
- 🆕[SwiftUI Views`](#swiftui-views)
- 🆕[`ListReader`](#listreader)
- 🆕[`ObjectReader`](#objectreader)
- 🆕[SwiftUI Property Wrappers](#swiftui-property-wrappers)
- 🆕[`ListState`](#liststate)
- 🆕[`ObjectState`](#objectstate)
- 🆕[SwiftUI Extensions](#swiftui-extensions)
- 🆕[`ForEach`](#foreach)
- [Roadmap](#roadmap)
- [Installation](#installation)
- [Changesets](#changesets)
@@ -112,6 +102,18 @@ CoreStore was (and is) heavily shaped by real-world needs of developing data-dep
## TL;DR (a.k.a. sample codes)
Pure-Swift models:
```swift
class Person: CoreStoreObject {
@Field.Stored("name")
var name: String = ""
@Field.Relationship("pets", inverse: \Dog.$master)
var pets: Set<Dog>
}
```
(Classic `NSManagedObject`s also supported)
Setting-up with progressive migration support:
```swift
dataStack = DataStack(
@@ -134,7 +136,7 @@ Starting transactions:
```swift
dataStack.perform(
asynchronous: { (transaction) -> Void in
let person = transaction.create(Into<MyPersonEntity>())
let person = transaction.create(Into<Person>())
person.name = "John Smith"
person.age = 42
},
@@ -149,13 +151,13 @@ dataStack.perform(
Fetching objects (simple):
```swift
let people = try dataStack.fetchAll(From<MyPersonEntity>())
let people = try dataStack.fetchAll(From<Person>())
```
Fetching objects (complex):
```swift
let people = try dataStack.fetchAll(
From<MyPersonEntity>()
From<Person>()
.where(\.age > 30),
.orderBy(.ascending(\.name), .descending(.\age)),
.tweak({ $0.includesPendingChanges = false })
@@ -165,7 +167,7 @@ let people = try dataStack.fetchAll(
Querying values:
```swift
let maxAge = try dataStack.queryValue(
From<MyPersonEntity>()
From<Person>()
.select(Int.self, .maximum(\.age))
)
```
@@ -175,6 +177,33 @@ But really, there's a reason I wrote this huge *README*. Read up on the details!
Check out the **Demo** app project for sample codes as well!
## Why use CoreStore?
CoreStore was (and is) heavily shaped by real-world needs of developing data-dependent apps. It enforces safe and convenient Core Data usage while letting you take advantage of the industry's encouraged best practices.
### Features
- **🆕[SwiftUI](#swiftui-utilities) and [Combine](#combine) API utilities.** `ListPublisher`s and `ObjectPublisher`s now have their `@ListState` and `@ObjectState` SwiftUI property wrappers. Combine `Publisher` s are also available through the `ListPublisher.reactive`, `ObjectPublisher.reactive`, and `DataStack.reactive` namespaces.
- **Backwards-portable [DiffableDataSources implementation](#observe-a-diffable-list)!** `UITableViews` and `UICollectionViews` now have a new ally: `ListPublisher`s provide diffable snapshots that make reloading animations very easy and very safe. Say goodbye to `UITableViews` and `UICollectionViews` reload errors!
- **💎Tight design around Swifts code elegance and type safety.** CoreStore fully utilizes Swift's community-driven language features.
- **🚦Safer concurrency architecture.** CoreStore makes it hard to fall into common concurrency mistakes. The main `NSManagedObjectContext` is strictly read-only, while all updates are done through serial *transactions*. *(See [Saving and processing transactions](#saving-and-processing-transactions))*
- **🔍Clean fetching and querying API.** Fetching objects is easy, but querying for raw aggregates (`min`, `max`, etc.) and raw property values is now just as convenient. *(See [Fetching and querying](#fetching-and-querying))*
- **🔭Type-safe, easy to configure observers.** You don't have to deal with the burden of setting up `NSFetchedResultsController`s and KVO. As an added bonus, list and object observable types all support multiple observers. This means you can have multiple view controllers efficiently share a single resource! *(See [Observing changes and notifications](#observing-changes-and-notifications))*
- **📥Efficient importing utilities.** Map your entities once with their corresponding import source (JSON for example), and importing from *transactions* becomes elegant. Uniquing is also done with an efficient find-and-replace algorithm. *(See [Importing data](#importing-data))*
- **🗑Say goodbye to *.xcdatamodeld* files!** While CoreStore supports `NSManagedObject`s, it offers `CoreStoreObject` whose subclasses can declare type-safe properties all in Swift code without the need to maintain separate resource files for the models. As bonus, these special properties support custom types, and can be used to create type-safe keypaths and queries. *(See [Type-safe `CoreStoreObject`s](#type-safe-corestoreobjects))*
- **🔗Progressive migrations.** No need to think how to migrate from all previous model versions to your latest model. Just tell the `DataStack` the sequence of version strings (`MigrationChain`s) and CoreStore will automatically use progressive migrations when needed. *(See [Migrations](#migrations))*
- **Easier custom migrations.** Say goodbye to *.xcmappingmodel* files; CoreStore can now infer entity mappings when possible, while still allowing an easy way to write custom mappings. *(See [Migrations](#migrations))*
- **📝Plug-in your own logging framework.** Although a default logger is built-in, all logging, asserting, and error reporting can be funneled to `CoreStoreLogger` protocol implementations. *(See [Logging and error reporting](#logging-and-error-reporting))*
- **⛓Heavy support for multiple persistent stores per data stack.** CoreStore lets you manage separate stores in a single `DataStack`, just the way *.xcdatamodeld* configurations are designed to. CoreStore will also manage one stack by default, but you can create and manage as many as you need. *(See [Setting up](#setting-up))*
- **🎯Free to name entities and their class names independently.** CoreStore gets around a restriction with other Core Data wrappers where the entity name should be the same as the `NSManagedObject` subclass name. CoreStore loads entity-to-class mappings from the managed object model file, so you can assign independent names for the entities and their class names.
- **📙Full Documentation.** No magic here; all public classes, functions, properties, etc. have detailed *Apple Docs*. This *README* also introduces a lot of concepts and explains a lot of CoreStore's behavior.
- **Informative (and pretty) logs.** All CoreStore and Core Data-related types now have very informative and pretty print outputs! *(See [Logging and error reporting](#logging-and-error-reporting))*
- **🎗Objective-C support!** Is your project transitioning from Objective-C to Swift but still can't quite fully convert some huge classes to Swift yet? CoreStore adjusts to the ever-increasing Swift adoption. While still written in pure Swift, all CoreStore types have their corresponding Objective-C-visible "bridging classes". *(See [Objective-C support](#objective-c-support))*
- **🛡More extensive Unit Tests.** Extending CoreStore is safe without having to worry about breaking old behavior.
*Have ideas that may benefit other Core Data users? [Feature Request](https://github.com/JohnEstropia/CoreStore/issues)s are welcome!*
## Architecture
For maximum safety and performance, CoreStore will enforce coding patterns and practices it was designed for. (Don't worry, it's not as scary as it sounds.) But it is advisable to understand the "magic" of CoreStore before you use it in your apps.
@@ -270,6 +299,19 @@ try dataStack.addStorageAndWait(
)
)
```
Asynchronous variant:
```swift
try dataStack.addStorage(
InMemoryStore(
configuration: "Config2
),
completion: { storage in
// ...
}
)
```
(A reactive-programming variant of this method is explained in detail in the section on [`DataStack` Combine publishers](#datastackreactive))
### Local Store
The most common `StorageInterface` you will probably use is the `SQLiteStore`, which saves data in a local SQLite file.
@@ -291,7 +333,9 @@ CoreStore can decide the default values for these properties, so `SQLiteStore`s
try dataStack.addStorageAndWait(SQLiteStore())
```
The file-related properties above are actually requirements of another protocol that `SQLiteStore` implements, the `LocalStorage` protocol:
(The asynchronous variant of this method is explained further in the next section on [Migrations](#starting-migrations), and a reactive-programming variant in the section on [`DataStack` Combine publishers](#datastackreactive))
The file-related properties of `SQLiteStore` are actually requirements of another protocol that it implements, the `LocalStorage` protocol:
```swift
public protocol LocalStorage: StorageInterface {
var fileURL: NSURL { get }
@@ -462,6 +506,8 @@ let migrationProgress: Progress? = try dataStack.addStorage(
```
The `completion` block reports a `SetupResult` that indicates success or failure.
(A reactive-programming variant of this method is explained further in the section on [`DataStack` Combine publishers](#datastackreactive))
Notice that this method also returns an optional `Progress`. If `nil`, no migrations are needed, thus progress reporting is unnecessary as well. If not `nil`, you can use this to track migration progress by using standard KVO on the `"fractionCompleted"` key, or by using a closure-based utility exposed in *Progress+Convenience.swift*:
```swift
migrationProgress?.setProgressHandler { [weak self] (progress) -> Void in
@@ -1459,6 +1505,8 @@ Note that the owner instance will not be retained. You may call `ObjectPublisher
The `ObjectSnapshot` returned from the `ObjectPublisher.snapshot` property returns a full-copy `struct` of all properties of the object. This is ideal for managing states as they are thread-safe and are not affected by further changes to the actual object. `ObjectPublisher` automatically updates its `snapshot` value to the latest state of the object.
(A reactive-programming variant of this method is explained in detail in the section on [`ObjectPublisher` Combine publishers](#objectpublisherreactive))
### Observe a single object's per-property updates
@@ -1512,6 +1560,8 @@ Note that the owner instance will not be retained. You may call `ListPublisher.r
The `ListSnapshot` returned from the `ListPublisher.snapshot` property returns a full-copy `struct` of all sections and `NSManagedObject` items in the list. This is ideal for managing states as they are thread-safe and are not affected by further changes to the result set. `ListPublisher` automatically updates its `snapshot` value to the latest state of the fetch.
(A reactive-programming variant of this method is explained in detail in the section on [`ListPublisher` Combine publishers](#listpublisherreactive))
Unlike `ListMonitor`s (See [`ListMonitor` examples](#observe-detailed-list-changes) below), a `ListPublisher` does not track detailed inserts, deletes, and moves. In return, a `ListPublisher` is a lot more lightweight and are designed to work well with `DiffableDataSource.TableViewAdapter`s and `DiffableDataSource.CollectionViewAdapter`s:
```swift
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
@@ -1814,13 +1864,13 @@ Starting CoreStore 7.1.0, `CoreStoreObject` properties may be converted to `@Fie
The `@Field.Stored` property wrapper is used for persisted value types. This is the replacement for "non-transient" `Value.Required` and `Value.Optional` properties.
<table>
<tr><th>Before</th><th>`@Field.Stored`</th></tr>
<tr><th>Before</th><th><pre lang=swift>@Field.Stored</pre></th></tr>
<tr>
<td><pre lang=swift>
class Person: CoreStoreObject {
<br />
let title = Value.Required<String>("title", initial: "Mr.")
let nickname = Value.Optional<String>("nickname")
let title = Value.Required&lt;String&gt;("title", initial: "Mr.")
let nickname = Value.Optional&lt;String&gt;("nickname")
}
</pre></td>
<td><pre lang=swift>
@@ -1845,20 +1895,20 @@ class Person: CoreStoreObject {
The `@Field.Virtual` property wrapper is used for unsaved, computed value types. This is the replacement for "transient" `Value.Required` and `Value.Optional` properties.
<table>
<tr><th>Before</th><th>`@Field.Virtual`</th></tr>
<tr><th>Before</th><th><pre lang=swift>@Field.Virtual</pre></th></tr>
<tr>
<td><pre lang=swift>
class Animal: CoreStoreObject {
<br />
let speciesPlural = Value.Required<String>(
let speciesPlural = Value.Required&lt;String&gt;(
"speciesPlural",
transient: true,
customGetter: Animal.getSpeciesPlural(_:)
)
<br />
let species = Value.Required<String>("species", initial: "")
let species = Value.Required&lt;String&gt;("species", initial: "")
<br />
static func getSpeciesPlural(_ partialObject: PartialObject<Animal>) -> String? {
static func getSpeciesPlural(_ partialObject: PartialObject&lt;Animal&gt;) -> String? {
let species = partialObject.value(for: { $0.species })
return species + "s"
}
@@ -1893,12 +1943,12 @@ The `@Field.Coded` property wrapper is used for binary-codable values. This is t
> ‼️ The current `Transformable.Required` and `Transformable.Optional` mechanism have no safe one-to-one conversion to `@Field.Coded`. Please use `@Field.Coded` only for newly added attributes.
<table>
<tr><th>Before</th><th>`@Field.Coded`</th></tr>
<tr><th>Before</th><th><pre lang=swift>@Field.Coded</pre></th></tr>
<tr>
<td><pre lang=swift>
class Vehicle: CoreStoreObject {
<br />
let color = Transformable.Optional<UIColor>("color", initial: .white)
let color = Transformable.Optional&lt;UIColor&gt;("color", initial: .white)
}
</pre></td>
<td><pre lang=swift>
@@ -1946,16 +1996,16 @@ The type of relationship is determined by the `@Field.Relationship` generic typ
- `Set<T>` : To-many unordered relationship
<table>
<tr><th>Before</th><th>`@Field.Stored`</th></tr>
<tr><th>Before</th><th><pre lang=swift>@Field.Stored</pre></th></tr>
<tr>
<td><pre lang=swift>
class Pet: CoreStoreObject {
<br />
let master = Relationship.ToOne<Person>("master")
let master = Relationship.ToOne&lt;Person&gt;("master")
}
class Person: CoreStoreObject {
<br />
let pets: Relationship.ToManyUnordered<Pet>("pets", inverse: \.$master)
let pets: Relationship.ToManyUnordered&lt;Pet&gt;("pets", inverse: \.$master)
}
</pre></td>
<td><pre lang=swift>
@@ -1967,7 +2017,7 @@ class Pet: CoreStoreObject {
class Person: CoreStoreObject {
<br />
@Field.Relationship("pets", inverse: \.$master)
var pets: Set<Pet>
var pets: Set&lt;Pet&gt;
}
</pre></td>
</tr>
@@ -2059,6 +2109,438 @@ Once the version lock is set, any changes in the properties or to the model will
<img width="700" alt="VersionLock failure" src="https://cloud.githubusercontent.com/assets/3029684/26525666/92f46f0c-4399-11e7-9395-4379f6f20876.png" />
## Reactive Programming
### RxSwift
RxSwift utilities are available through the [RxCoreStore](https://github.com/JohnEstropia/RxCoreStore) external module.
### Combine
Combine publishers are available from the `DataStack`, `ListPublisher`, and `ObjectPublisher`'s `.reactive` namespace property.
#### `DataStack.reactive`
Adding a storage through `DataStack.reactive.addStorage(_:)` returns a publisher that reports a `MigrationProgress` `enum` value. The `.migrating` value is only emitted if the storage goes through a migration. Refer to the [Setting up](#setting-up) section for details on the storage setup process itself.
```swift
dataStack.reactive
.addStorage(
SQLiteStore(fileName: "core_data.sqlite")
)
.sink(
receiveCompletion: { result in
// ...
},
receiveValue: { (progress) in
print("\(round(progress.fractionCompleted * 100)) %") // 0.0 ~ 1.0
switch progress {
case .migrating(let storage, let nsProgress):
// ...
case .finished(let storage, let migrationRequired):
// ...
}
}
)
.store(in: &cancellables)
```
[Transactions](#saving-and-processing-transactions) are also available as publishers through `DataStack.reactive.perform(_:)`, which returns a Combine `Future` that emits any type returned from the closure parameter:
```swift
dataStack.reactive
.perform(
asynchronous: { (transaction) -> (inserted: Set<NSManagedObject>, deleted: Set<NSManagedObject>) in
// ...
return (
transaction.insertedObjects(),
transaction.deletedObjects()
)
}
)
.sink(
receiveCompletion: { result in
// ...
},
receiveValue: { value in
let inserted = dataStack.fetchExisting(value0.inserted)
let deleted = dataStack.fetchExisting(value0.deleted)
// ...
}
)
.store(in: &cancellables)
```
For importing convenience, `ImportableObject` and `ImportableUniqueObjects` can be imported directly through `DataStack.reactive.import[Unique]Object(_:source:)` and `DataStack.reactive.import[Unique]Objects(_:sourceArray:)` without having to create a transaction block. In this case the publisher emits objects that are already usable directly from the main queue:
```swift
dataStack.reactive
.importUniqueObjects(
Into<Person>(),
sourceArray: [
["name": "John"],
["name": "Bob"],
["name": "Joe"]
]
)
.sink(
receiveCompletion: { result in
// ...
},
receiveValue: { (people) in
XCTAssertEqual(people?.count, 3)
// ...
}
)
.store(in: &cancellables)
```
#### `ListPublisher.reactive`
`ListPublisher`s can be used to emit `ListSnapshot`s through Combine using `ListPublisher.reactive.snapshot(emitInitialValue:)`. The snapshot values are emitted in the main queue:
```swift
listPublisher.reactive
.snapshot(emitInitialValue: true)
.sink(
receiveCompletion: { result in
// ...
},
receiveValue: { (listSnapshot) in
dataSource.apply(
listSnapshot,
animatingDifferences: true
)
}
)
.store(in: &cancellables)
```
#### `ObjectPublisher.reactive`
`ObjectPublisher`s can be used to emit `ObjectSnapshot`s through Combine using `ObjectPublisher.reactive.snapshot(emitInitialValue:)`. The snapshot values are emitted in the main queue:
```swift
objectPublisher.reactive
.snapshot(emitInitialValue: true)
.sink(
receiveCompletion: { result in
// ...
},
receiveValue: { (objectSnapshot) in
tableViewCell.setObject(objectSnapshot)
}
)
.store(in: &tableViewCell.cancellables)
```
## SwiftUI Utilities
Observing list and object changes in SwiftUI can be done through a couple of approaches. One is by creating [views that autoupdates their contents](#swiftui-views), or by declaring [property wrappers that trigger view updates](#swiftui-property-wrappers). Both approaches are implemented almost the same internally, but this lets you be flexible depending on the structure of your custom `View`s.
### SwiftUI Views
CoreStore provides `View` containers that automatically update their contents when data changes.
#### `ListReader`
A `ListReader` observes changes to a `ListPublisher` and creates its content views dynamically. The builder closure receives a `ListSnapshot` value that can be used to create the contents:
```swift
let people: ListPublisher<Person>
var body: some View {
List {
ListReader(self.people) { listSnapshot in
ForEach(objectIn: listSnapshot) { person in
// ...
}
}
}
.animation(.default)
}
```
As shown above, a typical use case is to use it together with CoreStore's [`ForEach` extensions](#foreach).
A `KeyPath` can also be optionally provided to extract specific properties of the `ListSnapshot`:
```swift
let people: ListPublisher<Person>
var body: some View {
ListReader(self.people, keyPath: \.count) { count in
Text("Number of members: \(count)")
}
}
```
#### `ObjectReader`
An `ObjectReader` observes changes to an `ObjectPublisher` and creates its content views dynamically. The builder closure receives an `ObjectSnapshot` value that can be used to create the contents:
```swift
let person: ObjectPublisher<Person>
var body: some View {
ObjectReader(self.person) { objectSnapshot in
// ...
}
.animation(.default)
}
```
A `KeyPath` can also be optionally provided to extract specific properties of the `ObjectSnapshot`:
```swift
let person: ObjectPublisher<Person>
var body: some View {
ObjectReader(self.person, keyPath: \.fullName) { fullName in
Text("Name: \(fullName)")
}
}
```
By default, an `ObjectReader` does not create its views wheen the object observed is deleted from the store. In those cases, the `placeholder:` argument can be used to provide a custom `View` to display when the object is deleted:
```swift
let person: ObjectPublisher<Person>
var body: some View {
ObjectReader(
self.person,
content: { objectSnapshot in
// ...
},
placeholder: { Text("Record not found") }
)
}
```
### SwiftUI Property Wrappers
As an alternative to `ListReader` and `ObjectReader`, CoreStore also provides property wrappers that trigger view updates when the data changes.
#### `ListState`
A `@ListState` property exposes a `ListSnapshot` value that automatically updates to the latest changes.
```swift
@ListState
var people: ListSnapshot<Person>
init(listPublisher: ListPublisher<Person>) {
self._people = .init(listPublisher)
}
var body: some View {
List {
ForEach(objectIn: self.people) { objectSnapshot in
// ...
}
}
.animation(.default)
}
```
As shown above, a typical use case is to use it together with CoreStore's [`ForEach` extensions](#foreach).
If a `ListPublisher` instance is not available yet, the fetch can be done inline by providing the fetch clauses and the `DataStack` instance. By doing so the property can be declared without an initial value:
```swift
@ListState(
From<Person>()
.sectionBy(\.age)
.where(\.isMember == true)
.orderBy(.ascending(\.lastName))
)
var people: ListSnapshot<Person>
var body: some View {
List {
ForEach(sectionIn: self.people) { section in
Section(header: Text(section.sectionID)) {
ForEach(objectIn: section) { person in
// ...
}
}
}
}
.animation(.default)
}
```
For other initialization variants, refer to the *ListState.swift* source documentations.
#### `ObjectState`
An `@ObjectState` property exposes an optional `ObjectSnapshot` value that automatically updates to the latest changes.
```swift
@ObjectState
var person: ObjectSnapshot<Person>?
init(objectPublisher: ObjectPublisher<Person>) {
self._person = .init(objectPublisher)
}
var body: some View {
HStack {
if let person = self.person {
AsyncImage(person.$avatarURL)
Text(person.$fullName)
}
else {
Text("Record removed")
}
}
}
```
As shown above, the property's value will be `nil` if the object has been deleted, so this can be used to display placeholders if needed.
### SwiftUI Extensions
For convenience, CoreStore provides extensions to the standard SwiftUI types.
#### `ForEach`
Several `ForEach` initializer overloads are available. Choose depending on your input data and the expected closure data. Refer to the table below (Take note of the argument labels as they are important):
<table>
<tr><th>Data</th><th>Example</th></tr>
<tr>
<td>
Signature:
<pre lang=swift>
ForEach(_: [ObjectSnapshot&lt;O&gt;])
</pre>
Closure:
<pre lang=swift>
ObjectSnapshot&lt;O&gt;
</pre>
</td>
<td><pre lang=swift>
let array: [ObjectSnapshot&lt;Person&gt;]
<br />
var body: some View {
<br />
List {
<br />
ForEach(self.array) { objectSnapshot in
<br />
// ...
}
}
}
</pre></td>
</tr>
<tr>
<td>
Signature:
<pre lang=swift>
ForEach(objectIn: ListSnapshot&lt;O&gt;)
</pre>
Closure:
<pre lang=swift>
ObjectPublisher&lt;O&gt;
</pre>
</td>
<td><pre lang=swift>
let listSnapshot: ListSnapshot&lt;Person&gt;
<br />
var body: some View {
<br />
List {
<br />
ForEach(objectIn: self.listSnapshot) { objectPublisher in
<br />
// ...
}
}
}
</pre></td>
</tr>
<tr>
<td>
Signature:
<pre lang=swift>
ForEach(objectIn: [ObjectSnapshot&lt;O&gt;])
</pre>
Closure:
<pre lang=swift>
ObjectPublisher&lt;O&gt;
</pre>
</td>
<td><pre lang=swift>
let array: [ObjectSnapshot&lt;Person&gt;]
<br />
var body: some View {
<br />
List {
<br />
ForEach(objectIn: self.array) { objectPublisher in
<br />
// ...
}
}
}
</pre></td>
</tr>
<tr>
<td>
Signature:
<pre lang=swift>
ForEach(sectionIn: ListSnapshot&lt;O&gt;)
</pre>
Closure:
<pre lang=swift>
[ListSnapshot&lt;O&gt;.SectionInfo]
</pre>
</td>
<td><pre lang=swift>
let listSnapshot: ListSnapshot&lt;Person&gt;
<br />
var body: some View {
<br />
List {
<br />
ForEach(sectionIn: self.listSnapshot) { sectionInfo in
<br />
// ...
}
}
}
</pre></td>
</tr>
<tr>
<td>
Signature:
<pre lang=swift>
ForEach(objectIn: ListSnapshot&lt;O&gt;.SectionInfo)
</pre>
Closure:
<pre lang=swift>
ObjectPublisher&lt;O&gt;
</pre>
</td>
<td><pre lang=swift>
let listSnapshot: ListSnapshot&lt;Person&gt;
<br />
var body: some View {
<br />
List {
<br />
ForEach(sectionIn: self.listSnapshot) { sectionInfo in
<br />
ForEach(objectIn: sectionInfo) { objectPublisher in
<br />
// ...
}
}
}
}
</pre></td>
</tr>
</table>
# Roadmap
### Prototyping stage
- [ ] Widget/Extensions storage-sharing support
- [ ] CloudKit support
### Under consideration
- [ ] Derived attributes
- [ ] Cross-storage relationships (via Fetched Attributes)
# Installation
- Requires:
@@ -2073,7 +2555,7 @@ Once the version lock is set, any changes in the properties or to the model will
### Install with CocoaPods
In your `Podfile`, add
```
pod 'CoreStore', '~> 7.2'
pod 'CoreStore', '~> 8.0'
```
and run
```
@@ -2084,7 +2566,7 @@ This installs CoreStore as a framework. Declare `import CoreStore` in your swift
### Install with Carthage
In your `Cartfile`, add
```
github "JohnEstropia/CoreStore" >= 7.3.0
github "JohnEstropia/CoreStore" >= 8.0.0
```
and run
```
@@ -2095,7 +2577,7 @@ This installs CoreStore as a framework. Declare `import CoreStore` in your swift
#### Install with Swift Package Manager:
```swift
dependencies: [
.package(url: "https://github.com/JohnEstropia/CoreStore.git", from: "7.3.1"))
.package(url: "https://github.com/JohnEstropia/CoreStore.git", from: "8.0.1"))
]
```
Declare `import CoreStore` in your swift file to use the library.

View File

@@ -158,9 +158,19 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
// MARK: Internal
internal init(mainContext: NSManagedObjectContext, queue: DispatchQueue) {
internal init(
mainContext: NSManagedObjectContext,
queue: DispatchQueue,
sourceIdentifier: Any?
) {
super.init(mainContext: mainContext, queue: queue, supportsUndo: false, bypassesQueueing: false)
super.init(
mainContext: mainContext,
queue: queue,
supportsUndo: false,
bypassesQueueing: false,
sourceIdentifier: sourceIdentifier
)
}
internal func autoCommit(_ completion: @escaping (_ hasChanges: Bool, _ error: CoreStoreError?) -> Void) {
@@ -168,12 +178,15 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
self.isCommitted = true
let group = DispatchGroup()
group.enter()
self.context.saveAsynchronouslyWithCompletion { (hasChanges, error) -> Void in
completion(hasChanges, error)
self.result = (hasChanges, error)
group.leave()
}
self.context.saveAsynchronously(
sourceIdentifier: self.sourceIdentifier,
completion: { (hasChanges, error) -> Void in
completion(hasChanges, error)
self.result = (hasChanges, error)
group.leave()
}
)
group.wait()
self.context.reset()
}

View File

@@ -412,6 +412,11 @@ public /*abstract*/ class BaseDataTransaction {
// MARK: 3rd Party Utilities
/**
An arbitrary value that identifies the source of this transaction. Callers of the transaction can provide this value through the `DataStack.perform(...)` methods.
*/
public let sourceIdentifier: Any?
/**
Allow external libraries to store custom data in the transaction. App code should rarely have a need for this.
```
@@ -435,7 +440,13 @@ public /*abstract*/ class BaseDataTransaction {
internal var isCommitted = false
internal var result: (hasChanges: Bool, error: CoreStoreError?)?
internal init(mainContext: NSManagedObjectContext, queue: DispatchQueue, supportsUndo: Bool, bypassesQueueing: Bool) {
internal init(
mainContext: NSManagedObjectContext,
queue: DispatchQueue,
supportsUndo: Bool,
bypassesQueueing: Bool,
sourceIdentifier: Any?
) {
let context = mainContext.temporaryContextInTransactionWithConcurrencyType(
queue == .main
@@ -446,6 +457,7 @@ public /*abstract*/ class BaseDataTransaction {
self.context = context
self.supportsUndo = supportsUndo
self.bypassesQueueing = bypassesQueueing
self.sourceIdentifier = sourceIdentifier
context.parentTransaction = self
context.isTransactionContext = true

View File

@@ -1,107 +0,0 @@
//
// CSCoreStore+Migrating.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - CSCoreStore
@available(*, deprecated, message: "Call methods directly from the CSDataStack instead")
extension CSCoreStore {
/**
Asynchronously adds a `CSInMemoryStore` to the `defaultStack`. Migrations are also initiated by default.
```
NSError *error;
NSProgress *migrationProgress = [dataStack
addInMemoryStorage:[CSInMemoryStore new]
completion:^(CSSetupResult *result) {
if (result.isSuccess) {
// ...
}
}
error: &error];
```
- parameter storage: the `CSInMemoryStore` instance
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `CSSetupResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a failure `CSSetupResult` result if an error occurs asynchronously.
*/
public static func addInMemoryStorage(_ storage: CSInMemoryStore, completion: @escaping (CSSetupResult) -> Void) {
self.defaultStack.addInMemoryStorage(storage, completion: completion)
}
/**
Asynchronously adds a `CSSQLiteStore` to the `defaultStack`. Migrations are also initiated by default.
```
NSError *error;
NSProgress *migrationProgress = [dataStack
addInMemoryStorage:[[CSSQLiteStore alloc]
initWithFileName:@"core_data.sqlite"
configuration:@"Config1"]
completion:^(CSSetupResult *result) {
if (result.isSuccess) {
// ...
}
}
error: &error];
```
- parameter storage: the `CSSQLiteStore` instance
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `CSSetupResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a failure `CSSetupResult` result if an error occurs asynchronously. Note that the `CSLocalStorage` associated to the `-[CSSetupResult storage]` may not always be the same instance as the parameter argument if a previous `CSLocalStorage` was already added at the same URL and with the same configuration.
- parameter error: the `NSError` pointer that indicates the reason in case of an failure
- returns: an `NSProgress` instance if a migration has started. `nil` if no migrations are required or if `error` was set.
*/
public static func addSQLiteStorage(_ storage: CSSQLiteStore, completion: @escaping (CSSetupResult) -> Void, error: NSErrorPointer) -> Progress? {
return self.defaultStack.addSQLiteStorage(storage, completion: completion, error: error)
}
/**
Migrates a `CSSQLiteStore` to match the `defaultStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
- parameter storage: the `CSSQLiteStore` instance
- parameter completion: the closure to be executed on the main queue when the migration completes, either due to success or failure. The closure's `CSMigrationResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a failure `CSSetupResult` result if an error occurs asynchronously.
- parameter error: the `NSError` pointer that indicates the reason in case of an failure
- returns: an `NSProgress` instance if a migration has started. `nil` if no migrations are required or if `error` was set.
*/
@objc
public static func upgradeStorageIfNeeded(_ storage: CSSQLiteStore, completion: @escaping (CSMigrationResult) -> Void, error: NSErrorPointer) -> Progress? {
return self.defaultStack.upgradeStorageIfNeeded(storage, completion: completion, error: error)
}
/**
Checks the migration steps required for the `CSSQLiteStore` to match the `defaultStack`'s managed object model version.
- parameter storage: the `CSSQLiteStore` instance
- parameter error: the `NSError` pointer that indicates the reason in case of an failure
- returns: a `CSMigrationType` array indicating the migration steps required for the store, or an empty array if the file does not exist yet. Otherwise, `nil` is returned and the `error` argument is set if either inspection of the store failed, or if no mapping model was found/inferred.
*/
@objc
public static func requiredMigrationsForSQLiteStore(_ storage: CSSQLiteStore, error: NSErrorPointer) -> [CSMigrationType]? {
return self.defaultStack.requiredMigrationsForSQLiteStore(storage, error: error)
}
}

View File

@@ -1,114 +0,0 @@
//
// CSCoreStore+Observing.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - CSCoreStore
@available(*, deprecated, message: "Call methods directly from the CSDataStack instead")
@available(macOS 10.12, *)
extension CSCoreStore {
/**
Using the `defaultStack`, creates an `CSObjectMonitor` for the specified `NSManagedObject`. Multiple `CSObjectObserver`s may then register themselves to be notified when changes are made to the `NSManagedObject`.
- parameter object: the `NSManagedObject` to observe changes from
- returns: a `CSObjectMonitor` that monitors changes to `object`
*/
@objc
public static func monitorObject(_ object: NSManagedObject) -> CSObjectMonitor {
return self.defaultStack.monitorObject(object)
}
/**
Using the `defaultStack`, creates a `CSListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `CSListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `CSFrom` clause indicating the entity type
- parameter fetchClauses: a series of `CSFetchClause` instances for fetching the object list. Accepts `CSWhere`, `CSOrderBy`, and `CSTweak` clauses.
- returns: a `CSListMonitor` instance that monitors changes to the list
*/
@objc
public static func monitorListFrom(_ from: CSFrom, fetchClauses: [CSFetchClause]) -> CSListMonitor {
return self.defaultStack.monitorListFrom(from, fetchClauses: fetchClauses)
}
/**
Using the `defaultStack`, asynchronously creates a `CSListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `CSListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
- parameter createAsynchronously: the closure that receives the created `CSListMonitor` instance
- parameter from: a `CSFrom` clause indicating the entity type
- parameter fetchClauses: a series of `CSFetchClause` instances for fetching the object list. Accepts `CSWhere`, `CSOrderBy`, and `CSTweak` clauses.
*/
@objc
public static func monitorListByCreatingAsynchronously(_ createAsynchronously: @escaping (CSListMonitor) -> Void, from: CSFrom, fetchClauses: [CSFetchClause]) {
return self.defaultStack.monitorListByCreatingAsynchronously(
createAsynchronously,
from: from,
fetchClauses: fetchClauses
)
}
/**
Using the `defaultStack`, creates a `CSListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `CSListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `CSFrom` clause indicating the entity type
- parameter sectionBy: a `CSSectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `CSFetchClause` instances for fetching the object list. Accepts `CSWhere`, `CSOrderBy`, and `CSTweak` clauses.
- returns: a `CSListMonitor` instance that monitors changes to the list
*/
@objc
public static func monitorSectionedListFrom(_ from: CSFrom, sectionBy: CSSectionBy, fetchClauses: [CSFetchClause]) -> CSListMonitor {
return self.defaultStack.monitorSectionedListFrom(
from,
sectionBy: sectionBy,
fetchClauses: fetchClauses
)
}
/**
Using the `defaultStack`, asynchronously creates a `CSListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `CSListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
- parameter createAsynchronously: the closure that receives the created `CSListMonitor` instance
- parameter from: a `CSFrom` clause indicating the entity type
- parameter sectionBy: a `CSSectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `CSFetchClause` instances for fetching the object list. Accepts `CSWhere`, `CSOrderBy`, and `CSTweak` clauses.
*/
@objc
public static func monitorSectionedListByCreatingAsynchronously(_ createAsynchronously: @escaping (CSListMonitor) -> Void, from: CSFrom, sectionBy: CSSectionBy, fetchClauses: [CSFetchClause]) {
self.defaultStack.monitorSectionedListByCreatingAsynchronously(
createAsynchronously,
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses
)
}
}

View File

@@ -1,179 +0,0 @@
//
// CSCoreStore+Querying.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - CSCoreStore
@available(*, deprecated, message: "Call methods directly from the DataStack instead")
extension CSCoreStore {
/**
Using the `defaultStack`, fetches the `NSManagedObject` instance in the transaction's context from a reference created from a transaction or from a different managed object context.
- parameter object: a reference to the object created/fetched outside the transaction
- returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found.
*/
@objc
public static func fetchExistingObject(_ object: NSManagedObject) -> Any? {
return self.defaultStack.fetchExistingObject(object)
}
/**
Using the `defaultStack`, fetches the `NSManagedObject` instance in the transaction's context from an `NSManagedObjectID`.
- parameter objectID: the `NSManagedObjectID` for the object
- returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found.
*/
@objc
public static func fetchExistingObjectWithID(_ objectID: NSManagedObjectID) -> Any? {
return self.defaultStack.fetchExistingObjectWithID(objectID)
}
/**
Using the `defaultStack`, fetches the `NSManagedObject` instances in the transaction's context from references created from a transaction or from a different managed object context.
- parameter objects: an array of `NSManagedObject`s created/fetched outside the transaction
- returns: the `NSManagedObject` array for objects that exists in the transaction
*/
@objc
public static func fetchExistingObjects(_ objects: [NSManagedObject]) -> [Any] {
return self.defaultStack.fetchExistingObjects(objects)
}
/**
Using the `defaultStack`, fetches the `NSManagedObject` instances in the transaction's context from a list of `NSManagedObjectID`.
- parameter objectIDs: the `NSManagedObjectID` array for the objects
- returns: the `NSManagedObject` array for objects that exists in the transaction
*/
@objc
public static func fetchExistingObjectsWithIDs(_ objectIDs: [NSManagedObjectID]) -> [Any] {
return self.defaultStack.fetchExistingObjectsWithIDs(objectIDs)
}
/**
Using the `defaultStack`, fetches the first `NSManagedObject` instance that satisfies the specified `CSFetchClause`s. Accepts `CSWhere`, `CSOrderBy`, and `CSTweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `CSFetchClause` instances for the fetch request. Accepts `CSWhere`, `CSOrderBy`, and `CSTweak` clauses.
- returns: the first `NSManagedObject` instance that satisfies the specified `CSFetchClause`s
*/
@objc
public static func fetchOneFrom(_ from: CSFrom, fetchClauses: [CSFetchClause]) -> Any? {
return self.defaultStack.fetchOneFrom(from, fetchClauses: fetchClauses)
}
/**
Using the `defaultStack`, fetches all `NSManagedObject` instances that satisfy the specified `CSFetchClause`s. Accepts `CSWhere`, `CSOrderBy`, and `CSTweak` clauses.
- parameter from: a `CSFrom` clause indicating the entity type
- parameter fetchClauses: a series of `CSFetchClause` instances for the fetch request. Accepts `CSWhere`, `CSOrderBy`, and `CSTweak` clauses.
- returns: all `NSManagedObject` instances that satisfy the specified `CSFetchClause`s
*/
@objc
public static func fetchAllFrom(_ from: CSFrom, fetchClauses: [CSFetchClause]) -> [Any]? {
return self.defaultStack.fetchAllFrom(from, fetchClauses: fetchClauses)
}
/**
Using the `defaultStack`, fetches the number of `NSManagedObject`s that satisfy the specified `CSFetchClause`s. Accepts `CSWhere`, `CSOrderBy`, and `CSTweak` clauses.
- parameter from: a `CSFrom` clause indicating the entity type
- parameter fetchClauses: a series of `CSFetchClause` instances for the fetch request. Accepts `CSWhere`, `CSOrderBy`, and `CSTweak` clauses.
- returns: the number `NSManagedObject`s that satisfy the specified `CSFetchClause`s
*/
@objc
public static func fetchCountFrom(_ from: CSFrom, fetchClauses: [CSFetchClause]) -> NSNumber? {
return self.defaultStack.fetchCountFrom(from, fetchClauses: fetchClauses)
}
/**
Using the `defaultStack`, fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `CSFetchClause`s. Accepts `CSWhere`, `CSOrderBy`, and `CSTweak` clauses.
- parameter from: a `CSFrom` clause indicating the entity type
- parameter fetchClauses: a series of `CSFetchClause` instances for the fetch request. Accepts `CSWhere`, `CSOrderBy`, and `CSTweak` clauses.
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `CSFetchClause`s
*/
@objc
public static func fetchObjectIDFrom(_ from: CSFrom, fetchClauses: [CSFetchClause]) -> NSManagedObjectID? {
return self.defaultStack.fetchObjectIDFrom(from, fetchClauses: fetchClauses)
}
/**
Using the `defaultStack`, fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `CSFetchClause`s. Accepts `CSWhere`, `CSOrderBy`, and `CSTweak` clauses.
- parameter from: a `CSFrom` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `CSWhere`, `CSOrderBy`, and `CSTweak` clauses.
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `CSFetchClause`s
*/
@objc
public static func fetchObjectIDsFrom(_ from: CSFrom, fetchClauses: [CSFetchClause]) -> [NSManagedObjectID]? {
return self.defaultStack.fetchObjectIDsFrom(from, fetchClauses: fetchClauses)
}
/**
Using the `defaultStack`, queries aggregate values as specified by the `CSQueryClause`s. Requires at least a `CSSelect` clause, and optional `CSWhere`, `CSOrderBy`, `CSGroupBy`, and `CSTweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `CSFrom` clause indicating the entity type
- parameter selectClause: a `CSSelect` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `CSQueryClause` instances for the query request. Accepts `CSWhere`, `CSOrderBy`, `CSGroupBy`, and `CSTweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `CSSelect` parameter.
*/
@objc
public static func queryValueFrom(_ from: CSFrom, selectClause: CSSelect, queryClauses: [CSQueryClause]) -> Any? {
return self.defaultStack.queryValueFrom(from, selectClause: selectClause, queryClauses: queryClauses)
}
/**
Using the `defaultStack`, queries a dictionary of attribute values as specified by the `CSQueryClause`s. Requires at least a `CSSelect` clause, and optional `CSWhere`, `CSOrderBy`, `CSGroupBy`, and `CSTweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `CSFrom` clause indicating the entity type
- parameter selectClause: a `CSSelect` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `CSQueryClause` instances for the query request. Accepts `CSWhere`, `CSOrderBy`, `CSGroupBy`, and `CSTweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `CSSelect` parameter.
*/
@objc
public static func queryAttributesFrom(_ from: CSFrom, selectClause: CSSelect, queryClauses: [CSQueryClause]) -> [[String: Any]]? {
return self.defaultStack.queryAttributesFrom(from, selectClause: selectClause, queryClauses: queryClauses)
}
}

View File

@@ -1,129 +0,0 @@
//
// CSCoreStore+Setup.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - CSCoreStore
@available(*, deprecated, message: "Call methods directly from the CSDataStack instead")
extension CSCoreStore {
/**
Returns the `defaultStack`'s model version. The version string is the same as the name of the version-specific .xcdatamodeld file.
*/
@objc
public static var modelVersion: String {
return self.defaultStack.modelVersion
}
/**
Returns the entity name-to-class type mapping from the `defaultStack`'s model.
*/
@objc
public static func entityTypesByNameForType(_ type: NSManagedObject.Type) -> [EntityName: NSManagedObject.Type] {
return self.defaultStack.bridgeToSwift.entityTypesByName(for: type)
}
/**
Returns the `NSEntityDescription` for the specified `NSManagedObject` subclass from `defaultStack`'s model.
*/
@objc
public static func entityDescriptionForClass(_ type: NSManagedObject.Type) -> NSEntityDescription? {
return self.defaultStack.bridgeToSwift.entityDescription(for: type)
}
/**
Creates an `CSInMemoryStore` with default parameters and adds it to the `defaultStack`. This method blocks until completion.
```
CSSQLiteStore *storage = [CSCoreStore addInMemoryStorageAndWaitAndReturnError:&error];
```
- parameter error: the `NSError` pointer that indicates the reason in case of an failure
- returns: the `CSInMemoryStore` added to the `defaultStack`
*/
@objc
@discardableResult
public static func addInMemoryStorageAndWaitAndReturnError(_ error: NSErrorPointer) -> CSInMemoryStore? {
return self.defaultStack.addInMemoryStorageAndWaitAndReturnError(error)
}
/**
Creates an `CSSQLiteStore` with default parameters and adds it to the `defaultStack`. This method blocks until completion.
```
CSSQLiteStore *storage = [CSCoreStore addSQLiteStorageAndWaitAndReturnError:&error];
```
- parameter error: the `NSError` pointer that indicates the reason in case of an failure
- returns: the `CSSQLiteStore` added to the `defaultStack`
*/
@objc
@discardableResult
public static func addSQLiteStorageAndWaitAndReturnError(_ error: NSErrorPointer) -> CSSQLiteStore? {
return self.defaultStack.addSQLiteStorageAndWaitAndReturnError(error)
}
/**
Adds a `CSInMemoryStore` to the `defaultStack` and blocks until completion.
```
NSError *error;
CSInMemoryStore *storage = [CSCoreStore
addStorageAndWait: [[CSInMemoryStore alloc] initWithConfiguration: @"Config1"]
error: &error];
```
- parameter storage: the `CSInMemoryStore`
- parameter error: the `NSError` pointer that indicates the reason in case of an failure
- returns: the `CSInMemoryStore` added to the `defaultStack`
*/
@objc
@discardableResult
public static func addInMemoryStorageAndWait(_ storage: CSInMemoryStore, error: NSErrorPointer) -> CSInMemoryStore? {
return self.defaultStack.addInMemoryStorageAndWait(storage, error: error)
}
/**
Adds a `CSSQLiteStore` to the `defaultStack` and blocks until completion.
```
NSError *error;
CSSQLiteStore *storage = [CSCoreStore
addStorageAndWait: [[CSSQLiteStore alloc] initWithConfiguration: @"Config1"]
error: &error];
```
- parameter storage: the `CSSQLiteStore`
- parameter error: the `NSError` pointer that indicates the reason in case of an failure
- returns: the `CSSQLiteStore` added to the `defaultStack`
*/
@objc
@discardableResult
public static func addSQLiteStorageAndWait(_ storage: CSSQLiteStore, error: NSErrorPointer) -> CSSQLiteStore? {
return self.defaultStack.addSQLiteStorageAndWait(storage, error: error)
}
}

View File

@@ -1,95 +0,0 @@
//
// CSCoreStore+Transaction.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - CSCoreStore
@available(*, deprecated, message: "Call methods directly from the CSDataStack instead")
extension CSCoreStore {
/**
Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
*/
@objc
public static func beginAsynchronous(_ closure: @escaping (_ transaction: CSAsynchronousDataTransaction) -> Void) {
self.defaultStack.beginAsynchronous(closure)
}
/**
Using the `defaultStack`, begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made.
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
- returns: `YES` if the commit succeeded, `NO` if the commit failed. If `NO`, the `error` argument will hold error information.
*/
@objc
public static func beginSynchronous(_ closure: @escaping (_ transaction: CSSynchronousDataTransaction) -> Void, error: NSErrorPointer) -> Bool {
return self.defaultStack.beginSynchronous(closure, error: error)
}
/**
Using the `defaultStack`, begins a child transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
To support "undo" methods such as `-undo`, `-redo`, and `-rollback`, use the `-beginSafeWithSupportsUndo:` method passing `YES` to the argument. Without "undo" support, calling those methods will raise an exception.
- returns: a `CSUnsafeDataTransaction` instance where creates, updates, and deletes can be made.
*/
@objc
public static func beginUnsafe() -> CSUnsafeDataTransaction {
return bridge {
self.defaultStack.bridgeToSwift.beginUnsafe()
}
}
/**
Using the `defaultStack`, begins a child transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
- prameter supportsUndo: `-undo`, `-redo`, and `-rollback` methods are only available when this parameter is `YES`, otherwise those method will raise an exception. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
- returns: a `CSUnsafeDataTransaction` instance where creates, updates, and deletes can be made.
*/
@objc
public static func beginUnsafeWithSupportsUndo(_ supportsUndo: Bool) -> CSUnsafeDataTransaction {
return bridge {
self.defaultStack.bridgeToSwift.beginUnsafe(supportsUndo: supportsUndo)
}
}
/**
Refreshes all registered objects `NSManagedObject`s in the `defaultStack`.
*/
@objc
public static func refreshAndMergeAllObjects() {
self.defaultStack.refreshAndMergeAllObjects()
}
}

View File

@@ -30,7 +30,6 @@ import CoreData
// MARK: - CSDataStack
@available(*, deprecated, message: "CoreStore Objective-C API will be removed soon.")
@available(macOS 10.12, *)
extension CSDataStack {
/**

View File

@@ -34,7 +34,6 @@ import CoreData
- SeeAlso: `ListMonitor`
*/
@available(macOS 10.12, *)
@objc
public final class CSListMonitor: NSObject {
@@ -546,7 +545,6 @@ public final class CSListMonitor: NSObject {
// MARK: - ListMonitor
@available(macOS 10.12, *)
extension ListMonitor where ListMonitor.ObjectType: NSManagedObject {
// MARK: CoreStoreSwiftType

View File

@@ -40,7 +40,6 @@ import CoreData
- SeeAlso: `ListObserver`
*/
@available(macOS 10.12, *)
@objc
public protocol CSListObserver: AnyObject {
@@ -91,7 +90,6 @@ public protocol CSListObserver: AnyObject {
- SeeAlso: `ListObjectObserver`
*/
@available(macOS 10.12, *)
@objc
public protocol CSListObjectObserver: CSListObserver {
@@ -152,7 +150,6 @@ public protocol CSListObjectObserver: CSListObserver {
- SeeAlso: `ListSectionObserver`
*/
@available(macOS 10.12, *)
@objc
public protocol CSListSectionObserver: CSListObjectObserver {

View File

@@ -34,7 +34,6 @@ import CoreData
- SeeAlso: `ObjectMonitor`
*/
@available(macOS 10.12, *)
@objc
public final class CSObjectMonitor: NSObject {
@@ -139,7 +138,6 @@ public final class CSObjectMonitor: NSObject {
// MARK: - ObjectMonitor
@available(macOS 10.12, *)
extension ObjectMonitor where ObjectMonitor.ObjectType: NSManagedObject {
// MARK: CoreStoreSwiftType

View File

@@ -38,7 +38,6 @@ import CoreData
- SeeAlso: `ObjectObserver`
*/
@available(macOS 10.12, *)
@objc
public protocol CSObjectObserver: AnyObject {

View File

@@ -34,7 +34,6 @@ import CoreData
- SeeAlso: `SectionBy`
*/
@available(macOS 10.12, *)
@objc
public final class CSSectionBy: NSObject {
@@ -51,16 +50,21 @@ public final class CSSectionBy: NSObject {
}
/**
Initializes a `CSSectionBy` clause with the key path to use to group `CSListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
Initializes a `CSSectionBy` clause with the key path to use to group `CSListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section index title
- parameter sectionKeyPath: the key path to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `CSSectionBy` clause with the key path to use to group `CSListMonitor` objects into sections
*/
@objc
public static func keyPath(_ sectionKeyPath: KeyPathString, sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> CSSectionBy {
return self.init(SectionBy<NSManagedObject>(sectionKeyPath, sectionIndexTransformer))
return self.init(
SectionBy<NSManagedObject>(
sectionKeyPath,
sectionIndexTransformer: sectionIndexTransformer
)
)
}
@@ -86,7 +90,6 @@ public final class CSSectionBy: NSObject {
// MARK: - SectionBy
@available(macOS 10.12, *)
extension SectionBy {
// MARK: CoreStoreSwiftType
@@ -101,6 +104,9 @@ extension SectionBy {
fileprivate func downcast() -> SectionBy<NSManagedObject> {
return SectionBy<NSManagedObject>(self.sectionKeyPath, self.sectionIndexTransformer)
return SectionBy<NSManagedObject>(
self.sectionKeyPath,
sectionIndexTransformer: self.sectionIndexTransformer
)
}
}

View File

@@ -49,7 +49,10 @@ public final class CSSynchronousDataTransaction: CSBaseDataTransaction, CoreStor
return bridge(error) {
if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(waitForMerge: true) {
if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(
waitForMerge: true,
sourceIdentifier: nil
) {
throw error
}

View File

@@ -46,21 +46,24 @@ public final class CSUnsafeDataTransaction: CSBaseDataTransaction, CoreStoreObje
@objc
public func commitWithSuccess(_ success: (() -> Void)?, _ failure: ((CSError) -> Void)?) {
self.bridgeToSwift.context.saveAsynchronouslyWithCompletion { (_, error) in
defer {
withExtendedLifetime(self, {})
self.bridgeToSwift.context.saveAsynchronously(
sourceIdentifier: nil,
completion: { (_, error) in
defer {
withExtendedLifetime(self, {})
}
if let error = error {
failure?(error.bridgeToObjectiveC)
}
else {
success?()
}
}
if let error = error {
failure?(error.bridgeToObjectiveC)
}
else {
success?()
}
}
)
}
/**
@@ -71,10 +74,13 @@ public final class CSUnsafeDataTransaction: CSBaseDataTransaction, CoreStoreObje
*/
@objc
public func commitAndWait(error: NSErrorPointer) -> Bool {
return bridge(error) {
if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(waitForMerge: true) {
if case (_, let error?) = self.bridgeToSwift.context.saveSynchronously(
waitForMerge: true,
sourceIdentifier: nil
) {
throw error
}

View File

@@ -33,7 +33,7 @@ import CoreData
Objective-C Foundation types that are natively supported by Core Data managed attributes all conform to `CoreDataNativeType`.
*/
@objc
public protocol CoreDataNativeType: AnyObject, NSObjectProtocol {}
public protocol CoreDataNativeType: NSObjectProtocol {}
// MARK: - NSNumber

View File

@@ -359,7 +359,6 @@ extension UnsafeDataModelSchema: CustomDebugStringConvertible, CoreStoreDebugStr
// MARK: - ListMonitor
@available(macOS 10.12, *)
fileprivate struct CoreStoreFetchedSectionInfoWrapper: CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
@@ -404,7 +403,6 @@ fileprivate struct CoreStoreFetchedSectionInfoWrapper: CoreStoreDebugStringConve
private let numberOfObjects: Int
}
@available(macOS 10.12, *)
extension ListMonitor: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
@@ -431,7 +429,6 @@ extension ListMonitor: CustomDebugStringConvertible, CoreStoreDebugStringConvert
// MARK: - ListPublisher
@available(macOS 10.12, *)
extension ListPublisher: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
@@ -614,7 +611,6 @@ extension MigrationType: CoreStoreDebugStringConvertible {
// MARK: - ObjectMonitor
@available(macOS 10.12, *)
extension ObjectMonitor: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
@@ -738,7 +734,6 @@ extension PartialObject: CustomDebugStringConvertible, CoreStoreDebugStringConve
// MARK: - SectionBy
@available(macOS 10.12, *)
extension SectionBy: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
@@ -1259,33 +1254,20 @@ extension NSEntityDescription: CoreStoreDebugStringConvertible {
public var coreStoreDumpString: String {
var info: DumpInfo = [
return createFormattedString(
"(", ")",
("managedObjectClassName", self.managedObjectClassName!),
("name", self.name as Any),
("indexes", self.indexes),
("isAbstract", self.isAbstract),
("superentity?.name", self.superentity?.name as Any),
("subentities", self.subentities.map({ $0.name })),
("properties", self.properties),
("uniquenessConstraints", self.uniquenessConstraints),
("userInfo", self.userInfo as Any),
("versionHash", self.versionHash),
("versionHashModifier", self.versionHashModifier as Any),
("renamingIdentifier", self.renamingIdentifier as Any)
]
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
info.append(("indexes", self.indexes))
}
else {
info.append(("compoundIndexes", self.compoundIndexes))
}
if #available(macOS 10.11, iOS 9.0, *) {
info.append(("uniquenessConstraints", self.uniquenessConstraints))
}
return createFormattedString(
"(", ")",
info
)
}
}

View File

@@ -1,102 +0,0 @@
//
// CoreStore+Migration.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - CoreStore
@available(*, deprecated, message: "Call methods directly from the DataStack instead")
extension CoreStore {
/**
Asynchronously adds a `StorageInterface` to the `CoreStoreDefaults.dataStack`. Migrations are also initiated by default.
```
dataStack.addStorage(
InMemoryStore(configuration: "Config1"),
completion: { result in
switch result {
case .success(let storage): // ...
case .failure(let error): // ...
}
}
)
```
- parameter storage: the storage
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `StorageInterface` associated to the `SetupResult.success` may not always be the same instance as the parameter argument if a previous `StorageInterface` was already added at the same URL and with the same configuration.
*/
public static func addStorage<T>(_ storage: T, completion: @escaping (SetupResult<T>) -> Void) {
CoreStoreDefaults.dataStack.addStorage(storage, completion: completion)
}
/**
Asynchronously adds a `LocalStorage` to the `CoreStoreDefaults.dataStack`. Migrations are also initiated by default.
```
let migrationProgress = dataStack.addStorage(
SQLiteStore(fileName: "core_data.sqlite", configuration: "Config1"),
completion: { result in
switch result {
case .success(let storage): // ...
case .failure(let error): // ...
}
}
)
```
- parameter storage: the local storage
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `LocalStorage` associated to the `SetupResult.success` may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration.
- returns: a `Progress` instance if a migration has started, or `nil` if either no migrations are required or if a failure occured.
*/
public static func addStorage<T: LocalStorage>(_ storage: T, completion: @escaping (SetupResult<T>) -> Void) -> Progress? {
return CoreStoreDefaults.dataStack.addStorage(storage, completion: completion)
}
/**
Migrates a local storage to match the `CoreStoreDefaults.dataStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
- parameter storage: the local storage
- parameter completion: the closure to be executed on the main queue when the migration completes, either due to success or failure. The closure's `MigrationResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.failure` result if an error occurs asynchronously.
- throws: a `CoreStoreError` value indicating the failure
- returns: a `Progress` instance if a migration has started, or `nil` is no migrations are required
*/
public static func upgradeStorageIfNeeded<T: LocalStorage>(_ storage: T, completion: @escaping (MigrationResult) -> Void) throws -> Progress? {
return try CoreStoreDefaults.dataStack.upgradeStorageIfNeeded(storage, completion: completion)
}
/**
Checks the migration steps required for the storage to match the `CoreStoreDefaults.dataStack`'s managed object model version.
- parameter storage: the local storage
- throws: a `CoreStoreError` value indicating the failure
- returns: a `MigrationType` array indicating the migration steps required for the store, or an empty array if the file does not exist yet. Otherwise, an error is thrown if either inspection of the store failed, or if no mapping model was found/inferred.
*/
public static func requiredMigrationsForStorage<T: LocalStorage>(_ storage: T) throws -> [MigrationType] {
return try CoreStoreDefaults.dataStack.requiredMigrationsForStorage(storage)
}
}

View File

@@ -1,236 +0,0 @@
//
// CoreStore+Observing.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - CoreStore
@available(*, deprecated, message: "Call methods directly from the DataStack instead")
@available(macOS 10.12, *)
extension CoreStore {
/**
Using the `CoreStoreDefaults.dataStack`, creates an `ObjectMonitor` for the specified `DynamicObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `DynamicObject`.
- parameter object: the `DynamicObject` to observe changes from
- returns: an `ObjectMonitor` that monitors changes to `object`
*/
public static func monitorObject<O: DynamicObject>(_ object: O) -> ObjectMonitor<O> {
return CoreStoreDefaults.dataStack.monitorObject(object)
}
/**
Using the `CoreStoreDefaults.dataStack`, creates a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public static func monitorList<O>(_ from: From<O>, _ fetchClauses: FetchClause...) -> ListMonitor<O> {
return CoreStoreDefaults.dataStack.monitorList(from, fetchClauses)
}
/**
Using the `CoreStoreDefaults.dataStack`, creates a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public static func monitorList<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) -> ListMonitor<O> {
return CoreStoreDefaults.dataStack.monitorList(from, fetchClauses)
}
/**
Creates a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses.
```
let monitor = dataStack.monitorList(
From<MyPersonEntity>()
.where(\.age > 18)
.orderBy(.ascending(\.age))
)
```
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
- returns: a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified `FetchChainableBuilderType`
*/
public static func monitorList<B: FetchChainableBuilderType>(_ clauseChain: B) -> ListMonitor<B.ObjectType> {
return CoreStoreDefaults.dataStack.monitorList(clauseChain.from, clauseChain.fetchClauses)
}
/**
Using the `CoreStoreDefaults.dataStack`, asynchronously creates a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public static func monitorList<O>(createAsynchronously: @escaping (ListMonitor<O>) -> Void, _ from: From<O>, _ fetchClauses: FetchClause...) {
CoreStoreDefaults.dataStack.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
}
/**
Using the `CoreStoreDefaults.dataStack`, asynchronously creates a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public static func monitorList<O>(createAsynchronously: @escaping (ListMonitor<O>) -> Void, _ from: From<O>, _ fetchClauses: [FetchClause]) {
CoreStoreDefaults.dataStack.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
}
/**
Asynchronously creates a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
```
dataStack.monitorList(
createAsynchronously: { (monitor) in
self.monitor = monitor
},
From<MyPersonEntity>()
.where(\.age > 18)
.orderBy(.ascending(\.age))
)
```
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
*/
public static func monitorList<B: FetchChainableBuilderType>(createAsynchronously: @escaping (ListMonitor<B.ObjectType>) -> Void, _ clauseChain: B) {
CoreStoreDefaults.dataStack.monitorList(
createAsynchronously: createAsynchronously,
clauseChain.from,
clauseChain.fetchClauses
)
}
/**
Using the `CoreStoreDefaults.dataStack`, creates a `ListMonitor` for a sectioned list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public static func monitorSectionedList<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: FetchClause...) -> ListMonitor<O> {
return CoreStoreDefaults.dataStack.monitorSectionedList(from, sectionBy, fetchClauses)
}
/**
Using the `CoreStoreDefaults.dataStack`, creates a `ListMonitor` for a sectioned list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public static func monitorSectionedList<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: [FetchClause]) -> ListMonitor<O> {
return CoreStoreDefaults.dataStack.monitorSectionedList(from, sectionBy, fetchClauses)
}
/**
Creates a `ListMonitor` for a sectioned list of `DynamicObject`s that satisfy the specified `SectionMonitorBuilderType` built from a chain of clauses.
```
let monitor = dataStack.monitorSectionedList(
From<MyPersonEntity>()
.sectionBy(\.age, { "\($0!) years old" })
.where(\.age > 18)
.orderBy(.ascending(\.age))
)
```
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
- returns: a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified `SectionMonitorBuilderType`
*/
public static func monitorSectionedList<B: SectionMonitorBuilderType>(_ clauseChain: B) -> ListMonitor<B.ObjectType> {
return CoreStoreDefaults.dataStack.monitorSectionedList(
clauseChain.from,
clauseChain.sectionBy,
clauseChain.fetchClauses
)
}
/**
Using the `CoreStoreDefaults.dataStack`, asynchronously creates a `ListMonitor` for a sectioned list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public static func monitorSectionedList<O>(createAsynchronously: @escaping (ListMonitor<O>) -> Void, _ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: FetchClause...) {
CoreStoreDefaults.dataStack.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
}
/**
Using the `CoreStoreDefaults.dataStack`, asynchronously creates a `ListMonitor` for a sectioned list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public static func monitorSectionedList<O>(createAsynchronously: @escaping (ListMonitor<O>) -> Void, _ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: [FetchClause]) {
CoreStoreDefaults.dataStack.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
}
/**
Asynchronously creates a `ListMonitor` for a sectioned list of `DynamicObject`s that satisfy the specified `SectionMonitorBuilderType` built from a chain of clauses.
```
dataStack.monitorSectionedList(
createAsynchronously: { (monitor) in
self.monitor = monitor
},
From<MyPersonEntity>()
.sectionBy(\.age, { "\($0!) years old" })
.where(\.age > 18)
.orderBy(.ascending(\.age))
)
```
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
*/
public static func monitorSectionedList<B: SectionMonitorBuilderType>(createAsynchronously: @escaping (ListMonitor<B.ObjectType>) -> Void, _ clauseChain: B) {
CoreStoreDefaults.dataStack.monitorSectionedList(
createAsynchronously: createAsynchronously,
clauseChain.from,
clauseChain.sectionBy,
clauseChain.fetchClauses
)
}
}

View File

@@ -1,411 +0,0 @@
//
// CoreStore+Querying.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - CoreStore
@available(*, deprecated, message: "Call methods directly from the DataStack instead")
extension CoreStore {
/**
Using the `CoreStoreDefaults.dataStack`, fetches the `DynamicObject` instance in the `DataStack`'s context from a reference created from a transaction or from a different managed object context.
- parameter object: a reference to the object created/fetched outside the `DataStack`
- returns: the `DynamicObject` instance if the object exists in the `DataStack`, or `nil` if not found.
*/
public static func fetchExisting<O: DynamicObject>(_ object: O) -> O? {
return CoreStoreDefaults.dataStack.fetchExisting(object)
}
/**
Using the `CoreStoreDefaults.dataStack`, fetches the `DynamicObject` instance in the `DataStack`'s context from an `NSManagedObjectID`.
- parameter objectID: the `NSManagedObjectID` for the object
- returns: the `DynamicObject` instance if the object exists in the `DataStack`, or `nil` if not found.
*/
public static func fetchExisting<O: DynamicObject>(_ objectID: NSManagedObjectID) -> O? {
return CoreStoreDefaults.dataStack.fetchExisting(objectID)
}
/**
Using the `CoreStoreDefaults.dataStack`, fetches the `DynamicObject` instances in the `DataStack`'s context from references created from a transaction or from a different managed object context.
- parameter objects: an array of `DynamicObject`s created/fetched outside the `DataStack`
- returns: the `DynamicObject` array for objects that exists in the `DataStack`
*/
public static func fetchExisting<O: DynamicObject, S: Sequence>(_ objects: S) -> [O] where S.Iterator.Element == O {
return CoreStoreDefaults.dataStack.fetchExisting(objects)
}
/**
Using the `CoreStoreDefaults.dataStack`, fetches the `DynamicObject` instances in the `DataStack`'s context from a list of `NSManagedObjectID`.
- parameter objectIDs: the `NSManagedObjectID` array for the objects
- returns: the `DynamicObject` array for objects that exists in the `DataStack`
*/
public static func fetchExisting<O: DynamicObject, S: Sequence>(_ objectIDs: S) -> [O] where S.Iterator.Element == NSManagedObjectID {
return CoreStoreDefaults.dataStack.fetchExisting(objectIDs)
}
/**
Using the `CoreStoreDefaults.dataStack`, fetches the first `DynamicObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the first `DynamicObject` instance that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchOne<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> O? {
return try CoreStoreDefaults.dataStack.fetchOne(from, fetchClauses)
}
/**
Using the `CoreStoreDefaults.dataStack`, fetches the first `DynamicObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the first `DynamicObject` instance that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchOne<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> O? {
return try CoreStoreDefaults.dataStack.fetchOne(from, fetchClauses)
}
/**
Fetches the first `DynamicObject` instance that satisfies the specified `FetchChainableBuilderType` built from a chain of clauses.
```
let youngestTeen = dataStack.fetchOne(
From<MyPersonEntity>()
.where(\.age > 18)
.orderBy(.ascending(\.age))
)
```
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
- returns: the first `DynamicObject` instance that satisfies the specified `FetchChainableBuilderType`, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchOne<B: FetchChainableBuilderType>(_ clauseChain: B) throws -> B.ObjectType? {
return try CoreStoreDefaults.dataStack.fetchOne(clauseChain)
}
/**
Using the `CoreStoreDefaults.dataStack`, fetches all `DynamicObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: all `DynamicObject` instances that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchAll<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> [O] {
return try CoreStoreDefaults.dataStack.fetchAll(from, fetchClauses)
}
/**
Using the `CoreStoreDefaults.dataStack`, fetches all `DynamicObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: all `DynamicObject` instances that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchAll<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> [O] {
return try CoreStoreDefaults.dataStack.fetchAll(from, fetchClauses)
}
/**
Fetches all `DynamicObject` instances that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses.
```
let people = dataStack.fetchAll(
From<MyPersonEntity>()
.where(\.age > 18)
.orderBy(.ascending(\.age))
)
```
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
- returns: all `DynamicObject` instances that satisfy the specified `FetchChainableBuilderType`, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchAll<B: FetchChainableBuilderType>(_ clauseChain: B) throws -> [B.ObjectType] {
return try CoreStoreDefaults.dataStack.fetchAll(clauseChain)
}
/**
Using the `CoreStoreDefaults.dataStack`, fetches the number of `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number of `DynamicObject`s that satisfy the specified `FetchClause`s
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchCount<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> Int {
return try CoreStoreDefaults.dataStack.fetchCount(from, fetchClauses)
}
/**
Using the `CoreStoreDefaults.dataStack`, fetches the number of `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number of `DynamicObject`s that satisfy the specified `FetchClause`s
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchCount<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> Int {
return try CoreStoreDefaults.dataStack.fetchCount(from, fetchClauses)
}
/**
Fetches the number of `DynamicObject`s that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses.
```
let numberOfAdults = dataStack.fetchCount(
From<MyPersonEntity>()
.where(\.age > 18)
.orderBy(.ascending(\.age))
)
```
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
- returns: the number of `DynamicObject`s that satisfy the specified `FetchChainableBuilderType`
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchCount<B: FetchChainableBuilderType>(_ clauseChain: B) throws -> Int {
return try CoreStoreDefaults.dataStack.fetchCount(clauseChain)
}
/**
Using the `CoreStoreDefaults.dataStack`, fetches the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchObjectID<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> NSManagedObjectID? {
return try CoreStoreDefaults.dataStack.fetchObjectID(from, fetchClauses)
}
/**
Using the `CoreStoreDefaults.dataStack`, fetches the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchObjectID<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> NSManagedObjectID? {
return try CoreStoreDefaults.dataStack.fetchObjectID(from, fetchClauses)
}
/**
Fetches the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchChainableBuilderType` built from a chain of clauses.
```
let youngestTeenID = dataStack.fetchObjectID(
From<MyPersonEntity>()
.where(\.age > 18)
.orderBy(.ascending(\.age))
)
```
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
- returns: the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchChainableBuilderType`, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchObjectID<B: FetchChainableBuilderType>(_ clauseChain: B) throws -> NSManagedObjectID? {
return try CoreStoreDefaults.dataStack.fetchObjectID(clauseChain)
}
/**
Using the `CoreStoreDefaults.dataStack`, fetches the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchObjectIDs<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> [NSManagedObjectID] {
return try CoreStoreDefaults.dataStack.fetchObjectIDs(from, fetchClauses)
}
/**
Using the `CoreStoreDefaults.dataStack`, fetches the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchObjectIDs<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> [NSManagedObjectID] {
return try CoreStoreDefaults.dataStack.fetchObjectIDs(from, fetchClauses)
}
/**
Fetches the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses.
```
let idsOfAdults = transaction.fetchObjectIDs(
From<MyPersonEntity>()
.where(\.age > 18)
.orderBy(.ascending(\.age))
)
```
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
- returns: the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchChainableBuilderType`, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchObjectIDs<B: FetchChainableBuilderType>(_ clauseChain: B) throws -> [NSManagedObjectID] {
return try CoreStoreDefaults.dataStack.fetchObjectIDs(clauseChain)
}
/**
Using the `CoreStoreDefaults.dataStack`, queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `From` clause indicating the entity type
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query, or `nil` if no match was found. The type of the return value is specified by the generic type of the `Select<U>` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func queryValue<O, U: QueryableAttributeType>(_ from: From<O>, _ selectClause: Select<O, U>, _ queryClauses: QueryClause...) throws -> U? {
return try CoreStoreDefaults.dataStack.queryValue(from, selectClause, queryClauses)
}
/**
Using the `CoreStoreDefaults.dataStack`, queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `From` clause indicating the entity type
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query, or `nil` if no match was found. The type of the return value is specified by the generic type of the `Select<U>` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func queryValue<O, U: QueryableAttributeType>(_ from: From<O>, _ selectClause: Select<O, U>, _ queryClauses: [QueryClause]) throws -> U? {
return try CoreStoreDefaults.dataStack.queryValue(from, selectClause, queryClauses)
}
/**
Queries a property value or aggregate as specified by the `QueryChainableBuilderType` built from a chain of clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
```
let averageAdultAge = dataStack.queryValue(
From<MyPersonEntity>()
.select(Int.self, .average(\.age))
.where(\.age > 18)
)
```
- parameter clauseChain: a `QueryChainableBuilderType` indicating the property/aggregate to fetch and the series of queries for the request.
- returns: the result of the the query as specified by the `QueryChainableBuilderType`, or `nil` if no match was found.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func queryValue<B: QueryChainableBuilderType>(_ clauseChain: B) throws -> B.ResultType? where B.ResultType: QueryableAttributeType {
return try CoreStoreDefaults.dataStack.queryValue(clauseChain)
}
/**
Using the `CoreStoreDefaults.dataStack`, queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `From` clause indicating the entity type
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func queryAttributes<O>(_ from: From<O>, _ selectClause: Select<O, NSDictionary>, _ queryClauses: QueryClause...) throws -> [[String: Any]] {
return try CoreStoreDefaults.dataStack.queryAttributes(from, selectClause, queryClauses)
}
/**
Using the `CoreStoreDefaults.dataStack`, queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `From` clause indicating the entity type
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func queryAttributes<O>(_ from: From<O>, _ selectClause: Select<O, NSDictionary>, _ queryClauses: [QueryClause]) throws -> [[String: Any]] {
return try CoreStoreDefaults.dataStack.queryAttributes(from, selectClause, queryClauses)
}
/**
Queries a dictionary of attribute values or as specified by the `QueryChainableBuilderType` built from a chain of clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
```
let results = dataStack.queryAttributes(
From<MyPersonEntity>()
.select(
NSDictionary.self,
.attribute(\.age, as: "age"),
.count(\.age, as: "numberOfPeople")
)
.groupBy(\.age)
)
for dictionary in results! {
let age = dictionary["age"] as! Int
let count = dictionary["numberOfPeople"] as! Int
print("There are \(count) people who are \(age) years old."
}
```
- parameter clauseChain: a `QueryChainableBuilderType` indicating the properties to fetch and the series of queries for the request.
- returns: the result of the the query as specified by the `QueryChainableBuilderType`
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func queryAttributes<B: QueryChainableBuilderType>(_ clauseChain: B) throws -> [[String: Any]] where B.ResultType == NSDictionary {
return try CoreStoreDefaults.dataStack.queryAttributes(clauseChain)
}
}

View File

@@ -1,117 +0,0 @@
//
// CoreStore+Setup.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - CoreStore
@available(*, deprecated, message: "Call methods directly from the DataStack instead")
extension CoreStore {
/**
Returns the `CoreStoreDefaults.dataStack`'s model version. The version string is the same as the name of a version-specific .xcdatamodeld file or `CoreStoreSchema`.
*/
public static var modelVersion: String {
return CoreStoreDefaults.dataStack.modelVersion
}
/**
Returns the entity name-to-class type mapping from the `CoreStoreDefaults.dataStack`'s model.
*/
public static func entityTypesByName(for type: NSManagedObject.Type) -> [EntityName: NSManagedObject.Type] {
return CoreStoreDefaults.dataStack.entityTypesByName(for: type)
}
/**
Returns the entity name-to-class type mapping from the `CoreStoreDefaults.dataStack`'s model.
*/
public static func entityTypesByName(for type: CoreStoreObject.Type) -> [EntityName: CoreStoreObject.Type] {
return CoreStoreDefaults.dataStack.entityTypesByName(for: type)
}
/**
Returns the `NSEntityDescription` for the specified `NSManagedObject` subclass from `CoreStoreDefaults.dataStack`'s model.
*/
public static func entityDescription(for type: NSManagedObject.Type) -> NSEntityDescription? {
return CoreStoreDefaults.dataStack.entityDescription(for: type)
}
/**
Returns the `NSEntityDescription` for the specified `CoreStoreObject` subclass from `CoreStoreDefaults.dataStack`'s model.
*/
public static func entityDescription(for type: CoreStoreObject.Type) -> NSEntityDescription? {
return CoreStoreDefaults.dataStack.entityDescription(for: type)
}
/**
Creates an `SQLiteStore` with default parameters and adds it to the `CoreStoreDefaults.dataStack`. This method blocks until completion.
```
try CoreStore.addStorageAndWait()
```
- returns: the local SQLite storage added to the `CoreStoreDefaults.dataStack`
*/
@discardableResult
public static func addStorageAndWait() throws -> SQLiteStore {
return try CoreStoreDefaults.dataStack.addStorageAndWait(SQLiteStore())
}
/**
Adds a `StorageInterface` to the `CoreStoreDefaults.dataStack` and blocks until completion.
```
try CoreStore.addStorageAndWait(InMemoryStore(configuration: "Config1"))
```
- parameter storage: the `StorageInterface`
- throws: a `CoreStoreError` value indicating the failure
- returns: the `StorageInterface` added to the `CoreStoreDefaults.dataStack`
*/
@discardableResult
public static func addStorageAndWait<T: StorageInterface>(_ storage: T) throws -> T {
return try CoreStoreDefaults.dataStack.addStorageAndWait(storage)
}
/**
Adds a `LocalStorage` to the `CoreStoreDefaults.dataStack` and blocks until completion.
```
try CoreStore.addStorageAndWait(SQLiteStore(configuration: "Config1"))
```
- parameter storage: the local storage
- throws: a `CoreStoreError` value indicating the failure
- returns: the local storage added to the `CoreStoreDefaults.dataStack`. Note that this may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration.
*/
@discardableResult
public static func addStorageAndWait<T: LocalStorage>(_ storage: T) throws -> T {
return try CoreStoreDefaults.dataStack.addStorageAndWait(storage)
}
}

View File

@@ -1,88 +0,0 @@
//
// CoreStore+Transaction.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - CoreStore
@available(*, deprecated, message: "Call methods directly from the DataStack instead")
extension CoreStore {
/**
Using the `CoreStoreDefaults.dataStack`, performs a transaction asynchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the wrapped as `.success(T)` in the `completion`'s `Result<T>`. Any errors thrown from inside the `task` will be reported as `.failure(CoreStoreError)`. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
- parameter task: the asynchronous closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
- parameter completion: the closure executed after the save completes. The `Result` argument of the closure will either wrap the return value of `task`, or any uncaught errors thrown from within `task`. Cancelled `task`s will be indicated by `.failure(error: CoreStoreError.userCancelled)`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
*/
public static func perform<T>(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, completion: @escaping (AsynchronousDataTransaction.Result<T>) -> Void) {
CoreStoreDefaults.dataStack.perform(asynchronous: task, completion: completion)
}
/**
Using the `CoreStoreDefaults.dataStack`, performs a transaction asynchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the argument of the `success` closure. Any errors thrown from inside the `task` will be wrapped in a `CoreStoreError` and reported in the `failure` closure. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
- parameter task: the asynchronous closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
- parameter success: the closure executed after the save succeeds. The `T` argument of the closure will be the value returned from `task`.
- parameter failure: the closure executed if the save fails or if any errors are thrown within `task`. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
*/
public static func perform<T>(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, success: @escaping (T) -> Void, failure: @escaping (CoreStoreError) -> Void) {
CoreStoreDefaults.dataStack.perform(asynchronous: task, success: success, failure: failure)
}
/**
Using the `CoreStoreDefaults.dataStack`, performs a transaction synchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the return value of `perform(synchronous:)`. Any errors thrown from inside the `task` will be thrown from `perform(synchronous:)`. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
- parameter task: the synchronous non-escaping closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
- parameter waitForAllObservers: When `true`, this method waits for all observers to be notified of the changes before returning. This results in more predictable data update order, but may risk triggering deadlocks. When `false`, this method does not wait for observers to be notified of the changes before returning. This results in lower risk for deadlocks, but the updated data may not have been propagated to the `DataStack` after returning. Defaults to `true`.
- throws: a `CoreStoreError` value indicating the failure. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
- returns: the value returned from `task`
*/
public static func perform<T>(synchronous task: ((_ transaction: SynchronousDataTransaction) throws -> T), waitForAllObservers: Bool = true) throws -> T {
return try CoreStoreDefaults.dataStack.perform(synchronous: task, waitForAllObservers: waitForAllObservers)
}
/**
Using the `CoreStoreDefaults.dataStack`, begins a non-contiguous transaction where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
- prameter supportsUndo: `undo()`, `redo()`, and `rollback()` methods are only available when this parameter is `true`, otherwise those method will raise an exception. Defaults to `false`. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
- returns: a `UnsafeDataTransaction` instance where creates, updates, and deletes can be made.
*/
public static func beginUnsafe(supportsUndo: Bool = false) -> UnsafeDataTransaction {
return CoreStoreDefaults.dataStack.beginUnsafe(supportsUndo: supportsUndo)
}
/**
Refreshes all registered objects `NSManagedObject`s or `CoreStoreObject`s in the `CoreStoreDefaults.dataStack`.
*/
public static func refreshAndMergeAllObjects() {
CoreStoreDefaults.dataStack.refreshAndMergeAllObjects()
}
}

View File

@@ -1,50 +0,0 @@
//
// CoreStore.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import CoreData
// MARK: - CoreStore
/**
`CoreStore` is the main entry point for all other APIs.
*/
@available(*, deprecated, message: "Call methods directly from the DataStack instead")
public enum CoreStore {
@available(*, unavailable, renamed: "CoreStoreDefaults.logger")
public static var logger: CoreStoreLogger {
get { return CoreStoreDefaults.logger }
set { CoreStoreDefaults.logger = newValue }
}
@available(*, unavailable, renamed: "CoreStoreDefaults.dataStack")
public static var defaultStack: DataStack {
get { return CoreStoreDefaults.dataStack }
set { CoreStoreDefaults.dataStack = newValue }
}
}

View File

@@ -564,18 +564,14 @@ public final class CoreStoreSchema: DynamicSchema {
)
}
for (entity, entityDescription) in entityDescriptionsByEntity {
if entity.uniqueConstraints.contains(where: { !$0.isEmpty }) {
if #available(macOS 10.11, iOS 9.0, *) {
let uniqueConstraints = entity.uniqueConstraints.filter({ !$0.isEmpty })
if !uniqueConstraints.isEmpty {
Internals.assert(
entityDescription.superentity == nil,
"Uniqueness constraints must be defined at the highest level possible."
)
entityDescription.uniquenessConstraints = entity.uniqueConstraints.map { $0.map { $0 as NSString } }
}
Internals.assert(
entityDescription.superentity == nil,
"Uniqueness constraints must be defined at the highest level possible."
)
entityDescription.uniquenessConstraints = entity.uniqueConstraints.map { $0.map { $0 as NSString } }
}
guard !entity.indexes.isEmpty else {
@@ -586,31 +582,18 @@ public final class CoreStoreSchema: DynamicSchema {
entityDescription.coreStoreEntity = entity // reserialize
}
let attributesByName = entityDescription.attributesByName
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
entityDescription.indexes = entity.indexes.map { (compoundIndexes) in
entityDescription.indexes = entity.indexes.map { (compoundIndexes) in
return NSFetchIndexDescription.init(
name: "_CoreStoreSchema_indexes_\(entityDescription.name!)_\(compoundIndexes.joined(separator: "-"))",
elements: compoundIndexes.map { (keyPath) in
return NSFetchIndexElementDescription(
property: attributesByName[keyPath]!,
collationType: .binary
)
}
)
}
}
else {
entityDescription.compoundIndexes = entity.indexes.map { (compoundIndexes) in
return compoundIndexes.map { (keyPath) in
return NSFetchIndexDescription.init(
name: "_CoreStoreSchema_indexes_\(entityDescription.name!)_\(compoundIndexes.joined(separator: "_"))",
elements: compoundIndexes.map { (keyPath) in
return attributesByName[keyPath]!
return NSFetchIndexElementDescription(
property: attributesByName[keyPath]!,
collationType: .binary
)
}
}
)
}
}
}

View File

@@ -56,6 +56,60 @@ extension DataStack {
return context.objectPublisher(objectID: objectID)
}
/**
Creates a `ListPublisher` that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses.
```
let listPublisher = dataStack.listPublisher(
From<MyPersonEntity>()
.where(\.age > 18)
.orderBy(.ascending(\.age))
)
```
Multiple objects may then register themselves to be notified when changes are made to the fetched results.
```
listPublisher.addObserver(self) { (listPublisher) in
// handle changes
}
```
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
- returns: a `ListPublisher` that broadcasts changes to the fetched results
*/
public func publishList<B: FetchChainableBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
return self.publishList(
clauseChain.from,
clauseChain.fetchClauses
)
}
/**
Creates a `ListPublisher` for a sectioned list that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses.
```
let listPublisher = dataStack.listPublisher(
From<MyPersonEntity>()
.sectionBy(\.age, { "\($0!) years old" })
.where(\.age > 18)
.orderBy(.ascending(\.age))
)
```
Multiple objects may then register themselves to be notified when changes are made to the fetched results.
```
listPublisher.addObserver(self) { (listPublisher) in
// handle changes
}
```
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
- returns: a `ListPublisher` that broadcasts changes to the fetched results
*/
public func publishList<B: SectionMonitorBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
return self.publishList(
clauseChain.from,
clauseChain.sectionBy,
clauseChain.fetchClauses
)
}
/**
Creates a `ListPublisher` for the specified `From` and `FetchClause`s. Multiple objects may then register themselves to be notified when changes are made to the fetched results.
@@ -93,32 +147,6 @@ extension DataStack {
)
}
/**
Creates a `ListPublisher` that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses.
```
let listPublisher = dataStack.listPublisher(
From<MyPersonEntity>()
.where(\.age > 18)
.orderBy(.ascending(\.age))
)
```
Multiple objects may then register themselves to be notified when changes are made to the fetched results.
```
listPublisher.addObserver(self) { (listPublisher) in
// handle changes
}
```
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
- returns: a `ListPublisher` that broadcasts changes to the fetched results
*/
public func publishList<B: FetchChainableBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
return self.publishList(
clauseChain.from,
clauseChain.fetchClauses
)
}
/**
Creates a `ListPublisher` for a sectioned list that satisfy the fetch clauses. Multiple objects may then register themselves to be notified when changes are made to the fetched results.
@@ -161,37 +189,9 @@ extension DataStack {
}
)
}
/**
Creates a `ListPublisher` for a sectioned list that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses.
```
let listPublisher = dataStack.listPublisher(
From<MyPersonEntity>()
.sectionBy(\.age, { "\($0!) years old" })
.where(\.age > 18)
.orderBy(.ascending(\.age))
)
```
Multiple objects may then register themselves to be notified when changes are made to the fetched results.
```
listPublisher.addObserver(self) { (listPublisher) in
// handle changes
}
```
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
- returns: a `ListPublisher` that broadcasts changes to the fetched results
*/
public func publishList<B: SectionMonitorBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
return self.publishList(
clauseChain.from,
clauseChain.sectionBy,
clauseChain.fetchClauses
)
}
// MARK: - Deprecated
// MARK: Deprecated
@available(*, deprecated, renamed: "publishObject(_:)")
public func objectPublisher<O: DynamicObject>(_ object: O) -> ObjectPublisher<O> {

View File

@@ -613,16 +613,7 @@ extension DataStack {
}
}
let fileManager = FileManager.default
let systemTemporaryDirectoryURL: URL
if #available(macOS 10.12, iOS 10.0, *) {
systemTemporaryDirectoryURL = fileManager.temporaryDirectory
}
else {
systemTemporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory())
}
let temporaryDirectoryURL = systemTemporaryDirectoryURL
let temporaryDirectoryURL = fileManager.temporaryDirectory
.appendingPathComponent(Bundle.main.bundleIdentifier ?? "com.CoreStore.DataStack")
.appendingPathComponent(ProcessInfo().globallyUniqueString)

View File

@@ -29,7 +29,6 @@ import CoreData
// MARK: - DataStack
@available(macOS 10.12, *)
extension DataStack {
/**

View File

@@ -0,0 +1,384 @@
//
// DataStack+Reactive.swift
// CoreStore
//
// Copyright © 2021 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(Combine)
import Combine
// MARK: - DataStack
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension DataStack {
// MARK: Public
/**
Combine utilities for the `DataStack` are exposed through this namespace
*/
public var reactive: DataStack.ReactiveNamespace {
return .init(self)
}
// MARK: - ReactiveNamespace
/**
Combine utilities for the `DataStack` are exposed through this namespace. Extend this type if you need to add other Combine Publisher utilities for `DataStack`.
*/
public struct ReactiveNamespace {
// MARK: Public
/**
The `DataStack` instance
*/
public let base: DataStack
// MARK: Internal
internal init(_ base: DataStack) {
self.base = base
}
}
}
// MARK: - DataStack.ReactiveNamespace
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension DataStack.ReactiveNamespace {
// MARK: Public
/**
Reactive extension for `CoreStore.DataStack`'s `addStorage(...)` API. Asynchronously adds a `StorageInterface` to the stack.
```
dataStack.reactive
.addStorage(
InMemoryStore(configuration: "Config1")
)
.sink(
receiveCompletion: { result in
// ...
},
receiveValue: { storage in
// ...
}
)
.store(in: &cancellables)
```
- parameter storage: the storage
- returns: A `Future` that emits a `StorageInterface` instance added to the `DataStack`. Note that the `StorageInterface` event value may not always be the same instance as the parameter argument if a previous `StorageInterface` was already added at the same URL and with the same configuration.
*/
public func addStorage<T: StorageInterface>(_ storage: T) -> Future<T, CoreStoreError> {
return .init { (promise) in
self.base.addStorage(
storage,
completion: { (result) in
switch result {
case .success(let storage):
promise(.success(storage))
case .failure(let error):
promise(.failure(error))
}
}
)
}
}
/**
Reactive extension for `CoreStore.DataStack`'s `addStorage(...)` API. Asynchronously adds a `LocalStorage` to the stack. Migrations are also initiated by default. The event emits `DataStack.AddStoragePublisher.MigrationProgress` `enum` values.
```
dataStack.reactive
.addStorage(
SQLiteStore(
fileName: "core_data.sqlite",
configuration: "Config1"
)
)
.sink(
receiveCompletion: { result in
// ...
},
receiveValue: { (progress) in
print("\(round(progress.fractionCompleted * 100)) %") // 0.0 ~ 1.0
}
)
.store(in: &cancellables)
```
- parameter storage: the local storage
- returns: A `DataStack.AddStoragePublisher` that emits a `DataStack.AddStoragePublisher.MigrationProgress` value with metadata for migration progress. Note that the `LocalStorage` event value may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration.
*/
public func addStorage<T: LocalStorage>(_ storage: T) -> DataStack.AddStoragePublisher<T> {
return .init(
dataStack: self.base,
storage: storage
)
}
/**
Reactive extension for `CoreStore.DataStack`'s `importObject(...)` API. Creates an `ImportableObject` by importing from the specified import source. The event value will be the object instance correctly associated for the `DataStack`.
```
dataStack.reactive
.importObject(
Into<Person>(),
source: ["name": "John"]
)
.sink(
receiveCompletion: { result in
// ...
},
receiveValue: { (person) in
XCTAssertNotNil(person)
// ...
}
)
.store(in: &cancellables)
```
- parameter into: an `Into` clause specifying the entity type
- parameter source: the object to import values from
- returns: A `Future` that publishes the imported object. The event value, if not `nil`, will be the object instance correctly associated for the `DataStack`.
*/
public func importObject<O: DynamicObject & ImportableObject>(
_ into: Into<O>,
source: O.ImportSource
) -> Future<O?, CoreStoreError> {
return .init { (promise) in
self.base.perform(
asynchronous: { (transaction) -> O? in
return try transaction.importObject(
into,
source: source
)
},
success: { promise(.success($0.flatMap(self.base.fetchExisting))) },
failure: { promise(.failure($0)) }
)
}
}
/**
Reactive extension for `CoreStore.DataStack`'s `importObject(...)` API. Updates an existing `ImportableObject` by importing values from the specified import source. The event value will be the object instance correctly associated for the `DataStack`.
```
let existingPerson: Person = // ...
dataStack.reactive
.importObject(
existingPerson,
source: ["name": "John", "age": 30]
)
.sink(
receiveCompletion: { result in
// ...
},
receiveValue: { (person) in
XCTAssertEqual(person?.age, 30)
// ...
}
)
.store(in: &cancellables)
```
- parameter object: the object to update
- parameter source: the object to import values from
- returns: A `Future` that publishes the imported object. The event value, if not `nil`, will be the object instance correctly associated for the `DataStack`.
*/
public func importObject<O: DynamicObject & ImportableObject>(
_ object: O,
source: O.ImportSource
) -> Future<O?, CoreStoreError> {
return .init { (promise) in
self.base.perform(
asynchronous: { (transaction) -> O? in
guard let object = transaction.edit(object) else {
try transaction.cancel()
}
try transaction.importObject(
object,
source: source
)
return object
},
success: { promise(.success($0.flatMap(self.base.fetchExisting))) },
failure: { promise(.failure($0)) }
)
}
}
/**
Reactive extension for `CoreStore.DataStack`'s `importUniqueObject(...)` API. Updates an existing `ImportableUniqueObject` or creates a new instance by importing from the specified import source. The event value will be the object instance correctly associated for the `DataStack`.
```
dataStack.reactive
.importUniqueObject(
Into<Person>(),
source: ["name": "John", "age": 30]
)
.sink(
receiveCompletion: { result in
// ...
},
receiveValue: { (person) in
XCTAssertEqual(person?.age, 30)
// ...
}
)
.store(in: &cancellables)
```
- parameter into: an `Into` clause specifying the entity type
- parameter source: the object to import values from
- returns: A `Future` for the imported object. The event value, if not `nil`, will be the object instance correctly associated for the `DataStack`.
*/
public func importUniqueObject<O: DynamicObject & ImportableUniqueObject>(
_ into: Into<O>,
source: O.ImportSource
) -> Future<O?, CoreStoreError> {
return .init { (promise) in
self.base.perform(
asynchronous: { (transaction) -> O? in
return try transaction.importUniqueObject(
into,
source: source
)
},
success: { promise(.success($0.flatMap(self.base.fetchExisting))) },
failure: { promise(.failure($0)) }
)
}
}
/**
Reactive extension for `CoreStore.DataStack`'s `importUniqueObjects(...)` API. Updates existing `ImportableUniqueObject`s or creates them by importing from the specified array of import sources. `ImportableUniqueObject` methods are called on the objects in the same order as they are in the `sourceArray`, and are returned in an array with that same order. The event values will be object instances correctly associated for the `DataStack`.
```
dataStack.reactive
.importUniqueObjects(
Into<Person>(),
sourceArray: [
["name": "John"],
["name": "Bob"],
["name": "Joe"]
]
)
.sink(
receiveCompletion: { result in
// ...
},
receiveValue: { (people) in
XCTAssertEqual(people?.count, 3)
// ...
}
)
.store(in: &cancellables)
```
- Warning: If `sourceArray` contains multiple import sources with same ID, no merging will occur and ONLY THE LAST duplicate will be imported.
- parameter into: an `Into` clause specifying the entity type
- parameter sourceArray: the array of objects to import values from
- parameter preProcess: a closure that lets the caller tweak the internal `UniqueIDType`-to-`ImportSource` mapping to be used for importing. Callers can remove from/add to/update `mapping` and return the updated array from the closure.
- returns: A `Future` for the imported objects. The event values will be the object instances correctly associated for the `DataStack`.
*/
public func importUniqueObjects<O: DynamicObject & ImportableUniqueObject, S: Sequence>(
_ into: Into<O>,
sourceArray: S,
preProcess: @escaping (_ mapping: [O.UniqueIDType: O.ImportSource]) throws -> [O.UniqueIDType: O.ImportSource] = { $0 }
) -> Future<[O], CoreStoreError> where S.Iterator.Element == O.ImportSource {
return .init { (promise) in
self.base.perform(
asynchronous: { (transaction) -> [O] in
return try transaction.importUniqueObjects(
into,
sourceArray: sourceArray,
preProcess: preProcess
)
},
success: { promise(.success(self.base.fetchExisting($0))) },
failure: { promise(.failure($0)) }
)
}
}
/**
Reactive extension for `CoreStore.DataStack`'s `perform(asynchronous:...)` API. Performs a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. The event value will be the value returned from the `task` closure. Any errors thrown from inside the `task` will be wrapped in a `CoreStoreError` and reported to the completion `.failure`. To cancel/rollback changes, call `transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
```
dataStack.reactive
.perform(
asynchronous: { (transaction) -> (inserted: Set<NSManagedObject>, deleted: Set<NSManagedObject>) in
// ...
return (
transaction.insertedObjects(),
transaction.deletedObjects()
)
}
)
.sink(
receiveCompletion: { result in
// ...
},
receiveValue: { value in
let inserted = dataStack.fetchExisting(value0.inserted)
let deleted = dataStack.fetchExisting(value0.deleted)
// ...
}
)
.store(in: &cancellables)
```
- parameter task: the asynchronous closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
- returns: A `Future` whose event value be the value returned from the `task` closure.
*/
public func perform<Output>(
_ asynchronous: @escaping (AsynchronousDataTransaction) throws -> Output
) -> Future<Output, CoreStoreError> {
return .init { (promise) in
self.base.perform(
asynchronous: asynchronous,
success: { promise(.success($0)) },
failure: { promise(.failure($0)) }
)
}
}
}
#endif

View File

@@ -35,12 +35,18 @@ extension DataStack {
Performs a transaction asynchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the wrapped as `.success(T)` in the `completion`'s `Result<T>`. Any errors thrown from inside the `task` will be reported as `.failure(CoreStoreError)`. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
- parameter task: the asynchronous closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
- parameter completion: the closure executed after the save completes. The `Result` argument of the closure will either wrap the return value of `task`, or any uncaught errors thrown from within `task`. Cancelled `task`s will be indicated by `.failure(error: CoreStoreError.userCancelled)`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
*/
public func perform<T>(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, completion: @escaping (AsynchronousDataTransaction.Result<T>) -> Void) {
public func perform<T>(
asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T,
sourceIdentifier: Any? = nil,
completion: @escaping (AsynchronousDataTransaction.Result<T>) -> Void
) {
self.perform(
asynchronous: task,
sourceIdentifier: sourceIdentifier,
success: { completion(.success($0)) },
failure: { completion(.failure($0)) }
)
@@ -50,14 +56,21 @@ extension DataStack {
Performs a transaction asynchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the argument of the `success` closure. Any errors thrown from inside the `task` will be wrapped in a `CoreStoreError` and reported in the `failure` closure. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
- parameter task: the asynchronous closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
- parameter success: the closure executed after the save succeeds. The `T` argument of the closure will be the value returned from `task`.
- parameter failure: the closure executed if the save fails or if any errors are thrown within `task`. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
*/
public func perform<T>(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, success: @escaping (T) -> Void, failure: @escaping (CoreStoreError) -> Void) {
public func perform<T>(
asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T,
sourceIdentifier: Any? = nil,
success: @escaping (T) -> Void,
failure: @escaping (CoreStoreError) -> Void
) {
let transaction = AsynchronousDataTransaction(
mainContext: self.rootSavingContext,
queue: self.childTransactionQueue
queue: self.childTransactionQueue,
sourceIdentifier: sourceIdentifier
)
transaction.transactionQueue.cs_async {
@@ -99,14 +112,20 @@ extension DataStack {
- parameter task: the synchronous non-escaping closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
- parameter waitForAllObservers: When `true`, this method waits for all observers to be notified of the changes before returning. This results in more predictable data update order, but may risk triggering deadlocks. When `false`, this method does not wait for observers to be notified of the changes before returning. This results in lower risk for deadlocks, but the updated data may not have been propagated to the `DataStack` after returning. Defaults to `true`.
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
- throws: a `CoreStoreError` value indicating the failure. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
- returns: the value returned from `task`
*/
public func perform<T>(synchronous task: ((_ transaction: SynchronousDataTransaction) throws -> T), waitForAllObservers: Bool = true) throws -> T {
public func perform<T>(
synchronous task: ((_ transaction: SynchronousDataTransaction) throws -> T),
waitForAllObservers: Bool = true,
sourceIdentifier: Any? = nil
) throws -> T {
let transaction = SynchronousDataTransaction(
mainContext: self.rootSavingContext,
queue: self.childTransactionQueue
queue: self.childTransactionQueue,
sourceIdentifier: sourceIdentifier
)
return try transaction.transactionQueue.cs_sync {
@@ -141,15 +160,20 @@ extension DataStack {
/**
Begins a non-contiguous transaction where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
- prameter supportsUndo: `undo()`, `redo()`, and `rollback()` methods are only available when this parameter is `true`, otherwise those method will raise an exception. Defaults to `false`. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
- parameter supportsUndo: `undo()`, `redo()`, and `rollback()` methods are only available when this parameter is `true`, otherwise those method will raise an exception. Defaults to `false`. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
- returns: a `UnsafeDataTransaction` instance where creates, updates, and deletes can be made.
*/
public func beginUnsafe(supportsUndo: Bool = false) -> UnsafeDataTransaction {
public func beginUnsafe(
supportsUndo: Bool = false,
sourceIdentifier: Any? = nil
) -> UnsafeDataTransaction {
return UnsafeDataTransaction(
mainContext: self.rootSavingContext,
queue: DispatchQueue.serial("com.coreStore.dataStack.unsafeTransactionQueue", qos: .userInitiated),
supportsUndo: supportsUndo
supportsUndo: supportsUndo,
sourceIdentifier: sourceIdentifier
)
}

View File

@@ -0,0 +1,239 @@
//
// DataStack.AddStoragePublisher.swift
// CoreStore
//
// Copyright © 2021 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(Combine)
import Combine
import CoreData
// MARK: - DataStack
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension DataStack {
// MARK: - AddStoragePublisher
/**
A `Publisher` that emits a `ListSnapshot` whenever changes occur in the `ListPublisher`.
- SeeAlso: DataStack.reactive.addStorage(_:)
*/
public struct AddStoragePublisher<Storage: LocalStorage>: Publisher {
// MARK: Internal
internal let dataStack: DataStack
internal let storage: Storage
// MARK: Publisher
public typealias Output = MigrationProgress
public typealias Failure = CoreStoreError
public func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
subscriber.receive(
subscription: AddStorageSubscription(
dataStack: self.dataStack,
storage: self.storage,
subscriber: subscriber
)
)
}
// MARK: - MigrationProgress
/**
A `MigrationProgress` contains info on a `LocalStorage`'s setup progress.
- SeeAlso: DataStack.reactive.addStorage(_:)
*/
public enum MigrationProgress {
/**
The `LocalStorage` is currently being migrated
*/
case migrating(storage: Storage, progressObject: Progress)
/**
The `LocalStorage` has been added to the `DataStack` and is ready for reading and writing
*/
case finished(storage: Storage, migrationRequired: Bool)
/**
The fraction of the overall work completed by the migration. Returns a value between 0.0 and 1.0, inclusive.
*/
public var fractionCompleted: Double {
switch self {
case .migrating(_, let progressObject):
return progressObject.fractionCompleted
case .finished:
return 1
}
}
/**
Returns `true` if the storage was successfully added to the stack, `false` otherwise.
*/
public var isCompleted: Bool {
switch self {
case .migrating:
return false
case .finished:
return true
}
}
}
// MARK: - AddStorageSubscriber
fileprivate final class AddStorageSubscriber: Subscriber {
// MARK: Subscriber
typealias Failure = CoreStoreError
func receive(subscription: Subscription) {
subscription.request(.unlimited)
}
func receive(_ input: Output) -> Subscribers.Demand {
return .unlimited
}
func receive(completion: Subscribers.Completion<Failure>) {}
}
// MARK: - AddStorageSubscription
fileprivate final class AddStorageSubscription<S: Subscriber>: Subscription where S.Input == Output, S.Failure == CoreStoreError {
// MARK: FilePrivate
fileprivate init(
dataStack: DataStack,
storage: Storage,
subscriber: S
) {
self.dataStack = dataStack
self.storage = storage
self.subscriber = subscriber
}
// MARK: Subscription
func request(_ demand: Subscribers.Demand) {
guard demand > 0 else {
return
}
var progress: Progress? = nil
progress = self.dataStack.addStorage(
self.storage,
completion: { [weak self] result in
progress?.setProgressHandler(nil)
guard
let self = self,
let subscriber = self.subscriber
else {
return
}
switch result {
case .success(let storage):
_ = subscriber.receive(
.finished(
storage: storage,
migrationRequired: progress != nil
)
)
subscriber.receive(
completion: .finished
)
case .failure(let error):
subscriber.receive(
completion: .failure(error)
)
}
}
)
if let progress = progress {
progress.cs_setProgressHandler { [weak self] progress in
guard
let self = self,
let subscriber = self.subscriber
else {
return
}
_ = subscriber.receive(
.migrating(
storage: self.storage,
progressObject: progress
)
)
}
}
}
// MARK: Cancellable
func cancel() {
self.subscriber = nil
}
// MARK: Private
private let dataStack: DataStack
private let storage: Storage
private var subscriber: S?
}
}
}
#endif

View File

@@ -116,10 +116,10 @@ extension DiffableDataSource {
target.reload(
using: changeset,
animated: animatingDifferences,
setData: setSections
setData: setSections,
completion: completion
)
},
completion: completion
}
)
}
@@ -148,10 +148,21 @@ extension DiffableDataSource {
target.reload(
using: changeset,
animated: animatingDifferences,
setData: setSections
setData: setSections,
completion: completion
)
},
completion: completion
}
)
}
/**
Creates a new empty `ListSnapshot` suitable for building custom lists inside subclass implementations of `apply(_:animatingDifferences:completion:)`.
*/
public func makeEmptySnapshot() -> ListSnapshot<O> {
return .init(
diffableSnapshot: .init(),
context: self.dataStack.unsafeContext()
)
}
@@ -209,6 +220,25 @@ extension DiffableDataSource {
return self.dispatcher.indexPath(for: itemID)
}
/**
Returns the section index title for the specified `section` if the `SectionBy` for this list has provided a `sectionIndexTransformer`
- parameter section: the section index to search for
- returns: the section index title for the specified `section`, or `nil` if not found
*/
public func sectionIndexTitle(for section: Int) -> String? {
return self.dispatcher.sectionIndexTitle(for: section)
}
/**
Returns the section index titles for all sections if the `SectionBy` for this list has provided a `sectionIndexTransformer`
*/
public func sectionIndexTitlesForAllSections() -> [String?] {
return self.dispatcher.sectionIndexTitlesForAllSections()
}
// MARK: Internal

View File

@@ -83,7 +83,12 @@ extension DiffableDataSource {
- parameter itemProvider: a closure that configures and returns the `NSCollectionViewItem` for the object
*/
@nonobjc
public init(collectionView: NSCollectionView, dataStack: DataStack, itemProvider: @escaping (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?, supplementaryViewProvider: @escaping (NSCollectionView, String, IndexPath) -> NSView? = { _, _, _ in nil }) {
public init(
collectionView: NSCollectionView,
dataStack: DataStack,
itemProvider: @escaping (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?,
supplementaryViewProvider: @escaping (NSCollectionView, String, IndexPath) -> NSView? = { _, _, _ in nil }
) {
self.itemProvider = itemProvider
self.supplementaryViewProvider = supplementaryViewProvider
@@ -97,19 +102,27 @@ extension DiffableDataSource {
// MARK: - NSCollectionViewDataSource
@objc
public dynamic func numberOfSections(in collectionView: NSCollectionView) -> Int {
public dynamic func numberOfSections(
in collectionView: NSCollectionView
) -> Int {
return self.numberOfSections()
}
@objc
public dynamic func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
public dynamic func collectionView(
_ collectionView: NSCollectionView,
numberOfItemsInSection section: Int
) -> Int {
return self.numberOfItems(inSection: section) ?? 0
}
@objc
open dynamic func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
open dynamic func collectionView(
_ collectionView: NSCollectionView,
itemForRepresentedObjectAt indexPath: IndexPath
) -> NSCollectionViewItem {
guard let objectID = self.itemID(for: indexPath) else {
@@ -127,7 +140,11 @@ extension DiffableDataSource {
}
@objc
open dynamic func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind, at indexPath: IndexPath) -> NSView {
open dynamic func collectionView(
_ collectionView: NSCollectionView,
viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind,
at indexPath: IndexPath
) -> NSView {
guard let view = self.supplementaryViewProvider(collectionView, kind, indexPath) else {
@@ -207,9 +224,9 @@ extension DiffableDataSource {
self.base?.moveItem(at: indexPath, to: newIndexPath)
}
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
public func performBatchUpdates(updates: () -> Void, animated: Bool, completion: @escaping () -> Void) {
self.base?.animator().performBatchUpdates(updates, completionHandler: nil)
self.base?.animator().performBatchUpdates(updates, completionHandler: { _ in completion() })
}
public func reloadData() {
@@ -220,7 +237,7 @@ extension DiffableDataSource {
}
// MARK: - Deprecated
// MARK: Deprecated
extension DiffableDataSource {

View File

@@ -83,7 +83,12 @@ extension DiffableDataSource {
- parameter cellProvider: a closure that configures and returns the `UICollectionViewCell` for the object
- parameter supplementaryViewProvider: an optional closure for providing `UICollectionReusableView` supplementary views. If not set, defaults to returning `nil`
*/
public init(collectionView: UICollectionView, dataStack: DataStack, cellProvider: @escaping (UICollectionView, IndexPath, O) -> UICollectionViewCell?, supplementaryViewProvider: @escaping (UICollectionView, String, IndexPath) -> UICollectionReusableView? = { _, _, _ in nil }) {
public init(
collectionView: UICollectionView,
dataStack: DataStack,
cellProvider: @escaping (UICollectionView, IndexPath, O) -> UICollectionViewCell?,
supplementaryViewProvider: @escaping (UICollectionView, String, IndexPath) -> UICollectionReusableView? = { _, _, _ in nil }
) {
self.cellProvider = cellProvider
self.supplementaryViewProvider = supplementaryViewProvider
@@ -97,19 +102,30 @@ extension DiffableDataSource {
// MARK: - UICollectionViewDataSource
@objc
public dynamic func numberOfSections(in collectionView: UICollectionView) -> Int {
@MainActor
public dynamic func numberOfSections(
in collectionView: UICollectionView
) -> Int {
return self.numberOfSections()
}
@objc
public dynamic func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
@MainActor
public dynamic func collectionView(
_ collectionView: UICollectionView,
numberOfItemsInSection section: Int
) -> Int {
return self.numberOfItems(inSection: section) ?? 0
}
@objc
open dynamic func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
@MainActor
open dynamic func collectionView(
_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath
) -> UICollectionViewCell {
guard let objectID = self.itemID(for: indexPath) else {
@@ -127,7 +143,12 @@ extension DiffableDataSource {
}
@objc
open dynamic func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
@MainActor
open dynamic func collectionView(
_ collectionView: UICollectionView,
viewForSupplementaryElementOfKind kind: String,
at indexPath: IndexPath
) -> UICollectionReusableView {
guard let view = self.supplementaryViewProvider(collectionView, kind, indexPath) else {
@@ -207,9 +228,9 @@ extension DiffableDataSource {
self.base?.moveItem(at: indexPath, to: newIndexPath)
}
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
public func performBatchUpdates(updates: () -> Void, animated: Bool, completion: @escaping () -> Void) {
self.base?.performBatchUpdates(updates, completion: nil)
self.base?.performBatchUpdates(updates, completion: { _ in completion() })
}
public func reloadData() {
@@ -220,7 +241,7 @@ extension DiffableDataSource {
}
// MARK: - Deprecated
// MARK: Deprecated
extension DiffableDataSource {

View File

@@ -82,7 +82,11 @@ extension DiffableDataSource {
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
- parameter cellProvider: a closure that configures and returns the `UITableViewCell` for the object
*/
public init(tableView: UITableView, dataStack: DataStack, cellProvider: @escaping (UITableView, IndexPath, O) -> UITableViewCell?) {
public init(
tableView: UITableView,
dataStack: DataStack,
cellProvider: @escaping (UITableView, IndexPath, O) -> UITableViewCell?
) {
self.cellProvider = cellProvider
super.init(target: .init(tableView), dataStack: dataStack)
@@ -102,31 +106,48 @@ extension DiffableDataSource {
// MARK: - UITableViewDataSource
@objc
@MainActor
public dynamic func numberOfSections(in tableView: UITableView) -> Int {
return self.numberOfSections()
}
@objc
public dynamic func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
@MainActor
public dynamic func tableView(
_ tableView: UITableView,
numberOfRowsInSection section: Int
) -> Int {
return self.numberOfItems(inSection: section) ?? 0
}
@objc
open dynamic func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
@MainActor
open dynamic func tableView(
_ tableView: UITableView,
titleForHeaderInSection section: Int
) -> String? {
return self.sectionID(for: section)
}
@objc
open dynamic func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
@MainActor
open dynamic func tableView(
_ tableView: UITableView,
titleForFooterInSection section: Int
) -> String? {
return nil
}
@objc
open dynamic func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@MainActor
open dynamic func tableView(
_ tableView: UITableView,
cellForRowAt indexPath: IndexPath
) -> UITableViewCell {
guard let objectID = self.itemID(for: indexPath) else {
@@ -144,19 +165,52 @@ extension DiffableDataSource {
}
@objc
open dynamic func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
@MainActor
open dynamic func tableView(
_ tableView: UITableView,
canEditRowAt indexPath: IndexPath
) -> Bool {
return true
}
@objc
open dynamic func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
@MainActor
open dynamic func tableView(
_ tableView: UITableView,
editingStyleForRowAt indexPath: IndexPath
) -> UITableViewCell.EditingStyle {
return .delete
}
@objc
open dynamic func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {}
@MainActor
open dynamic func tableView(
_ tableView: UITableView,
commit editingStyle: UITableViewCell.EditingStyle,
forRowAt indexPath: IndexPath
) {}
@objc
@MainActor
open dynamic func sectionIndexTitles(
for tableView: UITableView
) -> [String]? {
return nil
}
@objc
@MainActor
open dynamic func tableView(
_ tableView: UITableView,
sectionForSectionIndexTitle title: String,
at index: Int
) -> Int {
return index
}
// MARK: Private
@@ -229,22 +283,13 @@ extension DiffableDataSource {
self.base?.moveRow(at: indexPath, to: newIndexPath)
}
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
public func performBatchUpdates(updates: () -> Void, animated: Bool, completion: @escaping () -> Void) {
guard let base = self.base else {
return
}
if #available(iOS 11.0, tvOS 11.0, *) {
base.performBatchUpdates(updates)
}
else {
base.beginUpdates()
updates()
base.endUpdates()
}
base.performBatchUpdates(updates, completion: { _ in completion() })
}
public func reloadData() {
@@ -255,7 +300,7 @@ extension DiffableDataSource {
}
// MARK: - Deprecated
// MARK: Deprecated
extension DiffableDataSource {

View File

@@ -98,7 +98,7 @@ public protocol DiffableDataSourceTarget {
/**
Animates multiple insert, delete, reload, and move operations as a group.
*/
func performBatchUpdates(updates: () -> Void, animated: Bool)
func performBatchUpdates(updates: () -> Void, animated: Bool, completion: @escaping () -> Void)
/**
Reloads all sections and items.
@@ -114,9 +114,15 @@ extension DiffableDataSource.Target {
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
animated: Bool,
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
setData: (C) -> Void
setData: (C) -> Void,
completion: @escaping () -> Void
) {
let group = DispatchGroup()
defer {
group.notify(queue: .main, execute: completion)
}
if self.shouldSuspendBatchUpdates, let data = stagedChangeset.last?.data {
setData(data)
@@ -133,6 +139,7 @@ extension DiffableDataSource.Target {
self.reloadData()
return
}
group.enter()
self.performBatchUpdates(
updates: {
@@ -206,7 +213,8 @@ extension DiffableDataSource.Target {
)
}
},
animated: animated
animated: animated,
completion: group.leave
)
}
}

View File

@@ -44,19 +44,30 @@ internal protocol DiffableDataSourceSnapshotProtocol {
func indexOfItem(_ identifier: NSManagedObjectID) -> Int?
func indexOfSection(_ identifier: String) -> Int?
mutating func appendItems(_ identifiers: [NSManagedObjectID], toSection sectionIdentifier: String?)
mutating func insertItems(_ identifiers: [NSManagedObjectID], beforeItem beforeIdentifier: NSManagedObjectID)
mutating func insertItems(_ identifiers: [NSManagedObjectID], afterItem afterIdentifier: NSManagedObjectID)
mutating func deleteItems(_ identifiers: [NSManagedObjectID])
mutating func appendItems<C: Collection>(_ identifiers: C, toSection sectionIdentifier: String?) where C.Element == NSManagedObjectID
mutating func insertItems<C: Collection>(_ identifiers: C, beforeItem beforeIdentifier: NSManagedObjectID) where C.Element == NSManagedObjectID
mutating func insertItems<C: Collection>(_ identifiers: C, afterItem afterIdentifier: NSManagedObjectID) where C.Element == NSManagedObjectID
mutating func deleteItems<C: Collection>(_ identifiers: C) where C.Element == NSManagedObjectID
mutating func deleteAllItems()
mutating func moveItem(_ identifier: NSManagedObjectID, beforeItem toIdentifier: NSManagedObjectID)
mutating func moveItem(_ identifier: NSManagedObjectID, afterItem toIdentifier: NSManagedObjectID)
mutating func reloadItems(_ identifiers: [NSManagedObjectID])
mutating func appendSections(_ identifiers: [String])
mutating func insertSections(_ identifiers: [String], beforeSection toIdentifier: String)
mutating func insertSections(_ identifiers: [String], afterSection toIdentifier: String)
mutating func deleteSections(_ identifiers: [String])
mutating func reloadItems<C: Collection>(_ identifiers: C) where C.Element == NSManagedObjectID
mutating func appendSections<C: Collection>(_ identifiers: C) where C.Element == String
mutating func insertSections<C: Collection>(_ identifiers: C, beforeSection toIdentifier: String) where C.Element == String
mutating func insertSections<C: Collection>(_ identifiers: C, afterSection toIdentifier: String) where C.Element == String
mutating func deleteSections<C: Collection>(_ identifiers: C) where C.Element == String
mutating func moveSection(_ identifier: String, beforeSection toIdentifier: String)
mutating func moveSection(_ identifier: String, afterSection toIdentifier: String)
mutating func reloadSections(_ identifiers: [String])
mutating func reloadSections<C: Collection>(_ identifiers: C) where C.Element == String
mutating func unsafeAppendItems<C: Collection>(_ identifiers: C, toSectionAt sectionIndex: Int) where C.Element == NSManagedObjectID
mutating func unsafeInsertItems<C: Collection>(_ identifiers: C, at indexPath: IndexPath) where C.Element == NSManagedObjectID
mutating func unsafeDeleteItems<C: Collection>(at indexPaths: C) where C.Element == IndexPath
mutating func unsafeMoveItem(at indexPath: IndexPath, to newIndexPath: IndexPath)
mutating func unsafeReloadItems<C: Collection>(at indexPaths: C) where C.Element == IndexPath
mutating func unsafeInsertSections<C: Collection>(_ identifiers: C, at sectionIndex: Int) where C.Element == String
mutating func unsafeDeleteSections<C: Collection>(at sectionIndices: C) where C.Element == Int
mutating func unsafeMoveSection(at sectionIndex: Int, to newSectionIndex: Int)
mutating func unsafeReloadSections<C: Collection>(at sectionIndices: C) where C.Element == Int
}

View File

@@ -70,7 +70,6 @@ public final class Entity<O: CoreStoreObject>: DynamicEntity {
- parameter indexes: the compound indexes for the entity as an array of arrays. The arrays contained in the returned array contain `KeyPath`s to properties of the entity.
- parameter uniqueConstraints: sets uniqueness constraints for the entity. A uniqueness constraint is a set of one or more `KeyPath`s whose value must be unique over the set of instances of that entity. This value forms part of the entity's version hash. Uniqueness constraint violations can be computationally expensive to handle. It is highly suggested that there be only one uniqueness constraint per entity hierarchy. Uniqueness constraints must be defined at the highest level possible, and CoreStore will raise an assertion failure if unique constraints are added to a sub entity.
*/
@available(macOS 10.11, *)
public convenience init(_ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil, indexes: [[PartialKeyPath<O>]] = [], uniqueConstraints: [[PartialKeyPath<O>]]) {
self.init(
@@ -116,7 +115,6 @@ public final class Entity<O: CoreStoreObject>: DynamicEntity {
- parameter indexes: the compound indexes for the entity as an array of arrays. The arrays contained in the returned array contain KeyPath's to properties of the entity.
- parameter uniqueConstraints: sets uniqueness constraints for the entity. A uniqueness constraint is a set of one or more `KeyPath`s whose value must be unique over the set of instances of that entity. This value forms part of the entity's version hash. Uniqueness constraint violations can be computationally expensive to handle. It is highly suggested that there be only one uniqueness constraint per entity hierarchy. Uniqueness constraints must be defined at the highest level possible, and CoreStore will raise an assertion failure if unique constraints are added to a sub entity.
*/
@available(macOS 10.11, *)
public init(_ type: O.Type, _ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil, indexes: [[PartialKeyPath<O>]] = [], uniqueConstraints: [[PartialKeyPath<O>]]) {
let meta = O.meta

View File

@@ -47,17 +47,10 @@ extension FieldCoders {
return nil
}
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
return try! NSKeyedArchiver.archivedData(
withRootObject: fieldValue,
requiringSecureCoding: self.requiresSecureCoding
)
}
else {
return NSKeyedArchiver.archivedData(withRootObject: fieldValue)
}
return try! NSKeyedArchiver.archivedData(
withRootObject: fieldValue,
requiringSecureCoding: self.requiresSecureCoding
)
}
public static func decodeFromStoredData(_ data: Data?) -> FieldStoredValue? {
@@ -66,14 +59,10 @@ extension FieldCoders {
return nil
}
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
return try! NSKeyedUnarchiver.unarchivedObject(ofClass: FieldStoredValue.self, from: data)
}
else {
return NSKeyedUnarchiver.unarchiveObject(with: data) as! FieldStoredValue?
}
return try! NSKeyedUnarchiver.unarchivedObject(
ofClass: FieldStoredValue.self,
from: data
)
}

View File

@@ -0,0 +1,201 @@
//
// ForEach+SwiftUI.swift
// CoreStore
//
// Copyright © 2021 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(Combine) && canImport(SwiftUI)
import Combine
import SwiftUI
// MARK: - ForEach
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
extension ForEach where Content: View {
// MARK: Public
/**
Creates an instance that creates views for each object in a collection of `ObjectSnapshot`s. The objects' `NSManagedObjectID` are used as the identifier
```
let people: [ObjectSnapshot<Person>]
var body: some View {
List {
ForEach(self.people) { person in
ProfileView(person)
}
}
.animation(.default)
}
```
- parameter objectSnapshots: The collection of `ObjectSnapshot`s that the `ForEach` instance uses to create views dynamically
- parameter content: The view builder that receives an `ObjectPublisher` instance and creates views dynamically.
*/
public init<O: DynamicObject>(
_ objectSnapshots: Data,
@ViewBuilder content: @escaping (ObjectSnapshot<O>) -> Content
) where Data.Element == ObjectSnapshot<O>, ID == O.ObjectID {
self.init(objectSnapshots, id: \.cs_objectID, content: content)
}
/**
Creates an instance that creates views for each object in a `ListSnapshot`.
```
@ListState
var people: ListSnapshot<Person>
var body: some View {
List {
ForEach(objectIn: self.people) { person in
ProfileView(person)
}
}
.animation(.default)
}
```
- parameter listSnapshot: The `ListSnapshot` that the `ForEach` instance uses to create views dynamically
- parameter content: The view builder that receives an `ObjectPublisher` instance and creates views dynamically.
*/
public init<O: DynamicObject>(
objectIn listSnapshot: Data,
@ViewBuilder content: @escaping (ObjectPublisher<O>) -> Content
) where Data == ListSnapshot<O>, ID == O.ObjectID {
self.init(listSnapshot, id: \.cs_objectID, content: content)
}
/**
Creates an instance that creates views for each object in a collection of `ObjectPublisher`s.
```
let people: [ObjectPublisher<Person>]
var body: some View {
List {
ForEach(objectIn: self.people) { person in
ProfileView(person)
}
}
.animation(.default)
}
```
- parameter objectPublishers: The collection of `ObjectPublisher`s that the `ForEach` instance uses to create views dynamically
- parameter content: The view builder that receives an `ObjectPublisher` instance and creates views dynamically.
*/
public init<O: DynamicObject>(
objectIn objectPublishers: Data,
@ViewBuilder content: @escaping (ObjectPublisher<O>) -> Content
) where Data.Element == ObjectPublisher<O>, ID == O.ObjectID {
self.init(objectPublishers, id: \.cs_objectID, content: content)
}
/**
Creates an instance that creates views for `ListSnapshot` sections.
```
@ListState
var people: ListSnapshot<Person>
var body: some View {
List {
ForEach(sectionIn: self.people) { section in
Section(header: Text(section.sectionID)) {
ForEach(objectIn: section) { person in
ProfileView(person)
}
}
}
}
.animation(.default)
}
```
- parameter listSnapshot: The `ListSnapshot` that the `ForEach` instance uses to create views dynamically
- parameter content: The view builder that receives a `ListSnapshot.SectionInfo` instance and creates views dynamically.
*/
public init<O: DynamicObject>(
sectionIn listSnapshot: ListSnapshot<O>,
@ViewBuilder content: @escaping (ListSnapshot<O>.SectionInfo) -> Content
) where Data == [ListSnapshot<O>.SectionInfo], ID == ListSnapshot<O>.SectionID {
let sections = listSnapshot.sections()
self.init(sections, id: \.sectionID, content: content)
}
/**
Creates an instance that creates views for each object in a `ListSnapshot.SectionInfo`.
```
@ListState
var people: ListSnapshot<Person>
var body: some View {
List {
ForEach(sectionIn: self.people) { section in
Section(header: Text(section.sectionID)) {
ForEach(objectIn: section) { person in
ProfileView(person)
}
}
}
}
.animation(.default)
}
```
- parameter sectionInfo: The `ListSnapshot.SectionInfo` that the `ForEach` instance uses to create views dynamically
- parameter content: The view builder that receives an `ObjectPublisher` instance and creates views dynamically.
*/
public init<O: DynamicObject>(
objectIn sectionInfo: Data,
@ViewBuilder content: @escaping (ObjectPublisher<O>) -> Content
) where Data == ListSnapshot<O>.SectionInfo, ID == O.ObjectID {
self.init(sectionInfo, id: \.cs_objectID, content: content)
}
}
#endif

View File

@@ -71,7 +71,10 @@ extension From {
- parameter args: the arguments for `format`
- returns: a `FetchChainBuilder` with a predicate using the specified string format and arguments
*/
public func `where`(format: String, _ args: Any...) -> FetchChainBuilder<O> {
public func `where`(
format: String,
_ args: Any...
) -> FetchChainBuilder<O> {
return self.fetchChain(appending: Where<O>(format, argumentArray: args))
}
@@ -83,7 +86,10 @@ extension From {
- parameter argumentArray: the arguments for `format`
- returns: a `FetchChainBuilder` with a predicate using the specified string format and arguments
*/
public func `where`(format: String, argumentArray: [Any]?) -> FetchChainBuilder<O> {
public func `where`(
format: String,
argumentArray: [Any]?
) -> FetchChainBuilder<O> {
return self.fetchChain(appending: Where<O>(format, argumentArray: argumentArray))
}
@@ -106,7 +112,10 @@ extension From {
- parameter sortKeys: a series of other `SortKey`s
- returns: a `FetchChainBuilder` with a series of `SortKey`s
*/
public func orderBy(_ sortKey: OrderBy<O>.SortKey, _ sortKeys: OrderBy<O>.SortKey...) -> FetchChainBuilder<O> {
public func orderBy(
_ sortKey: OrderBy<O>.SortKey,
_ sortKeys: OrderBy<O>.SortKey...
) -> FetchChainBuilder<O> {
return self.fetchChain(appending: OrderBy<O>([sortKey] + sortKeys))
}
@@ -178,7 +187,11 @@ extension From {
- parameter selectTerms: a series of `SelectTerm`s
- returns: a `QueryChainBuilder` that starts with a `Select` clause created from the specified `SelectTerm`s
*/
public func select<R>(_ resultType: R.Type, _ selectTerm: SelectTerm<O>, _ selectTerms: SelectTerm<O>...) -> QueryChainBuilder<O, R> {
public func select<R>(
_ resultType: R.Type,
_ selectTerm: SelectTerm<O>,
_ selectTerms: SelectTerm<O>...
) -> QueryChainBuilder<O, R> {
return self.select(resultType, [selectTerm] + selectTerms)
}
@@ -190,7 +203,10 @@ extension From {
- parameter selectTerms: a series of `SelectTerm`s
- returns: a `QueryChainBuilder` that starts with a `Select` clause created from the specified `SelectTerm`s
*/
public func select<R>(_ resultType: R.Type, _ selectTerms: [SelectTerm<O>]) -> QueryChainBuilder<O, R> {
public func select<R>(
_ resultType: R.Type,
_ selectTerms: [SelectTerm<O>]
) -> QueryChainBuilder<O, R> {
return .init(
from: self,
@@ -205,7 +221,6 @@ extension From {
- parameter clause: the `SectionBy` to be used by the `ListMonitor`
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy(_ clause: SectionBy<O>) -> SectionMonitorChainBuilder<O> {
return .init(
@@ -221,26 +236,30 @@ extension From {
- parameter sectionKeyPath: the key path to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy(_ sectionKeyPath: KeyPathString) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(sectionKeyPath, { $0 })
return self.sectionBy(sectionKeyPath, sectionIndexTransformer: { _ in nil })
}
/**
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section index title
- Important: Some utilities (such as `ListMonitor`s) may keep `SectionBy`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter sectionKeyPath: the key path to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy(_ sectionKeyPath: KeyPathString, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
public func sectionBy(
_ sectionKeyPath: KeyPathString,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return .init(
from: self,
sectionBy: .init(sectionKeyPath, sectionIndexTransformer),
sectionBy: .init(
sectionKeyPath,
sectionIndexTransformer: sectionIndexTransformer
),
fetchClauses: []
)
}
@@ -257,6 +276,21 @@ extension From {
return .init(from: self, fetchClauses: Array(clauses))
}
// MARK: Deprecated
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
public func sectionBy(
_ sectionKeyPath: KeyPathString,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(
sectionKeyPath,
sectionIndexTransformer: sectionIndexTransformer
)
}
}
@@ -281,24 +315,46 @@ extension From where O: NSManagedObject {
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, T>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(sectionKeyPath._kvcKeyPathString!, { $0 })
return self.sectionBy(
sectionKeyPath._kvcKeyPathString!,
sectionIndexTransformer: { _ in nil }
)
}
/**
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section index title
- Important: Some utilities (such as `ListMonitor`s) may keep `SectionBy`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, T>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, T>,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(sectionKeyPath._kvcKeyPathString!, sectionIndexTransformer)
return self.sectionBy(
sectionKeyPath._kvcKeyPathString!,
sectionIndexTransformer: sectionIndexTransformer
)
}
// MARK: Deprecated
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, T>,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(
sectionKeyPath._kvcKeyPathString!,
sectionIndexTransformer: sectionIndexTransformer
)
}
}
@@ -368,10 +424,12 @@ extension From where O: CoreStoreObject {
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Stored<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, { $0 })
return self.sectionBy(
O.meta[keyPath: sectionKeyPath].keyPath,
sectionIndexTransformer: { _ in nil }
)
}
/**
@@ -380,10 +438,12 @@ extension From where O: CoreStoreObject {
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Virtual<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, { $0 })
return self.sectionBy(
O.meta[keyPath: sectionKeyPath].keyPath,
sectionIndexTransformer: { _ in nil }
)
}
/**
@@ -392,10 +452,12 @@ extension From where O: CoreStoreObject {
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, { $0 })
return self.sectionBy(
O.meta[keyPath: sectionKeyPath].keyPath,
sectionIndexTransformer: { _ in nil }
)
}
/**
@@ -404,10 +466,12 @@ extension From where O: CoreStoreObject {
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Required<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, { $0 })
return self.sectionBy(
O.meta[keyPath: sectionKeyPath].keyPath,
sectionIndexTransformer: { _ in nil }
)
}
/**
@@ -416,10 +480,12 @@ extension From where O: CoreStoreObject {
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Optional<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, { $0 })
return self.sectionBy(
O.meta[keyPath: sectionKeyPath].keyPath,
sectionIndexTransformer: { _ in nil }
)
}
/**
@@ -428,10 +494,12 @@ extension From where O: CoreStoreObject {
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Required<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, { $0 })
return self.sectionBy(
O.meta[keyPath: sectionKeyPath].keyPath,
sectionIndexTransformer: { _ in nil }
)
}
/**
@@ -440,108 +508,232 @@ extension From where O: CoreStoreObject {
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, { $0 })
return self.sectionBy(
O.meta[keyPath: sectionKeyPath].keyPath,
sectionIndexTransformer: { _ in nil }
)
}
/**
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section index title
- Important: Some utilities (such as `ListMonitor`s) may keep `SectionBy`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Stored<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Stored<T>>,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
return self.sectionBy(
O.meta[keyPath: sectionKeyPath].keyPath,
sectionIndexTransformer: sectionIndexTransformer
)
}
/**
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section index title
- Important: Some utilities (such as `ListMonitor`s) may keep `SectionBy`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Virtual<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Virtual<T>>,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
return self.sectionBy(
O.meta[keyPath: sectionKeyPath].keyPath,
sectionIndexTransformer: sectionIndexTransformer
)
}
/**
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section index title
- Important: Some utilities (such as `ListMonitor`s) may keep `SectionBy`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
return self.sectionBy(
O.meta[keyPath: sectionKeyPath].keyPath,
sectionIndexTransformer: sectionIndexTransformer
)
}
/**
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section index title
- Important: Some utilities (such as `ListMonitor`s) may keep `SectionBy`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Required<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Required<T>>,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
return self.sectionBy(
O.meta[keyPath: sectionKeyPath].keyPath,
sectionIndexTransformer: sectionIndexTransformer
)
}
/**
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section index title
- Important: Some utilities (such as `ListMonitor`s) may keep `SectionBy`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Optional<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Optional<T>>,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
return self.sectionBy(
O.meta[keyPath: sectionKeyPath].keyPath,
sectionIndexTransformer: sectionIndexTransformer
)
}
/**
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section index title
- Important: Some utilities (such as `ListMonitor`s) may keep `SectionBy`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Required<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Required<T>>,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
return self.sectionBy(
O.meta[keyPath: sectionKeyPath].keyPath,
sectionIndexTransformer: sectionIndexTransformer
)
}
/**
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
Creates a `SectionMonitorChainBuilder` with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section index title
- Important: Some utilities (such as `ListMonitor`s) may keep `SectionBy`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter sectionKeyPath: the `KeyPath` to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section index title
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>,
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
return self.sectionBy(
O.meta[keyPath: sectionKeyPath].keyPath,
sectionIndexTransformer: sectionIndexTransformer
)
}
// MARK: Deprecated
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Stored<T>>,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(
sectionKeyPath,
sectionIndexTransformer: sectionIndexTransformer
)
}
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Virtual<T>>,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(
sectionKeyPath,
sectionIndexTransformer: sectionIndexTransformer
)
}
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, FieldContainer<O>.Coded<T>>,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(
sectionKeyPath,
sectionIndexTransformer: sectionIndexTransformer
)
}
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Required<T>>,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(
sectionKeyPath,
sectionIndexTransformer: sectionIndexTransformer
)
}
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Optional<T>>,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(
sectionKeyPath,
sectionIndexTransformer: sectionIndexTransformer
)
}
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Required<T>>,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(
sectionKeyPath,
sectionIndexTransformer: sectionIndexTransformer
)
}
@available(*, deprecated, renamed: "sectionBy(_:sectionIndexTransformer:)")
public func sectionBy<T>(
_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>,
_ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?
) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(
sectionKeyPath,
sectionIndexTransformer: sectionIndexTransformer
)
}
}
@@ -1004,7 +1196,6 @@ extension QueryChainBuilder where O: CoreStoreObject {
// MARK: - SectionMonitorChainBuilder
@available(macOS 10.12, *)
extension SectionMonitorChainBuilder {
/**
@@ -1156,7 +1347,6 @@ extension SectionMonitorChainBuilder {
// MARK: - SectionMonitorChainBuilder where O: CoreStoreObject
@available(macOS 10.12, *)
extension SectionMonitorChainBuilder where O: CoreStoreObject {
/**

View File

@@ -77,27 +77,42 @@ extension Internals {
let transformerName = self.transformerName
if #available(iOS 12.0, tvOS 12.0, watchOS 5.0, macOS 10.14, *) {
if transformerName == .secureUnarchiveFromDataTransformerName {
switch transformerName {
case .secureUnarchiveFromDataTransformerName,
.isNotNilTransformerName,
.isNilTransformerName,
.negateBooleanTransformerName:
return
case let transformerName:
Self.cachedCoders[transformerName] = self
Foundation.ValueTransformer.setValueTransformer(
self.transformer,
forName: transformerName
)
}
}
switch transformerName {
else {
switch transformerName {
case .keyedUnarchiveFromDataTransformerName,
.unarchiveFromDataTransformerName,
.isNotNilTransformerName,
.isNilTransformerName,
.negateBooleanTransformerName:
return
case .keyedUnarchiveFromDataTransformerName,
.unarchiveFromDataTransformerName,
.isNotNilTransformerName,
.isNilTransformerName,
.negateBooleanTransformerName:
return
case let transformerName:
Self.cachedCoders[transformerName] = self
case let transformerName:
Self.cachedCoders[transformerName] = self
Foundation.ValueTransformer.setValueTransformer(
self.transformer,
forName: transformerName
)
Foundation.ValueTransformer.setValueTransformer(
self.transformer,
forName: transformerName
)
}
}
}

View File

@@ -34,7 +34,10 @@ extension Internals {
internal final class Closure<T, U> {
// MARK: FilePrivate
// MARK: Internal
internal typealias Arguments = T
internal typealias Result = U
internal init(_ closure: @escaping (T) -> U) {

View File

@@ -68,22 +68,8 @@ extension Internals {
}
set {
if #available(iOS 11.0, macOS 10.13, watchOS 4.0, tvOS 11.0, *) {
self.copiedAffectedStores = (newValue as NSArray?)?.copy() as! NSArray?
super.affectedStores = newValue
return
}
// Bugfix for NSFetchRequest messing up memory management for `affectedStores`
// http://stackoverflow.com/questions/14396375/nsfetchedresultscontroller-crashes-in-ios-6-if-affectedstores-is-specified
if let releaseArray = self.releaseArray {
releaseArray.release()
self.releaseArray = nil
}
self.copiedAffectedStores = (newValue as NSArray?)?.copy() as! NSArray?
super.affectedStores = newValue
self.releaseArray = (super.affectedStores as NSArray?).map(Unmanaged<NSArray>.passRetained(_:))
}
}

View File

@@ -33,7 +33,6 @@ extension Internals {
// MARK: - CoreStoreFetchedResultsController
@available(macOS 10.12, *)
internal final class CoreStoreFetchedResultsController: NSFetchedResultsController<NSManagedObject> {
// MARK: Internal

View File

@@ -47,10 +47,16 @@ extension Internals {
// MARK: Internal
init(sections: [NSFetchedResultsSectionInfo], fetchOffset: Int, fetchLimit: Int) {
init(
sections: [NSFetchedResultsSectionInfo],
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?,
fetchOffset: Int,
fetchLimit: Int
) {
self.structure = .init(
sections: sections,
sectionIndexTransformer: sectionIndexTransformer,
fetchOffset: Swift.max(0, fetchOffset),
fetchLimit: (fetchLimit > 0) ? fetchLimit : nil
)
@@ -194,7 +200,7 @@ extension Internals {
self.structure.insert(itemIDs: identifiers, after: afterIdentifier)
}
mutating func deleteItems<S: Sequence>(_ identifiers: S) where S.Element == NSManagedObjectID {
mutating func deleteItems<C: Collection>(_ identifiers: C) where C.Element == NSManagedObjectID {
self.structure.remove(itemIDs: identifiers)
}
@@ -214,7 +220,7 @@ extension Internals {
self.structure.move(itemID: identifier, after: toIdentifier)
}
mutating func reloadItems<S: Sequence>(_ identifiers: S) where S.Element == NSManagedObjectID {
mutating func reloadItems<C: Collection>(_ identifiers: C) where C.Element == NSManagedObjectID {
self.structure.update(itemIDs: identifiers)
}
@@ -234,7 +240,7 @@ extension Internals {
self.structure.insert(sectionIDs: identifiers, after: toIdentifier)
}
mutating func deleteSections<S: Sequence>(_ identifiers: S) where S.Element == String {
mutating func deleteSections<C: Collection>(_ identifiers: C) where C.Element == String {
self.structure.remove(sectionIDs: identifiers)
}
@@ -249,10 +255,55 @@ extension Internals {
self.structure.move(sectionID: identifier, after: toIdentifier)
}
mutating func reloadSections<S: Sequence>(_ identifiers: S) where S.Element == String {
mutating func reloadSections<C: Collection>(_ identifiers: C) where C.Element == String {
self.structure.update(sectionIDs: identifiers)
}
mutating func unsafeAppendItems<C: Collection>(_ identifiers: C, toSectionAt sectionIndex: Int) where C.Element == NSManagedObjectID {
self.structure.unsafeAppend(identifiers, toSectionAt: sectionIndex)
}
mutating func unsafeInsertItems<C: Collection>(_ identifiers: C, at indexPath: IndexPath) where C.Element == NSManagedObjectID {
self.structure.unsafeInsert(itemIDs: identifiers, at: indexPath)
}
mutating func unsafeDeleteItems<C: Collection>(at indexPaths: C) where C.Element == IndexPath {
self.structure.unsafeRemove(itemsAt: indexPaths)
}
mutating func unsafeMoveItem(at indexPath: IndexPath, to newIndexPath: IndexPath) {
self.structure.unsafeMove(itemAt: indexPath, to: newIndexPath)
}
mutating func unsafeReloadItems<C: Collection>(at indexPaths: C) where C.Element == IndexPath {
self.structure.unsafeUpdate(itemsAt: indexPaths)
}
mutating func unsafeInsertSections<C: Collection>(_ identifiers: C, at sectionIndex: Int) where C.Element == String {
self.structure.unsafeInsert(identifiers, at: sectionIndex)
}
mutating func unsafeDeleteSections<C: Collection>(at sectionIndices: C) where C.Element == Int {
self.structure.unsafeRemove(sectionsAt: sectionIndices)
}
mutating func unsafeMoveSection(at sectionIndex: Int, to newSectionIndex: Int) {
self.structure.unsafeMove(sectionAt: sectionIndex, to: newSectionIndex)
}
mutating func unsafeReloadSections<C: Collection>(at sectionIndices: C) where C.Element == Int {
self.structure.unsafeUpdate(sectionsAt: sectionIndices)
}
// MARK: Private
@@ -264,11 +315,18 @@ extension Internals {
internal struct Section: DifferentiableSection, Equatable {
let indexTitle: String?
var isReloaded: Bool
init(differenceIdentifier: String, items: [Item] = [], isReloaded: Bool = false) {
init(
differenceIdentifier: String,
indexTitle: String?,
items: [Item] = [],
isReloaded: Bool = false
) {
self.differenceIdentifier = differenceIdentifier
self.indexTitle = indexTitle
self.elements = items
self.isReloaded = isReloaded
}
@@ -292,6 +350,7 @@ extension Internals {
self.init(
differenceIdentifier: source.differenceIdentifier,
indexTitle: source.indexTitle,
items: Array(elements),
isReloaded: source.isReloaded
)
@@ -329,16 +388,23 @@ extension Internals {
// MARK: Internal
let sectionIndexTransformer: (_ sectionName: String?) -> String?
var sections: [Section]
private(set) var reloadedItems: Set<NSManagedObjectID>
init() {
self.sectionIndexTransformer = { _ in nil }
self.sections = []
self.reloadedItems = []
}
init(sections: [NSFetchedResultsSectionInfo], fetchOffset: Int, fetchLimit: Int?) {
init(
sections: [NSFetchedResultsSectionInfo],
sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?,
fetchOffset: Int,
fetchLimit: Int?
) {
let sliceItems: (_ array: [Any], _ offset: Int) -> Array<Any>.SubSequence
if let fetchLimit = fetchLimit {
@@ -373,9 +439,14 @@ extension Internals {
continue
}
newSections.append(
Section(differenceIdentifier: section.name, items: items)
Section(
differenceIdentifier: section.name,
indexTitle: section.indexTitle,
items: items
)
)
}
self.sectionIndexTransformer = sectionIndexTransformer
self.sections = newSections
self.reloadedItems = []
}
@@ -406,13 +477,23 @@ extension Internals {
}
return self.sections[sectionIndex].elements.map({ $0.differenceIdentifier })
}
func unsafeItem(at indexPath: IndexPath) -> NSManagedObjectID {
return self.sections[indexPath.section]
.elements[indexPath.item]
.differenceIdentifier
}
func section(containing itemID: NSManagedObjectID) -> String? {
return self.itemPositionMap(itemID)?.section.differenceIdentifier
}
mutating func append<C: Collection>(itemIDs: C, to sectionID: String?) where C.Element == NSManagedObjectID {
mutating func append<C: Collection>(
itemIDs: C,
to sectionID: String?
) where C.Element == NSManagedObjectID {
let index: Array<Section>.Index
if let sectionID = sectionID {
@@ -435,8 +516,30 @@ extension Internals {
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
self.sections[index].elements.append(contentsOf: items)
}
mutating func unsafeAppend<C: Collection>(
_ itemIDs: C,
toSectionAt sectionIndex: Int?
) where C.Element == NSManagedObjectID {
let index: Array<Section>.Index
if let sectionIndex = sectionIndex {
mutating func insert<C: Collection>(itemIDs: C, before beforeItemID: NSManagedObjectID) where C.Element == NSManagedObjectID {
index = sectionIndex
}
else {
let section = self.sections
index = section.index(before: section.endIndex)
}
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
self.sections[index].elements.append(contentsOf: items)
}
mutating func insert<C: Collection>(
itemIDs: C,
before beforeItemID: NSManagedObjectID
) where C.Element == NSManagedObjectID {
guard let itemPosition = self.itemPositionMap(beforeItemID) else {
@@ -447,7 +550,10 @@ extension Internals {
.insert(contentsOf: items, at: itemPosition.itemRelativeIndex)
}
mutating func insert<C: Collection>(itemIDs: C, after afterItemID: NSManagedObjectID) where C.Element == NSManagedObjectID {
mutating func insert<C: Collection>(
itemIDs: C,
after afterItemID: NSManagedObjectID
) where C.Element == NSManagedObjectID {
guard let itemPosition = self.itemPositionMap(afterItemID) else {
@@ -459,6 +565,16 @@ extension Internals {
self.sections[itemPosition.sectionIndex].elements
.insert(contentsOf: items, at: itemIndex)
}
mutating func unsafeInsert<C: Collection>(
itemIDs: C,
at indexPath: IndexPath
) where C.Element == NSManagedObjectID {
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
self.sections[indexPath.section].elements
.insert(contentsOf: items, at: indexPath.item)
}
mutating func remove<S: Sequence>(itemIDs: S) where S.Element == NSManagedObjectID {
@@ -482,6 +598,23 @@ extension Internals {
}
}
}
mutating func unsafeRemove<S: Sequence>(itemsAt indexPaths: S) where S.Element == IndexPath {
var removeIndexSetMap: [Int: IndexSet] = [:]
for indexPath in indexPaths {
removeIndexSetMap[indexPath.section, default: []]
.insert(indexPath.item)
}
for (sectionIndex, removeIndexSet) in removeIndexSetMap {
for range in removeIndexSet.rangeView.reversed() {
self.sections[sectionIndex].elements.removeSubrange(range)
}
}
}
mutating func removeAllItems() {
@@ -496,7 +629,10 @@ extension Internals {
self.sections.removeAll(where: { $0.elements.isEmpty })
}
mutating func move(itemID: NSManagedObjectID, before beforeItemID: NSManagedObjectID) {
mutating func move(
itemID: NSManagedObjectID,
before beforeItemID: NSManagedObjectID
) {
guard let removed = self.remove(itemID: itemID) else {
@@ -510,7 +646,10 @@ extension Internals {
.insert(removed, at: itemPosition.itemRelativeIndex)
}
mutating func move(itemID: NSManagedObjectID, after afterItemID: NSManagedObjectID) {
mutating func move(
itemID: NSManagedObjectID,
after afterItemID: NSManagedObjectID
) {
guard let removed = self.remove(itemID: itemID) else {
@@ -525,6 +664,17 @@ extension Internals {
self.sections[itemPosition.sectionIndex].elements
.insert(removed, at: itemIndex)
}
mutating func unsafeMove(
itemAt indexPath: IndexPath,
to newIndexPath: IndexPath
) {
let itemID = self.sections[indexPath.section].elements
.remove(at: indexPath.item)
self.sections[newIndexPath.section].elements
.insert(itemID, at: newIndexPath.item)
}
mutating func update<S: Sequence>(itemIDs: S) where S.Element == NSManagedObjectID {
@@ -542,31 +692,86 @@ extension Internals {
}
self.reloadedItems.formUnion(newItemIDs)
}
mutating func unsafeUpdate<S: Sequence>(itemsAt indexPaths: S) where S.Element == IndexPath {
var newItemIDs: Set<NSManagedObjectID> = []
for indexPath in indexPaths {
self.sections[indexPath.section]
.elements[indexPath.item].isReloaded = true
newItemIDs.insert(self.unsafeItem(at: indexPath))
}
self.reloadedItems.formUnion(newItemIDs)
}
mutating func append<C: Collection>(sectionIDs: C) where C.Element == String {
let newSections = sectionIDs.lazy.map({ Section(differenceIdentifier: $0) })
let sectionIndexTransformer = self.sectionIndexTransformer
let newSections = sectionIDs.lazy.map {
return Section(
differenceIdentifier: $0,
indexTitle: sectionIndexTransformer($0)
)
}
self.sections.append(contentsOf: newSections)
}
mutating func insert<C: Collection>(sectionIDs: C, before beforeSectionID: String) where C.Element == String {
mutating func insert<C: Collection>(
sectionIDs: C,
before beforeSectionID: String
) where C.Element == String {
guard let sectionIndex = self.sectionIndex(of: beforeSectionID) else {
Internals.abort("Section \"\(beforeSectionID)\" does not exist")
}
let newSections = sectionIDs.lazy.map({ Section(differenceIdentifier: $0) })
let sectionIndexTransformer = self.sectionIndexTransformer
let newSections = sectionIDs.lazy.map {
return Section(
differenceIdentifier: $0,
indexTitle: sectionIndexTransformer($0)
)
}
self.sections.insert(contentsOf: newSections, at: sectionIndex)
}
mutating func insert<C: Collection>(sectionIDs: C, after afterSectionID: String) where C.Element == String {
mutating func insert<C: Collection>(
sectionIDs: C,
after afterSectionID: String
) where C.Element == String {
guard let beforeIndex = self.sectionIndex(of: afterSectionID) else {
Internals.abort("Section \"\(afterSectionID)\" does not exist")
}
let sectionIndexTransformer = self.sectionIndexTransformer
let sectionIndex = self.sections.index(after: beforeIndex)
let newSections = sectionIDs.lazy.map({ Section(differenceIdentifier: $0) })
let newSections = sectionIDs.lazy.map {
return Section(
differenceIdentifier: $0,
indexTitle: sectionIndexTransformer($0)
)
}
self.sections.insert(contentsOf: newSections, at: sectionIndex)
}
mutating func unsafeInsert<C: Collection>(
_ sectionIDs: C,
at sectionIndex: Int
) where C.Element == String {
let sectionIndexTransformer = self.sectionIndexTransformer
let newSections = sectionIDs.lazy.map {
return Section(
differenceIdentifier: $0,
indexTitle: sectionIndexTransformer($0)
)
}
self.sections.insert(contentsOf: newSections, at: sectionIndex)
}
@@ -577,6 +782,16 @@ extension Internals {
self.remove(sectionID: sectionID)
}
}
mutating func unsafeRemove<S: Sequence>(
sectionsAt sectionIndices: S
) where S.Element == Int {
for sectionIndex in sectionIndices.sorted(by: >) {
self.sections.remove(at: sectionIndex)
}
}
mutating func move(sectionID: String, before beforeSectionID: String) {
@@ -604,8 +819,21 @@ extension Internals {
let sectionIndex = self.sections.index(after: beforeIndex)
self.sections.insert(removed, at: sectionIndex)
}
mutating func unsafeMove(
sectionAt sectionIndex: Int,
to newSectionIndex: Int
) {
self.sections.move(
fromOffsets: .init(integer: sectionIndex),
toOffset: newSectionIndex
)
}
mutating func update<S: Sequence>(sectionIDs: S) where S.Element == String {
mutating func update<S: Sequence>(
sectionIDs: S
) where S.Element == String {
for sectionID in sectionIDs {
@@ -616,6 +844,16 @@ extension Internals {
self.sections[sectionIndex].isReloaded = true
}
}
mutating func unsafeUpdate<S: Sequence>(
sectionsAt sectionIndices: S
) where S.Element == Int {
for sectionIndex in sectionIndices {
self.sections[sectionIndex].isReloaded = true
}
}
// MARK: Private

View File

@@ -59,16 +59,14 @@ extension Internals {
Target,
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
) -> Void,
completion: @escaping () -> Void
) -> Void
) {
self.apply(
.init(),
target: target,
animatingDifferences: animatingDifferences,
performUpdates: performUpdates,
completion: completion
performUpdates: performUpdates
)
}
@@ -80,8 +78,7 @@ extension Internals {
Target,
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
) -> Void,
completion: @escaping () -> Void
) -> Void
) {
self.dispatcher.dispatch { [weak self] in
@@ -112,7 +109,6 @@ extension Internals {
#if canImport(QuartzCore)
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
if !animatingDifferences {
@@ -122,11 +118,9 @@ extension Internals {
CATransaction.commit()
#else
performDiffingUpdates()
completion()
#endif
@@ -191,6 +185,20 @@ extension Internals {
}
return self.sections[section].elements.count
}
func sectionIndexTitle(for section: Int) -> String? {
guard self.sections.indices.contains(section) else {
return nil
}
return self.sections[section].indexTitle
}
func sectionIndexTitlesForAllSections() -> [String?] {
return self.sections.map({ $0.indexTitle })
}
// MARK: Private

View File

@@ -41,9 +41,12 @@ import AppKit
internal protocol FetchedDiffableDataSourceSnapshotHandler: AnyObject {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: Internals.DiffableDataSourceSnapshot)
var sectionIndexTransformer: (_ sectionName: KeyPathString?) -> String? { get }
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String?) -> String?
func controller(
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChangeContentWith snapshot: Internals.DiffableDataSourceSnapshot
)
}
@@ -92,6 +95,7 @@ extension Internals {
var snapshot = Internals.DiffableDataSourceSnapshot(
sections: controller.sections ?? [],
sectionIndexTransformer: self.handler.map({ $0.sectionIndexTransformer }) ?? { _ in nil },
fetchOffset: controller.fetchRequest.fetchOffset,
fetchLimit: controller.fetchRequest.fetchLimit
)
@@ -109,10 +113,7 @@ extension Internals {
@objc
dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String) -> String? {
return self.handler?.controller(
controller,
sectionIndexTitleForSectionName: sectionName
)
return self.handler?.sectionIndexTransformer(sectionName)
}
@objc

View File

@@ -29,18 +29,32 @@ import CoreData
// MARK: - FetchedResultsControllerHandler
@available(macOS 10.12, *)
internal protocol FetchedResultsControllerHandler: AnyObject {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeObject anObject: Any, atIndexPath indexPath: IndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: IndexPath?)
var sectionIndexTransformer: (_ sectionName: KeyPathString?) -> String? { get }
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType)
func controller(
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChangeObject anObject: Any,
atIndexPath indexPath: IndexPath?,
forChangeType type: NSFetchedResultsChangeType,
newIndexPath: IndexPath?
)
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
func controller(
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChangeSection sectionInfo: NSFetchedResultsSectionInfo,
atIndex sectionIndex: Int,
forChangeType type: NSFetchedResultsChangeType
)
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
func controllerWillChangeContent(
_ controller: NSFetchedResultsController<NSFetchRequestResult>
)
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String?) -> String?
func controllerDidChangeContent(
_ controller: NSFetchedResultsController<NSFetchRequestResult>
)
}
@@ -50,7 +64,6 @@ extension Internals {
// MARK: - FetchedResultsControllerDelegate
@available(macOS 10.12, *)
internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate {
// MARK: Internal
@@ -90,14 +103,6 @@ extension Internals {
return
}
if #available(iOS 10.3, tvOS 10.3, watchOS 3.2, macOS 10.13, *) {}
else {
self.deletedSections = []
self.insertedSections = []
}
self.handler?.controllerWillChangeContent(controller)
}
@@ -112,7 +117,6 @@ extension Internals {
return
}
self.handler?.controllerDidChangeContent(controller)
}
@@ -123,102 +127,11 @@ extension Internals {
return
}
if #available(iOS 10.3, tvOS 10.3, watchOS 3.2, macOS 10.13, *) {
self.handler?.controller(
controller,
didChangeObject: anObject,
atIndexPath: indexPath,
forChangeType: type,
newIndexPath: newIndexPath
)
return
}
guard var actualType = NSFetchedResultsChangeType(rawValue: type.rawValue) else {
// This fix is for a bug where iOS passes 0 for NSFetchedResultsChangeType, but this is not a valid enum case.
// Swift will then always execute the first case of the switch causing strange behaviour.
// https://forums.developer.apple.com/thread/12184#31850
return
}
// This whole dance is a workaround for a nasty bug introduced in XCode 7 targeted at iOS 8 devices
// http://stackoverflow.com/questions/31383760/ios-9-attempt-to-delete-and-reload-the-same-index-path/31384014#31384014
// https://forums.developer.apple.com/message/9998#9998
// https://forums.developer.apple.com/message/31849#31849
if case .update = actualType,
indexPath != nil,
newIndexPath != nil {
actualType = .move
}
switch actualType {
case .update:
guard let section = indexPath?[0] else {
return
}
if self.deletedSections.contains(section)
|| self.insertedSections.contains(section) {
return
}
case .move:
guard let indexPath = indexPath, let newIndexPath = newIndexPath else {
return
}
guard indexPath == newIndexPath else {
break
}
if self.insertedSections.contains(indexPath[0]) {
// Observers that handle the .Move change are advised to delete then reinsert the object instead of just moving. This is especially true when indexPath and newIndexPath are equal. For example, calling tableView.moveRowAtIndexPath(_:toIndexPath) when both indexPaths are the same will crash the tableView.
self.handler?.controller(
controller,
didChangeObject: anObject,
atIndexPath: indexPath,
forChangeType: .move,
newIndexPath: newIndexPath
)
return
}
if self.deletedSections.contains(indexPath[0]) {
self.handler?.controller(
controller,
didChangeObject: anObject,
atIndexPath: nil,
forChangeType: .insert,
newIndexPath: indexPath
)
return
}
self.handler?.controller(
controller,
didChangeObject: anObject,
atIndexPath: indexPath,
forChangeType: .update,
newIndexPath: nil
)
return
default:
break
}
self.handler?.controller(
controller,
didChangeObject: anObject,
atIndexPath: indexPath,
forChangeType: actualType,
forChangeType: type,
newIndexPath: newIndexPath
)
}
@@ -230,18 +143,6 @@ extension Internals {
return
}
if #available(iOS 10.3, tvOS 10.3, watchOS 3.2, macOS 10.13, *) {}
else {
switch type {
case .delete: self.deletedSections.insert(sectionIndex)
case .insert: self.insertedSections.insert(sectionIndex)
default: break
}
}
self.handler?.controller(
controller,
didChangeSection: sectionInfo,
@@ -253,19 +154,7 @@ extension Internals {
@objc
dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String) -> String? {
return self.handler?.controller(
controller,
sectionIndexTitleForSectionName: sectionName
)
return self.handler?.sectionIndexTransformer(sectionName)
}
// MARK: Private
@nonobjc
private var deletedSections = Set<Int>()
@nonobjc
private var insertedSections = Set<Int>()
}
}

View File

@@ -66,7 +66,6 @@ import CoreData
```
In the example above, both `person1` and `person2` will contain the object at section=2, index=3.
*/
@available(macOS 10.12, *)
public final class ListMonitor<O: DynamicObject>: Hashable {
// MARK: Public (Accessors)
@@ -394,19 +393,31 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
observer,
willChange: { (observer, monitor) in
observer.listMonitorWillChange(monitor)
observer.listMonitorWillChange(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didChange: { (observer, monitor) in
observer.listMonitorDidChange(monitor)
observer.listMonitorDidChange(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
willRefetch: { (observer, monitor) in
observer.listMonitorWillRefetch(monitor)
observer.listMonitorWillRefetch(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didRefetch: { (observer, monitor) in
observer.listMonitorDidRefetch(monitor)
observer.listMonitorDidRefetch(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
}
)
}
@@ -429,38 +440,71 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
observer,
willChange: { (observer, monitor) in
observer.listMonitorWillChange(monitor)
observer.listMonitorWillChange(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didChange: { (observer, monitor) in
observer.listMonitorDidChange(monitor)
observer.listMonitorDidChange(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
willRefetch: { (observer, monitor) in
observer.listMonitorWillRefetch(monitor)
observer.listMonitorWillRefetch(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didRefetch: { (observer, monitor) in
observer.listMonitorDidRefetch(monitor)
observer.listMonitorDidRefetch(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
}
)
self.registerObserver(
observer,
didInsertObject: { (observer, monitor, object, toIndexPath) in
observer.listMonitor(monitor, didInsertObject: object, toIndexPath: toIndexPath)
observer.listMonitor(
monitor,
didInsertObject: object,
toIndexPath: toIndexPath,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didDeleteObject: { (observer, monitor, object, fromIndexPath) in
observer.listMonitor(monitor, didDeleteObject: object, fromIndexPath: fromIndexPath)
observer.listMonitor(
monitor,
didDeleteObject: object,
fromIndexPath: fromIndexPath,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didUpdateObject: { (observer, monitor, object, atIndexPath) in
observer.listMonitor(monitor, didUpdateObject: object, atIndexPath: atIndexPath)
observer.listMonitor(
monitor,
didUpdateObject: object,
atIndexPath: atIndexPath,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didMoveObject: { (observer, monitor, object, fromIndexPath, toIndexPath) in
observer.listMonitor(monitor, didMoveObject: object, fromIndexPath: fromIndexPath, toIndexPath: toIndexPath)
observer.listMonitor(
monitor,
didMoveObject: object,
fromIndexPath: fromIndexPath,
toIndexPath: toIndexPath,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
}
)
}
@@ -483,49 +527,92 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
observer,
willChange: { (observer, monitor) in
observer.listMonitorWillChange(monitor)
observer.listMonitorWillChange(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didChange: { (observer, monitor) in
observer.listMonitorDidChange(monitor)
observer.listMonitorDidChange(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
willRefetch: { (observer, monitor) in
observer.listMonitorWillRefetch(monitor)
observer.listMonitorWillRefetch(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didRefetch: { (observer, monitor) in
observer.listMonitorDidRefetch(monitor)
observer.listMonitorDidRefetch(
monitor,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
}
)
self.registerObserver(
observer,
didInsertObject: { (observer, monitor, object, toIndexPath) in
observer.listMonitor(monitor, didInsertObject: object, toIndexPath: toIndexPath)
observer.listMonitor(
monitor,
didInsertObject: object,
toIndexPath: toIndexPath,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didDeleteObject: { (observer, monitor, object, fromIndexPath) in
observer.listMonitor(monitor, didDeleteObject: object, fromIndexPath: fromIndexPath)
observer.listMonitor(
monitor,
didDeleteObject: object,
fromIndexPath: fromIndexPath,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didUpdateObject: { (observer, monitor, object, atIndexPath) in
observer.listMonitor(monitor, didUpdateObject: object, atIndexPath: atIndexPath)
observer.listMonitor(
monitor,
didUpdateObject: object,
atIndexPath: atIndexPath,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didMoveObject: { (observer, monitor, object, fromIndexPath, toIndexPath) in
observer.listMonitor(monitor, didMoveObject: object, fromIndexPath: fromIndexPath, toIndexPath: toIndexPath)
observer.listMonitor(
monitor,
didMoveObject: object,
fromIndexPath: fromIndexPath,
toIndexPath: toIndexPath,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
}
)
self.registerObserver(
observer,
didInsertSection: { (observer, monitor, sectionInfo, toIndex) in
observer.listMonitor(monitor, didInsertSection: sectionInfo, toSectionIndex: toIndex)
observer.listMonitor(
monitor,
didInsertSection: sectionInfo,
toSectionIndex: toIndex,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
},
didDeleteSection: { (observer, monitor, sectionInfo, fromIndex) in
observer.listMonitor(monitor, didDeleteSection: sectionInfo, fromSectionIndex: fromIndex)
observer.listMonitor(
monitor,
didDeleteSection: sectionInfo,
fromSectionIndex: fromIndex,
sourceIdentifier: monitor.fetchedResultsController.managedObjectContext.saveMetadata?.sourceIdentifier
)
}
)
}
@@ -556,11 +643,18 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
`refetch(...)` broadcasts `listMonitorWillRefetch(...)` to its observers immediately, and then `listMonitorDidRefetch(...)` after the new fetch request completes.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
- Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`.
*/
public func refetch(_ fetchClauses: FetchClause...) {
public func refetch(
_ fetchClauses: FetchClause...,
sourceIdentifier: Any?
) {
self.refetch(fetchClauses)
self.refetch(
fetchClauses,
sourceIdentifier: sourceIdentifier
)
}
/**
@@ -569,14 +663,21 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
`refetch(...)` broadcasts `listMonitorWillRefetch(...)` to its observers immediately, and then `listMonitorDidRefetch(...)` after the new fetch request completes.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
- Important: Starting CoreStore 4.0, all `FetchClause`s required by the `ListMonitor` should be provided in the arguments list of `refetch(...)`.
*/
public func refetch(_ fetchClauses: [FetchClause]) {
public func refetch(
_ fetchClauses: [FetchClause],
sourceIdentifier: Any?
) {
self.refetch { (fetchRequest) in
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
}
self.refetch(
{ (fetchRequest) in
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
},
sourceIdentifier: sourceIdentifier
)
}
@@ -628,7 +729,12 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
// MARK: Internal
internal convenience init(dataStack: DataStack, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
internal convenience init(
dataStack: DataStack,
from: From<O>,
sectionBy: SectionBy<O>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
) {
self.init(
context: dataStack.mainContext,
@@ -640,7 +746,13 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal convenience init(dataStack: DataStack, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (ListMonitor<O>) -> Void) {
internal convenience init(
dataStack: DataStack,
from: From<O>,
sectionBy: SectionBy<O>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
createAsynchronously: @escaping (ListMonitor<O>) -> Void
) {
self.init(
context: dataStack.mainContext,
@@ -652,7 +764,12 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
internal convenience init(
unsafeTransaction: UnsafeDataTransaction,
from: From<O>,
sectionBy: SectionBy<O>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
) {
self.init(
context: unsafeTransaction.context,
@@ -664,7 +781,13 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (ListMonitor<O>) -> Void) {
internal convenience init(
unsafeTransaction: UnsafeDataTransaction,
from: From<O>,
sectionBy: SectionBy<O>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
createAsynchronously: @escaping (ListMonitor<O>) -> Void
) {
self.init(
context: unsafeTransaction.context,
@@ -676,7 +799,12 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal func registerChangeNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor<O>) -> Void) {
internal func registerChangeNotification(
_ notificationKey: UnsafeRawPointer,
name: Notification.Name,
toObserver observer: AnyObject,
callback: @escaping (_ monitor: ListMonitor<O>) -> Void
) {
Internals.setAssociatedRetainedObject(
Internals.NotificationObserver(
@@ -696,7 +824,16 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal func registerObjectNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor<O>, _ object: O, _ indexPath: IndexPath?, _ newIndexPath: IndexPath?) -> Void) {
internal func registerObjectNotification(
_ notificationKey: UnsafeRawPointer,
name: Notification.Name,
toObserver observer: AnyObject,
callback: @escaping (
_ monitor: ListMonitor<O>,
_ object: O,
_ indexPath: IndexPath?,
_ newIndexPath: IndexPath?
) -> Void) {
Internals.setAssociatedRetainedObject(
Internals.NotificationObserver(
@@ -723,7 +860,16 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal func registerSectionNotification(_ notificationKey: UnsafeRawPointer, name: Notification.Name, toObserver observer: AnyObject, callback: @escaping (_ monitor: ListMonitor<O>, _ sectionInfo: NSFetchedResultsSectionInfo, _ sectionIndex: Int) -> Void) {
internal func registerSectionNotification(
_ notificationKey: UnsafeRawPointer,
name: Notification.Name,
toObserver observer: AnyObject,
callback: @escaping (
_ monitor: ListMonitor<O>,
_ sectionInfo: NSFetchedResultsSectionInfo,
_ sectionIndex: Int
) -> Void
) {
Internals.setAssociatedRetainedObject(
Internals.NotificationObserver(
@@ -746,7 +892,24 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal func registerObserver<U: AnyObject>(_ observer: U, willChange: @escaping (_ observer: U, _ monitor: ListMonitor<O>) -> Void, didChange: @escaping (_ observer: U, _ monitor: ListMonitor<O>) -> Void, willRefetch: @escaping (_ observer: U, _ monitor: ListMonitor<O>) -> Void, didRefetch: @escaping (_ observer: U, _ monitor: ListMonitor<O>) -> Void) {
internal func registerObserver<U: AnyObject>(
_ observer: U,
willChange: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>
) -> Void,
didChange: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>
) -> Void,
willRefetch: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>
) -> Void,
didRefetch: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>
) -> Void) {
Internals.assert(
Thread.isMainThread,
@@ -806,7 +969,33 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal func registerObserver<U: AnyObject>(_ observer: U, didInsertObject: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ object: O, _ toIndexPath: IndexPath) -> Void, didDeleteObject: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ object: O, _ fromIndexPath: IndexPath) -> Void, didUpdateObject: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ object: O, _ atIndexPath: IndexPath) -> Void, didMoveObject: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ object: O, _ fromIndexPath: IndexPath, _ toIndexPath: IndexPath) -> Void) {
internal func registerObserver<U: AnyObject>(
_ observer: U,
didInsertObject: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>,
_ object: O,
_ toIndexPath: IndexPath
) -> Void,
didDeleteObject: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>,
_ object: O,
_ fromIndexPath: IndexPath
) -> Void,
didUpdateObject: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>,
_ object: O,
_ atIndexPath: IndexPath
) -> Void,
didMoveObject: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>,
_ object: O,
_ fromIndexPath: IndexPath,
_ toIndexPath: IndexPath
) -> Void) {
Internals.assert(
Thread.isMainThread,
@@ -867,7 +1056,20 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
)
}
internal func registerObserver<U: AnyObject>(_ observer: U, didInsertSection: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ sectionInfo: NSFetchedResultsSectionInfo, _ toIndex: Int) -> Void, didDeleteSection: @escaping (_ observer: U, _ monitor: ListMonitor<O>, _ sectionInfo: NSFetchedResultsSectionInfo, _ fromIndex: Int) -> Void) {
internal func registerObserver<U: AnyObject>(
_ observer: U,
didInsertSection: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>,
_ sectionInfo: NSFetchedResultsSectionInfo,
_ toIndex: Int
) -> Void,
didDeleteSection: @escaping (
_ observer: U,
_ monitor: ListMonitor<O>,
_ sectionInfo: NSFetchedResultsSectionInfo,
_ fromIndex: Int
) -> Void) {
Internals.assert(
Thread.isMainThread,
@@ -923,7 +1125,10 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
Internals.setAssociatedRetainedObject(nilValue, forKey: &self.didDeleteSectionKey, inObject: observer)
}
internal func refetch(_ applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
internal func refetch(
_ applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
sourceIdentifier: Any?
) {
Internals.assert(
Thread.isMainThread,
@@ -988,10 +1193,15 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
self.isPendingRefetch = false
newFetchedResultsController.managedObjectContext.saveMetadata = .init(
isSavingSynchronously: false,
sourceIdentifier: sourceIdentifier
)
NotificationCenter.default.post(
name: Notification.Name.listMonitorDidRefetchList,
object: self
)
newFetchedResultsController.managedObjectContext.saveMetadata = nil
}
}
}
@@ -1008,7 +1218,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
fileprivate var fetchedResultsController: Internals.CoreStoreFetchedResultsController
fileprivate let taskGroup = DispatchGroup()
fileprivate let sectionIndexTransformer: (_ sectionName: KeyPathString?) -> String?
internal let sectionByIndexTransformer: (_ sectionName: KeyPathString?) -> String?
private let isSectioned: Bool
@@ -1052,7 +1262,15 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
}
}
private static func recreateFetchedResultsController(context: NSManagedObjectContext, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) -> (controller: Internals.CoreStoreFetchedResultsController, delegate: Internals.FetchedResultsControllerDelegate) {
private static func recreateFetchedResultsController(
context: NSManagedObjectContext,
from: From<O>,
sectionBy: SectionBy<O>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
) -> (
controller: Internals.CoreStoreFetchedResultsController,
delegate: Internals.FetchedResultsControllerDelegate
) {
let fetchRequest = Internals.CoreStoreFetchRequest<NSManagedObject>()
fetchRequest.fetchLimit = 0
@@ -1078,7 +1296,14 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
private let from: From<O>
private let sectionBy: SectionBy<O>?
private init(context: NSManagedObjectContext, transactionQueue: DispatchQueue, from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: ((ListMonitor<O>) -> Void)?) {
private init(
context: NSManagedObjectContext,
transactionQueue: DispatchQueue,
from: From<O>,
sectionBy: SectionBy<O>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
createAsynchronously: ((ListMonitor<O>) -> Void)?
) {
self.isSectioned = (sectionBy != nil)
self.from = from
@@ -1092,11 +1317,11 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer {
self.sectionIndexTransformer = sectionIndexTransformer
self.sectionByIndexTransformer = sectionIndexTransformer
}
else {
self.sectionIndexTransformer = { $0 }
self.sectionByIndexTransformer = { _ in nil }
}
self.transactionQueue = transactionQueue
self.applyFetchClauses = applyFetchClauses
@@ -1125,7 +1350,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
return
}
self.refetch(self.applyFetchClauses)
self.refetch(self.applyFetchClauses, sourceIdentifier: nil)
}
)
@@ -1149,7 +1374,7 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
if previousStores != currentStores {
self.refetch(self.applyFetchClauses)
self.refetch(self.applyFetchClauses, sourceIdentifier: nil)
}
}
@@ -1178,7 +1403,6 @@ public final class ListMonitor<O: DynamicObject>: Hashable {
// MARK: - ListMonitor where O: NSManagedObject
@available(macOS 10.12, *)
extension ListMonitor where O: NSManagedObject {
/**
@@ -1236,7 +1460,6 @@ extension ListMonitor where O: NSManagedObject {
// MARK: - ListMonitor where O: CoreStoreObject
@available(macOS 10.12, *)
extension ListMonitor where O: CoreStoreObject {
/**
@@ -1279,7 +1502,7 @@ extension ListMonitor where O: CoreStoreObject {
}
// MARK: - Deprecated
// MARK: Deprecated
@available(*, deprecated, renamed: "O")
public typealias D = O
@@ -1288,12 +1511,22 @@ extension ListMonitor where O: CoreStoreObject {
// MARK: - ListMonitor: FetchedResultsControllerHandler
@available(macOS 10.12, *)
extension ListMonitor: FetchedResultsControllerHandler {
// MARK: FetchedResultsControllerHandler
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeObject anObject: Any, atIndexPath indexPath: IndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
internal var sectionIndexTransformer: (_ sectionName: KeyPathString?) -> String? {
return self.sectionByIndexTransformer
}
internal func controller(
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChangeObject anObject: Any,
atIndexPath indexPath: IndexPath?,
forChangeType type: NSFetchedResultsChangeType,
newIndexPath: IndexPath?
) {
switch type {
@@ -1343,7 +1576,12 @@ extension ListMonitor: FetchedResultsControllerHandler {
}
}
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
internal func controller(
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChangeSection sectionInfo: NSFetchedResultsSectionInfo,
atIndex sectionIndex: Int,
forChangeType type: NSFetchedResultsChangeType
) {
switch type {
@@ -1372,7 +1610,9 @@ extension ListMonitor: FetchedResultsControllerHandler {
}
}
internal func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
internal func controllerWillChangeContent(
_ controller: NSFetchedResultsController<NSFetchRequestResult>
) {
self.taskGroup.enter()
NotificationCenter.default.post(
@@ -1381,7 +1621,9 @@ extension ListMonitor: FetchedResultsControllerHandler {
)
}
internal func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
internal func controllerDidChangeContent(
_ controller: NSFetchedResultsController<NSFetchRequestResult>
) {
defer {
@@ -1392,17 +1634,11 @@ extension ListMonitor: FetchedResultsControllerHandler {
object: self
)
}
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String?) -> String? {
return self.sectionIndexTransformer(sectionName)
}
}
// MARK: - Notification Keys
@available(macOS 10.12, *)
extension Notification.Name {
fileprivate static let listMonitorWillChangeList = Notification.Name(rawValue: "listMonitorWillChangeList")

View File

@@ -39,7 +39,6 @@ import CoreData
monitor.addObserver(self)
```
*/
@available(macOS 10.12, *)
public protocol ListObserver: AnyObject {
/**
@@ -52,15 +51,54 @@ public protocol ListObserver: AnyObject {
The default implementation does nothing.
- parameter monitor: the `ListMonitor` monitoring the list being observed
- parameter sourceIdentifier: an optional identifier provided by the transaction source
*/
func listMonitorWillChange(_ monitor: ListMonitor<ListEntityType>)
func listMonitorWillChange(
_ monitor: ListMonitor<ListEntityType>,
sourceIdentifier: Any?
)
/**
Handles processing just before a change to the observed list occurs. (Optional)
The default implementation does nothing.
- parameter monitor: the `ListMonitor` monitoring the list being observed
*/
func listMonitorWillChange(
_ monitor: ListMonitor<ListEntityType>
)
/**
Handles processing right after a change to the observed list occurs. (Required)
- parameter monitor: the `ListMonitor` monitoring the object being observed
- parameter sourceIdentifier: an optional identifier provided by the transaction source
*/
func listMonitorDidChange(
_ monitor: ListMonitor<ListEntityType>,
sourceIdentifier: Any?
)
/**
Handles processing right after a change to the observed list occurs. (Required)
- parameter monitor: the `ListMonitor` monitoring the object being observed
*/
func listMonitorDidChange(_ monitor: ListMonitor<ListEntityType>)
func listMonitorDidChange(
_ monitor: ListMonitor<ListEntityType>
)
/**
This method is broadcast from within the `ListMonitor`'s `refetch(...)` method to let observers prepare for the internal `NSFetchedResultsController`'s pending change to its predicate, sort descriptors, etc. (Optional)
- Important: All `ListMonitor` access between `listMonitorWillRefetch(_:)` and `listMonitorDidRefetch(_:)` will raise and assertion. The actual refetch will happen after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes.
- parameter monitor: the `ListMonitor` monitoring the object being observed
- parameter sourceIdentifier: an optional identifier provided by the transaction source
*/
func listMonitorWillRefetch(
_ monitor: ListMonitor<ListEntityType>,
sourceIdentifier: Any?
)
/**
This method is broadcast from within the `ListMonitor`'s `refetch(...)` method to let observers prepare for the internal `NSFetchedResultsController`'s pending change to its predicate, sort descriptors, etc. (Optional)
@@ -68,7 +106,21 @@ public protocol ListObserver: AnyObject {
- Important: All `ListMonitor` access between `listMonitorWillRefetch(_:)` and `listMonitorDidRefetch(_:)` will raise and assertion. The actual refetch will happen after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes.
- parameter monitor: the `ListMonitor` monitoring the object being observed
*/
func listMonitorWillRefetch(_ monitor: ListMonitor<ListEntityType>)
func listMonitorWillRefetch(
_ monitor: ListMonitor<ListEntityType>
)
/**
After the `ListMonitor`'s `refetch(...)` method is called, this method is broadcast after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes. (Required)
- Important: When `listMonitorDidRefetch(_:)` is called it should be assumed that all `ListMonitor`'s previous data have been reset, including counts, objects, and persistent stores.
- parameter monitor: the `ListMonitor` monitoring the object being observed
- parameter sourceIdentifier: an optional identifier provided by the transaction source
*/
func listMonitorDidRefetch(
_ monitor: ListMonitor<ListEntityType>,
sourceIdentifier: Any?
)
/**
After the `ListMonitor`'s `refetch(...)` method is called, this method is broadcast after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes. (Required)
@@ -76,18 +128,55 @@ public protocol ListObserver: AnyObject {
- Important: When `listMonitorDidRefetch(_:)` is called it should be assumed that all `ListMonitor`'s previous data have been reset, including counts, objects, and persistent stores.
- parameter monitor: the `ListMonitor` monitoring the object being observed
*/
func listMonitorDidRefetch(_ monitor: ListMonitor<ListEntityType>)
func listMonitorDidRefetch(
_ monitor: ListMonitor<ListEntityType>
)
}
// MARK: - ListObserver (Default Implementations)
@available(macOS 10.12, *)
extension ListObserver {
public func listMonitorWillChange(_ monitor: ListMonitor<ListEntityType>) { }
public func listMonitorWillChange(
_ monitor: ListMonitor<ListEntityType>,
sourceIdentifier: Any?
) {
self.listMonitorWillChange(monitor)
}
public func listMonitorWillRefetch(_ monitor: ListMonitor<ListEntityType>) { }
public func listMonitorWillChange(
_ monitor: ListMonitor<ListEntityType>
) {}
public func listMonitorDidChange(
_ monitor: ListMonitor<ListEntityType>,
sourceIdentifier: Any?
) {
self.listMonitorDidChange(monitor)
}
public func listMonitorWillRefetch(
_ monitor: ListMonitor<ListEntityType>,
sourceIdentifier: Any?
) {
self.listMonitorWillRefetch(monitor)
}
public func listMonitorWillRefetch(
_ monitor: ListMonitor<ListEntityType>
) {}
public func listMonitorDidRefetch(
_ monitor: ListMonitor<ListEntityType>,
sourceIdentifier: Any?
) {
self.listMonitorDidRefetch(monitor)
}
}
@@ -103,7 +192,6 @@ extension ListObserver {
monitor.addObserver(self)
```
*/
@available(macOS 10.12, *)
public protocol ListObjectObserver: ListObserver {
/**
@@ -113,8 +201,44 @@ public protocol ListObjectObserver: ListObserver {
- parameter monitor: the `ListMonitor` monitoring the list being observed
- parameter object: the entity type for the inserted object
- parameter indexPath: the new `NSIndexPath` for the inserted object
- parameter sourceIdentifier: an optional identifier provided by the transaction source
*/
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertObject object: ListEntityType, toIndexPath indexPath: IndexPath)
func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didInsertObject object: ListEntityType,
toIndexPath indexPath: IndexPath,
sourceIdentifier: Any?
)
/**
Notifies that an object was inserted to the specified `NSIndexPath` in the list. (Optional)
The default implementation does nothing.
- parameter monitor: the `ListMonitor` monitoring the list being observed
- parameter object: the entity type for the inserted object
- parameter indexPath: the new `NSIndexPath` for the inserted object
*/
func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didInsertObject object: ListEntityType,
toIndexPath indexPath: IndexPath
)
/**
Notifies that an object was deleted from the specified `NSIndexPath` in the list. (Optional)
The default implementation does nothing.
- parameter monitor: the `ListMonitor` monitoring the list being observed
- parameter object: the entity type for the deleted object
- parameter indexPath: the `NSIndexPath` for the deleted object
- parameter sourceIdentifier: an optional identifier provided by the transaction source
*/
func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didDeleteObject object: ListEntityType,
fromIndexPath indexPath: IndexPath,
sourceIdentifier: Any?
)
/**
Notifies that an object was deleted from the specified `NSIndexPath` in the list. (Optional)
@@ -124,7 +248,27 @@ public protocol ListObjectObserver: ListObserver {
- parameter object: the entity type for the deleted object
- parameter indexPath: the `NSIndexPath` for the deleted object
*/
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didDeleteObject object: ListEntityType, fromIndexPath indexPath: IndexPath)
func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didDeleteObject object: ListEntityType,
fromIndexPath indexPath: IndexPath
)
/**
Notifies that an object at the specified `NSIndexPath` was updated. (Optional)
The default implementation does nothing.
- parameter monitor: the `ListMonitor` monitoring the list being observed
- parameter object: the entity type for the updated object
- parameter indexPath: the `NSIndexPath` for the updated object
- parameter sourceIdentifier: an optional identifier provided by the transaction source
*/
func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didUpdateObject object: ListEntityType,
atIndexPath indexPath: IndexPath,
sourceIdentifier: Any?
)
/**
Notifies that an object at the specified `NSIndexPath` was updated. (Optional)
@@ -134,7 +278,29 @@ public protocol ListObjectObserver: ListObserver {
- parameter object: the entity type for the updated object
- parameter indexPath: the `NSIndexPath` for the updated object
*/
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didUpdateObject object: ListEntityType, atIndexPath indexPath: IndexPath)
func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didUpdateObject object: ListEntityType,
atIndexPath indexPath: IndexPath
)
/**
Notifies that an object's index changed. (Optional)
The default implementation does nothing.
- parameter monitor: the `ListMonitor` monitoring the list being observed
- parameter object: the entity type for the moved object
- parameter fromIndexPath: the previous `NSIndexPath` for the moved object
- parameter toIndexPath: the new `NSIndexPath` for the moved object
- parameter sourceIdentifier: an optional identifier provided by the transaction source
*/
func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didMoveObject object: ListEntityType,
fromIndexPath: IndexPath,
toIndexPath: IndexPath,
sourceIdentifier: Any?
)
/**
Notifies that an object's index changed. (Optional)
@@ -145,22 +311,101 @@ public protocol ListObjectObserver: ListObserver {
- parameter fromIndexPath: the previous `NSIndexPath` for the moved object
- parameter toIndexPath: the new `NSIndexPath` for the moved object
*/
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didMoveObject object: ListEntityType, fromIndexPath: IndexPath, toIndexPath: IndexPath)
func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didMoveObject object: ListEntityType,
fromIndexPath: IndexPath,
toIndexPath: IndexPath
)
}
// MARK: - ListObjectObserver (Default Implementations)
@available(macOS 10.12, *)
extension ListObjectObserver {
public func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertObject object: ListEntityType, toIndexPath indexPath: IndexPath) { }
public func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didInsertObject object: ListEntityType,
toIndexPath indexPath: IndexPath,
sourceIdentifier: Any?
) {
self.listMonitor(
monitor,
didInsertObject: object,
toIndexPath: indexPath
)
}
public func listMonitor(_ monitor: ListMonitor<ListEntityType>, didDeleteObject object: ListEntityType, fromIndexPath indexPath: IndexPath) { }
public func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didInsertObject object: ListEntityType,
toIndexPath indexPath: IndexPath
) {}
public func listMonitor(_ monitor: ListMonitor<ListEntityType>, didUpdateObject object: ListEntityType, atIndexPath indexPath: IndexPath) { }
public func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didDeleteObject object: ListEntityType,
fromIndexPath indexPath: IndexPath,
sourceIdentifier: Any?
) {
self.listMonitor(
monitor,
didDeleteObject: object,
fromIndexPath: indexPath
)
}
public func listMonitor(_ monitor: ListMonitor<ListEntityType>, didMoveObject object: ListEntityType, fromIndexPath: IndexPath, toIndexPath: IndexPath) { }
public func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didDeleteObject object: ListEntityType,
fromIndexPath indexPath: IndexPath
) {}
public func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didUpdateObject object: ListEntityType,
atIndexPath indexPath: IndexPath,
sourceIdentifier: Any?
) {
self.listMonitor(
monitor,
didUpdateObject: object,
atIndexPath: indexPath
)
}
public func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didUpdateObject object: ListEntityType,
atIndexPath indexPath: IndexPath
) {}
public func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didMoveObject object: ListEntityType,
fromIndexPath: IndexPath,
toIndexPath: IndexPath,
sourceIdentifier: Any?
) {
self.listMonitor(
monitor,
didMoveObject: object,
fromIndexPath: fromIndexPath,
toIndexPath: toIndexPath
)
}
public func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didMoveObject object: ListEntityType,
fromIndexPath: IndexPath,
toIndexPath: IndexPath
) {}
}
@@ -177,7 +422,6 @@ extension ListObjectObserver {
monitor.addObserver(self)
```
*/
@available(macOS 10.12, *)
public protocol ListSectionObserver: ListObjectObserver {
/**
@@ -187,8 +431,44 @@ public protocol ListSectionObserver: ListObjectObserver {
- parameter monitor: the `ListMonitor` monitoring the list being observed
- parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the inserted section
- parameter sectionIndex: the new section index for the new section
- parameter sourceIdentifier: an optional identifier provided by the transaction source
*/
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)
func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didInsertSection sectionInfo: NSFetchedResultsSectionInfo,
toSectionIndex sectionIndex: Int,
sourceIdentifier: Any?
)
/**
Notifies that a section was inserted at the specified index. (Optional)
The default implementation does nothing.
- parameter monitor: the `ListMonitor` monitoring the list being observed
- parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the inserted section
- parameter sectionIndex: the new section index for the new section
*/
func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didInsertSection sectionInfo: NSFetchedResultsSectionInfo,
toSectionIndex sectionIndex: Int
)
/**
Notifies that a section was inserted at the specified index. (Optional)
The default implementation does nothing.
- parameter monitor: the `ListMonitor` monitoring the list being observed
- parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the deleted section
- parameter sectionIndex: the previous section index for the deleted section
- parameter sourceIdentifier: an optional identifier provided by the transaction source
*/
func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didDeleteSection sectionInfo: NSFetchedResultsSectionInfo,
fromSectionIndex sectionIndex: Int,
sourceIdentifier: Any?
)
/**
Notifies that a section was inserted at the specified index. (Optional)
@@ -198,16 +478,55 @@ public protocol ListSectionObserver: ListObjectObserver {
- parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the deleted section
- parameter sectionIndex: the previous section index for the deleted section
*/
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)
func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didDeleteSection sectionInfo: NSFetchedResultsSectionInfo,
fromSectionIndex sectionIndex: Int
)
}
// MARK: - ListSectionObserver (Default Implementations)
@available(macOS 10.12, *)
extension ListSectionObserver {
public func listMonitor(_ monitor: ListMonitor<ListEntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) { }
public func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didInsertSection sectionInfo: NSFetchedResultsSectionInfo,
toSectionIndex sectionIndex: Int,
sourceIdentifier: Any?
) {
self.listMonitor(
monitor,
didInsertSection: sectionInfo,
toSectionIndex: sectionIndex
)
}
public func listMonitor(_ monitor: ListMonitor<ListEntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) { }
public func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didInsertSection sectionInfo: NSFetchedResultsSectionInfo,
toSectionIndex sectionIndex: Int
) {}
public func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didDeleteSection sectionInfo: NSFetchedResultsSectionInfo,
fromSectionIndex sectionIndex: Int,
sourceIdentifier: Any?
) {
self.listMonitor(
monitor,
didDeleteSection: sectionInfo,
fromSectionIndex: sectionIndex
)
}
public func listMonitor(
_ monitor: ListMonitor<ListEntityType>,
didDeleteSection sectionInfo: NSFetchedResultsSectionInfo,
fromSectionIndex sectionIndex: Int
) {}
}

View File

@@ -0,0 +1,109 @@
//
// ListPublisher+Reactive.swift
// CoreStore
//
// Copyright © 2021 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(Combine)
import Combine
// MARK: - ListPublisher
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension ListPublisher {
// MARK: Public
/**
Combine utilities for the `ListPublisher` are exposed through this namespace
*/
public var reactive: ListPublisher.ReactiveNamespace {
return .init(self)
}
// MARK: - ReactiveNamespace
/**
Combine utilities for the `ListPublisher` are exposed through this namespace. Extend this type if you need to add other Combine Publisher utilities for `ListPublisher`.
*/
public struct ReactiveNamespace {
// MARK: Public
/**
The `ListPublisher` instance
*/
public let base: ListPublisher
// MARK: Internal
internal init(_ base: ListPublisher) {
self.base = base
}
}
}
// MARK: - ListPublisher.ReactiveNamespace
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension ListPublisher.ReactiveNamespace {
// MARK: Public
/**
Returns a `Publisher` that emits a `ListSnapshot` whenever changes occur in the `ListPublisher`
```
listPublisher.reactive
.snapshot(emitInitialValue: true)
.sink(
receiveCompletion: { result in
// ...
},
receiveValue: { (listSnapshot) in
dataSource.apply(
listSnapshot,
animatingDifferences: true
)
}
)
.store(in: &cancellables)
```
- parameter emitInitialValue: If `true`, the current value is immediately emitted to the first subscriber. If `false`, the event fires only starting the next `ListSnapshot` update.
- returns: A `Publisher` that emits a `ListSnapshot` whenever changes occur in the `ListPublisher`.
*/
public func snapshot(emitInitialValue: Bool = true) -> ListPublisher.SnapshotPublisher {
return .init(
listPublisher: self.base,
emitInitialValue: emitInitialValue
)
}
}
#endif

View File

@@ -0,0 +1,163 @@
//
// ListPublisher.SnapshotPublisher.swift
// CoreStore
//
// Copyright © 2021 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(Combine)
import Combine
import CoreData
// MARK: - ListPublisher
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension ListPublisher {
// MARK: - SnapshotPublisher
/**
A `Publisher` that emits a `ListSnapshot` whenever changes occur in the `ListPublisher`.
- SeeAlso: ListPublisher.reactive.snapshot(emitInitialValue:)
*/
public struct SnapshotPublisher: Publisher {
// MARK: Internal
internal let listPublisher: ListPublisher<O>
internal let emitInitialValue: Bool
// MARK: Publisher
public typealias Output = ListSnapshot<O>
public typealias Failure = Never
public func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
subscriber.receive(
subscription: ListSnapshotSubscription(
publisher: self.listPublisher,
emitInitialValue: self.emitInitialValue,
subscriber: subscriber
)
)
}
// MARK: - ListSnapshotSubscriber
fileprivate final class ListSnapshotSubscriber: Subscriber {
// MARK: Subscriber
typealias Failure = Never
func receive(subscription: Subscription) {
subscription.request(.unlimited)
}
func receive(_ input: Output) -> Subscribers.Demand {
return .unlimited
}
func receive(completion: Subscribers.Completion<Failure>) {}
}
// MARK: - ListSnapshotSubscription
fileprivate final class ListSnapshotSubscription<S: Subscriber>: Subscription where S.Input == Output, S.Failure == Never {
// MARK: FilePrivate
init(
publisher: ListPublisher<O>,
emitInitialValue: Bool,
subscriber: S
) {
self.publisher = publisher
self.emitInitialValue = emitInitialValue
self.subscriber = subscriber
}
// MARK: Subscription
func request(_ demand: Subscribers.Demand) {
guard demand > 0 else {
return
}
self.publisher.addObserver(
self,
notifyInitial: self.emitInitialValue,
{ [weak self] (publisher) in
guard
let self = self,
let subscriber = self.subscriber
else {
return
}
_ = subscriber.receive(publisher.snapshot)
}
)
}
// MARK: Cancellable
func cancel() {
self.subscriber = nil
if Thread.isMainThread {
self.publisher.removeObserver(self)
}
else {
DispatchQueue.main.async {
self.publisher.removeObserver(self)
}
}
}
// MARK: Private
private let publisher: ListPublisher<O>
private let emitInitialValue: Bool
private var subscriber: S?
}
}
}
#endif

View File

@@ -25,16 +25,6 @@
import CoreData
#if canImport(Combine)
import Combine
#endif
#if canImport(SwiftUI)
import SwiftUI
#endif
// MARK: - ListPublisher
@@ -64,7 +54,6 @@ import SwiftUI
```
All access to the `ListPublisher` items should be done via its `snapshot` value, which is a `struct` of type `ListSnapshot<O>`. `ListSnapshot`s are also designed to work well with `DiffableDataSource.TableViewAdapter`s and `DiffableDataSource.CollectionViewAdapter`s. For detailed examples, refer to the documentation for `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter`.
*/
@available(macOS 10.12, *)
public final class ListPublisher<O: DynamicObject>: Hashable {
// MARK: Public (Accessors)
@@ -87,18 +76,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
/**
A snapshot of the latest state of this list
*/
public fileprivate(set) var snapshot: ListSnapshot<O> = .init() {
willSet {
self.willChange()
}
didSet {
self.didChange()
self.notifyObservers()
}
}
public private(set) var snapshot: ListSnapshot<O> = .init()
// MARK: Public (Observers)
@@ -127,7 +105,7 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
"Attempted to add an observer of type \(Internals.typeName(observer)) outside the main thread."
)
self.observers.setObject(
Internals.Closure(callback),
Internals.Closure({ callback($0.listPublisher) }),
forKey: observer
)
if notifyInitial {
@@ -135,6 +113,44 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
callback(self)
}
}
/**
Registers an object as an observer to be notified when changes to the `ListPublisher`'s snapshot occur.
To prevent retain-cycles, `ListPublisher` only keeps `weak` references to its observers.
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
Calling `addObserver(_:_:)` multiple times on the same observer is safe.
- parameter observer: an object to become owner of the specified `callback`
- parameter notifyInitial: if `true`, the callback is executed immediately with the current publisher state. Otherwise only succeeding updates will notify the observer. Default value is `false`.
- parameter initialSourceIdentifier: an optional value that identifies the initial callback invocation if `notifyInitial` is `true`.
- parameter callback: the closure to execute when changes occur
*/
public func addObserver<T: AnyObject>(
_ observer: T,
notifyInitial: Bool = false,
initialSourceIdentifier: Any? = nil,
_ callback: @escaping (
_ listPublisher: ListPublisher<O>,
_ sourceIdentifier: Any?
) -> Void
) {
Internals.assert(
Thread.isMainThread,
"Attempted to add an observer of type \(Internals.typeName(observer)) outside the main thread."
)
self.observers.setObject(
Internals.Closure(callback),
forKey: observer
)
if notifyInitial {
callback(self, initialSourceIdentifier)
}
}
/**
Unregisters an object from receiving notifications for changes to the `ListPublisher`'s snapshot.
@@ -165,8 +181,12 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
)
```
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
*/
public func refetch<B: FetchChainableBuilderType>(_ clauseChain: B) throws where B.ObjectType == O {
public func refetch<B: FetchChainableBuilderType>(
_ clauseChain: B,
sourceIdentifier: Any? = nil
) throws where B.ObjectType == O {
try self.refetch(
from: clauseChain.from,
@@ -174,7 +194,8 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
applyFetchClauses: { (fetchRequest) in
clauseChain.fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
}
},
sourceIdentifier: sourceIdentifier
)
}
@@ -189,8 +210,12 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
)
```
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
- parameter sourceIdentifier: an optional value that identifies the source of this transaction. This identifier will be passed to the change notifications and callers can use it for custom handling that depends on the source.
*/
public func refetch<B: SectionMonitorBuilderType>(_ clauseChain: B) throws where B.ObjectType == O {
public func refetch<B: SectionMonitorBuilderType>(
_ clauseChain: B,
sourceIdentifier: Any? = nil
) throws where B.ObjectType == O {
try self.refetch(
from: clauseChain.from,
@@ -198,9 +223,18 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
applyFetchClauses: { (fetchRequest) in
clauseChain.fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
}
},
sourceIdentifier: sourceIdentifier
)
}
/**
Used internally by CoreStore. Do not call directly.
*/
public func cs_dataStack() -> DataStack? {
return self.context.parentStack
}
// MARK: Public (3rd Party Utilities)
@@ -235,8 +269,15 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
// MARK: Internal
internal private(set) lazy var context: NSManagedObjectContext = self.fetchedResultsController.managedObjectContext
internal convenience init(dataStack: DataStack, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
internal convenience init(
dataStack: DataStack,
from: From<ObjectType>,
sectionBy: SectionBy<ObjectType>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
) {
self.init(
context: dataStack.mainContext,
@@ -247,7 +288,13 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
)
}
internal convenience init(dataStack: DataStack, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (ListPublisher<ObjectType>) -> Void) {
internal convenience init(
dataStack: DataStack,
from: From<ObjectType>,
sectionBy: SectionBy<ObjectType>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
createAsynchronously: @escaping (ListPublisher<ObjectType>) -> Void
) {
self.init(
context: dataStack.mainContext,
@@ -258,7 +305,12 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
)
}
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
internal convenience init(
unsafeTransaction: UnsafeDataTransaction,
from: From<ObjectType>,
sectionBy: SectionBy<ObjectType>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
) {
self.init(
context: unsafeTransaction.context,
@@ -269,7 +321,13 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
)
}
internal convenience init(unsafeTransaction: UnsafeDataTransaction, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: @escaping (ListPublisher<ObjectType>) -> Void) {
internal convenience init(
unsafeTransaction: UnsafeDataTransaction,
from: From<ObjectType>,
sectionBy: SectionBy<ObjectType>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
createAsynchronously: @escaping (ListPublisher<ObjectType>) -> Void
) {
self.init(
context: unsafeTransaction.context,
@@ -280,7 +338,12 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
)
}
internal func refetch(from: From<O>, sectionBy: SectionBy<O>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) throws {
internal func refetch(
from: From<O>,
sectionBy: SectionBy<O>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
sourceIdentifier: Any?
) throws {
let (newFetchedResultsController, newFetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
context: self.fetchedResultsController.managedObjectContext,
@@ -291,13 +354,19 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
self.query = (
from: from,
sectionBy: sectionBy,
sectionIndexTransformer: sectionBy?.sectionIndexTransformer ?? { $0 },
sectionIndexTransformer: sectionBy?.sectionIndexTransformer ?? { _ in nil },
applyFetchClauses: applyFetchClauses
)
(self.fetchedResultsController, self.fetchedResultsControllerDelegate) = (newFetchedResultsController, newFetchedResultsControllerDelegate)
newFetchedResultsControllerDelegate.handler = self
newFetchedResultsController.managedObjectContext.saveMetadata = .init(
isSavingSynchronously: true,
sourceIdentifier: sourceIdentifier
)
try newFetchedResultsController.performFetchFromSpecifiedStores()
newFetchedResultsController.managedObjectContext.saveMetadata = nil
}
deinit {
@@ -305,11 +374,20 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
self.fetchedResultsControllerDelegate.fetchedResultsController = nil
self.observers.removeAllObjects()
}
// MARK: FilePrivate
fileprivate typealias ObserverClosureType = Internals.Closure<(listPublisher: ListPublisher<O>, sourceIdentifier: Any?), Void>
fileprivate func set(
snapshot: ListSnapshot<O>,
sourceIdentifier: Any?
) {
fileprivate let rawObjectWillChange: Any?
self.snapshot = snapshot
self.notifyObservers(sourceIdentifier: sourceIdentifier)
}
// MARK: Private
@@ -326,11 +404,17 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
private var observerForWillChangePersistentStore: Internals.NotificationObserver!
private var observerForDidChangePersistentStore: Internals.NotificationObserver!
private lazy var observers: NSMapTable<AnyObject, Internals.Closure<ListPublisher<O>, Void>> = .weakToStrongObjects()
private lazy var observers: NSMapTable<AnyObject, ObserverClosureType> = .weakToStrongObjects()
private lazy var context: NSManagedObjectContext = self.fetchedResultsController.managedObjectContext
private static func recreateFetchedResultsController(context: NSManagedObjectContext, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) -> (controller: Internals.CoreStoreFetchedResultsController, delegate: Internals.FetchedDiffableDataSourceSnapshotDelegate) {
private static func recreateFetchedResultsController(
context: NSManagedObjectContext,
from: From<ObjectType>,
sectionBy: SectionBy<ObjectType>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void
) -> (
controller: Internals.CoreStoreFetchedResultsController,
delegate: Internals.FetchedDiffableDataSourceSnapshotDelegate
) {
let fetchRequest = Internals.CoreStoreFetchRequest<NSManagedObject>()
fetchRequest.fetchLimit = 0
@@ -352,12 +436,18 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
return (fetchedResultsController, fetchedResultsControllerDelegate)
}
private init(context: NSManagedObjectContext, from: From<ObjectType>, sectionBy: SectionBy<ObjectType>?, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void, createAsynchronously: ((ListPublisher<ObjectType>) -> Void)?) {
private init(
context: NSManagedObjectContext,
from: From<ObjectType>,
sectionBy: SectionBy<ObjectType>?,
applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void,
createAsynchronously: ((ListPublisher<ObjectType>) -> Void)?
) {
self.query = (
from: from,
sectionBy: sectionBy,
sectionIndexTransformer: sectionBy?.sectionIndexTransformer ?? { $0 },
sectionIndexTransformer: sectionBy?.sectionIndexTransformer ?? { _ in nil },
applyFetchClauses: applyFetchClauses
)
(self.fetchedResultsController, self.fetchedResultsControllerDelegate) = Self.recreateFetchedResultsController(
@@ -367,35 +457,24 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
applyFetchClauses: applyFetchClauses
)
if #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) {
#if canImport(Combine)
self.rawObjectWillChange = ObservableObjectPublisher()
#else
self.rawObjectWillChange = nil
#endif
}
else {
self.rawObjectWillChange = nil
}
self.fetchedResultsControllerDelegate.handler = self
try! self.fetchedResultsController.performFetchFromSpecifiedStores()
}
private func notifyObservers() {
private func notifyObservers(sourceIdentifier: Any?) {
guard let enumerator = self.observers.objectEnumerator() else {
return
}
let arguments: ObserverClosureType.Arguments = (
listPublisher: self,
sourceIdentifier: sourceIdentifier
)
for closure in enumerator {
(closure as! Internals.Closure<ListPublisher<O>, Void>).invoke(with: self)
(closure as! ObserverClosureType).invoke(with: arguments)
}
}
}
@@ -406,157 +485,24 @@ public final class ListPublisher<O: DynamicObject>: Hashable {
extension ListPublisher: FetchedDiffableDataSourceSnapshotHandler {
// MARK: FetchedDiffableDataSourceSnapshotHandler
internal var sectionIndexTransformer: (_ sectionName: KeyPathString?) -> String? {
return self.query.sectionIndexTransformer
}
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: Internals.DiffableDataSourceSnapshot) {
internal func controller(
_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChangeContentWith snapshot: Internals.DiffableDataSourceSnapshot
) {
self.snapshot = .init(
diffableSnapshot: snapshot,
context: controller.managedObjectContext
let context = controller.managedObjectContext
self.set(
snapshot: .init(
diffableSnapshot: snapshot,
context: context
),
sourceIdentifier: context.saveMetadata?.sourceIdentifier
)
}
internal func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String?) -> String? {
return self.query.sectionIndexTransformer(sectionName)
}
}
#if canImport(Combine)
import Combine
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension ListPublisher: ObservableObject {}
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension ListPublisher: Publisher {
// MARK: Publisher
public typealias Output = ListSnapshot<O>
public typealias Failure = Never
public func receive<S: Subscriber>(subscriber: S) where S.Input == Output, S.Failure == Failure {
subscriber.receive(
subscription: ListSnapshotSubscription(
publisher: self,
subscriber: subscriber
)
)
}
// MARK: - ListSnapshotSubscriber
fileprivate final class ListSnapshotSubscriber: Subscriber {
// MARK: Subscriber
typealias Failure = Never
func receive(subscription: Subscription) {
subscription.request(.unlimited)
}
func receive(_ input: Output) -> Subscribers.Demand {
return .unlimited
}
func receive(completion: Subscribers.Completion<Failure>) {}
}
// MARK: - ListSnapshotSubscription
fileprivate final class ListSnapshotSubscription<S: Subscriber>: Subscription where S.Input == Output, S.Failure == Never {
// MARK: FilePrivate
init(publisher: ListPublisher<O>, subscriber: S) {
self.publisher = publisher
self.subscriber = subscriber
}
// MARK: Subscription
func request(_ demand: Subscribers.Demand) {
guard demand > 0 else {
return
}
self.publisher.addObserver(self) { [weak self] (publisher) in
guard let self = self, let subscriber = self.subscriber else {
return
}
_ = subscriber.receive(publisher.snapshot)
}
}
// MARK: Cancellable
func cancel() {
self.publisher.removeObserver(self)
self.subscriber = nil
}
// MARK: Private
private let publisher: ListPublisher<O>
private var subscriber: S?
}
}
#endif
// MARK: - ListPublisher
extension ListPublisher {
// MARK: ObservableObject
#if canImport(Combine)
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
public var objectWillChange: ObservableObjectPublisher {
return self.rawObjectWillChange! as! ObservableObjectPublisher
}
#endif
fileprivate func willChange() {
guard #available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *) else {
return
}
#if canImport(Combine)
#if canImport(SwiftUI)
withAnimation {
self.objectWillChange.send()
}
#endif
self.objectWillChange.send()
#endif
}
fileprivate func didChange() {
// nothing
}
}

123
Sources/ListReader.swift Normal file
View File

@@ -0,0 +1,123 @@
//
// ListReader.swift
// CoreStore
//
// Copyright © 2021 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(Combine) && canImport(SwiftUI)
import Combine
import SwiftUI
// MARK: - ListReader
/**
A container view that reads list changes in a `ListPublisher`
*/
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
public struct ListReader<Object: DynamicObject, Content: View, Value>: View {
// MARK: Internal
/**
Creates an instance that creates views for `ListPublisher` changes.
```
let people: ListPublisher<Person>
var body: some View {
List {
ListReader(self.people) { listSnapshot in
ForEach(objectIn: listSnapshot) { person in
ProfileView(person)
}
}
}
.animation(.default)
}
```
- parameter listPublisher: The `ListPublisher` that the `ListReader` instance uses to create views dynamically
- parameter content: The view builder that receives an `ListSnapshot` instance and creates views dynamically.
*/
public init(
_ listPublisher: ListPublisher<Object>,
@ViewBuilder content: @escaping (ListSnapshot<Object>) -> Content
) where Value == ListSnapshot<Object> {
self._list = .init(listPublisher)
self.content = content
}
/**
Creates an instance that creates views for `ListPublisher` changes.
```
let people: ListPublisher<Person>
var body: some View {
ListReader(self.people, keyPath: \.count) { count in
Text("Number of members: \(count)")
}
}
```
- parameter listPublisher: The `ListPublisher` that the `ListReader` instance uses to create views dynamically
- parameter keyPath: A `KeyPath` for a property in the `ListSnapshot` whose value will be sent to the views
- parameter content: The view builder that receives the value from the property `KeyPath` and creates views dynamically.
*/
public init(
_ listPublisher: ListPublisher<Object>,
keyPath: KeyPath<ListSnapshot<Object>, Value>,
@ViewBuilder content: @escaping (Value) -> Content
) {
self._list = .init(listPublisher)
self.content = {
content($0[keyPath: keyPath])
}
}
// MARK: View
public var body: some View {
self.content(self.list)
}
// MARK: Private
@ListState
private var list: ListSnapshot<Object>
private let content: (ListSnapshot<Object>) -> Content
}
#endif

View File

@@ -0,0 +1,132 @@
//
// ListSnapshot.SectionInfo.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import CoreData
// MARK: - ListSnapshot
extension ListSnapshot {
// MARK: - SectionInfo
public struct SectionInfo: Hashable, RandomAccessCollection {
// MARK: Private
public let sectionID: SectionID
public let itemIDs: [ItemID]
// MARK: RandomAccessCollection
public var startIndex: Index {
return self.itemIDs.startIndex
}
public var endIndex: Index {
return self.itemIDs.endIndex
}
public func index(after i: Index) -> Index {
return self.itemIDs.index(after: i)
}
public func formIndex(after i: inout Index) {
self.itemIDs.formIndex(after: &i)
}
public func index(before i: Index) -> Index {
return self.itemIDs.index(before: i)
}
public func formIndex(before i: inout Index) {
self.itemIDs.formIndex(before: &i)
}
// MARK: BidirectionalCollection
public subscript(position: Int) -> ObjectPublisher<O> {
let itemID = self.itemIDs[position]
return self.context.objectPublisher(objectID: itemID)
}
public func index(_ i: Index, offsetBy distance: Int) -> Index {
return self.itemIDs.index(i, offsetBy: distance)
}
public func index(_ i: Index, offsetBy distance: Int, limitedBy limit: Int) -> Index? {
return self.itemIDs.index(i, offsetBy: distance, limitedBy: limit)
}
public func distance(from start: Index, to end: Index) -> Int {
return self.itemIDs.distance(from: start, to: end)
}
public subscript(bounds: Range<Index>) -> ArraySlice<Element> {
let itemIDs = self.itemIDs[bounds]
return ArraySlice(itemIDs.map(self.context.objectPublisher(objectID:)))
}
// MARK: Sequence
public typealias Element = ObjectPublisher<O>
public typealias Index = Int
// MARK: Internal
internal let context: NSManagedObjectContext
internal init?(
sectionID: SectionID,
listSnapshot: ListSnapshot<O>
) {
guard let context = listSnapshot.context else {
return nil
}
self.sectionID = sectionID
self.itemIDs = listSnapshot.itemIDs(inSectionWithID: sectionID)
self.context = context
}
}
}

View File

@@ -100,7 +100,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will raise an exception.
- returns: the `ObjectPublisher<O>` interfacing the object at the specified section and item index
*/
public subscript(sectionIndex: Int, itemIndex: Int) -> ObjectPublisher<O> {
public subscript(
sectionIndex: Int,
itemIndex: Int
) -> ObjectPublisher<O> {
let context = self.context!
let snapshot = self.diffableSnapshot
@@ -116,7 +119,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will return `nil`.
- returns: the `ObjectPublisher<O>` interfacing the object at the specified section and item index, or `nil` if out of bounds
*/
public subscript(safeSectionIndex sectionIndex: Int, safeItemIndex itemIndex: Int) -> ObjectPublisher<O>? {
public subscript(
safeSectionIndex sectionIndex: Int,
safeItemIndex itemIndex: Int
) -> ObjectPublisher<O>? {
guard let context = self.context else {
@@ -283,6 +289,29 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
return self.diffableSnapshot.sectionIdentifier(containingItem: itemID)
}
/**
Returns an array of `SectionInfo` instances that contains a collection of `ObjectPublisher<O>` items for each section.
*/
public func sections() -> [SectionInfo] {
return self
.sectionIDs
.compactMap({ SectionInfo(sectionID: $0, listSnapshot: self) })
}
/**
Returns the `SectionInfo` that the specified `ItemID` belongs to, or `nil` if it is not in the list.
- parameter itemID: the `ItemID`
- returns: the `SectionInfo` that the specified `ItemID` belongs to, or `nil` if it is not in the list
*/
public func section(containingItemWithID itemID: ItemID) -> SectionInfo? {
return self
.sectionID(containingItemWithID: itemID)
.flatMap({ SectionInfo(sectionID: $0, listSnapshot: self) })
}
/**
All object identifiers in the `ListSnapshot`
@@ -310,7 +339,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter indices: the positions of the itemIDs to return. Specifying an invalid value will raise an exception.
- returns: the `ItemID` array belonging to the given `SectionID` at the specified indices
*/
public func itemIDs<S: Sequence>(inSectionWithID sectionID: SectionID, atIndices indices: S) -> [ItemID] where S.Element == Int {
public func itemIDs<S: Sequence>(
inSectionWithID sectionID: SectionID,
atIndices indices: S
) -> [ItemID] where S.Element == Int {
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
return indices.map({ itemIDs[$0] })
@@ -375,7 +407,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIndices: the positions of items within the section. Specifying an invalid value will raise an exception.
- returns: an array of `ObjectPublisher`s for the items in the specified `SectionID` and indices
*/
public func items<S: Sequence>(inSectionWithID sectionID: SectionID, atIndices itemIndices: S) -> [ObjectPublisher<O>] where S.Element == Int {
public func items<S: Sequence>(
inSectionWithID sectionID: SectionID,
atIndices itemIndices: S
) -> [ObjectPublisher<O>] where S.Element == Int {
let context = self.context!
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
@@ -423,7 +458,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIndices: the positions of items within the section. Specifying an invalid value will raise an exception.
- returns: a lazy sequence of `ObjectPublisher`s for the items in the specified `SectionID` and indices
*/
public func lazy<S: Sequence>(inSectionWithID sectionID: SectionID, atIndices itemIndices: S) -> LazyMapSequence<S, ObjectPublisher<O>> where S.Element == Int {
public func lazy<S: Sequence>(
inSectionWithID sectionID: SectionID,
atIndices itemIndices: S
) -> LazyMapSequence<S, ObjectPublisher<O>> where S.Element == Int {
let context = self.context!
let itemIDs = self.diffableSnapshot.itemIdentifiers(inSection: sectionID)
@@ -443,9 +481,32 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIDs: the object identifiers for the objects to append
- parameter sectionID: the section to append the items to
*/
public mutating func appendItems<C: Collection>(withIDs itemIDs: C, toSectionWithID sectionID: SectionID? = nil) where C.Element == ItemID {
public mutating func appendItems<C: Collection>(
withIDs itemIDs: C,
toSectionWithID sectionID: SectionID? = nil
) where C.Element == ItemID {
self.diffableSnapshot.appendItems(itemIDs, toSection: sectionID)
self.mutate {
$0.appendItems(itemIDs, toSection: sectionID)
}
}
/**
Appends extra items to the specified section index
- parameter itemIDs: the object identifiers for the objects to append
- parameter sectionIndex: the section index to append the items to. Specifying an invalid value will raise an exception.
*/
public mutating func appendItems<C: Collection>(
with itemIDs: C,
toSectionAt sectionIndex: Int
) where C.Element == ItemID {
self.mutate {
$0.unsafeAppendItems(itemIDs, toSectionAt: sectionIndex)
}
}
/**
@@ -454,9 +515,15 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIDs: the object identifiers for the objects to insert
- parameter beforeItemID: an existing identifier to insert items before of. Specifying an invalid value will raise an exception.
*/
public mutating func insertItems<C: Collection>(withIDs itemIDs: C, beforeItemID: ItemID) where C.Element == ItemID {
public mutating func insertItems<C: Collection>(
withIDs itemIDs: C,
beforeItemID: ItemID
) where C.Element == ItemID {
self.diffableSnapshot.insertItems(itemIDs, beforeItem: beforeItemID)
self.mutate {
$0.insertItems(itemIDs, beforeItem: beforeItemID)
}
}
/**
@@ -465,9 +532,32 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIDs: the object identifiers for the objects to insert
- parameter beforeItemID: an existing identifier to insert items after of. Specifying an invalid value will raise an exception.
*/
public mutating func insertItems<C: Collection>(withIDs itemIDs: C, afterItemID: ItemID) where C.Element == ItemID {
public mutating func insertItems<C: Collection>(
withIDs itemIDs: C,
afterItemID: ItemID
) where C.Element == ItemID {
self.diffableSnapshot.insertItems(itemIDs, afterItem: afterItemID)
self.mutate {
$0.insertItems(itemIDs, afterItem: afterItemID)
}
}
/**
Inserts extra items at a specified index path
- parameter itemIDs: the object identifiers for the objects to insert
- parameter indexPath: an indexPath to insert the items into. Specifying an invalid value will raise an exception.
*/
public mutating func insertItems<C: Collection>(
withIDs itemIDs: C,
at indexPath: IndexPath
) where C.Element == ItemID {
self.mutate {
$0.unsafeInsertItems(itemIDs, at: indexPath)
}
}
/**
@@ -475,9 +565,25 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIDs: the object identifiers for the objects to delete
*/
public mutating func deleteItems<S: Sequence>(withIDs itemIDs: S) where S.Element == ItemID {
public mutating func deleteItems<C: Collection>(withIDs itemIDs: C) where C.Element == ItemID {
self.diffableSnapshot.deleteItems(itemIDs)
self.mutate {
$0.deleteItems(itemIDs)
}
}
/**
Deletes the items at the specified index paths
- parameter itemIndexPaths: the index paths for the objects to delete. Specifying an invalid value will raise an exception.
*/
public mutating func deleteItems<C: Collection>(at itemIndexPaths: C) where C.Element == IndexPath {
self.mutate {
$0.unsafeDeleteItems(at: itemIndexPaths)
}
}
/**
@@ -485,7 +591,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
*/
public mutating func deleteAllItems() {
self.diffableSnapshot.deleteAllItems()
self.mutate {
$0.deleteAllItems()
}
}
/**
@@ -494,9 +603,15 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemID: an object identifier in the list to move. Specifying an invalid value will raise an exception.
- parameter beforeItemID: another identifier to move the item before of. Specifying an invalid value will raise an exception.
*/
public mutating func moveItem(withID itemID: ItemID, beforeItemID: ItemID) {
public mutating func moveItem(
withID itemID: ItemID,
beforeItemID: ItemID
) {
self.diffableSnapshot.moveItem(itemID, beforeItem: beforeItemID)
self.mutate {
$0.moveItem(itemID, beforeItem: beforeItemID)
}
}
/**
@@ -505,9 +620,32 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemID: an object identifier in the list to move. Specifying an invalid value will raise an exception.
- parameter beforeItemID: another identifier to move the item after of. Specifying an invalid value will raise an exception.
*/
public mutating func moveItem(withID itemID: ItemID, afterItemID: ItemID) {
public mutating func moveItem(
withID itemID: ItemID,
afterItemID: ItemID
) {
self.diffableSnapshot.moveItem(itemID, afterItem: afterItemID)
self.mutate {
$0.moveItem(itemID, afterItem: afterItemID)
}
}
/**
Moves an item at an index path to a new index path
- parameter itemIndexPath: an index path in the list to move. Specifying an invalid value will raise an exception.
- parameter newIndexPath: the new index path to move the item into. Specifying an invalid value will raise an exception.
*/
public mutating func moveItem(
at itemIndexPath: IndexPath,
to newIndexPath: IndexPath
) {
self.mutate {
$0.unsafeMoveItem(at: itemIndexPath, to: newIndexPath)
}
}
/**
@@ -515,9 +653,25 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter itemIDs: the object identifiers to reload
*/
public mutating func reloadItems<S: Sequence>(withIDs itemIDs: S) where S.Element == ItemID {
public mutating func reloadItems<C: Collection>(withIDs itemIDs: C) where C.Element == ItemID {
self.diffableSnapshot.reloadItems(itemIDs)
self.mutate {
$0.reloadItems(itemIDs)
}
}
/**
Marks the specified index paths as reloaded
- parameter itemIndexPaths: the index paths to reload. Specifying an invalid value will raise an exception.
*/
public mutating func reloadItems<C: Collection>(at itemIndexPaths: C) where C.Element == IndexPath {
self.mutate {
$0.unsafeReloadItems(at: itemIndexPaths)
}
}
/**
@@ -527,7 +681,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
*/
public mutating func appendSections<C: Collection>(withIDs sectionIDs: C) where C.Element == SectionID {
self.diffableSnapshot.appendSections(sectionIDs)
self.mutate {
$0.appendSections(sectionIDs)
}
}
/**
@@ -536,9 +693,15 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter sectionIDs: the section identifiers for the sections to insert
- parameter beforeSectionID: an existing identifier to insert items before of. Specifying an invalid value will raise an exception.
*/
public mutating func insertSections<C: Collection>(withIDs sectionIDs: C, beforeSectionID: SectionID) where C.Element == SectionID {
public mutating func insertSections<C: Collection>(
withIDs sectionIDs: C,
beforeSectionID: SectionID
) where C.Element == SectionID {
self.diffableSnapshot.insertSections(sectionIDs, beforeSection: beforeSectionID)
self.mutate {
$0.insertSections(sectionIDs, beforeSection: beforeSectionID)
}
}
/**
@@ -547,9 +710,32 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter sectionIDs: the section identifiers for the sections to insert
- parameter beforeSectionID: an existing identifier to insert items after of. Specifying an invalid value will raise an exception.
*/
public mutating func insertSections<C: Collection>(withIDs sectionIDs: C, afterSectionID: SectionID) where C.Element == SectionID {
public mutating func insertSections<C: Collection>(
withIDs sectionIDs: C,
afterSectionID: SectionID
) where C.Element == SectionID {
self.diffableSnapshot.insertSections(sectionIDs, afterSection: afterSectionID)
self.mutate {
$0.insertSections(sectionIDs, afterSection: afterSectionID)
}
}
/**
Inserts new sections into an existing section index
- parameter sectionIDs: the section identifiers for the sections to insert
- parameter sectionIndex: an existing section index to insert items into. Specifying an invalid value will raise an exception.
*/
public mutating func insertSections<C: Collection>(
_ sectionIDs: C,
at sectionIndex: Int
) where C.Element == String {
self.mutate {
$0.unsafeInsertSections(sectionIDs, at: sectionIndex)
}
}
/**
@@ -557,9 +743,25 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter sectionIDs: the section identifiers for the sections to delete
*/
public mutating func deleteSections<S: Sequence>(withIDs sectionIDs: S) where S.Element == SectionID {
public mutating func deleteSections<C: Collection>(withIDs sectionIDs: C) where C.Element == SectionID {
self.diffableSnapshot.deleteSections(sectionIDs)
self.mutate {
$0.deleteSections(sectionIDs)
}
}
/**
Deletes the specified section indices
- parameter sectionIndices: the section indices to delete. Specifying an invalid value will raise an exception.
*/
public mutating func deleteSections<C: Collection>(at sectionIndices: C) where C.Element == Int {
self.mutate {
$0.unsafeDeleteSections(at: sectionIndices)
}
}
/**
@@ -568,9 +770,15 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter sectionID: a section identifier in the list to move. Specifying an invalid value will raise an exception.
- parameter beforeSectionID: another identifier to move the section before of. Specifying an invalid value will raise an exception.
*/
public mutating func moveSection(withID sectionID: SectionID, beforeSectionID: SectionID) {
public mutating func moveSection(
withID sectionID: SectionID,
beforeSectionID: SectionID
) {
self.diffableSnapshot.moveSection(sectionID, beforeSection: beforeSectionID)
self.mutate {
$0.moveSection(sectionID, beforeSection: beforeSectionID)
}
}
/**
@@ -579,9 +787,32 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter sectionID: a section identifier in the list to move. Specifying an invalid value will raise an exception.
- parameter afterSectionID: another identifier to move the section after of. Specifying an invalid value will raise an exception.
*/
public mutating func moveSection(withID sectionID: SectionID, afterSectionID: SectionID) {
public mutating func moveSection(
withID sectionID: SectionID,
afterSectionID: SectionID
) {
self.diffableSnapshot.moveSection(sectionID, afterSection: afterSectionID)
self.mutate {
$0.moveSection(sectionID, afterSection: afterSectionID)
}
}
/**
Moves a section at a specified index to a new index
- parameter sectionIndex: a section index in the list to move. Specifying an invalid value will raise an exception.
- parameter newSectionIndex: the new section index to move into. Specifying an invalid value will raise an exception.
*/
public mutating func moveSection(
at sectionIndex: Int,
to newSectionIndex: Int
) {
self.mutate {
$0.unsafeMoveSection(at: sectionIndex, to: newSectionIndex)
}
}
/**
@@ -589,9 +820,25 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
- parameter sectionIDs: the section identifiers to reload
*/
public mutating func reloadSections<S: Sequence>(withIDs sectionIDs: S) where S.Element == SectionID {
public mutating func reloadSections<C: Collection>(withIDs sectionIDs: C) where C.Element == SectionID {
self.diffableSnapshot.reloadSections(sectionIDs)
self.mutate {
$0.reloadSections(sectionIDs)
}
}
/**
Marks the specified section indices as reloaded
- parameter sectionIndices: the section indices to reload. Specifying an invalid value will raise an exception.
*/
public mutating func reloadSections<C: Collection>(at sectionIndices: C) where C.Element == Int {
self.mutate {
$0.unsafeReloadSections(at: sectionIndices)
}
}
@@ -673,21 +920,24 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
// MARK: Equatable
public static func == (_ lhs: Self, _ rhs: Self) -> Bool {
return lhs.id == rhs.id
return lhs.context == rhs.context
&& lhs.generation == rhs.generation
}
// MARK: Hashable
public func hash(into hasher: inout Hasher) {
hasher.combine(self.id)
hasher.combine(self.context)
hasher.combine(self.generation)
}
// MARK: Internal
internal let context: NSManagedObjectContext?
internal private(set) var diffableSnapshot: Internals.DiffableDataSourceSnapshot
internal init() {
@@ -696,7 +946,10 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
self.context = nil
}
internal init(diffableSnapshot: Internals.DiffableDataSourceSnapshot, context: NSManagedObjectContext) {
internal init(
diffableSnapshot: Internals.DiffableDataSourceSnapshot,
context: NSManagedObjectContext
) {
self.diffableSnapshot = diffableSnapshot
self.context = context
@@ -705,7 +958,12 @@ public struct ListSnapshot<O: DynamicObject>: RandomAccessCollection, Hashable {
// MARK: Private
private let id: UUID = .init()
private let context: NSManagedObjectContext?
private var generation: UUID = .init()
@inline(__always)
private mutating func mutate<T>(_ mutation: (inout Internals.DiffableDataSourceSnapshot) -> T) -> T {
self.generation = .init()
return mutation(&self.diffableSnapshot)
}
}

Some files were not shown because too many files have changed in this diff Show More