-
Notifications
You must be signed in to change notification settings - Fork 161
Added assign(to: on: ownership:)
operator
#30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// | ||
// ObjectOwnership.swift | ||
// CombineExt | ||
// | ||
// Created by Dmitry Kuznetsov on 08/05/2020. | ||
// Copyright © 2020 Combine Community. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
/// The ownership of an object | ||
/// | ||
/// - seealso: https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html#ID52 | ||
public enum ObjectOwnership { | ||
/// Keep a strong hold of the object, preventing ARC | ||
/// from disposing it until its released or has no references. | ||
case strong | ||
|
||
/// Weakly owned. Does not keep a strong hold of the object, | ||
/// allowing ARC to dispose it even if its referenced. | ||
case weak | ||
|
||
/// Unowned. Similar to weak, but implicitly unwrapped so may | ||
/// crash if the object is released beore being accessed. | ||
case unowned | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
// | ||
// AssignOwnership.swift | ||
// CombineExt | ||
// | ||
// Created by Dmitry Kuznetsov on 08/05/2020. | ||
// Copyright © 2020 Combine Community. All rights reserved. | ||
// | ||
|
||
#if canImport(Combine) | ||
import Combine | ||
|
||
@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) | ||
public extension Publisher where Self.Failure == Never { | ||
/// Assigns a publisher’s output to a property of an object. | ||
/// | ||
/// - parameter keyPath: A key path that indicates the property to assign. | ||
/// - parameter object: The object that contains the property. | ||
/// The subscriber assigns the object’s property every time | ||
/// it receives a new value. | ||
/// - parameter ownership: The retainment / ownership strategy for the object, defaults to `strong`. | ||
/// | ||
/// - returns: An AnyCancellable instance. Call cancel() on this instance when you no longer want | ||
/// the publisher to automatically assign the property. Deinitializing this instance | ||
/// will also cancel automatic assignment. | ||
func assign<Root: AnyObject>(to keyPath: ReferenceWritableKeyPath<Root, Self.Output>, | ||
on object: Root, | ||
ownership: ObjectOwnership = .strong) -> AnyCancellable { | ||
switch ownership { | ||
case .strong: | ||
return assign(to: keyPath, on: object) | ||
case .weak: | ||
return sink { [weak object] value in | ||
object?[keyPath: keyPath] = value | ||
} | ||
case .unowned: | ||
return sink { [unowned object] value in | ||
object[keyPath: keyPath] = value | ||
} | ||
} | ||
} | ||
|
||
/// Assigns each element from a Publisher to properties of the provided objects | ||
/// | ||
/// - Parameters: | ||
/// - keyPath1: The key path of the first property to assign. | ||
/// - object1: The first object on which to assign the value. | ||
/// - keyPath2: The key path of the second property to assign. | ||
/// - object2: The second object on which to assign the value. | ||
/// - ownership: The retainment / ownership strategy for the object, defaults to `strong`. | ||
/// | ||
/// - Returns: A cancellable instance; used when you end assignment of the received value. | ||
/// Deallocation of the result will tear down the subscription stream. | ||
func assign<Root1: AnyObject, Root2: AnyObject>( | ||
to keyPath1: ReferenceWritableKeyPath<Root1, Output>, on object1: Root1, | ||
and keyPath2: ReferenceWritableKeyPath<Root2, Output>, on object2: Root2, | ||
ownership: ObjectOwnership = .strong | ||
) -> AnyCancellable { | ||
switch ownership { | ||
case .strong: | ||
return assign(to: keyPath1, on: object1, and: keyPath2, on: object2) | ||
case .weak: | ||
return sink { [weak object1, weak object2] value in | ||
object1?[keyPath: keyPath1] = value | ||
object2?[keyPath: keyPath2] = value | ||
} | ||
case .unowned: | ||
return sink { [unowned object1, unowned object2] value in | ||
object1[keyPath: keyPath1] = value | ||
object2[keyPath: keyPath2] = value | ||
} | ||
} | ||
} | ||
|
||
/// Assigns each element from a Publisher to properties of the provided objects | ||
/// | ||
/// - Parameters: | ||
/// - keyPath1: The key path of the first property to assign. | ||
/// - object1: The first object on which to assign the value. | ||
/// - keyPath2: The key path of the second property to assign. | ||
/// - object2: The second object on which to assign the value. | ||
/// - keyPath3: The key path of the third property to assign. | ||
/// - object3: The third object on which to assign the value. | ||
/// - ownership: The retainment / ownership strategy for the object, defaults to `strong`. | ||
/// | ||
/// - Returns: A cancellable instance; used when you end assignment of the received value. | ||
/// Deallocation of the result will tear down the subscription stream. | ||
func assign<Root1: AnyObject, Root2: AnyObject, Root3: AnyObject>( | ||
to keyPath1: ReferenceWritableKeyPath<Root1, Output>, on object1: Root1, | ||
and keyPath2: ReferenceWritableKeyPath<Root2, Output>, on object2: Root2, | ||
and keyPath3: ReferenceWritableKeyPath<Root3, Output>, on object3: Root3, | ||
ownership: ObjectOwnership = .strong | ||
) -> AnyCancellable { | ||
switch ownership { | ||
case .strong: | ||
return assign(to: keyPath1, on: object1, | ||
and: keyPath2, on: object2, | ||
and: keyPath3, on: object3) | ||
case .weak: | ||
return sink { [weak object1, weak object2, weak object3] value in | ||
object1?[keyPath: keyPath1] = value | ||
object2?[keyPath: keyPath2] = value | ||
object3?[keyPath: keyPath3] = value | ||
} | ||
case .unowned: | ||
return sink { [unowned object1, unowned object2, unowned object3] value in | ||
object1[keyPath: keyPath1] = value | ||
object2[keyPath: keyPath2] = value | ||
object3[keyPath: keyPath3] = value | ||
} | ||
} | ||
} | ||
} | ||
#endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// | ||
// AssignOwnershipTests.swift | ||
// CombineExt | ||
// | ||
// Created by Dmitry Kuznetsov on 08/05/2020. | ||
// Copyright © 2020 Combine Community. All rights reserved. | ||
// | ||
|
||
#if !os(watchOS) | ||
import XCTest | ||
import Combine | ||
import CombineExt | ||
|
||
@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) | ||
class AssignOwnershipTests: XCTestCase { | ||
var subscription: AnyCancellable! | ||
var value1 = 0 | ||
var value2 = 0 | ||
var value3 = 0 | ||
var subject: PassthroughSubject<Int, Never>! | ||
|
||
override func setUp() { | ||
super.setUp() | ||
|
||
subscription = nil | ||
subject = PassthroughSubject<Int, Never>() | ||
value1 = 0 | ||
value2 = 0 | ||
value3 = 0 | ||
} | ||
|
||
func testWeakOwnership() { | ||
let initialRetainCount = CFGetRetainCount(self) | ||
|
||
subscription = subject | ||
.assign(to: \.value1, on: self, ownership: .weak) | ||
subject.send(10) | ||
let resultRetainCount1 = CFGetRetainCount(self) | ||
XCTAssertEqual(initialRetainCount, resultRetainCount1) | ||
|
||
subscription = subject | ||
.assign(to: \.value1, on: self, and: \.value2, on: self, ownership: .weak) | ||
subject.send(15) | ||
let resultRetainCount2 = CFGetRetainCount(self) | ||
XCTAssertEqual(initialRetainCount, resultRetainCount2) | ||
|
||
subscription = subject | ||
.assign(to: \.value1, on: self, and: \.value2, on: self, and: \.value3, on: self, ownership: .weak) | ||
subject.send(20) | ||
let resultRetainCount3 = CFGetRetainCount(self) | ||
XCTAssertEqual(initialRetainCount, resultRetainCount3) | ||
} | ||
|
||
func testUnownedOwnership() { | ||
let initialRetainCount = CFGetRetainCount(self) | ||
|
||
subscription = subject | ||
.assign(to: \.value1, on: self, ownership: .unowned) | ||
subject.send(10) | ||
let resultRetainCount1 = CFGetRetainCount(self) | ||
XCTAssertEqual(initialRetainCount, resultRetainCount1) | ||
|
||
subscription = subject | ||
.assign(to: \.value1, on: self, and: \.value2, on: self, ownership: .unowned) | ||
subject.send(15) | ||
let resultRetainCount2 = CFGetRetainCount(self) | ||
XCTAssertEqual(initialRetainCount, resultRetainCount2) | ||
|
||
subscription = subject | ||
.assign(to: \.value1, on: self, and: \.value2, on: self, and: \.value3, on: self, ownership: .unowned) | ||
subject.send(20) | ||
let resultRetainCount3 = CFGetRetainCount(self) | ||
XCTAssertEqual(initialRetainCount, resultRetainCount3) | ||
} | ||
|
||
func testStrongOwnership() { | ||
let initialRetainCount = CFGetRetainCount(self) | ||
|
||
subscription = subject | ||
.assign(to: \.value1, on: self, ownership: .strong) | ||
subject.send(10) | ||
let resultRetainCount1 = CFGetRetainCount(self) | ||
XCTAssertEqual(initialRetainCount + 1, resultRetainCount1) | ||
|
||
subscription = subject | ||
.assign(to: \.value1, on: self, and: \.value2, on: self, ownership: .strong) | ||
subject.send(15) | ||
let resultRetainCount2 = CFGetRetainCount(self) | ||
XCTAssertEqual(initialRetainCount + 2, resultRetainCount2) | ||
|
||
subscription = subject | ||
.assign(to: \.value1, on: self, and: \.value2, on: self, and: \.value3, on: self, ownership: .strong) | ||
subject.send(20) | ||
let resultRetainCount3 = CFGetRetainCount(self) | ||
XCTAssertEqual(initialRetainCount + 3, resultRetainCount3) | ||
} | ||
} | ||
#endif |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh cool, great idea moving these here