Skip to content

Commit ed22985

Browse files
stephencelismbrandonw
authored andcommitted
Introduce FetchTask to tie database observation to view lifetime (pointfreeco#295)
* Support tasks for loading query. * wip * wip * new test * wip * wip * wip * Add @discardableResult to load functions * Added a migration guide. --------- Co-authored-by: Brandon Williams <mbrandonw@hey.com>
1 parent dcfcfab commit ed22985

13 files changed

Lines changed: 353 additions & 68 deletions

File tree

.github/workflows/ci.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ jobs:
1414
name: macOS
1515
strategy:
1616
matrix:
17-
xcode: ['16.4']
17+
xcode: ['26.1']
1818
config: ['debug', 'release']
19-
runs-on: macos-15
19+
runs-on: macos-26
2020
steps:
21-
- uses: actions/checkout@v4
21+
- uses: actions/checkout@v5
2222
- name: Select Xcode ${{ matrix.xcode }}
2323
run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
2424
- name: Run ${{ matrix.config }} tests
@@ -28,13 +28,13 @@ jobs:
2828
name: Examples
2929
strategy:
3030
matrix:
31-
xcode: ['16.4']
31+
xcode: ['26.1']
3232
config: ['debug']
3333
scheme: ['Reminders', 'CaseStudies', 'SyncUps']
34-
runs-on: macos-15
34+
runs-on: macos-26
3535
continue-on-error: true
3636
steps:
37-
- uses: actions/checkout@v4
37+
- uses: actions/checkout@v5
3838
- name: Select Xcode ${{ matrix.xcode }}
3939
run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
4040
- name: List devices available

Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 22 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Examples/Reminders/ReminderForm.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ struct ReminderFormView: View {
122122
}
123123
.task(id: reminder.remindersListID) {
124124
await withErrorReporting {
125-
try await $remindersList.load(RemindersList.find(reminder.remindersListID))
125+
try await $remindersList.load(RemindersList.find(reminder.remindersListID)).task
126126
}
127127
}
128128
}

Examples/Reminders/Schema.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,8 +387,14 @@ nonisolated private let logger = Logger(subsystem: "Reminders", category: "Datab
387387
func seedSampleData() throws {
388388
@Dependency(\.date.now) var now
389389
@Dependency(\.uuid) var uuid
390-
let remindersListIDs = (0...2).map { _ in uuid() }
391-
let reminderIDs = (0...10).map { _ in uuid() }
390+
var remindersListIDs: [UUID] = []
391+
for _ in 0...2 {
392+
remindersListIDs.append(uuid())
393+
}
394+
var reminderIDs: [UUID] = []
395+
for _ in 0...10 {
396+
reminderIDs.append(uuid())
397+
}
392398
try seed {
393399
RemindersList(
394400
id: remindersListIDs[0],

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ let package = Package(
5353
.package(url: "https://github.com/pointfreeco/swift-concurrency-extras", from: "1.3.0"),
5454
.package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.3"),
5555
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.9.0"),
56-
.package(url: "https://github.com/pointfreeco/swift-perception", "1.4.1"..<"3.0.0"),
56+
.package(url: "https://github.com/pointfreeco/swift-perception", from: "2.0.0"),
5757
.package(
5858
url: "https://github.com/bok-/swift-sharing-with-traits",
5959
from: "2.7.4+traits",
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Migration guides
2+
3+
Learn how to upgrade your application to the latest version of SQLiteData.
4+
5+
## Overview
6+
7+
SQLiteData is under constant development, and we are always looking for ways to simplify the
8+
library and make it more powerful. As such, we often need to deprecate certain APIs in favor of
9+
newer ones. We recommend people update their code as quickly as possible to the newest APIs, and
10+
these guides contain tips to do so.
11+
12+
> Important: Before following any particular migration guide be sure you have followed all the
13+
> preceding migration guides.
14+
15+
## Topics
16+
17+
- <doc:MigratingTo1.4>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Migrating to 1.4
2+
3+
SQLiteData 1.4 introduces a new tool for tying the lifecycle database subscriptions to the
4+
lifecycle of the surrounding async context, but it may incidentally cause "Result of call …
5+
is unused" warnings in your project.
6+
7+
## Overview
8+
9+
The `load` method defined on [`@FetchAll`](<doc:FetchAll>) / [`@FetchOne`](<doc:FetchOne>) /
10+
[`@Fetch`](<doc:Fetch>) all now return a discardable result, ``FetchSubscription``. Awaiting the
11+
``FetchSubscription/task`` of that result ties the lifecycle of the subscription to the database
12+
to the lifecycle of the surrounding async context, which can help views to automatically
13+
unsubscribe from the database when they are not visible.
14+
15+
However, when used with `withErrorReporting` you are likely to get the following warning:
16+
17+
```swift
18+
private func updateQuery() async {
19+
// ⚠️ Result of call to 'withErrorReporting(_:to:fileID:filePath:line:column:isolation:catching:)' is unused
20+
await withErrorReporting {
21+
try await $rows.load()
22+
}
23+
}
24+
```
25+
26+
This is happening because although `load` has a discardable result, Swift does not propagate that
27+
to `withErrorReporting`, and so Swift thinks you have an unused value. To fix you will need to
28+
explicitly ignore the result with `_ = `:
29+
30+
```swift
31+
private func updateQuery() async {
32+
_ = await withErrorReporting {
33+
try await $rows.load()
34+
}
35+
}
36+
```

Sources/SQLiteData/Documentation.docc/SQLiteData.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ with SQLite to take full advantage of GRDB and SQLiteData.
309309
- ``FetchAll``
310310
- ``FetchOne``
311311
- ``Fetch``
312+
- ``FetchSubscription``
312313

313314
### CloudKit synchronization and sharing
314315

Sources/SQLiteData/Fetch.swift

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,11 +139,14 @@ public struct Fetch<Value: Sendable>: Sendable {
139139
/// - request: A request describing the data to fetch.
140140
/// - database: The database to read from. A value of `nil` will use the default database
141141
/// (`@Dependency(\.defaultDatabase)`).
142+
/// - Returns: A subscription associated with the observation.
143+
@discardableResult
142144
public func load(
143145
_ request: some FetchKeyRequest<Value>,
144146
database: (any DatabaseReader)? = nil
145-
) async throws {
147+
) async throws -> FetchSubscription {
146148
try await sharedReader.load(.fetch(request, database: databaseOrDefault(database)))
149+
return FetchSubscription(sharedReader: sharedReader)
147150
}
148151
}
149152

@@ -183,13 +186,15 @@ extension Fetch {
183186
/// (`@Dependency(\.defaultDatabase)`).
184187
/// - scheduler: The scheduler to observe from. By default, database observation is performed
185188
/// asynchronously on the main queue.
189+
/// - Returns: A subscription associated with the observation.
190+
@discardableResult
186191
public func load(
187192
_ request: some FetchKeyRequest<Value>,
188193
database: (any DatabaseReader)? = nil,
189194
scheduler: some ValueObservationScheduler & Hashable
190-
) async throws {
191-
try await sharedReader.load(
192-
.fetch(request, database: databaseOrDefault(database), scheduler: scheduler))
195+
) async throws -> FetchSubscription {
196+
try await sharedReader.load(.fetch(request, database: databaseOrDefault(database), scheduler: scheduler))
197+
return FetchSubscription(sharedReader: sharedReader)
193198
}
194199
}
195200

@@ -271,14 +276,16 @@ extension Fetch: Equatable where Value: Equatable {
271276
/// (`@Dependency(\.defaultDatabase)`).
272277
/// - animation: The animation to use for user interface changes that result from changes to
273278
/// the fetched results.
279+
/// - Returns: A subscription associated with the observation.
274280
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
281+
@discardableResult
275282
public func load(
276283
_ request: some FetchKeyRequest<Value>,
277284
database: (any DatabaseReader)? = nil,
278285
animation: Animation
279-
) async throws {
280-
try await sharedReader.load(
281-
.fetch(request, database: databaseOrDefault(database), animation: animation))
286+
) async throws -> FetchSubscription {
287+
try await sharedReader.load(.fetch(request, database: databaseOrDefault(database), animation: animation))
288+
return FetchSubscription(sharedReader: sharedReader)
282289
}
283290
}
284291
#endif

0 commit comments

Comments
 (0)