Exposes `installForwardDelegate` and transforms `proxyForObject` into protocol extension.

This commit is contained in:
Krunoslav Zaher 2016-05-08 21:32:02 +02:00
parent 9b6f069f81
commit d197ce0fbb
12 changed files with 172 additions and 82 deletions

View File

@ -20,7 +20,7 @@ extension CLLocationManager {
For more information take a look at `DelegateProxyType` protocol documentation.
*/
public var rx_delegate: DelegateProxy {
return proxyForObject(RxCLLocationManagerDelegateProxy.self, self)
return RxCLLocationManagerDelegateProxy.proxyForObject(self)
}
// MARK: Responding to Location Events

View File

@ -147,82 +147,100 @@ public protocol DelegateProxyType : AnyObject {
func setForwardToDelegate(forwardToDelegate: AnyObject?, retainDelegate: Bool)
}
/**
Returns existing proxy for object or installs new instance of delegate proxy.
- parameter object: Target object on which to install delegate proxy.
- returns: Installed instance of delegate proxy.
extension UISearchBar {
public var rx_delegate: DelegateProxy {
return proxyForObject(RxSearchBarDelegateProxy.self, self)
}
public var rx_text: ControlProperty<String> {
let source: Observable<String> = self.rx_delegate.observe("searchBar:textDidChange:")
...
}
}
*/
@available(*, deprecated=2.5, renamed="DelegateProxyType.proxyForObject", message="You can just use normal static protocol extension. E.g. `RxScrollViewDelegateProxy.proxyForObject`")
public func proxyForObject<P: DelegateProxyType>(type: P.Type, _ object: AnyObject) -> P {
MainScheduler.ensureExecutingOnScheduler()
let maybeProxy = P.assignedProxyFor(object) as? P
let proxy: P
if maybeProxy == nil {
proxy = P.createProxyForObject(object) as! P
P.assignProxy(proxy, toObject: object)
assert(P.assignedProxyFor(object) === proxy)
}
else {
proxy = maybeProxy!
}
let currentDelegate: AnyObject? = P.currentDelegateFor(object)
if currentDelegate !== proxy {
proxy.setForwardToDelegate(currentDelegate, retainDelegate: false)
P.setCurrentDelegate(proxy, toObject: object)
assert(P.currentDelegateFor(object) === proxy)
assert(proxy.forwardToDelegate() === currentDelegate)
}
return proxy
return P.proxyForObject(object)
}
func installDelegate<P: DelegateProxyType>(proxy: P, delegate: AnyObject, retainDelegate: Bool, onProxyForObject object: AnyObject) -> Disposable {
weak var weakDelegate: AnyObject? = delegate
assert(proxy.forwardToDelegate() === nil, "There is already a delegate set -> `\(proxy.forwardToDelegate()!)` for object -> `\(object)`.\nMaybe delegate was already set in `xib` or `storyboard` and now it's being overwritten in code.")
proxy.setForwardToDelegate(delegate, retainDelegate: retainDelegate)
// refresh properties after delegate is set
// some views like UITableView cache `respondsToSelector`
P.setCurrentDelegate(nil, toObject: object)
P.setCurrentDelegate(proxy, toObject: object)
assert(proxy.forwardToDelegate() === delegate, "Setting of delegate failed")
return AnonymousDisposable {
extension DelegateProxyType {
/**
Returns existing proxy for object or installs new instance of delegate proxy.
- parameter object: Target object on which to install delegate proxy.
- returns: Installed instance of delegate proxy.
extension UISearchBar {
public var rx_delegate: DelegateProxy {
return proxyForObject(RxSearchBarDelegateProxy.self, self)
}
public var rx_text: ControlProperty<String> {
let source: Observable<String> = self.rx_delegate.observe("searchBar:textDidChange:")
...
}
}
*/
public static func proxyForObject(object: AnyObject) -> Self {
MainScheduler.ensureExecutingOnScheduler()
let maybeProxy = Self.assignedProxyFor(object) as? Self
let proxy: Self
if maybeProxy == nil {
proxy = Self.createProxyForObject(object) as! Self
Self.assignProxy(proxy, toObject: object)
assert(Self.assignedProxyFor(object) === proxy)
}
else {
proxy = maybeProxy!
}
let currentDelegate: AnyObject? = Self.currentDelegateFor(object)
if currentDelegate !== proxy {
proxy.setForwardToDelegate(currentDelegate, retainDelegate: false)
Self.setCurrentDelegate(proxy, toObject: object)
assert(Self.currentDelegateFor(object) === proxy)
assert(proxy.forwardToDelegate() === currentDelegate)
}
let delegate: AnyObject? = weakDelegate
return proxy
}
/**
Sets forward delegate for `DelegateProxyType` associated with a specific object and return disposable that can be used to unset the forward to delegate.
Using this method will also make sure that potential original object cached selectors are cleared and will report any accidental forward delegate mutations.
- parameter forwardDelegate: Delegate object to set.
- parameter retainDelegate: Retain `forwardDelegate` while it's being set.
- parameter onProxyForObject: Object that has `delegate` property.
- returns: Disposable object that can be used to clear forward delegate.
*/
public static func installForwardDelegate(forwardDelegate: AnyObject, retainDelegate: Bool, onProxyForObject object: AnyObject) -> Disposable {
weak var weakForwardDelegate: AnyObject? = forwardDelegate
let proxy = Self.proxyForObject(object)
assert(delegate == nil || proxy.forwardToDelegate() === delegate, "Delegate was changed from time it was first set. Current \(proxy.forwardToDelegate()), and it should have been \(proxy)")
assert(proxy.forwardToDelegate() === nil, "There is already a delegate set -> `\(proxy.forwardToDelegate()!)` for object -> `\(object)`.\nMaybe delegate was already set in `xib` or `storyboard` and now it's being overwritten in code.")
proxy.setForwardToDelegate(nil, retainDelegate: retainDelegate)
proxy.setForwardToDelegate(forwardDelegate, retainDelegate: retainDelegate)
// refresh properties after delegate is set
// some views like UITableView cache `respondsToSelector`
Self.setCurrentDelegate(nil, toObject: object)
Self.setCurrentDelegate(proxy, toObject: object)
assert(proxy.forwardToDelegate() === forwardDelegate, "Setting of delegate failed")
return AnonymousDisposable {
MainScheduler.ensureExecutingOnScheduler()
let delegate: AnyObject? = weakForwardDelegate
assert(delegate == nil || proxy.forwardToDelegate() === delegate, "Delegate was changed from time it was first set. Current \(proxy.forwardToDelegate()), and it should have been \(proxy)")
proxy.setForwardToDelegate(nil, retainDelegate: retainDelegate)
}
}
}
extension ObservableType {
func subscribeProxyDataSourceForObject<P: DelegateProxyType>(object: AnyObject, dataSource: AnyObject, retainDataSource: Bool, binding: (P, Event<E>) -> Void)
-> Disposable {
let proxy = proxyForObject(P.self, object)
let disposable = installDelegate(proxy, delegate: dataSource, retainDelegate: retainDataSource, onProxyForObject: object)
let proxy = P.proxyForObject(object)
let disposable = P.installForwardDelegate(dataSource, retainDelegate: retainDataSource, onProxyForObject: object)
let subscription = self.asObservable()
// source can't ever end, otherwise it will release the subscriber

View File

@ -93,14 +93,14 @@ extension NSTextField {
For more information take a look at `DelegateProxyType` protocol documentation.
*/
public var rx_delegate: DelegateProxy {
return proxyForObject(RxTextFieldDelegateProxy.self, self)
return RxTextFieldDelegateProxy.proxyForObject(self)
}
/**
Reactive wrapper for `text` property.
*/
public var rx_text: ControlProperty<String> {
let delegate = proxyForObject(RxTextFieldDelegateProxy.self, self)
let delegate = RxTextFieldDelegateProxy.proxyForObject(self)
let source = Observable.deferred { [weak self] in
delegate.textSubject.startWith(self?.stringValue ?? "")

View File

@ -22,7 +22,7 @@ extension NSTextStorage {
For more information take a look at `DelegateProxyType` protocol documentation.
*/
public var rx_delegate:DelegateProxy {
return proxyForObject(RxTextStorageDelegateProxy.self, self)
return RxTextStorageDelegateProxy.proxyForObject(self)
}
/**

View File

@ -112,7 +112,7 @@ extension UICollectionView {
For more information take a look at `DelegateProxyType` protocol documentation.
*/
public var rx_dataSource: DelegateProxy {
return proxyForObject(RxCollectionViewDataSourceProxy.self, self)
return RxCollectionViewDataSourceProxy.proxyForObject(self)
}
/**
@ -125,8 +125,7 @@ extension UICollectionView {
*/
public func rx_setDataSource(dataSource: UICollectionViewDataSource)
-> Disposable {
let proxy = proxyForObject(RxCollectionViewDataSourceProxy.self, self)
return installDelegate(proxy, delegate: dataSource, retainDelegate: false, onProxyForObject: self)
return RxCollectionViewDataSourceProxy.installForwardDelegate(dataSource, retainDelegate: false, onProxyForObject: self)
}
/**

View File

@ -24,7 +24,7 @@ import Foundation
For more information take a look at `DelegateProxyType` protocol documentation.
*/
public var rx_delegate: DelegateProxy {
return proxyForObject(RxImagePickerDelegateProxy.self, self)
return RxImagePickerDelegateProxy.proxyForObject(self)
}
/**

View File

@ -31,14 +31,14 @@ extension UIScrollView {
For more information take a look at `DelegateProxyType` protocol documentation.
*/
public var rx_delegate: DelegateProxy {
return proxyForObject(RxScrollViewDelegateProxy.self, self)
return RxScrollViewDelegateProxy.proxyForObject(self)
}
/**
Reactive wrapper for `contentOffset`.
*/
public var rx_contentOffset: ControlProperty<CGPoint> {
let proxy = proxyForObject(RxScrollViewDelegateProxy.self, self)
let proxy = RxScrollViewDelegateProxy.proxyForObject(self)
let bindingObserver = UIBindingObserver(UIElement: self) { scrollView, contentOffset in
scrollView.contentOffset = contentOffset
@ -57,8 +57,7 @@ extension UIScrollView {
*/
public func rx_setDelegate(delegate: UIScrollViewDelegate)
-> Disposable {
let proxy = proxyForObject(RxScrollViewDelegateProxy.self, self)
return installDelegate(proxy, delegate: delegate, retainDelegate: false, onProxyForObject: self)
return RxScrollViewDelegateProxy.installForwardDelegate(delegate, retainDelegate: false, onProxyForObject: self)
}
}

View File

@ -35,7 +35,7 @@ extension UISearchBar {
For more information take a look at `DelegateProxyType` protocol documentation.
*/
public var rx_delegate: DelegateProxy {
return proxyForObject(RxSearchBarDelegateProxy.self, self)
return RxSearchBarDelegateProxy.proxyForObject(self)
}
/**

View File

@ -22,7 +22,7 @@ extension UISearchController {
For more information take a look at `DelegateProxyType` protocol documentation.
*/
public var rx_delegate: DelegateProxy {
return proxyForObject(RxSearchControllerDelegateProxy.self, self)
return RxSearchControllerDelegateProxy.proxyForObject(self)
}
/**
Reactive wrapper for `delegate` message.

View File

@ -111,7 +111,7 @@ extension UITableView {
For more information take a look at `DelegateProxyType` protocol documentation.
*/
public var rx_dataSource: DelegateProxy {
return proxyForObject(RxTableViewDataSourceProxy.self, self)
return RxTableViewDataSourceProxy.proxyForObject(self)
}
/**
@ -124,9 +124,7 @@ extension UITableView {
*/
public func rx_setDataSource(dataSource: UITableViewDataSource)
-> Disposable {
let proxy = proxyForObject(RxTableViewDataSourceProxy.self, self)
return installDelegate(proxy, delegate: dataSource, retainDelegate: false, onProxyForObject: self)
return RxTableViewDataSourceProxy.installForwardDelegate(dataSource, retainDelegate: false, onProxyForObject: self)
}
// events

View File

@ -154,6 +154,10 @@ class UITableViewSubclass1
.observe(#selector(TestDelegateProtocol.testEventHappened(_:)))
.map { a in (a[0] as! NSNumber).integerValue }
}
func setMineForwardDelegate(testDelegate: TestDelegateProtocol) -> Disposable {
return RxScrollViewDelegateProxy.installForwardDelegate(testDelegate, retainDelegate: false, onProxyForObject: self)
}
}
class ExtendTableViewDataSourceProxy
@ -183,6 +187,10 @@ class UITableViewSubclass2
.observe(#selector(TestDelegateProtocol.testEventHappened(_:)))
.map { a in (a[0] as! NSNumber).integerValue }
}
func setMineForwardDelegate(testDelegate: TestDelegateProtocol) -> Disposable {
return RxTableViewDataSourceProxy.installForwardDelegate(testDelegate, retainDelegate: false, onProxyForObject: self)
}
}
class ExtendCollectionViewDelegateProxy
@ -212,6 +220,10 @@ class UICollectionViewSubclass1
.observe(#selector(TestDelegateProtocol.testEventHappened(_:)))
.map { a in (a[0] as! NSNumber).integerValue }
}
func setMineForwardDelegate(testDelegate: TestDelegateProtocol) -> Disposable {
return RxScrollViewDelegateProxy.installForwardDelegate(testDelegate, retainDelegate: false, onProxyForObject: self)
}
}
class ExtendCollectionViewDataSourceProxy
@ -241,6 +253,10 @@ class UICollectionViewSubclass2
.observe(#selector(TestDelegateProtocol.testEventHappened(_:)))
.map { a in (a[0] as! NSNumber).integerValue }
}
func setMineForwardDelegate(testDelegate: TestDelegateProtocol) -> Disposable {
return RxCollectionViewDataSourceProxy.installForwardDelegate(testDelegate, retainDelegate: false, onProxyForObject: self)
}
}
class ExtendScrollViewDelegateProxy
@ -270,6 +286,10 @@ class UIScrollViewSubclass
.observe(#selector(TestDelegateProtocol.testEventHappened(_:)))
.map { a in (a[0] as! NSNumber).integerValue }
}
func setMineForwardDelegate(testDelegate: TestDelegateProtocol) -> Disposable {
return RxScrollViewDelegateProxy.installForwardDelegate(testDelegate, retainDelegate: false, onProxyForObject: self)
}
}
#if os(iOS)
@ -301,6 +321,10 @@ class UISearchBarSubclass
.observe(#selector(TestDelegateProtocol.testEventHappened(_:)))
.map { a in (a[0] as! NSNumber).integerValue }
}
func setMineForwardDelegate(testDelegate: TestDelegateProtocol) -> Disposable {
return RxSearchBarDelegateProxy.installForwardDelegate(testDelegate, retainDelegate: false, onProxyForObject: self)
}
}
#endif
@ -331,6 +355,10 @@ class UITextViewSubclass
.observe(#selector(TestDelegateProtocol.testEventHappened(_:)))
.map { a in (a[0] as! NSNumber).integerValue }
}
func setMineForwardDelegate(testDelegate: TestDelegateProtocol) -> Disposable {
return RxScrollViewDelegateProxy.installForwardDelegate(testDelegate, retainDelegate: false, onProxyForObject: self)
}
}
#if os(iOS)
class UISearchControllerSubclass
@ -346,5 +374,9 @@ class UISearchControllerSubclass
.observe(#selector(TestDelegateProtocol.testEventHappened(_:)))
.map { a in (a[0] as! NSNumber).integerValue }
}
func setMineForwardDelegate(testDelegate: TestDelegateProtocol) -> Disposable {
return RxSearchControllerDelegateProxy.installForwardDelegate(testDelegate, retainDelegate: false, onProxyForObject: self)
}
}
#endif

View File

@ -20,10 +20,42 @@ import UIKit
optional func testEventHappened(value: Int)
}
@objc class MockTestDelegateProtocol:
NSObject,
TestDelegateProtocol,
UICollectionViewDataSource,
UIScrollViewDelegate,
UITableViewDataSource,
UITableViewDelegate {
var numbers = [Int]()
func testEventHappened(value: Int) {
numbers.append(value)
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
fatalError()
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
fatalError()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
fatalError()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
fatalError()
}
}
protocol TestDelegateControl: NSObjectProtocol {
func doThatTest(value: Int)
var test: Observable<Int> { get }
func setMineForwardDelegate(testDelegate: TestDelegateProtocol) -> Disposable
}
// MARK: Tests
@ -208,6 +240,18 @@ extension DelegateProxyTest {
}
XCTAssertEqual(receivedValue, 382763)
autoreleasepool {
let mine = MockTestDelegateProtocol()
let disposable = control.setMineForwardDelegate(mine)
XCTAssertEqual(mine.numbers, [])
control.doThatTest(2)
XCTAssertEqual(mine.numbers, [2])
disposable.dispose()
control.doThatTest(3)
XCTAssertEqual(mine.numbers, [2])
}
XCTAssertFalse(deallocated)
XCTAssertFalse(completed)
autoreleasepool {
@ -280,7 +324,7 @@ class ThreeDSectionedViewDelegateProxy : DelegateProxy
extension ThreeDSectionedView {
var rx_proxy: DelegateProxy {
return proxyForObject(ThreeDSectionedViewDelegateProxy.self, self)
return ThreeDSectionedViewDelegateProxy.proxyForObject(self)
}
}