@@ -78,11 +78,17 @@ - (instancetype)initWithTarget:(id<NSObject>)target interceptor:(ASCollectionVie
7878
7979- (BOOL )respondsToSelector : (SEL )aSelector
8080{
81+ ASDisplayNodeAssert (_target, @" target must not be nil" ); // catch weak ref's being nilled early
82+ ASDisplayNodeAssert (_interceptor, @" interceptor must not be nil" );
83+
8184 return (_isInterceptedSelector (aSelector) || [_target respondsToSelector: aSelector]);
8285}
8386
8487- (id )forwardingTargetForSelector : (SEL )aSelector
8588{
89+ ASDisplayNodeAssert (_target, @" target must not be nil" ); // catch weak ref's being nilled early
90+ ASDisplayNodeAssert (_interceptor, @" interceptor must not be nil" );
91+
8692 if (_isInterceptedSelector (aSelector)) {
8793 return _interceptor;
8894 }
@@ -159,6 +165,12 @@ - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionVi
159165 return self;
160166}
161167
168+ -(void )dealloc {
169+ // a little defense move here.
170+ super.delegate = nil ;
171+ super.dataSource = nil ;
172+ }
173+
162174#pragma mark -
163175#pragma mark Overrides.
164176
@@ -189,13 +201,15 @@ - (void)setDelegate:(id<UICollectionViewDelegate>)delegate
189201
190202- (void )setAsyncDataSource : (id <ASCollectionViewDataSource>)asyncDataSource
191203{
192- if (_asyncDataSource == asyncDataSource)
193- return ;
204+ // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
205+ // the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource
206+ // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to nil out
207+ // super.dataSource in this case because calls to _ASTableViewProxy will start failing and cause crashes.
194208
195209 if (asyncDataSource == nil ) {
210+ super.dataSource = nil ;
196211 _asyncDataSource = nil ;
197212 _proxyDataSource = nil ;
198- super.dataSource = nil ;
199213 } else {
200214 _asyncDataSource = asyncDataSource;
201215 _proxyDataSource = [[_ASCollectionViewProxy alloc ] initWithTarget: _asyncDataSource interceptor: self ];
@@ -205,13 +219,17 @@ - (void)setAsyncDataSource:(id<ASCollectionViewDataSource>)asyncDataSource
205219
206220- (void )setAsyncDelegate : (id <ASCollectionViewDelegate>)asyncDelegate
207221{
208- if (_asyncDelegate == asyncDelegate)
209- return ;
222+ // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
223+ // the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate
224+ // will return as nil (ARC magic) even though the _proxyDelegate still exists. It's really important to nil out
225+ // super.delegate in this case because calls to _ASTableViewProxy will start failing and cause crashes.
210226
211227 if (asyncDelegate == nil ) {
228+ // order is important here, the delegate must be callable while nilling super.delegate to avoid random crashes
229+ // in UIScrollViewAccessibility.
230+ super.delegate = nil ;
212231 _asyncDelegate = nil ;
213232 _proxyDelegate = nil ;
214- super.delegate = nil ;
215233 } else {
216234 _asyncDelegate = asyncDelegate;
217235 _proxyDelegate = [[_ASCollectionViewProxy alloc ] initWithTarget: _asyncDelegate interceptor: self ];
0 commit comments