-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
DelegateProxyType.swift
429 lines (349 loc) · 18.2 KB
/
DelegateProxyType.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
//
// DelegateProxyType.swift
// RxCocoa
//
// Created by Krunoslav Zaher on 6/15/15.
// Copyright © 2015 Krunoslav Zaher. All rights reserved.
//
#if !os(Linux)
import func Foundation.objc_getAssociatedObject
import func Foundation.objc_setAssociatedObject
import RxSwift
/**
`DelegateProxyType` protocol enables using both normal delegates and Rx observable sequences with
views that can have only one delegate/datasource registered.
`Proxies` store information about observers, subscriptions and delegates
for specific views.
Type implementing `DelegateProxyType` should never be initialized directly.
To fetch initialized instance of type implementing `DelegateProxyType`, `proxy` method
should be used.
This is more or less how it works.
+-------------------------------------------+
| |
| UIView subclass (UIScrollView) |
| |
+-----------+-------------------------------+
|
| Delegate
|
|
+-----------v-------------------------------+
| |
| Delegate proxy : DelegateProxyType +-----+----> Observable<T1>
| , UIScrollViewDelegate | |
+-----------+-------------------------------+ +----> Observable<T2>
| |
| +----> Observable<T3>
| |
| forwards events |
| to custom delegate |
| v
+-----------v-------------------------------+
| |
| Custom delegate (UIScrollViewDelegate) |
| |
+-------------------------------------------+
Since RxCocoa needs to automagically create those Proxys and because views that have delegates can be hierarchical
UITableView : UIScrollView : UIView
.. and corresponding delegates are also hierarchical
UITableViewDelegate : UIScrollViewDelegate : NSObject
... this mechanism can be extended by using the following snippet in `registerKnownImplementations` or in some other
part of your app that executes before using `rx.*` (e.g. appDidFinishLaunching).
RxScrollViewDelegateProxy.register { RxTableViewDelegateProxy(parentObject: $0) }
*/
public protocol DelegateProxyType: class {
associatedtype ParentObject: AnyObject
associatedtype Delegate
/// It is require that enumerate call `register` of the extended DelegateProxy subclasses here.
static func registerKnownImplementations()
/// Unique identifier for delegate
static var identifier: UnsafeRawPointer { get }
/// Returns designated delegate property for object.
///
/// Objects can have multiple delegate properties.
///
/// Each delegate property needs to have it's own type implementing `DelegateProxyType`.
///
/// It's abstract method.
///
/// - parameter object: Object that has delegate property.
/// - returns: Value of delegate property.
static func currentDelegate(for object: ParentObject) -> Delegate?
/// Sets designated delegate property for object.
///
/// Objects can have multiple delegate properties.
///
/// Each delegate property needs to have it's own type implementing `DelegateProxyType`.
///
/// It's abstract method.
///
/// - parameter toObject: Object that has delegate property.
/// - parameter delegate: Delegate value.
static func setCurrentDelegate(_ delegate: Delegate?, to object: ParentObject)
/// Returns reference of normal delegate that receives all forwarded messages
/// through `self`.
///
/// - returns: Value of reference if set or nil.
func forwardToDelegate() -> Delegate?
/// Sets reference of normal delegate that receives all forwarded messages
/// through `self`.
///
/// - parameter forwardToDelegate: Reference of delegate that receives all messages through `self`.
/// - parameter retainDelegate: Should `self` retain `forwardToDelegate`.
func setForwardToDelegate(_ forwardToDelegate: Delegate?, retainDelegate: Bool)
}
// default implementations
extension DelegateProxyType {
/// Unique identifier for delegate
public static var identifier: UnsafeRawPointer {
let delegateIdentifier = ObjectIdentifier(Delegate.self)
let integerIdentifier = Int(bitPattern: delegateIdentifier)
return UnsafeRawPointer(bitPattern: integerIdentifier)!
}
}
// workaround of Delegate: class
extension DelegateProxyType {
static func _currentDelegate(for object: ParentObject) -> AnyObject? {
currentDelegate(for: object).map { $0 as AnyObject }
}
static func _setCurrentDelegate(_ delegate: AnyObject?, to object: ParentObject) {
setCurrentDelegate(castOptionalOrFatalError(delegate), to: object)
}
func _forwardToDelegate() -> AnyObject? {
self.forwardToDelegate().map { $0 as AnyObject }
}
func _setForwardToDelegate(_ forwardToDelegate: AnyObject?, retainDelegate: Bool) {
self.setForwardToDelegate(castOptionalOrFatalError(forwardToDelegate), retainDelegate: retainDelegate)
}
}
extension DelegateProxyType {
/// Store DelegateProxy subclass to factory.
/// When make 'Rx*DelegateProxy' subclass, call 'Rx*DelegateProxySubclass.register(for:_)' 1 time, or use it in DelegateProxyFactory
/// 'Rx*DelegateProxy' can have one subclass implementation per concrete ParentObject type.
/// Should call it from concrete DelegateProxy type, not generic.
public static func register<Parent>(make: @escaping (Parent) -> Self) {
self.factory.extend(make: make)
}
/// Creates new proxy for target object.
/// Should not call this function directory, use 'DelegateProxy.proxy(for:)'
public static func createProxy(for object: AnyObject) -> Self {
castOrFatalError(factory.createProxy(for: object))
}
/// 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 Reactive where Base: UISearchBar {
///
/// public var delegate: DelegateProxy<UISearchBar, UISearchBarDelegate> {
/// return RxSearchBarDelegateProxy.proxy(for: base)
/// }
///
/// public var text: ControlProperty<String> {
/// let source: Observable<String> = self.delegate.observe(#selector(UISearchBarDelegate.searchBar(_:textDidChange:)))
/// ...
/// }
/// }
public static func proxy(for object: ParentObject) -> Self {
MainScheduler.ensureRunningOnMainThread()
let maybeProxy = self.assignedProxy(for: object)
let proxy: AnyObject
if let existingProxy = maybeProxy {
proxy = existingProxy
}
else {
proxy = castOrFatalError(self.createProxy(for: object))
self.assignProxy(proxy, toObject: object)
assert(self.assignedProxy(for: object) === proxy)
}
let currentDelegate = self._currentDelegate(for: object)
let delegateProxy: Self = castOrFatalError(proxy)
if currentDelegate !== delegateProxy {
delegateProxy._setForwardToDelegate(currentDelegate, retainDelegate: false)
assert(delegateProxy._forwardToDelegate() === currentDelegate)
self._setCurrentDelegate(proxy, to: object)
assert(self._currentDelegate(for: object) === proxy)
assert(delegateProxy._forwardToDelegate() === currentDelegate)
}
return delegateProxy
}
/// 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: Delegate, retainDelegate: Bool, onProxyForObject object: ParentObject) -> Disposable {
weak var weakForwardDelegate: AnyObject? = forwardDelegate as AnyObject
let proxy = self.proxy(for: object)
assert(proxy._forwardToDelegate() === nil, "This is a feature to warn you that there is already a delegate (or data source) set somewhere previously. The action you are trying to perform will clear that delegate (data source) and that means that some of your features that depend on that delegate (data source) being set will likely stop working.\n" +
"If you are ok with this, try to set delegate (data source) to `nil` in front of this operation.\n" +
" This is the source object value: \(object)\n" +
" This is the original delegate (data source) value: \(proxy.forwardToDelegate()!)\n" +
"Hint: Maybe delegate was already set in xib or storyboard and now it's being overwritten in code.\n")
proxy.setForwardToDelegate(forwardDelegate, retainDelegate: retainDelegate)
return Disposables.create {
MainScheduler.ensureRunningOnMainThread()
let delegate: AnyObject? = weakForwardDelegate
assert(delegate == nil || proxy._forwardToDelegate() === delegate, "Delegate was changed from time it was first set. Current \(String(describing: proxy.forwardToDelegate())), and it should have been \(proxy)")
proxy.setForwardToDelegate(nil, retainDelegate: retainDelegate)
}
}
}
// private extensions
extension DelegateProxyType {
private static var factory: DelegateProxyFactory {
DelegateProxyFactory.sharedFactory(for: self)
}
private static func assignedProxy(for object: ParentObject) -> AnyObject? {
let maybeDelegate = objc_getAssociatedObject(object, self.identifier)
return castOptionalOrFatalError(maybeDelegate)
}
private static func assignProxy(_ proxy: AnyObject, toObject object: ParentObject) {
objc_setAssociatedObject(object, self.identifier, proxy, .OBJC_ASSOCIATION_RETAIN)
}
}
/// Describes an object that has a delegate.
public protocol HasDelegate: AnyObject {
/// Delegate type
associatedtype Delegate
/// Delegate
var delegate: Delegate? { get set }
}
extension DelegateProxyType where ParentObject: HasDelegate, Self.Delegate == ParentObject.Delegate {
public static func currentDelegate(for object: ParentObject) -> Delegate? {
object.delegate
}
public static func setCurrentDelegate(_ delegate: Delegate?, to object: ParentObject) {
object.delegate = delegate
}
}
/// Describes an object that has a data source.
public protocol HasDataSource: AnyObject {
/// Data source type
associatedtype DataSource
/// Data source
var dataSource: DataSource? { get set }
}
extension DelegateProxyType where ParentObject: HasDataSource, Self.Delegate == ParentObject.DataSource {
public static func currentDelegate(for object: ParentObject) -> Delegate? {
return object.dataSource
}
public static func setCurrentDelegate(_ delegate: Delegate?, to object: ParentObject) {
object.dataSource = delegate
}
}
/// Describes an object that has a prefetch data source.
@available(iOS 10.0, tvOS 10.0, *)
public protocol HasPrefetchDataSource: AnyObject {
/// Prefetch data source type
associatedtype PrefetchDataSource
/// Prefetch data source
var prefetchDataSource: PrefetchDataSource? { get set }
}
@available(iOS 10.0, tvOS 10.0, *)
extension DelegateProxyType where ParentObject: HasPrefetchDataSource, Self.Delegate == ParentObject.PrefetchDataSource {
public static func currentDelegate(for object: ParentObject) -> Delegate? {
return object.prefetchDataSource
}
public static func setCurrentDelegate(_ delegate: Delegate?, to object: ParentObject) {
object.prefetchDataSource = delegate
}
}
#if os(iOS) || os(tvOS)
import UIKit
extension ObservableType {
func subscribeProxyDataSource<DelegateProxy: DelegateProxyType>(ofObject object: DelegateProxy.ParentObject, dataSource: DelegateProxy.Delegate, retainDataSource: Bool, binding: @escaping (DelegateProxy, Event<Element>) -> Void)
-> Disposable
where DelegateProxy.ParentObject: UIView
, DelegateProxy.Delegate: AnyObject {
let proxy = DelegateProxy.proxy(for: object)
let unregisterDelegate = DelegateProxy.installForwardDelegate(dataSource, retainDelegate: retainDataSource, onProxyForObject: object)
// this is needed to flush any delayed old state (https://github.com/RxSwiftCommunity/RxDataSources/pull/75)
object.layoutIfNeeded()
let subscription = self.asObservable()
.observeOn(MainScheduler())
.catchError { error in
bindingError(error)
return Observable.empty()
}
// source can never end, otherwise it would release the subscriber, and deallocate the data source
.concat(Observable.never())
.takeUntil(object.rx.deallocated)
.subscribe { [weak object] (event: Event<Element>) in
if let object = object {
assert(proxy === DelegateProxy.currentDelegate(for: object), "Proxy changed from the time it was first set.\nOriginal: \(proxy)\nExisting: \(String(describing: DelegateProxy.currentDelegate(for: object)))")
}
binding(proxy, event)
switch event {
case .error(let error):
bindingError(error)
unregisterDelegate.dispose()
case .completed:
unregisterDelegate.dispose()
default:
break
}
}
return Disposables.create { [weak object] in
subscription.dispose()
object?.layoutIfNeeded()
unregisterDelegate.dispose()
}
}
}
#endif
/**
To add delegate proxy subclasses call `DelegateProxySubclass.register()` in `registerKnownImplementations` or in some other
part of your app that executes before using `rx.*` (e.g. appDidFinishLaunching).
class RxScrollViewDelegateProxy: DelegateProxy {
public static func registerKnownImplementations() {
self.register { RxTableViewDelegateProxy(parentObject: $0) }
}
...
*/
private class DelegateProxyFactory {
private static var _sharedFactories: [UnsafeRawPointer: DelegateProxyFactory] = [:]
fileprivate static func sharedFactory<DelegateProxy: DelegateProxyType>(for proxyType: DelegateProxy.Type) -> DelegateProxyFactory {
MainScheduler.ensureRunningOnMainThread()
let identifier = DelegateProxy.identifier
if let factory = _sharedFactories[identifier] {
return factory
}
let factory = DelegateProxyFactory(for: proxyType)
_sharedFactories[identifier] = factory
DelegateProxy.registerKnownImplementations()
return factory
}
private var _factories: [ObjectIdentifier: ((AnyObject) -> AnyObject)]
private var _delegateProxyType: Any.Type
private var _identifier: UnsafeRawPointer
private init<DelegateProxy: DelegateProxyType>(for proxyType: DelegateProxy.Type) {
self._factories = [:]
self._delegateProxyType = proxyType
self._identifier = proxyType.identifier
}
fileprivate func extend<DelegateProxy: DelegateProxyType, ParentObject>(make: @escaping (ParentObject) -> DelegateProxy) {
MainScheduler.ensureRunningOnMainThread()
precondition(self._identifier == DelegateProxy.identifier, "Delegate proxy has inconsistent identifier")
guard self._factories[ObjectIdentifier(ParentObject.self)] == nil else {
rxFatalError("The factory of \(ParentObject.self) is duplicated. DelegateProxy is not allowed of duplicated base object type.")
}
self._factories[ObjectIdentifier(ParentObject.self)] = { make(castOrFatalError($0)) }
}
fileprivate func createProxy(for object: AnyObject) -> AnyObject {
MainScheduler.ensureRunningOnMainThread()
var maybeMirror: Mirror? = Mirror(reflecting: object)
while let mirror = maybeMirror {
if let factory = self._factories[ObjectIdentifier(mirror.subjectType)] {
return factory(object)
}
maybeMirror = mirror.superclassMirror
}
rxFatalError("DelegateProxy has no factory of \(object). Implement DelegateProxy subclass for \(object) first.")
}
}
#endif