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
7 changes: 4 additions & 3 deletions Cache.xcworkspace/xcshareddata/swiftpm/Package.resolved

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

10 changes: 5 additions & 5 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
// swift-tools-version: 5.7
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "Cache",
platforms: [
.iOS(.v14),
.macOS(.v12)
.iOS(.v16),
.macOS(.v13)
],
products: [
.library(
name: "Cache",
targets: ["Cache"])
],
dependencies: [
.package(url: "https://github.com/krogerco/Gauntlet-iOS.git", from: Version(1, 0, 0))
.package(url: "https://github.com/krogerco/Gauntlet-iOS.git", from: "2.2.0")
],
targets: [
.target(name: "Cache"),
.testTarget(
name: "CacheTests",
dependencies: [
.byName(name: "Cache"),
.product(name: "Gauntlet", package: "Gauntlet-iOS")
.product(name: "GauntletLegacy", package: "Gauntlet-iOS")
]
)
]
Expand Down
2 changes: 1 addition & 1 deletion Sources/Cache/Cache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import Foundation
typealias EventPublisher = PassthroughSubject<CacheEvent, Never>

/// A memory based cache with configurable policies and optional persistence.
public final class Cache<Key: CacheKey, Value: Codable> {
public final class Cache<Key: CacheKey, Value: Codable>: @unchecked Sendable {
let lock = NSRecursiveLock()
let layer: MemoryCacheLayer<Key, Value>
let identifier: String
Expand Down
2 changes: 1 addition & 1 deletion Sources/Cache/CacheConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import Combine
import Foundation

class CacheConfig {
struct CacheConfig {
let location: CacheLocation?
let eventPublisher: EventPublisher

Expand Down
2 changes: 1 addition & 1 deletion Sources/Cache/CacheEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import Foundation

/// Cache internal events that might be useful for debugging.
public enum CacheEvent {
public enum CacheEvent: Sendable {
/// Cache was unable to load from the file. Will happen on first launch or if the Value type changes.
case unableToLoad(URL, Error)

Expand Down
2 changes: 1 addition & 1 deletion Sources/Cache/CacheKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@
import Foundation

/// The key used to look items up in cache objects.
public typealias CacheKey = Hashable & Codable
public typealias CacheKey = Hashable & Codable & Sendable
12 changes: 6 additions & 6 deletions Sources/Cache/CacheLayer/MemoryCacheLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import UIKit
#endif

/// A fully in-memory based cache with persistence.
final class MemoryCacheLayer<Key: CacheKey, Value: Codable>: CacheLayer {
final class MemoryCacheLayer<Key: CacheKey, Value: Codable>: CacheLayer, @unchecked Sendable {
var config = CacheConfig()
let policies: [CachePolicy]
var cache: [Key: Item] = [:]
Expand Down Expand Up @@ -89,16 +89,16 @@ final class MemoryCacheLayer<Key: CacheKey, Value: Codable>: CacheLayer {
object: nil,
queue: .main
) { [weak self] _ in
guard let strongSelf = self else { return }
guard let self else { return }

strongSelf.lock.lock(); defer { strongSelf.lock.unlock() }
lock.lock(); defer { lock.unlock() }

// Bail if empty.
guard !strongSelf.cache.isEmpty else { return }
guard !cache.isEmpty else { return }

// Apply a policy that will purge half of the current cache.
let purgeAmount = strongSelf.cache.count / 2
strongSelf.apply([.maxItemCount(purgeAmount)])
let purgeAmount = cache.count / 2
apply([.maxItemCount(purgeAmount)])
}
#endif
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Cache/CacheLocation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import Foundation

/// Specifies a directory where ``CacheLayer``s are stored.
public struct CacheLocation {
public struct CacheLocation: Sendable {
/// Name of this location.
public let locationName: String

Expand Down
2 changes: 1 addition & 1 deletion Sources/Cache/CachePolicy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import Foundation
/// Policies that may be applied to each cache source.
/// Generally, you would create a different and more lenient policy for each level of cache source.
/// Specifying no policies results in caches with unbounded growth.
public enum CachePolicy {
public enum CachePolicy: Sendable {
/// The maximum number of key/values the source should track.
/// When this count is exceeded, the least recently used values will be purged.
case maxItemCount(Int)
Expand Down
10 changes: 5 additions & 5 deletions Sources/DemoApp/DemoApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1420;
LastUpgradeCheck = 1420;
LastUpgradeCheck = 1630;
TargetAttributes = {
6732C75029BBAD7100D13B5C = {
CreatedOnToolsVersion = 14.2;
Expand Down Expand Up @@ -297,6 +297,7 @@
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
Expand Down Expand Up @@ -357,6 +358,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
Expand Down Expand Up @@ -398,7 +400,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.kroger.DemoApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
Expand Down Expand Up @@ -426,15 +428,14 @@
PRODUCT_BUNDLE_IDENTIFIER = com.kroger.DemoApp;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
6732C77929BBAD7200D13B5C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
Expand All @@ -453,7 +454,6 @@
6732C77A29BBAD7200D13B5C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1420"
LastUpgradeVersion = "1630"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
15 changes: 10 additions & 5 deletions Sources/DemoApp/DemoApp/SampleModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ struct ProductInfo: Codable {
// Object responsible for providing product information.
// Typically this would be a network call.
struct ProductProvider {
func getInfo(for product: SampleProduct, _ completion: @escaping (ProductInfo) -> Void) {
func getInfo(for product: SampleProduct, _ completion: @escaping @Sendable (ProductInfo) -> Void) {
let info: ProductInfo

switch product {
Expand All @@ -71,6 +71,7 @@ struct ProductProvider {
}

// Sample view model that manages the selected product and the fetched details for that product.
@MainActor
class SampleModel: ObservableObject {
// The currently selected product and it's details.
@Published var selectedProduct: SampleProduct = .product3
Expand Down Expand Up @@ -101,11 +102,15 @@ class SampleModel: ObservableObject {
// Info not in the cache. Fetch the info and update when complete.
self.productInfo = nil
self.provider.getInfo(for: product) { info in
// Update the cache.
self.cache[product] = info
Task { @MainActor [weak self] in
guard let self else { return }

// Publish the info.
self.productInfo = info
// Update the cache.
self.cache[product] = info

// Publish the info.
self.productInfo = info
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/CacheTests/CacheLocationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
// SOFTWARE.

@testable import Cache
import Gauntlet
import GauntletLegacy
import XCTest

class CacheLocationTests: XCTestCase {
Expand Down
2 changes: 1 addition & 1 deletion Tests/CacheTests/CacheTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
// SOFTWARE.

@testable import Cache
import Gauntlet
import GauntletLegacy
import XCTest

final class CacheTestCases: XCTestCase {
Expand Down
2 changes: 1 addition & 1 deletion Tests/CacheTests/CodableFileTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import Gauntlet
import GauntletLegacy
@testable import Cache
import XCTest

Expand Down
2 changes: 1 addition & 1 deletion Tests/CacheTests/MemoryCacheLayerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
// SOFTWARE.

@testable import Cache
import Gauntlet
import GauntletLegacy
import XCTest

class MemoryCacheLayerTestCase: XCTestCase {
Expand Down
2 changes: 1 addition & 1 deletion Tests/CacheTests/MockCacheLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
@testable import Cache
import Foundation

extension CacheItem: @retroactive Equatable where Key == String, Value: Equatable {
extension CacheItem: Equatable where Key == String, Value: Equatable {
public static func == (lhs: CacheItem<Key, Value>, rhs: CacheItem<Key, Value>) -> Bool {
lhs.key == rhs.key &&
lhs.creationDate == rhs.creationDate &&
Expand Down
Loading