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
9 changes: 2 additions & 7 deletions .github/workflows/docc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
environment:
name: github-pages
url: '${{ steps.deployment.outputs.page_url }}'
runs-on: macos-latest
runs-on: macos-15

steps:
- name: Checkout code
Expand All @@ -27,12 +27,7 @@ jobs:
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: 16.0

- name: Set up Swift
uses: swift-actions/setup-swift@v2
with:
swift-version: '6.1.0'
xcode-version: latest-stable

- name: Build and Export DocC
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/macOS.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ on:

jobs:
build:
runs-on: macos-latest
runs-on: macos-15
steps:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: 16.0
xcode-version: latest-stable
- uses: actions/checkout@v4
- name: Build
run: swift build -v
Expand Down
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [2.1.3] - 2026-07-17

Release intended for consumers such as **AppState 3.0**, which can replace a revision pin of Cache#30 with `from: "2.1.3"`.

### Fixed

- **WebAssembly / WASI builds** — Guard `@Published` / `ObservableObject` with `#if canImport(Combine)` instead of `#if !os(Linux) && !os(Windows)`, so wasm no longer takes the Combine path ([#30](https://github.com/0xLeif/Cache/pull/30), AppState#149).
- **Linux / WASI collection cast crash** — `Dictionary.get(_:as:)` unwraps the optional value before casting, avoiding `swift_dynamicCastFailure` / `_arrayForceCast` aborts when reading collection-typed values from an `Any` cache ([#30](https://github.com/0xLeif/Cache/pull/30), AppState#151).
- **CI** — macOS and DocC workflows no longer pin Xcode 16 (unavailable on current `macos-latest` images). Use `macos-15` with `latest-stable` Xcode, matching AppState.

### Changed

- Explicitly import Combine when available so `@Published` / `ObservableObject` compile reliably across build configurations.

## [2.1.2] - 2025-09-20

### Added

- DocC plugin dependency and GitHub Pages documentation workflow updates.

[Unreleased]: https://github.com/0xLeif/Cache/compare/2.1.3...HEAD
[2.1.3]: https://github.com/0xLeif/Cache/compare/2.1.2...2.1.3
[2.1.2]: https://github.com/0xLeif/Cache/releases/tag/2.1.2
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Cache is a Swift library for caching arbitrary data types in memory. It provides
- Flexible caching, allowing for multiple Cache objects with different configurations
- Thread-safe implementation
- Property Wrappers
- Cross-platform: Apple platforms, Linux, Windows, and WebAssembly (WASI)

## Installation

Expand All @@ -24,7 +25,7 @@ Add the following line to your `Package.swift` file in the dependencies array:

```swift
dependencies: [
.package(url: "https://github.com/0xLeif/Cache.git", from: "2.0.0")
.package(url: "https://github.com/0xLeif/Cache.git", from: "2.1.3")
]
```

Expand Down
4 changes: 4 additions & 0 deletions Sources/Cache/Cache/Cache.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import Foundation

#if canImport(Combine)
import Combine
#endif

/**
`Cache` class provides a cache functionality that can be used to store key-value pairs. It is thread-safe using a locking mechanism.

Expand Down
12 changes: 5 additions & 7 deletions Sources/Cache/Dictionary/Dictionary+Cacheable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@ extension Dictionary: Cacheable {
- Returns: The casted value for the given key, or `nil` if the key doesn't exist or the cast fails.
*/
public func get<Item>(_ key: Key, as: Item.Type = Item.self) -> Item? {
// Route through an explicit `Any` boxing step before the conditional cast. This avoids
// `swift_dynamicCastFailure` / `_arrayForceCast` aborts on Linux and WASI when casting an
// `Optional<Any>` that wraps a collection type (e.g. `[SomeStruct]`): without the
// intermediate cast, the runtime takes the `Optional<Any>` → `[T]` forced-array-cast path,
// which is not implemented the same way off Darwin. Boxing to `Any` first normalises the
// dynamic type to the concrete `Item` path.
guard let value = (self[key] as Any) as? Item else {
// Unwrap the optional value first. Casting `Optional<Any>` (or `Optional<Value>`)
// directly to a collection type (e.g. `[T]`) can abort on Linux/WASI with
// `swift_dynamicCastFailure` / `_arrayForceCast`. Unwrapping first takes the
// concrete `Value` → `Item` path instead.
guard let rawValue = self[key], let value = rawValue as? Item else {
return nil
}

Expand Down
Loading