From fce20248f377386c8ad91ed167f88ce6badd3ebc Mon Sep 17 00:00:00 2001 From: Jasdev Singh Date: Thu, 2 Apr 2020 21:44:25 -0400 Subject: [PATCH 1/2] Adds `Publisher.weaklyAssign(to:on:)`. --- CombineExt.xcodeproj/project.pbxproj | 8 +++++ README.md | 5 +++ Sources/Operators/WeakAssign.swift | 18 +++++++++++ Tests/WeakAssignTests.swift | 46 ++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+) create mode 100644 Sources/Operators/WeakAssign.swift create mode 100644 Tests/WeakAssignTests.swift diff --git a/CombineExt.xcodeproj/project.pbxproj b/CombineExt.xcodeproj/project.pbxproj index 5ff78ed..9fc7340 100644 --- a/CombineExt.xcodeproj/project.pbxproj +++ b/CombineExt.xcodeproj/project.pbxproj @@ -41,6 +41,8 @@ 78C193E0241D4D8D0001B7FD /* MaterializeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C193DF241D4D8D0001B7FD /* MaterializeTests.swift */; }; 78C193E2241D596F0001B7FD /* Dematerialize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C193E1241D596F0001B7FD /* Dematerialize.swift */; }; 78C193E4241D63620001B7FD /* DematerializeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78C193E3241D63620001B7FD /* DematerializeTests.swift */; }; + AAEAF0E32436C8A8007C35E0 /* WeakAssign.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEAF0E22436C8A8007C35E0 /* WeakAssign.swift */; }; + AAEAF0E52436C9C2007C35E0 /* WeakAssignTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEAF0E42436C9C2007C35E0 /* WeakAssignTests.swift */; }; BF50924B241FFE8E00600DF4 /* ZipManyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF50924A241FFE8E00600DF4 /* ZipManyTests.swift */; }; BF8121BC241FF42C006A93B8 /* ZipMany.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF8121BB241FF42C006A93B8 /* ZipMany.swift */; }; BFB4EA132428256B0096E9E9 /* CombineLatestMany.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB4EA122428256B0096E9E9 /* CombineLatestMany.swift */; }; @@ -83,6 +85,8 @@ 78C193DF241D4D8D0001B7FD /* MaterializeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaterializeTests.swift; sourceTree = ""; }; 78C193E1241D596F0001B7FD /* Dematerialize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dematerialize.swift; sourceTree = ""; }; 78C193E3241D63620001B7FD /* DematerializeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DematerializeTests.swift; sourceTree = ""; }; + AAEAF0E22436C8A8007C35E0 /* WeakAssign.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakAssign.swift; sourceTree = ""; }; + AAEAF0E42436C9C2007C35E0 /* WeakAssignTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakAssignTests.swift; sourceTree = ""; }; BF50924A241FFE8E00600DF4 /* ZipManyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZipManyTests.swift; sourceTree = ""; }; BF8121BB241FF42C006A93B8 /* ZipMany.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZipMany.swift; sourceTree = ""; }; BFB4EA122428256B0096E9E9 /* CombineLatestMany.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineLatestMany.swift; sourceTree = ""; }; @@ -166,6 +170,7 @@ BF50924A241FFE8E00600DF4 /* ZipManyTests.swift */, BFB4EA1424283ECF0096E9E9 /* CombineLatestManyTests.swift */, DC1691112428228200B234C4 /* MapManyTests.swift */, + AAEAF0E42436C9C2007C35E0 /* WeakAssignTests.swift */, ); path = Tests; sourceTree = SOURCE_ROOT; @@ -215,6 +220,7 @@ BF8121BB241FF42C006A93B8 /* ZipMany.swift */, BFB4EA122428256B0096E9E9 /* CombineLatestMany.swift */, DC16910E24281A1800B234C4 /* MapMany.swift */, + AAEAF0E22436C8A8007C35E0 /* WeakAssign.swift */, ); path = Operators; sourceTree = ""; @@ -331,6 +337,7 @@ 78C193E2241D596F0001B7FD /* Dematerialize.swift in Sources */, 78002BB7241E915E0018AA28 /* CurrentValueRelay.swift in Sources */, 78C193DE241D46F40001B7FD /* Materialize.swift in Sources */, + AAEAF0E32436C8A8007C35E0 /* WeakAssign.swift in Sources */, 78988A23241FFE2400F3A4AF /* Partition.swift in Sources */, BF8121BC241FF42C006A93B8 /* ZipMany.swift in Sources */, 78C193D4241C2DE00001B7FD /* Create.swift in Sources */, @@ -362,6 +369,7 @@ 78C193E0241D4D8D0001B7FD /* MaterializeTests.swift in Sources */, 78002BBB241E97350018AA28 /* CurrentValueRelayTests.swift in Sources */, 78C193E4241D63620001B7FD /* DematerializeTests.swift in Sources */, + AAEAF0E52436C9C2007C35E0 /* WeakAssignTests.swift in Sources */, BF50924B241FFE8E00600DF4 /* ZipManyTests.swift in Sources */, 78988A1E241EAFDD00F3A4AF /* PassthroughRelayTests.swift in Sources */, DC1691122428228200B234C4 /* MapManyTests.swift in Sources */, diff --git a/README.md b/README.md index 8fa901a..3a013d4 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ All operators, utilities and helpers respect Combine's publisher contract, inclu * [zip(with:) and Collection.zip](#ZipMany) * [combineLatest(with:) and Collection.combineLatest](#CombineLatestMany) * [mapMany(_:)](#MapMany) +* [weaklyAssign(to:on:)](#weaklyAssign) ### Publishers * [AnyPublisher.create](#anypublisher.create) @@ -362,6 +363,10 @@ intArrayPublisher.send([10, 2, 2, 4, 3, 8]) ["10", "2", "2", "4", "3", "8"] ``` +### weaklyAssign + +Like Apple’s [`Publisher.assign(to:on)`](https://developer.apple.com/documentation/combine/publisher/3235801-assign), except it only `weak`ly captures `on` instead of `strong`ly. + ## Publishers This section outlines some of the custom Combine publishers CombineExt provides diff --git a/Sources/Operators/WeakAssign.swift b/Sources/Operators/WeakAssign.swift new file mode 100644 index 0000000..b155a58 --- /dev/null +++ b/Sources/Operators/WeakAssign.swift @@ -0,0 +1,18 @@ +// +// WeakAssign.swift +// CombineExt +// +// Created by Jasdev Singh on 2/04/2020. +// Copyright © 2020 Combine Community. All rights reserved. +// + +import Combine + +public extension Publisher where Failure == Never { + func weaklyAssign(to keyPath: ReferenceWritableKeyPath, + on object: Root) -> AnyCancellable { + sink { [weak object] value in + object?[keyPath: keyPath] = value + } + } +} diff --git a/Tests/WeakAssignTests.swift b/Tests/WeakAssignTests.swift new file mode 100644 index 0000000..7f061ca --- /dev/null +++ b/Tests/WeakAssignTests.swift @@ -0,0 +1,46 @@ +// +// WeakAssignTests.swift +// CombineExtTests +// +// Created by Jasdev Singh on 2/04/2020. +// Copyright © 2020 Combine Community. All rights reserved. +// + +import XCTest +import Combine +import CombineExt + +final class WeakAssignTests: XCTestCase { + private var subscription: AnyCancellable! + + func testWeakAssignment() { + let source = PassthroughSubject() + var destination = Fake(property: 1) + + XCTAssertEqual(destination.property, 1) + + subscription = source + .assign(to: \.property, on: destination) + + XCTAssertFalse(isKnownUniquelyReferenced(&destination)) + + subscription = source + .weaklyAssign(to: \.property, on: destination) + + XCTAssertTrue(isKnownUniquelyReferenced(&destination)) + + source.send(2) + XCTAssertEqual(destination.property, 2) + } +} + +// MARK: - Private Helpers + +private final class Fake { + var property: PropertyType + + init(property: PropertyType) { + self.property = property + } +} + From 6beaa5288c5c4c49a358987e5250ffcfea300962 Mon Sep 17 00:00:00 2001 From: Jasdev Singh Date: Thu, 2 Apr 2020 22:12:41 -0400 Subject: [PATCH 2/2] Adds operator docs. --- Sources/Operators/WeakAssign.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/Operators/WeakAssign.swift b/Sources/Operators/WeakAssign.swift index b155a58..a1573ea 100644 --- a/Sources/Operators/WeakAssign.swift +++ b/Sources/Operators/WeakAssign.swift @@ -9,6 +9,13 @@ import Combine public extension Publisher where Failure == Never { + /// `weak`ly assigns onto an object at a specified key path. + /// + /// - Parameters: + /// - keyPath: The key path to assign onto on the referenced object. + /// - object: The object reference to assign onto. + /// + /// - Returns: A subscription cancellation token. func weaklyAssign(to keyPath: ReferenceWritableKeyPath, on object: Root) -> AnyCancellable { sink { [weak object] value in