88
99#import " ASAssert.h"
1010#import " ASBatchFetching.h"
11+ #import " ASDelegateProxy.h"
1112#import " ASCollectionView.h"
1213#import " ASCollectionNode.h"
1314#import " ASCollectionDataController.h"
2223static const ASSizeRange kInvalidSizeRange = {CGSizeZero, CGSizeZero};
2324static NSString * const kCellReuseIdentifier = @" _ASCollectionViewCell" ;
2425
25- #pragma mark -
26- #pragma mark Proxying.
27-
28- /* *
29- * ASCollectionView intercepts and/or overrides a few of UICollectionView's critical data source and delegate methods.
30- *
31- * Any selector included in this function *MUST* be implemented by ASCollectionView.
32- */
33- static BOOL _isInterceptedSelector (SEL sel)
34- {
35- return (
36- // handled by ASCollectionView node<->cell machinery
37- sel == @selector (collectionView:cellForItemAtIndexPath: ) ||
38- sel == @selector (collectionView:layout:sizeForItemAtIndexPath: ) ||
39- sel == @selector (collectionView:viewForSupplementaryElementOfKind:atIndexPath: ) ||
40-
41- // handled by ASRangeController
42- sel == @selector (numberOfSectionsInCollectionView: ) ||
43- sel == @selector (collectionView:numberOfItemsInSection: ) ||
44-
45- // used for ASRangeController visibility updates
46- sel == @selector (collectionView:willDisplayCell:forItemAtIndexPath: ) ||
47- sel == @selector (collectionView:didEndDisplayingCell:forItemAtIndexPath: ) ||
48-
49- // used for batch fetching API
50- sel == @selector (scrollViewWillEndDragging:withVelocity:targetContentOffset: )
51- );
52- }
53-
54-
55- /* *
56- * Stand-in for UICollectionViewDataSource and UICollectionViewDelegate. Any method calls we intercept are routed to ASCollectionView;
57- * everything else leaves AsyncDisplayKit safely and arrives at the original intended data source and delegate.
58- */
59- @interface _ASCollectionViewProxy : NSProxy
60- - (instancetype )initWithTarget : (id <NSObject >)target interceptor : (ASCollectionView *)interceptor ;
61- @end
62-
63- @implementation _ASCollectionViewProxy {
64- id <NSObject > __weak _target;
65- ASCollectionView * __weak _interceptor;
66- }
67-
68- - (instancetype )initWithTarget : (id <NSObject >)target interceptor : (ASCollectionView *)interceptor
69- {
70- // -[NSProxy init] is undefined
71- if (!self) {
72- return nil ;
73- }
74-
75- ASDisplayNodeAssert (target, @" target must not be nil" );
76- ASDisplayNodeAssert (interceptor, @" interceptor must not be nil" );
77-
78- _target = target;
79- _interceptor = interceptor;
80-
81- return self;
82- }
83-
84- - (BOOL )respondsToSelector : (SEL )aSelector
85- {
86- ASDisplayNodeAssert (_target, @" target must not be nil" ); // catch weak ref's being nilled early
87- ASDisplayNodeAssert (_interceptor, @" interceptor must not be nil" );
88-
89- return (_isInterceptedSelector (aSelector) || [_target respondsToSelector: aSelector]);
90- }
91-
92- - (id )forwardingTargetForSelector : (SEL )aSelector
93- {
94- ASDisplayNodeAssert (_target, @" target must not be nil" ); // catch weak ref's being nilled early
95- ASDisplayNodeAssert (_interceptor, @" interceptor must not be nil" );
96-
97- if (_isInterceptedSelector (aSelector)) {
98- return _interceptor;
99- }
100-
101- return [_target respondsToSelector: aSelector] ? _target : nil ;
102- }
103-
104- @end
105-
10626#pragma mark -
10727#pragma mark ASCellNode<->UICollectionViewCell bridging.
10828
@@ -138,9 +58,9 @@ - (void)setHighlighted:(BOOL)highlighted
13858#pragma mark -
13959#pragma mark ASCollectionView.
14060
141- @interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASDataControllerSource, ASCellNodeLayoutDelegate> {
142- _ASCollectionViewProxy *_proxyDataSource;
143- _ASCollectionViewProxy *_proxyDelegate;
61+ @interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASDataControllerSource, ASCellNodeLayoutDelegate, ASDelegateProxyInterceptor > {
62+ ASCollectionViewProxy *_proxyDataSource;
63+ ASCollectionViewProxy *_proxyDelegate;
14464
14565 ASCollectionDataController *_dataController;
14666 ASRangeController *_rangeController;
@@ -155,6 +75,7 @@ @interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDe
15575 BOOL _collectionViewLayoutImplementsInsetSection;
15676 BOOL _asyncDataSourceImplementsConstrainedSizeForNode;
15777 BOOL _queuedNodeSizeUpdate;
78+ BOOL _isDeallocating;
15879
15980 ASBatchContext *_batchContext;
16081
@@ -244,6 +165,12 @@ - (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionV
244165 _layoutInspector = [self flowLayoutInspector ];
245166 }
246167
168+ _proxyDelegate = [[ASCollectionViewProxy alloc ] initWithTarget: nil interceptor: self ];
169+ super.delegate = (id <UICollectionViewDelegate>)_proxyDelegate;
170+
171+ _proxyDataSource = [[ASCollectionViewProxy alloc ] initWithTarget: nil interceptor: self ];
172+ super.dataSource = (id <UICollectionViewDataSource>)_proxyDataSource;
173+
247174 _registeredSupplementaryKinds = [NSMutableSet set ];
248175
249176 self.backgroundColor = [UIColor whiteColor ];
@@ -255,10 +182,10 @@ - (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionV
255182
256183- (void )dealloc
257184{
258- // Sometimes the UIKit classes can call back to their delegate even during deallocation.
259- // This bug might be iOS 7-specific.
260- super. delegate = nil ;
261- super. dataSource = nil ;
185+ // Sometimes the UIKit classes can call back to their delegate even during deallocation, due to animation completion blocks etc .
186+ _isDeallocating = YES ;
187+ [ self setAsyncDelegate: nil ] ;
188+ [ self setAsyncDataSource: nil ] ;
262189}
263190
264191/* *
@@ -312,47 +239,61 @@ - (void)setDelegate:(id<UICollectionViewDelegate>)delegate
312239 ASDisplayNodeAssert (delegate == nil , @" ASCollectionView uses asyncDelegate, not UICollectionView's delegate property." );
313240}
314241
242+ - (void )proxyTargetHasDeallocated : (ASDelegateProxy *)proxy
243+ {
244+ if (proxy == _proxyDelegate) {
245+ [self setAsyncDelegate: nil ];
246+ } else if (proxy == _proxyDataSource) {
247+ [self setAsyncDataSource: nil ];
248+ }
249+ }
250+
315251- (void )setAsyncDataSource : (id <ASCollectionViewDataSource>)asyncDataSource
316252{
317253 // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
318254 // the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource
319255 // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to nil out
320- // super.dataSource in this case because calls to _ASTableViewProxy will start failing and cause crashes.
256+ // super.dataSource in this case because calls to ASCollectionViewProxy will start failing and cause crashes.
257+
258+ super.dataSource = nil ;
321259
322260 if (asyncDataSource == nil ) {
323- super.dataSource = nil ;
324261 _asyncDataSource = nil ;
325- _proxyDataSource = nil ;
262+ _proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc ] initWithTarget: nil interceptor: self ] ;
326263 _asyncDataSourceImplementsConstrainedSizeForNode = NO ;
327264 } else {
328265 _asyncDataSource = asyncDataSource;
329- _proxyDataSource = [[_ASCollectionViewProxy alloc ] initWithTarget: _asyncDataSource interceptor: self ];
330- super.dataSource = (id <UICollectionViewDataSource>)_proxyDataSource;
266+ _proxyDataSource = [[ASCollectionViewProxy alloc ] initWithTarget: _asyncDataSource interceptor: self ];
331267 _asyncDataSourceImplementsConstrainedSizeForNode = ([_asyncDataSource respondsToSelector: @selector (collectionView:constrainedSizeForNodeAtIndexPath: )] ? 1 : 0 );
332268 }
269+
270+ super.dataSource = (id <UICollectionViewDataSource>)_proxyDataSource;
333271}
334272
335273- (void )setAsyncDelegate : (id <ASCollectionViewDelegate>)asyncDelegate
336274{
337275 // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
338276 // the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate
339277 // will return as nil (ARC magic) even though the _proxyDelegate still exists. It's really important to nil out
340- // super.delegate in this case because calls to _ASTableViewProxy will start failing and cause crashes.
278+ // super.delegate in this case because calls to ASCollectionViewProxy will start failing and cause crashes.
279+
280+ // Order is important here, the asyncDelegate must be callable while nilling super.delegate to avoid random crashes
281+ // in UIScrollViewAccessibility.
282+
283+ super.delegate = nil ;
341284
342285 if (asyncDelegate == nil ) {
343- // order is important here, the delegate must be callable while nilling super.delegate to avoid random crashes
344- // in UIScrollViewAccessibility.
345- super.delegate = nil ;
346286 _asyncDelegate = nil ;
347- _proxyDelegate = nil ;
287+ _proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc ] initWithTarget: nil interceptor: self ] ;
348288 _asyncDelegateImplementsInsetSection = NO ;
349289 } else {
350290 _asyncDelegate = asyncDelegate;
351- _proxyDelegate = [[_ASCollectionViewProxy alloc ] initWithTarget: _asyncDelegate interceptor: self ];
352- super.delegate = (id <UICollectionViewDelegate>)_proxyDelegate;
291+ _proxyDelegate = [[ASCollectionViewProxy alloc ] initWithTarget: _asyncDelegate interceptor: self ];
353292 _asyncDelegateImplementsInsetSection = ([_asyncDelegate respondsToSelector: @selector (collectionView:layout:insetForSectionAtIndex: )] ? 1 : 0 );
354293 }
355294
295+ super.delegate = (id <UICollectionViewDelegate>)_proxyDelegate;
296+
356297 [_layoutInspector didChangeCollectionViewDelegate: asyncDelegate];
357298}
358299
0 commit comments