-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add withUnretained operator and tests for ObservableType
- Loading branch information
Showing
4 changed files
with
191 additions
and
2 deletions.
There are no files selected for viewing
This file contains 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 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,53 @@ | ||
// | ||
// WithUnretained.swift | ||
// RxSwift | ||
// | ||
// Created by Vincent Pradeilles on 01/01/2021. | ||
// Copyright © 2020 Krunoslav Zaher. All rights reserved. | ||
// | ||
|
||
extension ObservableType { | ||
/** | ||
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. | ||
- parameter resultSelector: A function to combine the unretained referenced on `obj` and the value of the observable sequence. | ||
- returns: An observable 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 | ||
) -> Observable<Out> { | ||
map { [weak obj] element -> Out in | ||
guard let obj = obj else { throw UnretainedError.failedRetaining } | ||
|
||
return resultSelector((obj, element)) | ||
} | ||
.catch{ error -> Observable<Out> in | ||
guard let unretainedError = error as? UnretainedError, | ||
unretainedError == .failedRetaining else { | ||
return .error(error) | ||
} | ||
|
||
return .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: 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) } | ||
} | ||
} | ||
|
||
private enum UnretainedError: Swift.Error { | ||
case failedRetaining | ||
} |
This file contains 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
124 changes: 124 additions & 0 deletions
124
Tests/RxSwiftTests/Observable+WithUnretainedTests.swift
This file contains 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,124 @@ | ||
// | ||
// Observable+WithUnretainedTests.swift | ||
// RxSwift | ||
// | ||
// Created by Vincent Pradeilles on 01/01/2021. | ||
// Copyright © 2021 Krunoslav Zaher. All rights reserved. | ||
// | ||
|
||
import XCTest | ||
import RxSwift | ||
import RxTest | ||
|
||
class WithUnretainedTests: XCTestCase { | ||
fileprivate var testClass: TestClass! | ||
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 testObjectAttached() { | ||
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 | ||
.withUnretained(self.testClass) | ||
.map { "\($0.id), \($1)" } | ||
} | ||
|
||
XCTAssertEqual(res.events, correctValues) | ||
} | ||
|
||
func testObjectDeallocates() { | ||
_ = self.values | ||
.withUnretained(self.testClass) | ||
.subscribe() | ||
|
||
// Confirm the object can be deallocated | ||
XCTAssertTrue(testClass != nil) | ||
testClass = nil | ||
XCTAssertTrue(testClass == nil) | ||
} | ||
|
||
func testObjectDeallocatesSequenceCompletes() { | ||
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 | ||
.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)" } | ||
} | ||
|
||
XCTAssertEqual(res.events, correctValues) | ||
} | ||
|
||
func testResultsSelector() { | ||
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 | ||
.withUnretained(self.testClass) { ($0, $1.0, $1.1) } | ||
.map { "\($0.id), \($1), \($2)" } | ||
} | ||
|
||
XCTAssertEqual(res.events, correctValues) | ||
} | ||
} | ||
|
||
private class TestClass { | ||
let id: String = UUID().uuidString | ||
} |