Skip to content

Finish async concurrency migration - #28

Merged
zigdanis merged 16 commits into
codex/concurrency-async-apifrom
codex/concurrency-call-sites-cleanup
Aug 3, 2026
Merged

Finish async concurrency migration#28
zigdanis merged 16 commits into
codex/concurrency-async-apifrom
codex/concurrency-call-sites-cleanup

Conversation

@zigdanis

@zigdanis zigdanis commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • make PersonsGateway and use-case contracts async-only
  • implement Core Data operations with checked continuations and preserve cancellation boundaries
  • migrate presenters, image loading, and WidgetKit call sites to structured Task usage with main-actor UI updates
  • simplify focused gateway/use-case tests around async behavior

Stack

Depends on #27. Part 5 of the native GitHub stack rooted at #8.

Checks

  • scripts/check-formatting.sh
  • scripts/check-secrets.sh
  • parsed every Swift source with Linux Swift 6.3.3
  • git diff --check

Summary by CodeRabbit

  • New Features
    • Updated person management and image loading to use modern asynchronous operations.
    • Improved cancellation and error handling during data and image loading.
    • Enhanced widget updates and person editing workflows.
  • Bug Fixes
    • Improved UI safety by ensuring interface updates occur on the main thread.
    • Improved button layout and image presentation.
  • Chores
    • SwiftLint now runs consistently and reports missing installations as build errors.
    • Updated automated tests for asynchronous behavior.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@zigdanis, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 48 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d22ba585-22aa-4f50-a883-0cb805f1de4b

📥 Commits

Reviewing files that changed from the base of the PR and between 3317612 and b7db232.

📒 Files selected for processing (3)
  • GrowingUp.xcodeproj/project.pbxproj
  • GrowingUp/Scenes/EditPerson/Cells/ImagePickersTableViewCell/ImagesTableViewCell.swift
  • GrowingUp/Scenes/PersonOverview/PersonOverviewPresenter.swift
📝 Walkthrough

Walkthrough

The PR migrates person and image operations from completion handlers to async/throws, adds cancellation handling, applies Sendable and @MainActor annotations, updates widgets and tests, enforces SwiftLint availability, and simplifies VerticalButton layout.

Changes

Swift concurrency migration

Layer / File(s) Summary
Async contracts and use cases
Core/Entities/*, Core/Gateways/PersonsGateway.swift, Core/UseCases/*
Public person contracts and use cases now expose async/throwing APIs. Completion-handler requirements and typealiases were removed.
Async gateway implementations
Core/EntityGateway/Cache/*, Core/EntityGateway/LocalPersistance/*
Core Data and cache operations now use throwing continuations, cancellation checks, direct error propagation, and async image loading.
Main-actor application flows
GrowingUp/Scenes/..., Widget/AgeWidgetProvider.swift
Presenters, configurators, routers, image cells, and widget loading now use MainActor tasks and async/await.
Concurrency test coverage
GrowingUpTests/Gateways/*, GrowingUpTests/Helpers/*, GrowingUpTests/Presenters/*, GrowingUpTests/UseCases/*
Tests and spies now use async/throws methods, continuations, MainActor isolation, and asynchronous polling.
SwiftLint build enforcement
GrowingUp.xcodeproj/project.pbxproj
The SwiftLint build phase always runs and fails when the executable is unavailable.
VerticalButton configuration
GrowingUp/Views/VerticalButton.swift
Button layout now uses UIButton.Configuration with top image placement and six points of padding.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant View
  participant MainActorTask
  participant UseCase
  participant PersonsGateway
  participant ImagesCache

  View->>MainActorTask: Start async operation
  MainActorTask->>UseCase: Await person operation
  UseCase->>PersonsGateway: Await gateway operation
  PersonsGateway-->>UseCase: Return value or CoreError
  UseCase-->>MainActorTask: Return value or CoreError
  MainActorTask->>ImagesCache: Await image loading
  ImagesCache-->>MainActorTask: Return UIImage or CoreError
  MainActorTask-->>View: Update UI or widget entry
Loading

Possibly related PRs

  • zigdanis/GrowingUp#2: Both PRs modify Person.swift and Widget/AgeWidgetProvider.swift.
  • zigdanis/GrowingUp#8: This PR implements the planned async/await migration across gateways, use cases, presenters, and widgets.
  • zigdanis/GrowingUp#26: Both PRs modify CachePersonsGateway and related image-loading concurrency.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the PR's primary change: completing the async concurrency migration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/concurrency-call-sites-cleanup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@zigdanis

zigdanis commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@zigdanis
zigdanis marked this pull request as ready for review August 1, 2026 19:48
@zigdanis

zigdanis commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 59 minutes.

@zigdanis
zigdanis force-pushed the codex/concurrency-call-sites-cleanup branch 2 times, most recently from 44740fe to 3a4937a Compare August 1, 2026 19:57
@zigdanis
zigdanis force-pushed the codex/concurrency-call-sites-cleanup branch from ae81069 to 2307078 Compare August 2, 2026 06:14

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
Widget/AgeWidgetProvider.swift (1)

92-107: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Keep widget image loading concurrent.

resolveImages() awaits ImagesCache.loadImageFromDiskOrMemory inside each loop iteration, so line 100 cannot run before line 97 completes. This serializes image loading and can delay the widget timeline callback while waiting for a slow image decode/retrieve.

Start the loads concurrently, then collect them in input order. Keep the try? fallback for failed image loads.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Widget/AgeWidgetProvider.swift` around lines 92 - 107, Update
resolveImages(for:) to launch all person image loads concurrently rather than
awaiting each ImagesCache.loadImageFromDiskOrMemory call inside the loop, then
collect results in the original people order when constructing WidgetPerson
values. Preserve nil images for missing PersonImage values or failed loads by
retaining the existing try? fallback.

Source: Coding guidelines

GrowingUpTests/Gateways/CoreDataPersonsGatewayTests.swift (1)

60-62: 📐 Maintainability & Code Quality | 🔵 Trivial

Add an assertion to test_SUT_FetchPersons_ShouldSucceed.

The test discards the fetched array with _ = try await inMemoryCoreDataGateway.fetchPersons(). It only checks that the call does not throw. Add an assertion on the returned array, for example that it is empty in this fixture, to catch regressions in fetchPersons filtering or ordering.

✅ Proposed assertion
 func test_SUT_FetchPersons_ShouldSucceed() async throws {
-	_ = try await inMemoryCoreDataGateway.fetchPersons()
+	let persons = try await inMemoryCoreDataGateway.fetchPersons()
+	XCTAssertTrue(persons.isEmpty)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@GrowingUpTests/Gateways/CoreDataPersonsGatewayTests.swift` around lines 60 -
62, Update test_SUT_FetchPersons_ShouldSucceed to retain the array returned by
inMemoryCoreDataGateway.fetchPersons() and assert its expected contents for this
fixture, such as verifying that it is empty, while preserving the async throwing
test flow.
GrowingUpTests/UseCases/RemovePersonUseCaseTests.swift (1)

7-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restore the error-propagation test.

RemovePersonUseCaseImplementation.remove(person:) is async throws, but this change removes its failure-path coverage. Add a test that configures PersonsGatewaySpy with .failure(.coreDataFetchFailed) and verifies that sut.remove(person:) throws that error.

Proposed test
+	func testRemovePropagatesError() async {
+		let gateway = PersonsGatewaySpy()
+		let sut = RemovePersonUseCaseImplementation(personsGateway: gateway)
+		gateway.removePersonResultToBeReturned = .failure(.coreDataFetchFailed)
+
+		do {
+			try await sut.remove(person: Person.createPerson())
+			XCTFail("Expected removal failure")
+		} catch {
+			XCTAssertEqual(error as? CoreError, .coreDataFetchFailed)
+		}
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@GrowingUpTests/UseCases/RemovePersonUseCaseTests.swift` around lines 7 - 14,
Add an error-propagation test alongside testRemoveCallsGateway using
PersonsGatewaySpy configured with .failure(.coreDataFetchFailed), then assert
that awaiting sut.remove(person:) throws the same .coreDataFetchFailed error.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@GrowingUp.xcodeproj/project.pbxproj`:
- Line 1145: Update the SwiftLint setup documentation in Readme.md to reflect
the script’s required behavior: SwiftLint must be installed for builds, and
missing SwiftLint causes the Xcode phase to fail rather than emit only a
warning. Remove or revise language describing the phase as optional or
warning-only.
- Line 1145: Update the SwiftLint invocation in the build phase shellScript to
run the strict lint command, matching scripts/lint-swift.sh, while preserving
the existing availability check and failure handling.

In
`@GrowingUp/Scenes/EditPerson/Cells/ImagePickersTableViewCell/ImagesTableViewCell.swift`:
- Around line 121-125: Bind both asynchronous image-loading tasks in
ImagesTableViewCell to the cell’s current content: track each app-image and
widget-image request or capture a content identity, then validate it before
calling drawImage so reused cells cannot display stale results. Apply this at
the app-image site in
GrowingUp/Scenes/EditPerson/Cells/ImagePickersTableViewCell/ImagesTableViewCell.swift
lines 121-125 and the widget-image site in the same file lines 140-144.

In `@GrowingUp/Scenes/PersonOverview/PersonOverviewPresenter.swift`:
- Around line 48-51: Ensure the repeating timer created in loadPerson is
invalidated when PersonOverviewPresenter is released by adding a deinit teardown
that calls timer?.invalidate(). Keep the existing refresh-time invalidation
unchanged.

---

Nitpick comments:
In `@GrowingUpTests/Gateways/CoreDataPersonsGatewayTests.swift`:
- Around line 60-62: Update test_SUT_FetchPersons_ShouldSucceed to retain the
array returned by inMemoryCoreDataGateway.fetchPersons() and assert its expected
contents for this fixture, such as verifying that it is empty, while preserving
the async throwing test flow.

In `@GrowingUpTests/UseCases/RemovePersonUseCaseTests.swift`:
- Around line 7-14: Add an error-propagation test alongside
testRemoveCallsGateway using PersonsGatewaySpy configured with
.failure(.coreDataFetchFailed), then assert that awaiting sut.remove(person:)
throws the same .coreDataFetchFailed error.

In `@Widget/AgeWidgetProvider.swift`:
- Around line 92-107: Update resolveImages(for:) to launch all person image
loads concurrently rather than awaiting each
ImagesCache.loadImageFromDiskOrMemory call inside the loop, then collect results
in the original people order when constructing WidgetPerson values. Preserve nil
images for missing PersonImage values or failed loads by retaining the existing
try? fallback.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a4a3594c-09f8-4324-9c4c-0606b1592952

📥 Commits

Reviewing files that changed from the base of the PR and between 68c2e91 and 3317612.

📒 Files selected for processing (47)
  • Core/Entities/AddPersonParameters.swift
  • Core/Entities/Person.swift
  • Core/EntityGateway/Cache/CachePersonsGateway.swift
  • Core/EntityGateway/Cache/ImagesCache.swift
  • Core/EntityGateway/LocalPersistance/CoreDataPersonsGateway.swift
  • Core/Gateways/PersonsGateway.swift
  • Core/UseCases/AddPersonUseCase.swift
  • Core/UseCases/EditPersonUseCase.swift
  • Core/UseCases/FetchPersonsUseCase.swift
  • Core/UseCases/RemovePersonUseCase.swift
  • GrowingUp.xcodeproj/project.pbxproj
  • GrowingUp/Scenes/EditPerson/Cells/DateTableViewCell/DateTableViewCell.swift
  • GrowingUp/Scenes/EditPerson/Cells/ImagePickersTableViewCell/ImagesTableViewCell.swift
  • GrowingUp/Scenes/EditPerson/Cells/TextFieldTableViewCell/TextFieldTableViewCell.swift
  • GrowingUp/Scenes/EditPerson/Cells/ToggleTableViewCell/ToggleTableViewCell.swift
  • GrowingUp/Scenes/EditPerson/Create/AddPersonConfigurator.swift
  • GrowingUp/Scenes/EditPerson/Create/AddPersonPresenter.swift
  • GrowingUp/Scenes/EditPerson/Edit/EditPersonConfigurator.swift
  • GrowingUp/Scenes/EditPerson/Edit/EditPersonPresenter.swift
  • GrowingUp/Scenes/EmptyPerson/EmptyPersonConfigurator.swift
  • GrowingUp/Scenes/EmptyPerson/EmptyPersonPresenter.swift
  • GrowingUp/Scenes/EmptyPerson/EmptyPersonViewRouter.swift
  • GrowingUp/Scenes/PersonOverview/PersonOverviewConfigurator.swift
  • GrowingUp/Scenes/PersonOverview/PersonOverviewPresenter.swift
  • GrowingUp/Scenes/PersonOverview/PersonOverviewRouter.swift
  • GrowingUp/Scenes/PersonOverview/PersonOverviewViewController.swift
  • GrowingUp/Scenes/PersonsList/PersonsListConfigurator.swift
  • GrowingUp/Scenes/PersonsList/PersonsListPresenter.swift
  • GrowingUp/Views/VerticalButton.swift
  • GrowingUpTests/Gateways/CachePersonsGatewayTests.swift
  • GrowingUpTests/Gateways/CoreDataPersonsGatewayTests.swift
  • GrowingUpTests/Helpers/Gateways/PersonsGatewaySpy.swift
  • GrowingUpTests/Helpers/UserCases/AddPersonUseCaseSpy.swift
  • GrowingUpTests/Helpers/UserCases/DisplayPersonsUseCaseSpy.swift
  • GrowingUpTests/Helpers/UserCases/EditPersonUseCaseSpy.swift
  • GrowingUpTests/Helpers/UserCases/FetchPersonsUseCaseSpy.swift
  • GrowingUpTests/Helpers/UserCases/RemovePersonUseCaseSpy.swift
  • GrowingUpTests/Presenters/AddPersonPresenterTests.swift
  • GrowingUpTests/Presenters/EditPersonPresenterTests.swift
  • GrowingUpTests/Presenters/EmptyPersonPresenterTests.swift
  • GrowingUpTests/Presenters/PersonsListPresenterTests.swift
  • GrowingUpTests/Presenters/TextFieldCellPresenterTests.swift
  • GrowingUpTests/UseCases/AddPersonUseCaseTests.swift
  • GrowingUpTests/UseCases/EditPersonUseCaseTests.swift
  • GrowingUpTests/UseCases/FetchPersonsUseCaseTests.swift
  • GrowingUpTests/UseCases/RemovePersonUseCaseTests.swift
  • Widget/AgeWidgetProvider.swift
💤 Files with no reviewable changes (9)
  • Core/UseCases/AddPersonUseCase.swift
  • GrowingUpTests/Helpers/UserCases/RemovePersonUseCaseSpy.swift
  • GrowingUpTests/Helpers/UserCases/FetchPersonsUseCaseSpy.swift
  • GrowingUpTests/Helpers/UserCases/DisplayPersonsUseCaseSpy.swift
  • Core/UseCases/EditPersonUseCase.swift
  • Core/UseCases/FetchPersonsUseCase.swift
  • Core/Gateways/PersonsGateway.swift
  • Core/UseCases/RemovePersonUseCase.swift
  • Core/EntityGateway/Cache/CachePersonsGateway.swift

Comment thread GrowingUp.xcodeproj/project.pbxproj Outdated
Comment thread GrowingUp/Scenes/PersonOverview/PersonOverviewPresenter.swift
@zigdanis
zigdanis merged commit aef4c3b into master Aug 3, 2026
6 checks passed
@zigdanis
zigdanis deleted the codex/concurrency-call-sites-cleanup branch August 3, 2026 13:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant