Skip to content

Commit dbb8c2e

Browse files
committed
Merge pull request facebookarchive#451 from eanagel/setDelegate-fix
Fix a crash in setAsyncDelegate: that happens randomly (UIScrollViewAccessibility or NSProxy)
2 parents f248dbd + d9acd7b commit dbb8c2e

2 files changed

Lines changed: 49 additions & 13 deletions

File tree

AsyncDisplayKit/ASCollectionView.mm

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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];

AsyncDisplayKit/ASTableView.mm

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,17 @@ - (instancetype)initWithTarget:(id<NSObject>)target interceptor:(ASTableView *)i
7777

7878
- (BOOL)respondsToSelector:(SEL)aSelector
7979
{
80+
ASDisplayNodeAssert(_target, @"target must not be nil"); // catch weak ref's being nilled early
81+
ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil");
82+
8083
return (_isInterceptedSelector(aSelector) || [_target respondsToSelector:aSelector]);
8184
}
8285

8386
- (id)forwardingTargetForSelector:(SEL)aSelector
8487
{
88+
ASDisplayNodeAssert(_target, @"target must not be nil"); // catch weak ref's being nilled early
89+
ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil");
90+
8591
if (_isInterceptedSelector(aSelector)) {
8692
return _interceptor;
8793
}
@@ -159,6 +165,12 @@ - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncD
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

@@ -175,13 +187,15 @@ - (void)setDelegate:(id<UITableViewDelegate>)delegate
175187

176188
- (void)setAsyncDataSource:(id<ASTableViewDataSource>)asyncDataSource
177189
{
178-
if (_asyncDataSource == asyncDataSource)
179-
return;
190+
// Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
191+
// the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource
192+
// will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to nil out
193+
// super.dataSource in this case because calls to _ASTableViewProxy will start failing and cause crashes.
180194

181195
if (asyncDataSource == nil) {
196+
super.dataSource = nil;
182197
_asyncDataSource = nil;
183198
_proxyDataSource = nil;
184-
super.dataSource = nil;
185199
} else {
186200
_asyncDataSource = asyncDataSource;
187201
_proxyDataSource = [[_ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self];
@@ -191,13 +205,17 @@ - (void)setAsyncDataSource:(id<ASTableViewDataSource>)asyncDataSource
191205

192206
- (void)setAsyncDelegate:(id<ASTableViewDelegate>)asyncDelegate
193207
{
194-
if (_asyncDelegate == asyncDelegate)
195-
return;
208+
// Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
209+
// the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate
210+
// will return as nil (ARC magic) even though the _proxyDelegate still exists. It's really important to nil out
211+
// super.delegate in this case because calls to _ASTableViewProxy will start failing and cause crashes.
196212

197213
if (asyncDelegate == nil) {
198-
_asyncDelegate = nil;
199-
_proxyDelegate = nil;
214+
// order is important here, the delegate must be callable while nilling super.delegate to avoid random crashes
215+
// in UIScrollViewAccessibility.
200216
super.delegate = nil;
217+
_asyncDelegate = nil;
218+
_proxyDelegate = nil;
201219
} else {
202220
_asyncDelegate = asyncDelegate;
203221
_proxyDelegate = [[_ASTableViewProxy alloc] initWithTarget:asyncDelegate interceptor:self];

0 commit comments

Comments
 (0)