Skip to content

Commit 639fa91

Browse files
committed
Merge branch 'development'
2 parents 77b85be + 1c5017a commit 639fa91

File tree

19 files changed

+796
-154
lines changed

19 files changed

+796
-154
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ GRDB adheres to [Semantic Versioning](https://semver.org/), with one exception:
77

88
#### 6.x Releases
99

10+
- `6.27.x` Releases - [6.27.0](#6270)
1011
- `6.26.x` Releases - [6.26.0](#6260)
1112
- `6.25.x` Releases - [6.25.0](#6250)
1213
- `6.24.x` Releases - [6.24.0](#6240) - [6.24.1](#6241) - [6.24.2](#6242)
@@ -124,6 +125,14 @@ GRDB adheres to [Semantic Versioning](https://semver.org/), with one exception:
124125

125126
---
126127

128+
## 6.27.0
129+
130+
Released April 21, 2024
131+
132+
- **Fixed**: [#1533](https://github.com/groue/GRDB.swift/pull/1533) by [@groue](https://github.com/groue): Fix a bug in Decodable support
133+
- **Documentation Update**: [#1534](https://github.com/groue/GRDB.swift/pull/1534) The [Single-Row Tables](Documentation/SingleRowTables.md) guide was updated with guidance about default configuration values.
134+
- **Documentation Update**: [#1535](https://github.com/groue/GRDB.swift/pull/1535) The [ValueObservation Scheduling](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/valueobservation#ValueObservation-Scheduling) documentation chapter explains the default behavior of `ValueObservation` fetches, and explains how to make sure they are never performed on the main thread.
135+
127136
## 6.26.0
128137

129138
Released March 23, 2024

Documentation/DemoApps/GRDBAsyncDemo/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ The topics covered in this demo are:
1313

1414
- How to setup a database in an iOS app.
1515
- How to define a simple [Codable Record](../../../README.md#codable-records).
16-
- How to track database changes and animate a SwiftUI List with an async sequence built from [ValueObservation](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/valueobservation).
16+
- How to track database changes and animate a SwiftUI List with [ValueObservation](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/valueobservation) Combine publishers.
1717
- How to apply the recommendations of [Recommended Practices for Designing Record Types](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/recordrecommendedpractices).
18+
- How to perform `async` database accesses.
1819
- How to feed SwiftUI previews with a transient database.
1920

2021
**Files of interest:**

GRDB.swift.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'GRDB.swift'
3-
s.version = '6.26.0'
3+
s.version = '6.27.0'
44

55
s.license = { :type => 'MIT', :file => 'LICENSE' }
66
s.summary = 'A toolkit for SQLite databases, with a focus on application development.'

GRDB.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@
276276
56AFEF2F29969F6E00CA1E51 /* TransactionClock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AFEF2E29969F6E00CA1E51 /* TransactionClock.swift */; };
277277
56AFEF372996B9DC00CA1E51 /* TransactionDateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56AFEF362996B9DC00CA1E51 /* TransactionDateTests.swift */; };
278278
56B021C91D8C0D3900B239BB /* MutablePersistableRecordPersistenceConflictPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B021C81D8C0D3900B239BB /* MutablePersistableRecordPersistenceConflictPolicyTests.swift */; };
279+
56B6AB062BD3DCAC009A0B71 /* SingletonUserDefaultsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B6AB052BD3DCAC009A0B71 /* SingletonUserDefaultsTest.swift */; };
279280
56B6EF56208CB4E3002F0ACB /* ColumnExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B6EF55208CB4E3002F0ACB /* ColumnExpressionTests.swift */; };
280281
56B7EE832863781300C0525F /* WALSnapshot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B7EE822863781300C0525F /* WALSnapshot.swift */; };
281282
56B7F43A1BEB42D500E39BBF /* Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56B7F4391BEB42D500E39BBF /* Migration.swift */; };
@@ -770,6 +771,7 @@
770771
56AFEF362996B9DC00CA1E51 /* TransactionDateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDateTests.swift; sourceTree = "<group>"; };
771772
56B021C81D8C0D3900B239BB /* MutablePersistableRecordPersistenceConflictPolicyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MutablePersistableRecordPersistenceConflictPolicyTests.swift; sourceTree = "<group>"; };
772773
56B14E7E1D4DAE54000BF4A3 /* RowFromDictionaryLiteralTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RowFromDictionaryLiteralTests.swift; sourceTree = "<group>"; };
774+
56B6AB052BD3DCAC009A0B71 /* SingletonUserDefaultsTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingletonUserDefaultsTest.swift; sourceTree = "<group>"; };
773775
56B6EF55208CB4E3002F0ACB /* ColumnExpressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumnExpressionTests.swift; sourceTree = "<group>"; };
774776
56B7EE822863781300C0525F /* WALSnapshot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WALSnapshot.swift; sourceTree = "<group>"; };
775777
56B7F4291BE14A1900E39BBF /* CGFloatTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGFloatTests.swift; sourceTree = "<group>"; };
@@ -1552,6 +1554,7 @@
15521554
children = (
15531555
564E73DE203D50B9000C443C /* JoinSupportTests.swift */,
15541556
5616B4FA28B5F5220052017E /* SingletonRecordTest.swift */,
1557+
56B6AB052BD3DCAC009A0B71 /* SingletonUserDefaultsTest.swift */,
15551558
5674A7251F30A8EF0095F066 /* FetchableRecord */,
15561559
560B3FA41C19DFF800C58EC7 /* PersistableRecord */,
15571560
56176C9E1EACEDF9000F3F2B /* Record */,
@@ -2086,6 +2089,7 @@
20862089
562393181DECC02000A6B01F /* RowFetchTests.swift in Sources */,
20872090
56677C0D241CD0D00050755D /* ValueObservationRecorder.swift in Sources */,
20882091
5653EADA20944B4F00F46237 /* AssociationRowScopeSearchTests.swift in Sources */,
2092+
56B6AB062BD3DCAC009A0B71 /* SingletonUserDefaultsTest.swift in Sources */,
20892093
563B5336267E2F90009549B5 /* TableTests.swift in Sources */,
20902094
56D4965A1D81304E008276D7 /* FoundationNSDataTests.swift in Sources */,
20912095
56D496791D81309E008276D7 /* RecordWithColumnNameManglingTests.swift in Sources */,

GRDB/Core/Database.swift

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,11 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
820820

821821
/// Reports the database region to ``ValueObservation``.
822822
///
823+
/// Calling this method does not fetch any database values. It just
824+
/// helps optimizing `ValueObservation`. See
825+
/// ``ValueObservation/trackingConstantRegion(_:)`` for more
826+
/// information, and some examples of usage.
827+
///
823828
/// For example:
824829
///
825830
/// ```swift
@@ -831,12 +836,9 @@ public final class Database: CustomStringConvertible, CustomDebugStringConvertib
831836
/// }
832837
/// ```
833838
///
834-
/// See ``ValueObservation/trackingConstantRegion(_:)`` for some examples
835-
/// of region reporting.
836-
///
837-
/// This method has no effect on a ``ValueObservation`` created with an
838-
/// explicit list of tracked regions. In the example below, only the
839-
/// `player` table is tracked:
839+
/// This method has no effect on a `ValueObservation` created with
840+
/// ``ValueObservation/tracking(regions:fetch:)``. In the example below,
841+
/// only the `player` table is tracked:
840842
///
841843
/// ```swift
842844
/// // Observes the 'player' table only

GRDB/Documentation.docc/Extension/ValueObservation.md

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ By default, `ValueObservation` notifies a fresh value whenever any component of
8080

8181
By default, `ValueObservation` notifies the initial value, as well as eventual changes and errors, on the main dispatch queue, asynchronously. This can be configured: see <doc:ValueObservation#ValueObservation-Scheduling>.
8282

83+
By default, `ValueObservation` fetches a fresh value immediately after a change is committed in the database. In particular, modifying the database on the main thread triggers a fetch on the main thread as well. This behavior can be configured: see <doc:ValueObservation#ValueObservation-Scheduling>.
84+
8385
`ValueObservation` may coalesce subsequent changes into a single notification.
8486

8587
`ValueObservation` may notify consecutive identical values. You can filter out the undesired duplicates with the ``removeDuplicates()`` method.
@@ -117,16 +119,45 @@ It is very useful in graphic applications, because you can configure views right
117119
The `immediate` scheduling requires that the observation starts from the main dispatch queue (a fatal error is raised otherwise):
118120

119121
```swift
120-
let cancellable = observation.start(in: dbQueue, scheduling: .immediate) { error in
121-
// Called on the main dispatch queue
122-
} onChange: { value in
123-
// Called on the main dispatch queue
124-
print("Fresh value", value)
125-
}
122+
// Immediate scheduling notifies
123+
// the initial value right on subscription.
124+
let cancellable = observation
125+
.start(in: dbQueue, scheduling: .immediate) { error in
126+
// Called on the main dispatch queue
127+
} onChange: { value in
128+
// Called on the main dispatch queue
129+
print("Fresh value", value)
130+
}
126131
// <- Here "Fresh value" has already been printed.
127132
```
128133

129-
The other built-in scheduler ``ValueObservationScheduler/async(onQueue:)`` asynchronously schedules values and errors on the dispatch queue of your choice.
134+
The other built-in scheduler ``ValueObservationScheduler/async(onQueue:)`` asynchronously schedules values and errors on the dispatch queue of your choice. Make sure you provide a serial queue, because a concurrent one such as `DispachQueue.global(qos: .default)` would mess with the ordering of fresh value notifications:
135+
136+
```swift
137+
// Async scheduling notifies all values
138+
// on the specified dispatch queue.
139+
let myQueue: DispatchQueue
140+
let cancellable = observation
141+
.start(in: dbQueue, scheduling: .async(myQueue)) { error in
142+
// Called asynchronously on myQueue
143+
} onChange: { value in
144+
// Called asynchronously on myQueue
145+
print("Fresh value", value)
146+
}
147+
```
148+
149+
As described above, the `scheduling` argument controls the execution of the change and error callbacks. You also have some control on the execution of the database fetch:
150+
151+
- With the `.immediate` scheduling, the initial fetch is always performed synchronously, on the main thread, when the observation starts, so that the initial value can be notified immediately.
152+
153+
- With the default `.async` scheduling, the initial fetch is always performed asynchronouly. It never blocks the main thread.
154+
155+
- By default, fresh values are fetched immediately after the database was changed. In particular, modifying the database on the main thread triggers a fetch on the main thread as well.
156+
157+
To change this behavior, and guarantee that fresh values are never fetched from the main thread, you need a ``DatabasePool`` and an optimized observation created with the ``tracking(regions:fetch:)`` or ``trackingConstantRegion(_:)`` methods. Make sure you read the documentation of those methods, or you might write an observation that misses some database changes.
158+
159+
It is possible to use a ``DatabasePool`` in the application, and an in-memory ``DatabaseQueue`` in tests and Xcode previews, with the common protocol ``DatabaseWriter``.
160+
130161

131162
## ValueObservation Sharing
132163

@@ -237,7 +268,7 @@ When needed, you can help GRDB optimize observations and reduce database content
237268
>
238269
> The `map` operator performs its job without blocking database accesses, and without blocking the main thread.
239270

240-
> Tip: When the observation tracks a constant database region, create an optimized observation with the ``trackingConstantRegion(_:)`` method. See the documentation of this method for more information about what constitutes a "constant region", and the nature of the optimization.
271+
> Tip: When the observation tracks a constant database region, create an optimized observation with the ``tracking(regions:fetch:)`` or ``trackingConstantRegion(_:)`` methods. Make sure you read the documentation of those methods, or you might write an observation that misses some database changes.
241272

242273
**Truncating WAL checkpoints impact ValueObservation.** Such checkpoints are performed with ``Database/checkpoint(_:on:)`` or [`PRAGMA wal_checkpoint`](https://www.sqlite.org/pragma.html#pragma_wal_checkpoint). When an observation is started on a ``DatabasePool``, from a database that has a missing or empty [wal file](https://www.sqlite.org/tempfiles.html#write_ahead_log_wal_files), the observation will always notify two values when it starts, even if the database content is not changed. This is a consequence of the impossibility to create the [wal snapshot](https://www.sqlite.org/c3ref/snapshot_get.html) needed for detecting that no changes were performed during the observation startup. If your application performs truncating checkpoints, you will avoid this behavior if you recreate a non-empty wal file before starting observations. To do so, perform any kind of no-op transaction (such a creating and dropping a dummy table).
243274

0 commit comments

Comments
 (0)