Skip to content

Commit ca57059

Browse files
author
Scott Goodson
committed
New ASDelegateProxy class to unify logic for Table & Collection forwarding. Fix dealloc-during-animation crash.
1 parent 928c440 commit ca57059

14 files changed

Lines changed: 382 additions & 233 deletions

File tree

AsyncDisplayKit.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,10 @@
461461
DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; };
462462
DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; };
463463
DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; };
464+
DEC447B51C2B9DBC00C8CBD1 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC447B31C2B9DBC00C8CBD1 /* ASDelegateProxy.h */; };
465+
DEC447B61C2B9DBC00C8CBD1 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC447B31C2B9DBC00C8CBD1 /* ASDelegateProxy.h */; };
466+
DEC447B71C2B9DBC00C8CBD1 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC447B41C2B9DBC00C8CBD1 /* ASDelegateProxy.m */; };
467+
DEC447B81C2B9DBC00C8CBD1 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC447B41C2B9DBC00C8CBD1 /* ASDelegateProxy.m */; };
464468
DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
465469
DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
466470
DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; };
@@ -754,6 +758,8 @@
754758
D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = "<group>"; };
755759
D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = "<group>"; };
756760
DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = "<group>"; };
761+
DEC447B31C2B9DBC00C8CBD1 /* ASDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDelegateProxy.h; sourceTree = "<group>"; };
762+
DEC447B41C2B9DBC00C8CBD1 /* ASDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDelegateProxy.m; sourceTree = "<group>"; };
757763
DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = "<group>"; };
758764
DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = "<group>"; };
759765
EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1149,6 +1155,8 @@
11491155
25B171EA1C12242700508A7A /* Data Controller */ = {
11501156
isa = PBXGroup;
11511157
children = (
1158+
DEC447B31C2B9DBC00C8CBD1 /* ASDelegateProxy.h */,
1159+
DEC447B41C2B9DBC00C8CBD1 /* ASDelegateProxy.m */,
11521160
251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */,
11531161
251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */,
11541162
464052191A3F83C40061C0BA /* ASDataController.h */,
@@ -1264,6 +1272,7 @@
12641272
18C2ED7E1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */,
12651273
257754C01BEE458E00737CA5 /* ASTextNodeWordKerner.h in Headers */,
12661274
AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */,
1275+
DEC447B51C2B9DBC00C8CBD1 /* ASDelegateProxy.h in Headers */,
12671276
205F0E1D1B373A2C007741D0 /* ASCollectionViewLayoutController.h in Headers */,
12681277
AC3C4A541A113EEC00143C57 /* ASCollectionViewProtocols.h in Headers */,
12691278
058D0A49195D05CB00B7D73C /* ASControlNode+Subclasses.h in Headers */,
@@ -1432,6 +1441,7 @@
14321441
34EFC7791B701D3600AD841F /* ASLayoutSpecUtilities.h in Headers */,
14331442
B350625C1B010F070018CF92 /* ASLog.h in Headers */,
14341443
0442850E1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */,
1444+
DEC447B61C2B9DBC00C8CBD1 /* ASDelegateProxy.h in Headers */,
14351445
B35062041B010EFD0018CF92 /* ASMultiplexImageNode.h in Headers */,
14361446
DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */,
14371447
B35062241B010EFD0018CF92 /* ASMutableAttributedStringBuilder.h in Headers */,
@@ -1687,6 +1697,7 @@
16871697
0549634A1A1EA066000F8E56 /* ASBasicImageDownloader.mm in Sources */,
16881698
299DA1AA1A828D2900162D41 /* ASBatchContext.mm in Sources */,
16891699
AC6456091B0A335000CF11B8 /* ASCellNode.m in Sources */,
1700+
DEC447B71C2B9DBC00C8CBD1 /* ASDelegateProxy.m in Sources */,
16901701
ACF6ED1D1B17843500DA7C62 /* ASCenterLayoutSpec.mm in Sources */,
16911702
18C2ED801B9B7DE800F627B3 /* ASCollectionNode.m in Sources */,
16921703
92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */,
@@ -1816,6 +1827,7 @@
18161827
509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */,
18171828
254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */,
18181829
34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */,
1830+
DEC447B81C2B9DBC00C8CBD1 /* ASDelegateProxy.m in Sources */,
18191831
B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */,
18201832
B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */,
18211833
AC47D9421B3B891B00AAEE9D /* ASCellNode.m in Sources */,

AsyncDisplayKit.xcworkspace/contents.xcworkspacedata

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

AsyncDisplayKit/ASCollectionView.mm

Lines changed: 41 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
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"
@@ -22,87 +23,6 @@
2223
static const ASSizeRange kInvalidSizeRange = {CGSizeZero, CGSizeZero};
2324
static 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

Comments
 (0)