Skip to content

Commit

Permalink
Merge pull request #549 from groue/dev/CombineSupport
Browse files Browse the repository at this point in the history
Support for Combine
  • Loading branch information
groue authored Jun 13, 2019
2 parents 1db76a9 + a595462 commit 47b8130
Show file tree
Hide file tree
Showing 31 changed files with 1,879 additions and 583 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,17 @@ GRDB adheres to [Semantic Versioning](https://semver.org/), with one expection:
- [#540](https://github.com/groue/GRDB.swift/pull/540): Update SQLite to 3.28.0 (thanks to [@swiftlyfalling](https://github.com/swiftlyfalling/SQLiteLib))
- [#542](https://github.com/groue/GRDB.swift/pull/542): Move eager loading of hasMany associations to FetchRequest
- [#546](https://github.com/groue/GRDB.swift/pull/546) by [@robcas3](https://github.com/robcas3): Fix SPM errors with Xcode 11 beta
- You can now [combine](README.md#valueobservationcombine) up to eight ValueObservations in a single shot.
- [#549](https://github.com/groue/GRDB.swift/pull/549) Support for Combine

### Documentation Diff

The [Define Record Requests](Documentation/GoodPracticesForDesigningRecordTypes.md#define-record-requests) chapter of the The [Good Practices for Designing Record Types](Documentation/GoodPracticesForDesigningRecordTypes.md) has been rewritten. Yes, good practices evolve.

The [Examples of Record Definitions](README.md#examples-of-record-definitions) has been extended with a sample record optimized for fetching performance.

The [ValueObservation](README.md#valueobservation) chapter has been updated with the new APIs.


### API Diff

```diff
Expand Down
4 changes: 2 additions & 2 deletions Documentation/AssociationsBasics.md
Original file line number Diff line number Diff line change
Expand Up @@ -827,8 +827,8 @@ Those requests can also turn out useful when you want to track their changes wit
```swift
// Track changes in the author's books:
let author: Author = ...
ValueObservation
.trackingAll(author.books)
author.books
.observationForAll()
.start(in: dbQueue) { (books: [Book]) in
print("Author's book have changed")
}
Expand Down
2 changes: 1 addition & 1 deletion Documentation/GRDB3MigrationGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ To guarantee asynchronous notifications, and never ever block your main thread,

```swift
// On main queue
var observation = ValueObservation.trackingAll(Player.all())
var observation = Player.observationForAll()
observation.scheduling = .async(onQueue: .main, startImmediately: true)
let observer = try observation.start(in: dbQueue) { (players: [Player]) in
// On main queue
Expand Down
16 changes: 8 additions & 8 deletions GRDB.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -610,9 +610,9 @@
56A8C2331D1914540096E9D4 /* UUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A8C22F1D1914540096E9D4 /* UUID.swift */; };
56A8C2471D1918F00096E9D4 /* FoundationNSUUIDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A8C2361D1914790096E9D4 /* FoundationNSUUIDTests.swift */; };
56A8C2481D1918F00096E9D4 /* FoundationUUIDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56A8C21E1D1914110096E9D4 /* FoundationUUIDTests.swift */; };
56AACAA822ACED7100A40F2A /* Passthrough.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AACAA722ACED7100A40F2A /* Passthrough.swift */; };
56AACAA922ACED7100A40F2A /* Passthrough.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AACAA722ACED7100A40F2A /* Passthrough.swift */; };
56AACAAA22ACED7100A40F2A /* Passthrough.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AACAA722ACED7100A40F2A /* Passthrough.swift */; };
56AACAA822ACED7100A40F2A /* Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AACAA722ACED7100A40F2A /* Fetch.swift */; };
56AACAA922ACED7100A40F2A /* Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AACAA722ACED7100A40F2A /* Fetch.swift */; };
56AACAAA22ACED7100A40F2A /* Fetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AACAA722ACED7100A40F2A /* Fetch.swift */; };
56AE64122229A53700AD1B0B /* HasOneThroughAssociation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AE64112229A53700AD1B0B /* HasOneThroughAssociation.swift */; };
56AE64132229A53700AD1B0B /* HasOneThroughAssociation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AE64112229A53700AD1B0B /* HasOneThroughAssociation.swift */; };
56AE64142229A53700AD1B0B /* HasOneThroughAssociation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AE64112229A53700AD1B0B /* HasOneThroughAssociation.swift */; };
Expand Down Expand Up @@ -1222,7 +1222,7 @@
56A8C21E1D1914110096E9D4 /* FoundationUUIDTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationUUIDTests.swift; sourceTree = "<group>"; };
56A8C22F1D1914540096E9D4 /* UUID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UUID.swift; sourceTree = "<group>"; };
56A8C2361D1914790096E9D4 /* FoundationNSUUIDTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FoundationNSUUIDTests.swift; sourceTree = "<group>"; };
56AACAA722ACED7100A40F2A /* Passthrough.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Passthrough.swift; sourceTree = "<group>"; };
56AACAA722ACED7100A40F2A /* Fetch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fetch.swift; sourceTree = "<group>"; };
56AE64112229A53700AD1B0B /* HasOneThroughAssociation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HasOneThroughAssociation.swift; sourceTree = "<group>"; };
56AE6423222AAC9500AD1B0B /* AssociationHasOneThroughSQLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssociationHasOneThroughSQLTests.swift; sourceTree = "<group>"; };
56AF746A1D41FB9C005E9FF3 /* DatabaseValueConvertibleEscapingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseValueConvertibleEscapingTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1999,8 +1999,8 @@
children = (
5613ED3E21A95A8B00DC7A68 /* Combine.swift */,
564CE4D521B2DEB500652B19 /* CompactMap.swift */,
56AACAA722ACED7100A40F2A /* Fetch.swift */,
5613ED3421A95A5C00DC7A68 /* Map.swift */,
56AACAA722ACED7100A40F2A /* Passthrough.swift */,
564CE59621B7A8B500652B19 /* RemoveDuplicates.swift */,
5613ED4321A95B2C00DC7A68 /* ValueReducer.swift */,
);
Expand Down Expand Up @@ -2720,7 +2720,7 @@
5695962B222C462D002CB7C9 /* HasManyThroughAssociation.swift in Sources */,
565490D31D5AE252005622CB /* (null) in Sources */,
566475D91D981D5E00FF74B8 /* SQLOperators.swift in Sources */,
56AACAAA22ACED7100A40F2A /* Passthrough.swift in Sources */,
56AACAAA22ACED7100A40F2A /* Fetch.swift in Sources */,
566B910F1FA4C3970012D5B0 /* Database+Statements.swift in Sources */,
56F5ABDD1D814330001F60CB /* UUID.swift in Sources */,
565490CB1D5AE252005622CB /* (null) in Sources */,
Expand Down Expand Up @@ -2780,7 +2780,7 @@
5613ED4921A95C1200DC7A68 /* ValueObservation+Row.swift in Sources */,
566B91361FA4D3810012D5B0 /* TransactionObserver.swift in Sources */,
564CE43221AA901800652B19 /* ValueObserver.swift in Sources */,
56AACAA922ACED7100A40F2A /* Passthrough.swift in Sources */,
56AACAA922ACED7100A40F2A /* Fetch.swift in Sources */,
5605F1721C672E4000235C62 /* DatabaseValueConvertible+RawRepresentable.swift in Sources */,
56CEB5141EAA324B00BFAF62 /* FTS3+QueryInterface.swift in Sources */,
566475CF1D981D5E00FF74B8 /* SQLFunctions.swift in Sources */,
Expand Down Expand Up @@ -3310,7 +3310,7 @@
56CEB4F11EAA2EFA00BFAF62 /* FetchableRecord.swift in Sources */,
56D91AA22205E03700770D8D /* SQLRelation.swift in Sources */,
56300B781C53F592005A543B /* QueryInterfaceRequest.swift in Sources */,
56AACAA822ACED7100A40F2A /* Passthrough.swift in Sources */,
56AACAA822ACED7100A40F2A /* Fetch.swift in Sources */,
5605F18D1C6B1A8700235C62 /* SQLCollatedExpression.swift in Sources */,
5613ED5421A95DD000DC7A68 /* ValueObservation+Count.swift in Sources */,
566B91331FA4D3810012D5B0 /* TransactionObserver.swift in Sources */,
Expand Down
12 changes: 12 additions & 0 deletions GRDB/Core/DatabasePool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ public final class DatabasePool: DatabaseWriter {
return writer.path
}

public var configuration: Configuration {
return writer.configuration
}

// MARK: - Initializer

/// Opens the SQLite database at path *path*.
Expand Down Expand Up @@ -547,6 +551,14 @@ extension DatabasePool : DatabaseReader {
return try writer.reentrantSync(block)
}

/// Asynchronously executes an update block in a protected dispatch queue.
///
/// Eventual concurrent reads may see changes performed in the block before
/// the block completes.
public func unsafeAsyncWrite(_ block: @escaping (Database) -> Void) {
writer.async(block)
}

// MARK: - Functions

/// Add or redefine an SQL function.
Expand Down
4 changes: 4 additions & 0 deletions GRDB/Core/DatabaseQueue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,10 @@ extension DatabaseQueue {
return try writer.reentrantSync(block)
}

public func unsafeAsyncWrite(_ block: @escaping (Database) -> Void) {
writer.async(block)
}

// MARK: - Functions

/// Add or redefine an SQL function.
Expand Down
10 changes: 5 additions & 5 deletions GRDB/Core/DatabaseReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,9 @@ public protocol DatabaseReader : class {
/// - returns: a TransactionObserver
func add<Reducer: ValueReducer>(
observation: ValueObservation<Reducer>,
onError: ((Error) -> Void)?,
onError: @escaping (Error) -> Void,
onChange: @escaping (Reducer.Value) -> Void)
throws -> TransactionObserver
-> TransactionObserver

/// Remove a transaction observer.
func remove(transactionObserver: TransactionObserver)
Expand Down Expand Up @@ -257,11 +257,11 @@ public final class AnyDatabaseReader : DatabaseReader {
/// :nodoc:
public func add<Reducer: ValueReducer>(
observation: ValueObservation<Reducer>,
onError: ((Error) -> Void)?,
onError: @escaping (Error) -> Void,
onChange: @escaping (Reducer.Value) -> Void)
throws -> TransactionObserver
-> TransactionObserver
{
return try base.add(observation: observation, onError: onError, onChange: onChange)
return base.add(observation: observation, onError: onError, onChange: onChange)
}

/// :nodoc:
Expand Down
47 changes: 26 additions & 21 deletions GRDB/Core/DatabaseSnapshot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,36 +100,41 @@ extension DatabaseSnapshot {

public func add<Reducer: ValueReducer>(
observation: ValueObservation<Reducer>,
onError: ((Error) -> Void)?,
onError: @escaping (Error) -> Void,
onChange: @escaping (Reducer.Value) -> Void)
throws -> TransactionObserver
-> TransactionObserver
{
// Deal with initial value
switch observation.scheduling {
case .mainQueue:
if let value = try unsafeReentrantRead(observation.fetchFirst) {
if DispatchQueue.isMain {
onChange(value)
} else {
DispatchQueue.main.async {
// TODO: fetch asynchronously when possible
do {
// Deal with initial value
switch observation.scheduling {
case .mainQueue:
if let value = try unsafeReentrantRead(observation.fetchFirst) {
if DispatchQueue.isMain {
onChange(value)
} else {
DispatchQueue.main.async {
onChange(value)
}
}
}
}
case let .async(onQueue: queue, startImmediately: startImmediately):
if startImmediately {
if let value = try unsafeReentrantRead(observation.fetchFirst) {
queue.async {
onChange(value)
case let .async(onQueue: queue, startImmediately: startImmediately):
if startImmediately {
if let value = try unsafeReentrantRead(observation.fetchFirst) {
queue.async {
onChange(value)
}
}
}
}
case let .unsafe(startImmediately: startImmediately):
if startImmediately {
if let value = try unsafeReentrantRead(observation.fetchFirst) {
onChange(value)
case let .unsafe(startImmediately: startImmediately):
if startImmediately {
if let value = try unsafeReentrantRead(observation.fetchFirst) {
onChange(value)
}
}
}
} catch {
onError(error)
}

// Return a dummy observer, because snapshots never change
Expand Down
Loading

0 comments on commit 47b8130

Please sign in to comment.