Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 60 additions & 40 deletions .github/workflows/VerifyChanges.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
name: Verify Changes

on:
merge_group:
pull_request:
Expand All @@ -10,7 +9,7 @@ on:
jobs:
lint:
name: Lint
runs-on: macos-15
runs-on: macos-26
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -20,10 +19,11 @@ jobs:
- name: Lint
run: |
Scripts/lint

build-and-test:
name: Build and Test (${{ matrix.platform }})
needs: lint
runs-on: macos-15
runs-on: macos-26
strategy:
fail-fast: false
matrix:
Expand All @@ -44,31 +44,40 @@ jobs:
# xcode_destination: "platform=watchOS Simulator,name=GitHub_Actions_Simulator"
# simulator_device_type: "com.apple.CoreSimulator.SimDeviceType.Apple-Watch-Series-10-46mm"
# simulator_runtime: "com.apple.CoreSimulator.SimRuntime.watchOS-26-0"

env:
DEV_BUILDS: DevBuilds/Sources
OTHER_XCBEAUTIFY_FLAGS: --renderer github-actions
XCCOV_PRETTY_VERSION: 1.2.0
XCODE_SCHEME: DevFoundation-Package
XCODE_DESTINATION: ${{ matrix.xcode_destination }}
XCODE_TEST_PLAN: AllTests
XCODE_TEST_PRODUCTS_PATH: .build/DevFoundation.xctestproducts

steps:
- name: Select Xcode 26.0.0
run: |
sudo xcode-select -s /Applications/Xcode_26.0.0.app

- name: Checkout
uses: actions/checkout@v4

- name: Checkout DevBuilds
uses: actions/checkout@v4
with:
repository: DevKitOrganization/DevBuilds
path: DevBuilds
- name: Download xccovPretty

- name: Restore XCTestProducts
if: github.event_name != 'push'
run: |
gh release download ${{ env.XCCOV_PRETTY_VERSION }} \
--repo DevKitOrganization/xccovPretty \
--pattern "xccovPretty-macos.tar.gz" \
-O - | tar -xz
chmod +x xccovPretty
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
id: cache-xctestproducts-restore
uses: actions/cache/restore@v4
with:
path: ${{ env.XCODE_TEST_PRODUCTS_PATH }}
key: cache-xctestproducts-${{ github.workflow }}-${{ matrix.platform }}-${{ github.sha }}

- uses: irgaly/xcode-cache@v1
if: steps.cache-xctestproducts-restore.outputs.cache-hit != 'true'
with:
key: xcode-cache-deriveddata-${{ github.workflow }}-${{ matrix.platform }}-${{ github.sha }}
restore-keys: |
Expand All @@ -78,61 +87,72 @@ jobs:
sourcepackages-directory: .build/DerivedData/SourcePackages
swiftpm-package-resolved-file: Package.resolved
verbose: true
- name: Select Xcode 26.0.0
run: |
sudo xcode-select -s /Applications/Xcode_26.0.0.app
- name: Create Simulator
if: ${{ matrix.platform != 'macOS' }}
run: |
xcrun simctl create GitHub_Actions_Simulator \
${{ matrix.simulator_device_type }} \
${{ matrix.simulator_runtime }}

- name: Build for Testing
run: |
"$DEV_BUILDS"/build_and_test.sh --action build-for-testing
- name: Test
id: build-for-testing
if: steps.cache-xctestproducts-restore.outputs.cache-hit != 'true'
run: ${{ env.DEV_BUILDS }}/build_and_test.sh --action build-for-testing

- name: Test Without Building
id: test-without-building
if: github.event_name != 'push'
run: |
"$DEV_BUILDS"/build_and_test.sh --action test
run: ${{ env.DEV_BUILDS }}/build_and_test.sh --action test-without-building

- name: Save XCTestProducts
if: failure() && steps.cache-xctestproducts-restore.outputs.cache-hit != 'true'
uses: actions/cache/save@v4
with:
path: ${{ env.XCODE_TEST_PRODUCTS_PATH }}
key: ${{ steps.cache-xctestproducts-restore.outputs.cache-primary-key }}

- name: Log Code Coverage
if: github.event_name != 'push'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
xcrun xccov view --report .build/DevFoundation-Package_test.xcresult --json \
| ./xccovPretty --github-comment \
> .build/xccovPretty-${{ matrix.platform }}.output
- name: Upload Logs
uses: actions/upload-artifact@v4
gh release download ${{ env.XCCOV_PRETTY_VERSION }} \
--repo DevKitOrganization/xccovPretty \
--pattern "xccovPretty-macos.tar.gz" \
-O - \
| tar -xz
chmod +x xccovPretty

xcrun xccov view --report .build/${XCODE_SCHEME}_test-without-building.xcresult --json \
| ./xccovPretty --github-comment \
> .build/xccovPretty-${{ matrix.platform }}.output

- name: Upload Logs and XCResults
if: success() || failure()
with:
name: Logs-${{ matrix.platform }}
path: .build/*.log
include-hidden-files: true
- name: Upload XCResults
uses: actions/upload-artifact@v4
if: success() || failure()
with:
name: XCResults-${{ matrix.platform }}
path: .build/*.xcresult
name: Logs_and_XCResults-${{ matrix.platform }}
path: |
.build/*.log
.build/*.xcresult
include-hidden-files: true

- name: Upload xccovPretty output
if: github.event_name != 'push'
uses: actions/upload-artifact@v4
with:
name: xccovPrettyOutput-${{ matrix.platform }}
path: .build/xccovPretty-${{ matrix.platform }}.output
include-hidden-files: true

post-pr-comments:
name: Post PR Comments
needs: build-and-test
if: ${{ github.event_name == 'pull_request' }}
permissions:
pull-requests: write
runs-on: ubuntu-latest
if: ${{ github.event_name == 'pull_request' }}

steps:
- name: Download xccovPretty output
uses: actions/download-artifact@v4
with:
name: xccovPrettyOutput-macOS

- name: Post Code Coverage Comment
uses: thollander/actions-comment-pull-request@v3
with:
Expand Down
17 changes: 11 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@

## 1.2.0: September 24, 2025

This release contains updates to `ExecutionGroup` to enable greater flexibility and testability.

There are now two variants of `addTask(priority:operation:)`: the original version for non-throwing
operations, and a new version for throwing operations. Both functions now have a generic parameter
for the operation’s return type. Together, these changes allow us to return the created `Task`,
which you can use to monitor its progress, get its result, or cancel it.
This release introduces the `ObservableReference` type and updates `ExecutionGroup` to enable
greater flexibility and testability.

- This version updates the minimum supported versions of Apple’s OSes to 26.
- `ObservableReference` is a simple reference type that conforms to `Observable`. This enables
easily observing changes to the value.
- There are now two variants of `ExecutionGroup.addTask(priority:operation:)`: the original
version for non-throwing operations, and a new version for throwing operations. Both functions
now have a generic parameter for the operation’s return type. Together, these changes allow us
to return the created `Task`, which you can use to monitor its progress, get its result, or
cancel it.


## 1.1.0: September 17, 2025
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ Follow the project's Markdown Style Guide:

- Follows Swift API Design Guidelines
- Uses Swift 6.2 with `ExistentialAny` and `MemberImportVisibility` features enabled
- Minimum deployment targets: iOS 18+, macOS 15+, tvOS 18+, visionOS 2+, watchOS 11+
- Minimum deployment targets: iOS, macOS, tvOS, visionOS, and watchOS 26
- Reverse DNS prefix: `com.gauriar.devfoundation`
- All public APIs are documented and tested
- Test coverage target: >99%
6 changes: 3 additions & 3 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ let swiftSettings: [SwiftSetting] = [
let package = Package(
name: "DevFoundation",
platforms: [
.iOS(.v18),
.macOS(.v15),
.tvOS(.v18),
.visionOS(.v2),
.watchOS(.v11),
.iOS(.v26),
.macOS(.v26),
.tvOS(.v26),
.visionOS(.v26),
.watchOS(.v26),
],
products: [
.library(
Expand All @@ -32,7 +32,7 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.6.1"),
.package(url: "https://github.com/apple/swift-numerics.git", from: "1.1.0"),
.package(url: "https://github.com/DevKitOrganization/DevTesting", from: "1.1.0"),
.package(url: "https://github.com/DevKitOrganization/DevTesting", from: "1.2.0"),
.package(url: "https://github.com/prachigauriar/URLMock.git", from: "1.3.6"),
],
targets: [
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ DevFoundation is a Swift 6 package that provides useful types and protocols for
and apps on Apple platforms. It makes it easy to consume web services, mock web service responses,
page through data, post and handle type-safe events across multiple modules, and more.

DevFoundation is fully documented and tested and supports iOS 18+, macOS 15+, tvOS 18+, visionOS 2+,
and watchOS 11+.
DevFoundation is fully documented and tested and supports iOS 26+, macOS 26+, tvOS 26+, visionOS 26+,
and watchOS 26+.

View our [changelog](CHANGELOG.md) to see what’s new.

Expand Down
18 changes: 4 additions & 14 deletions Sources/DevFoundation/Concurrency/ExecutionGroup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,26 +116,16 @@ public final class ExecutionGroup: Sendable {

/// Increments the group’s task count and mutates its execution state if needed.
private func incrementTaskCount() {
let isFirstTask = taskCount.withLock { (count) in
count += 1
return count == 1
}

if isFirstTask {
withMutation(keyPath: \.isExecuting) { () }
withMutation(keyPath: \.isExecuting) {
taskCount.withLock { $0 += 1 }
}
}


/// Decrements the group’s task count and mutates its execution state if needed.
private func decrementTaskCount() {
let isLastTask = taskCount.withLock { (count) in
count -= 1
return count == 0
}

if isLastTask {
withMutation(keyPath: \.isExecuting) { () }
withMutation(keyPath: \.isExecuting) {
taskCount.withLock { $0 -= 1 }
}
}
}
4 changes: 4 additions & 0 deletions Sources/DevFoundation/Documentation.docc/Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ for paging through data, and essential utility types for building robust applica

- ``ExpiringValue``

### Observing Changes

- ``ObservableReference``

### Concurrency Utilities

- ``ExecutionGroup``
Expand Down
41 changes: 41 additions & 0 deletions Sources/DevFoundation/Utility Types/ObservableReference.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// ObservableReference.swift
// DevFoundation
//
// Created by Prachi Gauriar on 9/24/25.
//

import Foundation
import Synchronization

/// A reference whose value changes can be observed.
@Observable
public final class ObservableReference<Value>: Sendable where Value: Sendable {
/// A mutex that synchronizes access to the reference’s value.
private let valueMutex: Mutex<Value>


/// Creates a new observable reference with the specified initial value.
///
/// - Parameter initialValue: The initial value that the reference contains.
public init(_ initialValue: Value) {
self.valueMutex = .init(initialValue)
}


/// The reference’s value.
public var value: Value {
get {
access(keyPath: \.value)
return valueMutex.withLock { $0 }
}

set {
withMutation(keyPath: \.value) {
valueMutex.withLock { (value) in
value = newValue
}
}
}
}
}
Loading