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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ xcuserdata/
DerivedData/
.netrc
Package.resolved
/.swiftpm
/.swiftpm
/AGENTS.md
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ let package = Package(
),
],
dependencies: [
.package(url: "https://github.com/swiftlang/swift-syntax.git", .upToNextMajor(from: "601.0.0")),
.package(url: "https://github.com/swiftlang/swift-syntax.git", .upToNextMajor(from: "602.0.0")),
// .package(url: "https://github.com/swiftlang/swift-syntax.git", "509.0.0" ..< "603.0.0"),
],
targets: [
.macro(
Expand Down
18 changes: 18 additions & 0 deletions Sources/OversizeArchitecture/Protocols/ModuleProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,22 @@ public extension ModuleProtocol where ViewScene: View {
let reducer = Reducer(viewModel: viewModel)
return ViewScene(viewState: state, reducer: reducer)
}

@MainActor
static func buildCached(
cacheKey: String = String(describing: Self.self),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Derive default cache key from input identity

The new buildCached API uses String(describing: Self.self) as its default cache key, so every invocation of the same module type shares one ViewState regardless of input. In modules where state is initialized from input (for example ProductDetailViewState.init(input:) sets the product ID), opening the module for a different entity will silently reuse stale state and ignore the new input because getOrCreate never runs the factory again once the key is populated.

Useful? React with 👍 / 👎.

input: Input? = nil,
output: Output? = nil
) -> some View {
let state = ViewStateCache.shared.getOrCreate(key: cacheKey) {
ViewState(input: input)
}
let viewModel = ViewModel(
state: state,
input: input,
output: output
)
let reducer = Reducer(viewModel: viewModel)
return ViewScene(viewState: state, reducer: reducer)
}
}
2 changes: 1 addition & 1 deletion Sources/OversizeArchitecture/Reducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import Foundation

public final class Reducer<ViewModel>: Sendable where ViewModel: ViewModelProtocol {
public final class Reducer<ViewModel: ViewModelProtocol>: Sendable {
private let viewModel: ViewModel

public init(viewModel: ViewModel) {
Expand Down
34 changes: 34 additions & 0 deletions Sources/OversizeArchitecture/ViewStateCache.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// Copyright © 2025 Alexander Romanov
// ViewStateCache.swift, created on 09.12.2025
//

import Foundation

@MainActor
public final class ViewStateCache {
public static let shared = ViewStateCache()
private var cache: [String: AnyObject] = [:]

private init() {}

public func getOrCreate<ViewState: ViewStateProtocol>(
key: String,
create: () -> ViewState
) -> ViewState {
if let cached = cache[key] as? ViewState {
return cached
}
let newState = create()
cache[key] = newState as AnyObject
return newState
}

public func invalidate(key: String) {
cache.removeValue(forKey: key)
}

public func invalidateAll() {
cache.removeAll()
}
}
3 changes: 1 addition & 2 deletions Sources/OversizeArchitectureMacros/Macros/ModuleMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ public struct ModuleMacro: MemberMacro {

let prefix = extractPrefix(from: node) ?? extractPrefixFromName(declaration: declaration)

let members = generateModuleTypealiases(prefix: prefix)
return members
return generateModuleTypealiases(prefix: prefix)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
import Testing

@Suite("Module Macro Tests")
struct ModuleMacroTests {
let testMacros: [String: Macro.Type] = [
"ModuleMacro": ModuleMacro.self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
import Testing

@Suite("View Macro Tests")
struct ViewMacroTests {
let testMacros: [String: Macro.Type] = [
"ViewMacro": ViewMacro.self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
import Testing

@Suite("ViewModel Macro Tests")
struct ViewModelMacroTests {
let testMacros: [String: Macro.Type] = [
"ViewModelMacro": ViewModelMacro.self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import Foundation
@testable import OversizeArchitecture
import Testing

@Suite("Input Output Tests")
struct InputOutputTests {
// MARK: - Product Detail Input Tests

Expand Down Expand Up @@ -131,7 +130,7 @@ struct InputOutputTests {

@Test("Sendable conformance")
func sendableConformance() {
// Test that types conform to Sendable
/// Test that types conform to Sendable
func testSendable(_: (some Sendable).Type) {}

testSendable(Product.self)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import Foundation
@testable import OversizeArchitecture
import Testing

@Suite("Module Tests")
struct ModuleTests {
@Test("Module protocol conformance")
func moduleProtocolConformance() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import Foundation
@testable import OversizeArchitecture
import Testing

@Suite("Product Detail ViewModel Tests")
struct ProductDetailViewModelTests {
private func createViewModel(
input: ProductDetailInput?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ actor ProductTestCapture {
}
}

@Suite("Product Edit ViewModel Tests")
struct ProductEditViewModelTests {
private func createViewModel(
input: ProductEditInput?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import Foundation
@testable import OversizeArchitecture
import Testing

@Suite("Product List ViewModel Tests")
struct ProductListViewModelTests {
private func createViewModel(
input: ProductListInput?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import Foundation
@testable import OversizeArchitecture
import Testing

@Suite("Product Swift Testing Demo")
struct ProductTests {
@Test("Product initialization")
func productInitialization() {
Expand Down Expand Up @@ -51,7 +50,7 @@ struct ProductTests {
}

@Test("Product detail input with different sources")
func productDetailInputSources() async {
func productDetailInputSources() {
let productId = UUID()
let product = Product(id: productId, name: "Test Product")

Expand Down Expand Up @@ -125,7 +124,7 @@ extension Tag {
@Tag static var async: Self
}

@Suite("Tagged Tests Demo", .tags(.product, .async))
@Suite(.tags(.product, .async))
struct TaggedProductTests {
@Test("Async product creation", .tags(.product))
func asyncProductCreation() async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import Foundation
@testable import OversizeArchitecture
import Testing

@Suite("View State Tests")
struct ViewStateTests {
// MARK: - Product Detail View State Tests

Expand Down