diff --git a/Sources/Network/Network+URLSessionNetworkStack.swift b/Sources/Network/Network+URLSessionNetworkStack.swift index 0de9abc..6497fa8 100644 --- a/Sources/Network/Network+URLSessionNetworkStack.swift +++ b/Sources/Network/Network+URLSessionNetworkStack.swift @@ -114,20 +114,20 @@ extension Network { fatalError("🔥 `session` is `nil`! Forgot to 💉?") } - let taskIdentifierBox = VarBox(.max) + @Box var taskIdentifierBox = Int.max let cancelableBag = CancelableBag() let completionHandler = makeTaskCompletionHandler( request: request, resource: resource, - taskIdentifierBox: taskIdentifierBox, + taskIdentifierBox: _taskIdentifierBox, cancelableBag: cancelableBag, completion: completion ) let task = session.dataTask(with: request, completionHandler: completionHandler) - taskIdentifierBox.value = task.taskIdentifier + taskIdentifierBox = task.taskIdentifier cancelableBag.add(cancelable: WeakCancelable(task)) resource.interceptScheduledTask(withIdentifier: task.taskIdentifier, request: request) @@ -141,7 +141,7 @@ extension Network { private func makeTaskCompletionHandler( request: URLRequest, resource: Resource, - taskIdentifierBox: VarBox, + taskIdentifierBox: Box, cancelableBag: CancelableBag, completion: @escaping FetchCompletionClosure ) -> URLSessionDataTaskClosure { diff --git a/Sources/Utils/Box.swift b/Sources/Utils/Box.swift index bbd8890..b6c55ba 100644 --- a/Sources/Utils/Box.swift +++ b/Sources/Utils/Box.swift @@ -1,33 +1,39 @@ -/// An arbitrary container which stores a **constant** value of type `T`. +/// An arbitrary container (and property wrapper) which stores a **mutable** value of type `T`. /// -/// This main purpose of this object is to encapsulate value types so that they can be used like a reference type -/// (e.g. pass value types around without copying them, "share" value types between closures, etc) +/// The main purpose of this object is to encapsulate value types so that they can be used like a reference type +/// (e.g. pass value types around without copying them, memoize value types inside closures via capture list, etc) +@propertyWrapper +@dynamicMemberLookup public final class Box { - /// The encapsulated value. - public let value: T + /// The wrapped value. + public var wrappedValue: T - /// Instantiate a new constant value box with the given value. + /// Instantiate a new mutable value box with the given value. /// - /// - parameter value: the value to encapsulate. + /// - parameter wrappedValue: the value to encapsulate. /// /// - returns: a newly instantiated box with the encapsulated value. - public init(_ value: T) { self.value = value } + public init(wrappedValue: T) { self.wrappedValue = wrappedValue } + + public subscript(dynamicMember keyPath: KeyPath) -> U { wrappedValue[keyPath: keyPath] } } -/// An arbitrary container which stores a **variable** value of type `T`. -/// -/// This main purpose of this object is to encapsulate value types so that they can be used like a reference type -/// (e.g. pass value types around without copying them, "share" value types between closures, etc) -public final class VarBox { +extension Box { - /// The encapsulated value. - public var value: T + /// The wrapped value (compact). + public var value: T { + get { wrappedValue } + set { wrappedValue = newValue } + } - /// Instantiate a new variable value box with the given value. + /// Instantiate a new mutable value box with the given value (compact). /// - /// - parameter value: the value to encapsulate. + /// - parameter wrappedValue: the value to encapsulate. /// /// - returns: a newly instantiated box with the encapsulated value. - public init(_ value: T) { self.value = value } + public convenience init(_ value: T) { self.init(wrappedValue: value) } } + +@available(*, unavailable, renamed: "Box") +public typealias VarBox = Box diff --git a/Tests/AlicerceTests/Persistence/DiskMemoryPersistenceTestCase.swift b/Tests/AlicerceTests/Persistence/DiskMemoryPersistenceTestCase.swift index 6d55a64..36f6f7f 100644 --- a/Tests/AlicerceTests/Persistence/DiskMemoryPersistenceTestCase.swift +++ b/Tests/AlicerceTests/Persistence/DiskMemoryPersistenceTestCase.swift @@ -266,7 +266,7 @@ final class DiskMemoryPersistenceTestCase: XCTestCase { let measureExpectation = expectation(description: "measure") measureExpectation.expectedFulfillmentCount = 2 - performanceMetrics.measureInvokedClosure = { [count = VarBox(0)] identifier, metadata in + performanceMetrics.measureInvokedClosure = { [count = Box(0)] identifier, metadata in if count.value == 0 { XCTAssertEqual(identifier, performanceMetrics.memoryWriteIdentifier) XCTAssertDumpsEqual(metadata, [performanceMetrics.blobSizeMetadataKey : mrMinderSize, @@ -313,7 +313,7 @@ final class DiskMemoryPersistenceTestCase: XCTestCase { let measure = self.expectation(description: "measure") measure.expectedFulfillmentCount = 2 - performanceMetrics.measureInvokedClosure = { [count = VarBox(0)] identifier, metadata in + performanceMetrics.measureInvokedClosure = { [count = Box(0)] identifier, metadata in if count.value == 0 { XCTAssertEqual(identifier, performanceMetrics.memoryWriteIdentifier) XCTAssertDumpsEqual(metadata, [performanceMetrics.blobSizeMetadataKey : mrMinderSize, @@ -371,7 +371,7 @@ final class DiskMemoryPersistenceTestCase: XCTestCase { let measureExpectation = expectation(description: "measure") measureExpectation.expectedFulfillmentCount = 2 - performanceMetrics.measureInvokedClosure = { [count = VarBox(0)] identifier, metadata in + performanceMetrics.measureInvokedClosure = { [count = Box(0)] identifier, metadata in if count.value == 0 { XCTAssertEqual(identifier, performanceMetrics.memoryWriteIdentifier) XCTAssertDumpsEqual(metadata, [performanceMetrics.blobSizeMetadataKey : mrMinderSize, @@ -437,7 +437,7 @@ final class DiskMemoryPersistenceTestCase: XCTestCase { let measureExpectation = expectation(description: "measure") measureExpectation.expectedFulfillmentCount = 2 - performanceMetrics.measureInvokedClosure = { [count = VarBox(0)] identifier, metadata in + performanceMetrics.measureInvokedClosure = { [count = Box(0)] identifier, metadata in if count.value == 0 { XCTAssertEqual(identifier, performanceMetrics.memoryWriteIdentifier) XCTAssertDumpsEqual(metadata, [performanceMetrics.blobSizeMetadataKey : mrMinderSize, @@ -477,7 +477,7 @@ final class DiskMemoryPersistenceTestCase: XCTestCase { let measureExpectation2 = expectation(description: "measure") measureExpectation2.expectedFulfillmentCount = 3 - performanceMetrics.measureInvokedClosure = { [count = VarBox(0)] identifier, metadata in + performanceMetrics.measureInvokedClosure = { [count = Box(0)] identifier, metadata in if count.value == 0 { XCTAssertEqual(identifier, performanceMetrics.memoryReadIdentifier) // cache miss @@ -522,7 +522,7 @@ final class DiskMemoryPersistenceTestCase: XCTestCase { let measureExpectation = expectation(description: "measure") measureExpectation.expectedFulfillmentCount = 2 - performanceMetrics.measureInvokedClosure = { [count = VarBox(0)] identifier, metadata in + performanceMetrics.measureInvokedClosure = { [count = Box(0)] identifier, metadata in if count.value == 0 { XCTAssertEqual(identifier, performanceMetrics.memoryReadIdentifier) XCTAssertDumpsEqual(metadata, [performanceMetrics.blobSizeMetadataKey : 0, diff --git a/Tests/AlicerceTests/Utils/BoxTestCase.swift b/Tests/AlicerceTests/Utils/BoxTestCase.swift index 2cfd4e1..e03f9b0 100644 --- a/Tests/AlicerceTests/Utils/BoxTestCase.swift +++ b/Tests/AlicerceTests/Utils/BoxTestCase.swift @@ -3,24 +3,43 @@ import XCTest class BoxTestCase: XCTestCase { - func testBox_ShouldWrapValue() { + func test_init_ShouldWrapValueAndAllowMutation() { let value = 1337 let box = Box(value) XCTAssertEqual(value, box.value) + + let newValue = 7331 + box.value = newValue + + XCTAssertEqual(newValue, box.value) } - func testVarBox_ShouldWrapValueAndAllowModifying() { + func test_propertyWrapper_ShouldWrapValueAndAllowMutation() { let value = 1337 - let varBox = VarBox(value) + @Box var box = value - XCTAssertEqual(value, varBox.value) + XCTAssertEqual(value, box) let newValue = 7331 - varBox.value = newValue + box = newValue + + XCTAssertEqual(newValue, box) + } + + func test_dynamicMember_ShouldExposePropertiesInWrappedValue() { + + struct Foo { + + var foo: Int = 1337 + } + + let box = Box(.init()) + XCTAssertEqual(box.foo, 1337) - XCTAssertEqual(newValue, varBox.value) + @Box var box2 = Foo() + XCTAssertEqual(_box2.foo, 1337) } } diff --git a/Tests/AlicerceTests/Utils/PthreadLockTestCase.swift b/Tests/AlicerceTests/Utils/PthreadLockTestCase.swift index c5b4db8..cd09c21 100644 --- a/Tests/AlicerceTests/Utils/PthreadLockTestCase.swift +++ b/Tests/AlicerceTests/Utils/PthreadLockTestCase.swift @@ -112,13 +112,13 @@ class PthreadLockTestCase: XCTestCase { return q } - let box = VarBox(0) + @Box var box = 0 for _ in 1...numWrites { queues.forEach { $0.addOperation { self.lock.lock() - box.value += 1 + box += 1 self.lock.unlock() writeExpectation.fulfill() } @@ -130,6 +130,6 @@ class PthreadLockTestCase: XCTestCase { waitForExpectations(timeout: 1) - XCTAssertEqual(box.value, numWrites * numQueues) + XCTAssertEqual(box, numWrites * numQueues) } } diff --git a/Tests/AlicerceTests/Utils/UnfairLockTestCase.swift b/Tests/AlicerceTests/Utils/UnfairLockTestCase.swift index 8ccd260..82a32d6 100644 --- a/Tests/AlicerceTests/Utils/UnfairLockTestCase.swift +++ b/Tests/AlicerceTests/Utils/UnfairLockTestCase.swift @@ -112,13 +112,13 @@ class UnfairLockTestCase: XCTestCase { return q } - let box = VarBox(0) + @Box var box = 0 for _ in 1...numWrites { queues.forEach { $0.addOperation { self.lock.lock() - box.value += 1 + box += 1 self.lock.unlock() writeExpectation.fulfill() } @@ -130,7 +130,7 @@ class UnfairLockTestCase: XCTestCase { waitForExpectations(timeout: 1) - XCTAssertEqual(box.value, numWrites * numQueues) + XCTAssertEqual(box, numWrites * numQueues) } }