-
Notifications
You must be signed in to change notification settings - Fork 212
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
Add withUnretained to SharedSequence #243
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// | ||
// withUnretained+RxCocoa.swift | ||
// RxSwiftExt | ||
// | ||
// Created by Shai Mishali on 04/10/2019. | ||
// Copyright © 2019 RxSwift Community. All rights reserved. | ||
// | ||
|
||
import RxSwift | ||
import RxCocoa | ||
|
||
extension SharedSequence { | ||
/** | ||
Provides an unretained, safe to use (i.e. not implicitly unwrapped), reference to an object along with the events emitted by the shared sequence. | ||
In the case the provided object cannot be retained successfully, the seqeunce will with an error. | ||
|
||
- parameter obj: The object to provide an unretained reference on. | ||
- parameter resultSelector: A function to combine the unretained referenced on `obj` and the value of the observable sequence. | ||
- returns: A shared sequence that contains the result of `resultSelector` being called with an unretained reference on `obj` and the values of the original sequence. | ||
*/ | ||
public func withUnretained<Object: AnyObject, Out>(_ obj: Object, | ||
resultSelector: @escaping ((Object, Element)) -> Out) | ||
-> SharedSequence<SharingStrategy, Out> { | ||
asObservable() | ||
.map { [weak obj] element -> Out in | ||
guard let obj = obj else { throw UnretainedError.failedRetaining } | ||
|
||
return resultSelector((obj, element)) | ||
} | ||
.asSharedSequence(onErrorDriveWith: .empty()) | ||
} | ||
|
||
/** | ||
Provides an unretained, safe to use (i.e. not implicitly unwrapped), reference to an object along with the events emitted by the sequence. | ||
In the case the provided object cannot be retained successfully, the seqeunce will complete. | ||
|
||
- parameter obj: The object to provide an unretained reference on. | ||
- returns: A shjared sequence of tuples that contains both an unretained reference on `obj` and the values of the original sequence. | ||
*/ | ||
public func withUnretained<Object: AnyObject>(_ obj: Object) -> SharedSequence<SharingStrategy, (Object, Element)> { | ||
withUnretained(obj) { ($0, $1) } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,7 @@ extension ObservableType { | |
*/ | ||
public func withUnretained<Object: AnyObject, Out>(_ obj: Object, | ||
resultSelector: @escaping ((Object, Element)) -> Out) -> Observable<Out> { | ||
return map { [weak obj] element -> Out in | ||
map { [weak obj] element -> Out in | ||
guard let obj = obj else { throw UnretainedError.failedRetaining } | ||
|
||
return resultSelector((obj, element)) | ||
|
@@ -42,10 +42,10 @@ extension ObservableType { | |
- returns: An observable sequence of tuples that contains both an unretained reference on `obj` and the values of the original sequence. | ||
*/ | ||
public func withUnretained<Object: AnyObject>(_ obj: Object) -> Observable<(Object, Element)> { | ||
return withUnretained(obj) { ($0, $1) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Package.swift has There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Uhm yeah, we'll just do the migration in a separate PR. Once RxSwift 6 is out, it will only support Xcode 11 & Swift 5.1. So we'll release RxSwiftExt 6 accordingly. |
||
withUnretained(obj) { ($0, $1) } | ||
} | ||
} | ||
|
||
private enum UnretainedError: Swift.Error { | ||
enum UnretainedError: Swift.Error { | ||
case failedRetaining | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
// | ||
// withUnretainedTests+RxCocoa.swift | ||
// RxSwiftExt | ||
// | ||
// Created by Shai Mishali on 04/10/2019. | ||
// Copyright © 2019 RxSwift Community. All rights reserved. | ||
// | ||
|
||
import XCTest | ||
import RxCocoa | ||
import RxSwift | ||
import RxTest | ||
|
||
class WithUnretainedCocoaTests: XCTestCase { | ||
fileprivate var testClass: TestClass! | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: all properties could be private There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point! |
||
var values: TestableObservable<Int>! | ||
var tupleValues: TestableObservable<(Int, String)>! | ||
let scheduler = TestScheduler(initialClock: 0) | ||
|
||
override func setUp() { | ||
super.setUp() | ||
|
||
testClass = TestClass() | ||
values = scheduler.createColdObservable([ | ||
.next(210, 1), | ||
.next(215, 2), | ||
.next(220, 3), | ||
.next(225, 5), | ||
.next(230, 8), | ||
.completed(250) | ||
]) | ||
|
||
tupleValues = scheduler.createColdObservable([ | ||
.next(210, (1, "a")), | ||
.next(215, (2, "b")), | ||
.next(220, (3, "c")), | ||
.next(225, (5, "d")), | ||
.next(230, (8, "e")), | ||
.completed(250) | ||
]) | ||
} | ||
|
||
func testObjectAttachedDriver() { | ||
let testClassId = testClass.id | ||
|
||
let correctValues: [Recorded<Event<String>>] = [ | ||
.next(410, "\(testClassId), 1"), | ||
.next(415, "\(testClassId), 2"), | ||
.next(420, "\(testClassId), 3"), | ||
.next(425, "\(testClassId), 5"), | ||
.next(430, "\(testClassId), 8"), | ||
.completed(450) | ||
] | ||
|
||
let res = scheduler.start { | ||
self.values | ||
.asDriver(onErrorDriveWith: .empty()) | ||
.withUnretained(self.testClass) | ||
.map { "\($0.id), \($1)" } | ||
.asObservable() | ||
} | ||
|
||
XCTAssertEqual(res.events, correctValues) | ||
} | ||
|
||
func testObjectAttachedSignal() { | ||
let testClassId = testClass.id | ||
|
||
let correctValues: [Recorded<Event<String>>] = [ | ||
.next(410, "\(testClassId), 1"), | ||
.next(415, "\(testClassId), 2"), | ||
.next(420, "\(testClassId), 3"), | ||
.next(425, "\(testClassId), 5"), | ||
.next(430, "\(testClassId), 8"), | ||
.completed(450) | ||
] | ||
|
||
let res = scheduler.start { | ||
self.values | ||
.asSignal(onErrorSignalWith: .empty()) | ||
.withUnretained(self.testClass) | ||
.map { "\($0.id), \($1)" } | ||
.asObservable() | ||
} | ||
|
||
XCTAssertEqual(res.events, correctValues) | ||
} | ||
|
||
func testObjectDeallocatesDriver() { | ||
_ = self.values | ||
.asDriver(onErrorDriveWith: .never()) | ||
.withUnretained(self.testClass) | ||
.drive() | ||
|
||
// Confirm the object can be deallocated | ||
XCTAssertTrue(testClass != nil) | ||
testClass = nil | ||
XCTAssertTrue(testClass == nil) | ||
} | ||
|
||
func testObjectDeallocatesSignal() { | ||
_ = self.values | ||
.asSignal(onErrorSignalWith: .never()) | ||
.withUnretained(self.testClass) | ||
.emit() | ||
|
||
// Confirm the object can be deallocated | ||
XCTAssertTrue(testClass != nil) | ||
testClass = nil | ||
XCTAssertTrue(testClass == nil) | ||
} | ||
|
||
func testObjectDeallocatesDriverCompletes() { | ||
let testClassId = testClass.id | ||
|
||
let correctValues: [Recorded<Event<String>>] = [ | ||
.next(410, "\(testClassId), 1"), | ||
.next(415, "\(testClassId), 2"), | ||
.next(420, "\(testClassId), 3"), | ||
.completed(425) | ||
] | ||
|
||
let res = scheduler.start { | ||
self.values | ||
.asDriver(onErrorDriveWith: .never()) | ||
.withUnretained(self.testClass) | ||
.do(onNext: { _, value in | ||
// Release the object in the middle of the sequence | ||
// to confirm it properly terminates the sequence | ||
if value == 3 { | ||
self.testClass = nil | ||
} | ||
}) | ||
.map { "\($0.id), \($1)" } | ||
.asObservable() | ||
} | ||
|
||
XCTAssertEqual(res.events, correctValues) | ||
} | ||
|
||
func testObjectDeallocatesSignalCompletes() { | ||
let testClassId = testClass.id | ||
|
||
let correctValues: [Recorded<Event<String>>] = [ | ||
.next(410, "\(testClassId), 1"), | ||
.next(415, "\(testClassId), 2"), | ||
.next(420, "\(testClassId), 3"), | ||
.completed(425) | ||
] | ||
|
||
let res = scheduler.start { | ||
self.values | ||
.asSignal(onErrorSignalWith: .never()) | ||
.withUnretained(self.testClass) | ||
.do(onNext: { _, value in | ||
// Release the object in the middle of the sequence | ||
// to confirm it properly terminates the sequence | ||
if value == 3 { | ||
self.testClass = nil | ||
} | ||
}) | ||
.map { "\($0.id), \($1)" } | ||
.asObservable() | ||
} | ||
|
||
XCTAssertEqual(res.events, correctValues) | ||
} | ||
|
||
func testResultsSelectorSignal() { | ||
let testClassId = testClass.id | ||
|
||
let correctValues: [Recorded<Event<String>>] = [ | ||
.next(410, "\(testClassId), 1, a"), | ||
.next(415, "\(testClassId), 2, b"), | ||
.next(420, "\(testClassId), 3, c"), | ||
.next(425, "\(testClassId), 5, d"), | ||
.next(430, "\(testClassId), 8, e"), | ||
.completed(450) | ||
] | ||
|
||
let res = scheduler.start { | ||
self.tupleValues | ||
.asSignal(onErrorSignalWith: .never()) | ||
.withUnretained(self.testClass) { ($0, $1.0, $1.1) } | ||
.map { "\($0.id), \($1), \($2)" } | ||
.asObservable() | ||
} | ||
|
||
XCTAssertEqual(res.events, correctValues) | ||
} | ||
|
||
func testResultsSelectorDriver() { | ||
let testClassId = testClass.id | ||
|
||
let correctValues: [Recorded<Event<String>>] = [ | ||
.next(410, "\(testClassId), 1, a"), | ||
.next(415, "\(testClassId), 2, b"), | ||
.next(420, "\(testClassId), 3, c"), | ||
.next(425, "\(testClassId), 5, d"), | ||
.next(430, "\(testClassId), 8, e"), | ||
.completed(450) | ||
] | ||
|
||
let res = scheduler.start { | ||
self.tupleValues | ||
.asDriver(onErrorDriveWith: .never()) | ||
.withUnretained(self.testClass) { ($0, $1.0, $1.1) } | ||
.map { "\($0.id), \($1), \($2)" } | ||
.asObservable() | ||
} | ||
|
||
XCTAssertEqual(res.events, correctValues) | ||
} | ||
} | ||
|
||
private class TestClass { | ||
let id: String = UUID().uuidString | ||
} |
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.
I'm casting back and forth to be able to throw a sequence-terminating error. The original implementation used a flatMap but that would be tricky given a completion doesn't affect the upstream.