Skip to content

Commit

Permalink
Shared initializers modified to lazy load the initial value (#3060)
Browse files Browse the repository at this point in the history
Co-authored-by: Sean <[email protected]>
  • Loading branch information
seanmrich and Sean authored May 9, 2024
1 parent 1792402 commit f248eff
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Foundation

extension Shared {
public init(
wrappedValue value: Value,
wrappedValue value: @autoclosure @escaping () -> Value,
_ persistenceKey: some PersistenceKey<Value>,
fileID: StaticString = #fileID,
line: UInt = #line
Expand All @@ -31,7 +31,7 @@ extension Shared {
return reference
} else {
let reference = ValueReference(
initialValue: value,
initialValue: value(),
persistenceKey: persistenceKey,
fileID: fileID,
line: line
Expand Down Expand Up @@ -60,11 +60,52 @@ extension Shared {
fileID: StaticString = #fileID,
line: UInt = #line
) throws {
guard let initialValue = persistenceKey.load(initialValue: nil)
else {
throw LoadError()
}
self.init(wrappedValue: initialValue, persistenceKey, fileID: fileID, line: line)
try self.init(
throwingValue: {
guard let initialValue = persistenceKey.load(initialValue: nil)
else { throw LoadError() }
return initialValue
}(),
persistenceKey,
fileID: fileID,
line: line
)
}

private init(
throwingValue value: @autoclosure @escaping () throws -> Value,
_ persistenceKey: some PersistenceKey<Value>,
fileID: StaticString = #fileID,
line: UInt = #line
) throws {
try self.init(
reference: {
@Dependency(\.persistentReferences) var references
return try references.withValue {
if let reference = $0[persistenceKey.id] {
precondition(
reference.valueType == Value.self,
"""
"\(typeName(Value.self, genericsAbbreviated: false))" does not match existing \
persistent reference "\(typeName(reference.valueType, genericsAbbreviated: false))" \
(key: "\(persistenceKey.id)")
"""
)
return reference
} else {
let reference = try ValueReference(
initialValue: value(),
persistenceKey: persistenceKey,
fileID: fileID,
line: line
)
$0[persistenceKey.id] = reference
return reference
}
}
}(),
keyPath: \Value.self
)
}

public init<Key: PersistenceKey>(
Expand All @@ -81,13 +122,13 @@ extension Shared {
}

public init<Key: PersistenceKey>(
wrappedValue: Value,
wrappedValue: @autoclosure @escaping () -> Value,
_ persistenceKey: PersistenceKeyDefault<Key>,
fileID: StaticString = #fileID,
line: UInt = #line
) where Key.Value == Value {
self.init(
wrappedValue: wrappedValue,
wrappedValue: wrappedValue(),
persistenceKey.base,
fileID: fileID,
line: line
Expand All @@ -97,7 +138,7 @@ extension Shared {

extension SharedReader {
public init(
wrappedValue value: Value,
wrappedValue value: @autoclosure @escaping () -> Value,
_ persistenceKey: some PersistenceReaderKey<Value>,
fileID: StaticString = #fileID,
line: UInt = #line
Expand All @@ -117,7 +158,7 @@ extension SharedReader {
return reference
} else {
let reference = ValueReference(
initialValue: value,
initialValue: value(),
persistenceKey: persistenceKey,
fileID: fileID,
line: line
Expand Down Expand Up @@ -146,11 +187,51 @@ extension SharedReader {
fileID: StaticString = #fileID,
line: UInt = #line
) throws {
guard let initialValue = persistenceKey.load(initialValue: nil)
else {
throw LoadError()
}
self.init(wrappedValue: initialValue, persistenceKey, fileID: fileID, line: line)
try self.init(
throwingValue: {
guard let initialValue = persistenceKey.load(initialValue: nil)
else { throw LoadError() }
return initialValue
}(),
persistenceKey,
fileID: fileID,
line: line
)
}

private init(
throwingValue value: @autoclosure @escaping () throws -> Value,
_ persistenceKey: some PersistenceReaderKey<Value>,
fileID: StaticString = #fileID,
line: UInt = #line
) throws {
try self.init(
reference: {
@Dependency(\.persistentReferences) var references
return try references.withValue {
if let reference = $0[persistenceKey.id] {
precondition(
reference.valueType == Value.self,
"""
Type mismatch at persistence key "\(persistenceKey.id)": \
\(reference.valueType) != \(Value.self)
"""
)
return reference
} else {
let reference = ValueReference(
initialValue: try value(),
persistenceKey: persistenceKey,
fileID: fileID,
line: line
)
$0[persistenceKey.id] = reference
return reference
}
}
}(),
keyPath: \Value.self
)
}

public init<Key: PersistenceReaderKey>(
Expand All @@ -167,13 +248,13 @@ extension SharedReader {
}

public init<Key: PersistenceReaderKey>(
wrappedValue: Value,
wrappedValue: @autoclosure @escaping () -> Value,
_ persistenceKey: PersistenceKeyDefault<Key>,
fileID: StaticString = #fileID,
line: UInt = #line
) where Key.Value == Value {
self.init(
wrappedValue: wrappedValue,
wrappedValue: wrappedValue(),
persistenceKey.base,
fileID: fileID,
line: line
Expand Down
84 changes: 84 additions & 0 deletions Tests/ComposableArchitectureTests/SharedTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,84 @@ final class SharedTests: XCTestCase {
XCTAssertEqual(isActive, false)
XCTAssertEqual(didAccess.value, false)
}

func testSharedInitialValueUnused() {
let accessedIsOn1 = LockIsolated(false)
let accessedIsOn2 = LockIsolated(false)
@Shared(.isOn) var isOn1 = {
accessedIsOn1.setValue(true)
return false
}()
@Shared(.isOn) var isOn2 = {
accessedIsOn2.setValue(true)
return true
}()
XCTAssertEqual(isOn1, false)
XCTAssertEqual(isOn2, false)
XCTAssertEqual(accessedIsOn1.value, true)
XCTAssertEqual(accessedIsOn2.value, false)
}

func testSharedOverrideDefault() {
let accessedActive1 = LockIsolated(false)
let accessedDefault = LockIsolated(false)
let logDefault: () -> Bool = {
accessedDefault.setValue(true)
return true
}
@Shared(.isActive(default: logDefault)) var isActive1 = {
accessedActive1.setValue(true)
return false
}()
@Shared(.isActive(default: logDefault)) var isActive2

XCTAssertEqual(isActive1, false)
XCTAssertEqual(isActive2, false)
XCTAssertEqual(accessedActive1.value, true)
}

func testSharedReaderInitialValueUnused() {
let accessedIsOn1 = LockIsolated(false)
let accessedIsOn2 = LockIsolated(false)
@SharedReader(.isOn) var isOn1 = {
accessedIsOn1.setValue(true)
return false
}()
@SharedReader(.isOn) var isOn2 = {
accessedIsOn2.setValue(true)
return true
}()
XCTAssertEqual(isOn1, false)
XCTAssertEqual(isOn2, false)
XCTAssertEqual(accessedIsOn1.value, true)
XCTAssertEqual(accessedIsOn2.value, false)
}

func testSharedReaderOverrideDefault() {
let accessedActive1 = LockIsolated(false)
let accessedDefault = LockIsolated(false)
let logDefault: () -> Bool = {
accessedDefault.setValue(true)
return true
}
@SharedReader(.isActive(default: logDefault)) var isActive1 = {
accessedActive1.setValue(true)
return false
}()
@SharedReader(.isActive(default: logDefault)) var isActive2

XCTAssertEqual(isActive1, false)
XCTAssertEqual(isActive2, false)
XCTAssertEqual(accessedActive1.value, true)
}

func testSharedThrowingInitialValueUnused() throws {
try XCTAssertThrowsError(Shared(.noDefaultIsOn))
}

func testSharedReaderThrowingInitialValueUnused() throws {
try XCTAssertThrowsError(SharedReader(.noDefaultIsOn))
}

func testSharedReaderDefaults_MultipleWithDifferentDefaults() async throws {
@Shared(.appStorage("isOn")) var isOn = false
Expand Down Expand Up @@ -1072,3 +1150,9 @@ extension PersistenceKey where Self == PersistenceKeyDefault<AppStorageKey<Bool?
return PersistenceKeyDefault(.appStorage("optionalValueWithDefault"), nil)
}
}

extension PersistenceReaderKey where Self == AppStorageKey<Bool> {
static var noDefaultIsOn: Self {
appStorage("noDefaultIsOn")
}
}

0 comments on commit f248eff

Please sign in to comment.