New ASDelegateProxy class to unify logic for Table & Collection forwarding. Fix dealloc-during-animation crash.

This commit is contained in:
Scott Goodson
2015-12-23 20:01:52 -08:00
parent 928c440b4c
commit ca57059322
14 changed files with 382 additions and 233 deletions

View File

@@ -461,6 +461,10 @@
DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; }; DE040EF91C2B40AC004692FF /* ASCollectionViewFlowLayoutInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; settings = {ATTRIBUTES = (Public, ); }; };
DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; };
DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; };
DEC447B51C2B9DBC00C8CBD1 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC447B31C2B9DBC00C8CBD1 /* ASDelegateProxy.h */; };
DEC447B61C2B9DBC00C8CBD1 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DEC447B31C2B9DBC00C8CBD1 /* ASDelegateProxy.h */; };
DEC447B71C2B9DBC00C8CBD1 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC447B41C2B9DBC00C8CBD1 /* ASDelegateProxy.m */; };
DEC447B81C2B9DBC00C8CBD1 /* ASDelegateProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = DEC447B41C2B9DBC00C8CBD1 /* ASDelegateProxy.m */; };
DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; };
@@ -754,6 +758,8 @@
D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = "<group>"; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = "<group>"; };
D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = "<group>"; }; D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = "<group>"; };
DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = "<group>"; }; DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = "<group>"; };
DEC447B31C2B9DBC00C8CBD1 /* ASDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDelegateProxy.h; sourceTree = "<group>"; };
DEC447B41C2B9DBC00C8CBD1 /* ASDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDelegateProxy.m; sourceTree = "<group>"; };
DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = "<group>"; }; DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = "<group>"; };
DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = "<group>"; }; DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = "<group>"; };
EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1149,6 +1155,8 @@
25B171EA1C12242700508A7A /* Data Controller */ = { 25B171EA1C12242700508A7A /* Data Controller */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DEC447B31C2B9DBC00C8CBD1 /* ASDelegateProxy.h */,
DEC447B41C2B9DBC00C8CBD1 /* ASDelegateProxy.m */,
251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */, 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */,
251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */, 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */,
464052191A3F83C40061C0BA /* ASDataController.h */, 464052191A3F83C40061C0BA /* ASDataController.h */,
@@ -1264,6 +1272,7 @@
18C2ED7E1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */, 18C2ED7E1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */,
257754C01BEE458E00737CA5 /* ASTextNodeWordKerner.h in Headers */, 257754C01BEE458E00737CA5 /* ASTextNodeWordKerner.h in Headers */,
AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */, AC3C4A511A1139C100143C57 /* ASCollectionView.h in Headers */,
DEC447B51C2B9DBC00C8CBD1 /* ASDelegateProxy.h in Headers */,
205F0E1D1B373A2C007741D0 /* ASCollectionViewLayoutController.h in Headers */, 205F0E1D1B373A2C007741D0 /* ASCollectionViewLayoutController.h in Headers */,
AC3C4A541A113EEC00143C57 /* ASCollectionViewProtocols.h in Headers */, AC3C4A541A113EEC00143C57 /* ASCollectionViewProtocols.h in Headers */,
058D0A49195D05CB00B7D73C /* ASControlNode+Subclasses.h in Headers */, 058D0A49195D05CB00B7D73C /* ASControlNode+Subclasses.h in Headers */,
@@ -1432,6 +1441,7 @@
34EFC7791B701D3600AD841F /* ASLayoutSpecUtilities.h in Headers */, 34EFC7791B701D3600AD841F /* ASLayoutSpecUtilities.h in Headers */,
B350625C1B010F070018CF92 /* ASLog.h in Headers */, B350625C1B010F070018CF92 /* ASLog.h in Headers */,
0442850E1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */, 0442850E1BAA64EC00D16268 /* ASMultidimensionalArrayUtils.h in Headers */,
DEC447B61C2B9DBC00C8CBD1 /* ASDelegateProxy.h in Headers */,
B35062041B010EFD0018CF92 /* ASMultiplexImageNode.h in Headers */, B35062041B010EFD0018CF92 /* ASMultiplexImageNode.h in Headers */,
DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */, DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */,
B35062241B010EFD0018CF92 /* ASMutableAttributedStringBuilder.h in Headers */, B35062241B010EFD0018CF92 /* ASMutableAttributedStringBuilder.h in Headers */,
@@ -1687,6 +1697,7 @@
0549634A1A1EA066000F8E56 /* ASBasicImageDownloader.mm in Sources */, 0549634A1A1EA066000F8E56 /* ASBasicImageDownloader.mm in Sources */,
299DA1AA1A828D2900162D41 /* ASBatchContext.mm in Sources */, 299DA1AA1A828D2900162D41 /* ASBatchContext.mm in Sources */,
AC6456091B0A335000CF11B8 /* ASCellNode.m in Sources */, AC6456091B0A335000CF11B8 /* ASCellNode.m in Sources */,
DEC447B71C2B9DBC00C8CBD1 /* ASDelegateProxy.m in Sources */,
ACF6ED1D1B17843500DA7C62 /* ASCenterLayoutSpec.mm in Sources */, ACF6ED1D1B17843500DA7C62 /* ASCenterLayoutSpec.mm in Sources */,
18C2ED801B9B7DE800F627B3 /* ASCollectionNode.m in Sources */, 18C2ED801B9B7DE800F627B3 /* ASCollectionNode.m in Sources */,
92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */, 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */,
@@ -1816,6 +1827,7 @@
509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */, 509E68621B3AEDA5009B9150 /* ASAbstractLayoutController.mm in Sources */,
254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */, 254C6B861BF94F8A003EC431 /* ASTextKitContext.mm in Sources */,
34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */, 34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */,
DEC447B81C2B9DBC00C8CBD1 /* ASDelegateProxy.m in Sources */,
B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */, B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */,
B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */, B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */,
AC47D9421B3B891B00AAEE9D /* ASCellNode.m in Sources */, AC47D9421B3B891B00AAEE9D /* ASCellNode.m in Sources */,

View File

@@ -1,6 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Workspace <Workspace
version = "1.0"> version = "1.0">
<FileRef
location = "group:AsyncDisplayKit/Details/ASDelegateProxy.h">
</FileRef>
<FileRef
location = "group:AsyncDisplayKit/Details/ASDelegateProxy.m">
</FileRef>
<FileRef <FileRef
location = "group:AsyncDisplayKit.xcodeproj"> location = "group:AsyncDisplayKit.xcodeproj">
</FileRef> </FileRef>

View File

@@ -8,6 +8,7 @@
#import "ASAssert.h" #import "ASAssert.h"
#import "ASBatchFetching.h" #import "ASBatchFetching.h"
#import "ASDelegateProxy.h"
#import "ASCollectionView.h" #import "ASCollectionView.h"
#import "ASCollectionNode.h" #import "ASCollectionNode.h"
#import "ASCollectionDataController.h" #import "ASCollectionDataController.h"
@@ -22,87 +23,6 @@ static const NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimation
static const ASSizeRange kInvalidSizeRange = {CGSizeZero, CGSizeZero}; static const ASSizeRange kInvalidSizeRange = {CGSizeZero, CGSizeZero};
static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
#pragma mark -
#pragma mark Proxying.
/**
* ASCollectionView intercepts and/or overrides a few of UICollectionView's critical data source and delegate methods.
*
* Any selector included in this function *MUST* be implemented by ASCollectionView.
*/
static BOOL _isInterceptedSelector(SEL sel)
{
return (
// handled by ASCollectionView node<->cell machinery
sel == @selector(collectionView:cellForItemAtIndexPath:) ||
sel == @selector(collectionView:layout:sizeForItemAtIndexPath:) ||
sel == @selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) ||
// handled by ASRangeController
sel == @selector(numberOfSectionsInCollectionView:) ||
sel == @selector(collectionView:numberOfItemsInSection:) ||
// used for ASRangeController visibility updates
sel == @selector(collectionView:willDisplayCell:forItemAtIndexPath:) ||
sel == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) ||
// used for batch fetching API
sel == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)
);
}
/**
* Stand-in for UICollectionViewDataSource and UICollectionViewDelegate. Any method calls we intercept are routed to ASCollectionView;
* everything else leaves AsyncDisplayKit safely and arrives at the original intended data source and delegate.
*/
@interface _ASCollectionViewProxy : NSProxy
- (instancetype)initWithTarget:(id<NSObject>)target interceptor:(ASCollectionView *)interceptor;
@end
@implementation _ASCollectionViewProxy {
id<NSObject> __weak _target;
ASCollectionView * __weak _interceptor;
}
- (instancetype)initWithTarget:(id<NSObject>)target interceptor:(ASCollectionView *)interceptor
{
// -[NSProxy init] is undefined
if (!self) {
return nil;
}
ASDisplayNodeAssert(target, @"target must not be nil");
ASDisplayNodeAssert(interceptor, @"interceptor must not be nil");
_target = target;
_interceptor = interceptor;
return self;
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
ASDisplayNodeAssert(_target, @"target must not be nil"); // catch weak ref's being nilled early
ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil");
return (_isInterceptedSelector(aSelector) || [_target respondsToSelector:aSelector]);
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
ASDisplayNodeAssert(_target, @"target must not be nil"); // catch weak ref's being nilled early
ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil");
if (_isInterceptedSelector(aSelector)) {
return _interceptor;
}
return [_target respondsToSelector:aSelector] ? _target : nil;
}
@end
#pragma mark - #pragma mark -
#pragma mark ASCellNode<->UICollectionViewCell bridging. #pragma mark ASCellNode<->UICollectionViewCell bridging.
@@ -138,9 +58,9 @@ static BOOL _isInterceptedSelector(SEL sel)
#pragma mark - #pragma mark -
#pragma mark ASCollectionView. #pragma mark ASCollectionView.
@interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASDataControllerSource, ASCellNodeLayoutDelegate> { @interface ASCollectionView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASDataControllerSource, ASCellNodeLayoutDelegate, ASDelegateProxyInterceptor> {
_ASCollectionViewProxy *_proxyDataSource; ASCollectionViewProxy *_proxyDataSource;
_ASCollectionViewProxy *_proxyDelegate; ASCollectionViewProxy *_proxyDelegate;
ASCollectionDataController *_dataController; ASCollectionDataController *_dataController;
ASRangeController *_rangeController; ASRangeController *_rangeController;
@@ -155,6 +75,7 @@ static BOOL _isInterceptedSelector(SEL sel)
BOOL _collectionViewLayoutImplementsInsetSection; BOOL _collectionViewLayoutImplementsInsetSection;
BOOL _asyncDataSourceImplementsConstrainedSizeForNode; BOOL _asyncDataSourceImplementsConstrainedSizeForNode;
BOOL _queuedNodeSizeUpdate; BOOL _queuedNodeSizeUpdate;
BOOL _isDeallocating;
ASBatchContext *_batchContext; ASBatchContext *_batchContext;
@@ -244,6 +165,12 @@ static BOOL _isInterceptedSelector(SEL sel)
_layoutInspector = [self flowLayoutInspector]; _layoutInspector = [self flowLayoutInspector];
} }
_proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
super.delegate = (id<UICollectionViewDelegate>)_proxyDelegate;
_proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
super.dataSource = (id<UICollectionViewDataSource>)_proxyDataSource;
_registeredSupplementaryKinds = [NSMutableSet set]; _registeredSupplementaryKinds = [NSMutableSet set];
self.backgroundColor = [UIColor whiteColor]; self.backgroundColor = [UIColor whiteColor];
@@ -255,10 +182,10 @@ static BOOL _isInterceptedSelector(SEL sel)
- (void)dealloc - (void)dealloc
{ {
// Sometimes the UIKit classes can call back to their delegate even during deallocation. // Sometimes the UIKit classes can call back to their delegate even during deallocation, due to animation completion blocks etc.
// This bug might be iOS 7-specific. _isDeallocating = YES;
super.delegate = nil; [self setAsyncDelegate:nil];
super.dataSource = nil; [self setAsyncDataSource:nil];
} }
/** /**
@@ -312,24 +239,35 @@ static BOOL _isInterceptedSelector(SEL sel)
ASDisplayNodeAssert(delegate == nil, @"ASCollectionView uses asyncDelegate, not UICollectionView's delegate property."); ASDisplayNodeAssert(delegate == nil, @"ASCollectionView uses asyncDelegate, not UICollectionView's delegate property.");
} }
- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy
{
if (proxy == _proxyDelegate) {
[self setAsyncDelegate:nil];
} else if (proxy == _proxyDataSource) {
[self setAsyncDataSource:nil];
}
}
- (void)setAsyncDataSource:(id<ASCollectionViewDataSource>)asyncDataSource - (void)setAsyncDataSource:(id<ASCollectionViewDataSource>)asyncDataSource
{ {
// Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
// the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource // the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource
// will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to nil out // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to nil out
// super.dataSource in this case because calls to _ASTableViewProxy will start failing and cause crashes. // super.dataSource in this case because calls to ASCollectionViewProxy will start failing and cause crashes.
super.dataSource = nil;
if (asyncDataSource == nil) { if (asyncDataSource == nil) {
super.dataSource = nil;
_asyncDataSource = nil; _asyncDataSource = nil;
_proxyDataSource = nil; _proxyDataSource = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
_asyncDataSourceImplementsConstrainedSizeForNode = NO; _asyncDataSourceImplementsConstrainedSizeForNode = NO;
} else { } else {
_asyncDataSource = asyncDataSource; _asyncDataSource = asyncDataSource;
_proxyDataSource = [[_ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; _proxyDataSource = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self];
super.dataSource = (id<UICollectionViewDataSource>)_proxyDataSource;
_asyncDataSourceImplementsConstrainedSizeForNode = ([_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)] ? 1 : 0); _asyncDataSourceImplementsConstrainedSizeForNode = ([_asyncDataSource respondsToSelector:@selector(collectionView:constrainedSizeForNodeAtIndexPath:)] ? 1 : 0);
} }
super.dataSource = (id<UICollectionViewDataSource>)_proxyDataSource;
} }
- (void)setAsyncDelegate:(id<ASCollectionViewDelegate>)asyncDelegate - (void)setAsyncDelegate:(id<ASCollectionViewDelegate>)asyncDelegate
@@ -337,22 +275,25 @@ static BOOL _isInterceptedSelector(SEL sel)
// Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
// the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate // the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate
// will return as nil (ARC magic) even though the _proxyDelegate still exists. It's really important to nil out // will return as nil (ARC magic) even though the _proxyDelegate still exists. It's really important to nil out
// super.delegate in this case because calls to _ASTableViewProxy will start failing and cause crashes. // super.delegate in this case because calls to ASCollectionViewProxy will start failing and cause crashes.
// Order is important here, the asyncDelegate must be callable while nilling super.delegate to avoid random crashes
// in UIScrollViewAccessibility.
super.delegate = nil;
if (asyncDelegate == nil) { if (asyncDelegate == nil) {
// order is important here, the delegate must be callable while nilling super.delegate to avoid random crashes
// in UIScrollViewAccessibility.
super.delegate = nil;
_asyncDelegate = nil; _asyncDelegate = nil;
_proxyDelegate = nil; _proxyDelegate = _isDeallocating ? nil : [[ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
_asyncDelegateImplementsInsetSection = NO; _asyncDelegateImplementsInsetSection = NO;
} else { } else {
_asyncDelegate = asyncDelegate; _asyncDelegate = asyncDelegate;
_proxyDelegate = [[_ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self]; _proxyDelegate = [[ASCollectionViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self];
super.delegate = (id<UICollectionViewDelegate>)_proxyDelegate;
_asyncDelegateImplementsInsetSection = ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)] ? 1 : 0); _asyncDelegateImplementsInsetSection = ([_asyncDelegate respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)] ? 1 : 0);
} }
super.delegate = (id<UICollectionViewDelegate>)_proxyDelegate;
[_layoutInspector didChangeCollectionViewDelegate:asyncDelegate]; [_layoutInspector didChangeCollectionViewDelegate:asyncDelegate];
} }

View File

@@ -13,6 +13,7 @@
#import "ASBatchFetching.h" #import "ASBatchFetching.h"
#import "ASChangeSetDataController.h" #import "ASChangeSetDataController.h"
#import "ASCollectionViewLayoutController.h" #import "ASCollectionViewLayoutController.h"
#import "ASDelegateProxy.h"
#import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+FrameworkPrivate.h"
#import "ASInternalHelpers.h" #import "ASInternalHelpers.h"
#import "ASLayout.h" #import "ASLayout.h"
@@ -24,87 +25,6 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
//#define LOG(...) NSLog(__VA_ARGS__) //#define LOG(...) NSLog(__VA_ARGS__)
#define LOG(...) #define LOG(...)
#pragma mark -
#pragma mark Proxying.
/**
* ASTableView intercepts and/or overrides a few of UITableView's critical data source and delegate methods.
*
* Any selector included in this function *MUST* be implemented by ASTableView.
*/
static BOOL _isInterceptedSelector(SEL sel)
{
return (
// handled by ASTableView node<->cell machinery
sel == @selector(tableView:cellForRowAtIndexPath:) ||
sel == @selector(tableView:heightForRowAtIndexPath:) ||
// handled by ASRangeController
sel == @selector(numberOfSectionsInTableView:) ||
sel == @selector(tableView:numberOfRowsInSection:) ||
// used for ASRangeController visibility updates
sel == @selector(tableView:willDisplayCell:forRowAtIndexPath:) ||
sel == @selector(tableView:didEndDisplayingCell:forRowAtIndexPath:) ||
// used for batch fetching API
sel == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)
);
}
/**
* Stand-in for UITableViewDataSource and UITableViewDelegate. Any method calls we intercept are routed to ASTableView;
* everything else leaves AsyncDisplayKit safely and arrives at the original intended data source and delegate.
*/
@interface _ASTableViewProxy : NSProxy
- (instancetype)initWithTarget:(id<NSObject>)target interceptor:(ASTableView *)interceptor;
@end
@implementation _ASTableViewProxy {
id<NSObject> __weak _target;
ASTableView * __weak _interceptor;
}
- (instancetype)initWithTarget:(id<NSObject>)target interceptor:(ASTableView *)interceptor
{
// -[NSProxy init] is undefined
if (!self) {
return nil;
}
ASDisplayNodeAssert(target, @"target must not be nil");
ASDisplayNodeAssert(interceptor, @"interceptor must not be nil");
_target = target;
_interceptor = interceptor;
return self;
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
ASDisplayNodeAssert(_target, @"target must not be nil"); // catch weak ref's being nilled early
ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil");
return (_isInterceptedSelector(aSelector) || [_target respondsToSelector:aSelector]);
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
ASDisplayNodeAssert(_target, @"target must not be nil"); // catch weak ref's being nilled early
ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil");
if (_isInterceptedSelector(aSelector)) {
return _interceptor;
}
return [_target respondsToSelector:aSelector] ? _target : nil;
}
@end
#pragma mark - #pragma mark -
#pragma mark ASCellNode<->UITableViewCell bridging. #pragma mark ASCellNode<->UITableViewCell bridging.
@@ -156,13 +76,12 @@ static BOOL _isInterceptedSelector(SEL sel)
@end @end
#pragma mark - #pragma mark -
#pragma mark ASTableView #pragma mark ASTableView
@interface ASTableView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASDataControllerSource, _ASTableViewCellDelegate, ASCellNodeLayoutDelegate> { @interface ASTableView () <ASRangeControllerDataSource, ASRangeControllerDelegate, ASDataControllerSource, _ASTableViewCellDelegate, ASCellNodeLayoutDelegate, ASDelegateProxyInterceptor> {
_ASTableViewProxy *_proxyDataSource; ASTableViewProxy *_proxyDataSource;
_ASTableViewProxy *_proxyDelegate; ASTableViewProxy *_proxyDelegate;
ASFlowLayoutController *_layoutController; ASFlowLayoutController *_layoutController;
@@ -180,6 +99,7 @@ static BOOL _isInterceptedSelector(SEL sel)
CGFloat _nodesConstrainedWidth; CGFloat _nodesConstrainedWidth;
BOOL _ignoreNodesConstrainedWidthChange; BOOL _ignoreNodesConstrainedWidthChange;
BOOL _queuedNodeHeightUpdate; BOOL _queuedNodeHeightUpdate;
BOOL _isDeallocating;
} }
@property (atomic, assign) BOOL asyncDataSourceLocked; @property (atomic, assign) BOOL asyncDataSourceLocked;
@@ -224,6 +144,12 @@ static BOOL _isInterceptedSelector(SEL sel)
// If the initial size is 0, expect a size change very soon which is part of the initial configuration // If the initial size is 0, expect a size change very soon which is part of the initial configuration
// and should not trigger a relayout. // and should not trigger a relayout.
_ignoreNodesConstrainedWidthChange = (_nodesConstrainedWidth == 0); _ignoreNodesConstrainedWidthChange = (_nodesConstrainedWidth == 0);
_proxyDelegate = [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self];
super.delegate = (id<UITableViewDelegate>)_proxyDelegate;
_proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self];
super.dataSource = (id<UITableViewDataSource>)_proxyDataSource;
[self registerClass:_ASTableViewCell.class forCellReuseIdentifier:kCellReuseIdentifier]; [self registerClass:_ASTableViewCell.class forCellReuseIdentifier:kCellReuseIdentifier];
} }
@@ -265,9 +191,9 @@ static BOOL _isInterceptedSelector(SEL sel)
- (void)dealloc - (void)dealloc
{ {
// Sometimes the UIKit classes can call back to their delegate even during deallocation. // Sometimes the UIKit classes can call back to their delegate even during deallocation.
// This bug might be iOS 7-specific. _isDeallocating = YES;
super.delegate = nil; [self setAsyncDelegate:nil];
super.dataSource = nil; [self setAsyncDataSource:nil];
} }
#pragma mark - #pragma mark -
@@ -290,17 +216,19 @@ static BOOL _isInterceptedSelector(SEL sel)
// Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
// the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource // the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource
// will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to nil out // will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to nil out
// super.dataSource in this case because calls to _ASTableViewProxy will start failing and cause crashes. // super.dataSource in this case because calls to ASTableViewProxy will start failing and cause crashes.
super.dataSource = nil;
if (asyncDataSource == nil) { if (asyncDataSource == nil) {
super.dataSource = nil;
_asyncDataSource = nil; _asyncDataSource = nil;
_proxyDataSource = nil; _proxyDataSource = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self];
} else { } else {
_asyncDataSource = asyncDataSource; _asyncDataSource = asyncDataSource;
_proxyDataSource = [[_ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self]; _proxyDataSource = [[ASTableViewProxy alloc] initWithTarget:_asyncDataSource interceptor:self];
super.dataSource = (id<UITableViewDataSource>)_proxyDataSource;
} }
super.dataSource = (id<UITableViewDataSource>)_proxyDataSource;
} }
- (void)setAsyncDelegate:(id<ASTableViewDelegate>)asyncDelegate - (void)setAsyncDelegate:(id<ASTableViewDelegate>)asyncDelegate
@@ -308,18 +236,30 @@ static BOOL _isInterceptedSelector(SEL sel)
// Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle // Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
// the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate // the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate
// will return as nil (ARC magic) even though the _proxyDelegate still exists. It's really important to nil out // will return as nil (ARC magic) even though the _proxyDelegate still exists. It's really important to nil out
// super.delegate in this case because calls to _ASTableViewProxy will start failing and cause crashes. // super.delegate in this case because calls to ASTableViewProxy will start failing and cause crashes.
// Order is important here, the asyncDelegate must be callable while nilling super.delegate to avoid random crashes
// in UIScrollViewAccessibility.
super.delegate = nil;
if (asyncDelegate == nil) { if (asyncDelegate == nil) {
// order is important here, the delegate must be callable while nilling super.delegate to avoid random crashes
// in UIScrollViewAccessibility.
super.delegate = nil;
_asyncDelegate = nil; _asyncDelegate = nil;
_proxyDelegate = nil; _proxyDelegate = _isDeallocating ? nil : [[ASTableViewProxy alloc] initWithTarget:nil interceptor:self];
} else { } else {
_asyncDelegate = asyncDelegate; _asyncDelegate = asyncDelegate;
_proxyDelegate = [[_ASTableViewProxy alloc] initWithTarget:asyncDelegate interceptor:self]; _proxyDelegate = [[ASTableViewProxy alloc] initWithTarget:_asyncDelegate interceptor:self];
super.delegate = (id<UITableViewDelegate>)_proxyDelegate; }
super.delegate = (id<UITableViewDelegate>)_proxyDelegate;
}
- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy
{
if (proxy == _proxyDelegate) {
[self setAsyncDelegate:nil];
} else if (proxy == _proxyDataSource) {
[self setAsyncDataSource:nil];
} }
} }

View File

@@ -0,0 +1,51 @@
/* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <Foundation/Foundation.h>
@class ASDelegateProxy;
@protocol ASDelegateProxyInterceptor
@required
// Called if the target object is discovered to be nil if it had been non-nil at init time.
// This happens if the object is deallocated, because the proxy must maintain a weak reference to avoid cycles.
// Though the target object may become nil, the interceptor must not; it is assumed the interceptor owns the proxy.
- (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy;
@end
/**
* Stand-in for delegates like UITableView or UICollectionView's delegate / dataSource.
* Any selectors flagged by "interceptsSelector" are routed to the interceptor object and are not delivered to the target.
* Everything else leaves AsyncDisplayKit safely and arrives at the original target object.
*/
@interface ASDelegateProxy : NSProxy
- (instancetype)initWithTarget:(id<NSObject>)target interceptor:(id <ASDelegateProxyInterceptor>)interceptor;
// This method must be overridden by a subclass.
- (BOOL)interceptsSelector:(SEL)selector;
@end
/**
* ASTableView intercepts and/or overrides a few of UITableView's critical data source and delegate methods.
*
* Any selector included in this function *MUST* be implemented by ASTableView.
*/
@interface ASTableViewProxy : ASDelegateProxy
@end
/**
* ASCollectionView intercepts and/or overrides a few of UICollectionView's critical data source and delegate methods.
*
* Any selector included in this function *MUST* be implemented by ASCollectionView.
*/
@interface ASCollectionViewProxy : ASDelegateProxy
@end

View File

@@ -0,0 +1,117 @@
/* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "ASDelegateProxy.h"
#import "ASTableView.h"
#import "ASCollectionView.h"
#import "ASAssert.h"
@implementation ASTableViewProxy
- (BOOL)interceptsSelector:(SEL)selector
{
return (
// handled by ASTableView node<->cell machinery
selector == @selector(tableView:cellForRowAtIndexPath:) ||
selector == @selector(tableView:heightForRowAtIndexPath:) ||
// handled by ASRangeController
selector == @selector(numberOfSectionsInTableView:) ||
selector == @selector(tableView:numberOfRowsInSection:) ||
// used for ASRangeController visibility updates
selector == @selector(tableView:willDisplayCell:forRowAtIndexPath:) ||
selector == @selector(tableView:didEndDisplayingCell:forRowAtIndexPath:) ||
// used for batch fetching API
selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)
);
}
@end
@implementation ASCollectionViewProxy
- (BOOL)interceptsSelector:(SEL)selector
{
return (
// handled by ASCollectionView node<->cell machinery
selector == @selector(collectionView:cellForItemAtIndexPath:) ||
selector == @selector(collectionView:layout:sizeForItemAtIndexPath:) ||
selector == @selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:) ||
// handled by ASRangeController
selector == @selector(numberOfSectionsInCollectionView:) ||
selector == @selector(collectionView:numberOfItemsInSection:) ||
// used for ASRangeController visibility updates
selector == @selector(collectionView:willDisplayCell:forItemAtIndexPath:) ||
selector == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) ||
// used for batch fetching API
selector == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)
);
}
@end
@implementation ASDelegateProxy {
id <NSObject> __weak _target;
id <ASDelegateProxyInterceptor> __weak _interceptor;
}
- (instancetype)initWithTarget:(id <NSObject>)target interceptor:(id <ASDelegateProxyInterceptor>)interceptor
{
// -[NSProxy init] is undefined
if (!self) {
return nil;
}
ASDisplayNodeAssert(interceptor, @"interceptor must not be nil");
_target = target ? : [NSNull null];
_interceptor = interceptor;
return self;
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil");
if ([self interceptsSelector:aSelector]) {
return YES;
} else {
// Also return NO if _target has become nil due to zeroing weak reference (or placeholder initialization).
return [_target respondsToSelector:aSelector];
}
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
ASDisplayNodeAssert(_interceptor, @"interceptor must not be nil");
if ([self interceptsSelector:aSelector]) {
return _interceptor;
} else {
if (_target) {
return [_target respondsToSelector:aSelector] ? _target : nil;
} else {
[_interceptor proxyTargetHasDeallocated:self];
return nil;
}
}
}
- (BOOL)interceptsSelector:(SEL)selector
{
ASDisplayNodeAssert(NO, @"This method must be overridden by subclasses.");
return NO;
}
@end

View File

@@ -11,45 +11,38 @@
#import <AsyncDisplayKit/ASBasicImageDownloader.h> #import <AsyncDisplayKit/ASBasicImageDownloader.h>
// Z in the name to delay running until after the test instance is operating normally. // Z in the name to delay running until after the test instance is operating normally.
@interface ASZBasicImageDownloaderTests : XCTestCase @interface ASBasicImageDownloaderTests : XCTestCase
@end @end
@implementation ASZBasicImageDownloaderTests @implementation ASBasicImageDownloaderTests
- (void)testAsynchronouslyDownloadTheSameURLTwice - (void)testAsynchronouslyDownloadTheSameURLTwice
{ {
ASBasicImageDownloader *downloader = [ASBasicImageDownloader sharedImageDownloader]; ASBasicImageDownloader *downloader = [ASBasicImageDownloader sharedImageDownloader];
NSURL *URL = [NSURL URLWithString:@"http://wrongPath/wrongResource.png"]; NSURL *URL = [NSURL URLWithString:@"http://wrongPath/wrongResource.png"];
dispatch_group_t group = dispatch_group_create();
__block BOOL firstDone = NO; __block BOOL firstDone = NO;
dispatch_group_enter(group);
[downloader downloadImageWithURL:URL [downloader downloadImageWithURL:URL
callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
downloadProgressBlock:nil downloadProgressBlock:nil
completion:^(CGImageRef image, NSError *error) { completion:^(CGImageRef image, NSError *error) {
firstDone = YES; firstDone = YES;
dispatch_group_leave(group);
}]; }];
__block BOOL secondDone = NO; __block BOOL secondDone = NO;
dispatch_group_enter(group);
[downloader downloadImageWithURL:URL [downloader downloadImageWithURL:URL
callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
downloadProgressBlock:nil downloadProgressBlock:nil
completion:^(CGImageRef image, NSError *error) { completion:^(CGImageRef image, NSError *error) {
secondDone = YES; secondDone = YES;
dispatch_group_leave(group);
}]; }];
XCTAssert(0 == dispatch_group_wait(group, dispatch_time(0, 10 * 1000000000)), @"URL loading takes too long"); sleep(3);
XCTAssert(firstDone && secondDone, @"Not all ASBasicImageDownloader completion handlers have been called after 3 seconds");
XCTAssert(firstDone && secondDone, @"Not all handlers has been called");
} }
@end @end

View File

@@ -15,6 +15,7 @@
AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A691A11F47200143C57 /* ViewController.m */; }; AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = AC3C4A691A11F47200143C57 /* ViewController.m */; };
AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AC3C4A8D1A11F80C00143C57 /* Images.xcassets */; }; AC3C4A8E1A11F80C00143C57 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AC3C4A8D1A11F80C00143C57 /* Images.xcassets */; };
FABD6D156A3EB118497E5CE6 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F02BAF78E68BC56FD8C161B7 /* libPods.a */; }; FABD6D156A3EB118497E5CE6 /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F02BAF78E68BC56FD8C161B7 /* libPods.a */; };
FC3FCA801C2B1564009F6D6D /* PresentingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = FC3FCA7F1C2B1564009F6D6D /* PresentingViewController.m */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
@@ -34,6 +35,8 @@
AC3C4A8D1A11F80C00143C57 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; }; AC3C4A8D1A11F80C00143C57 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
CD1ABB23007FEDB31D8C1978 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = "<group>"; }; CD1ABB23007FEDB31D8C1978 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = "<group>"; };
F02BAF78E68BC56FD8C161B7 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; F02BAF78E68BC56FD8C161B7 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; };
FC3FCA7E1C2B1564009F6D6D /* PresentingViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PresentingViewController.h; sourceTree = "<group>"; };
FC3FCA7F1C2B1564009F6D6D /* PresentingViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PresentingViewController.m; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -82,6 +85,8 @@
AC3C4A661A11F47200143C57 /* AppDelegate.m */, AC3C4A661A11F47200143C57 /* AppDelegate.m */,
AC3C4A681A11F47200143C57 /* ViewController.h */, AC3C4A681A11F47200143C57 /* ViewController.h */,
AC3C4A691A11F47200143C57 /* ViewController.m */, AC3C4A691A11F47200143C57 /* ViewController.m */,
FC3FCA7E1C2B1564009F6D6D /* PresentingViewController.h */,
FC3FCA7F1C2B1564009F6D6D /* PresentingViewController.m */,
AC3C4A8D1A11F80C00143C57 /* Images.xcassets */, AC3C4A8D1A11F80C00143C57 /* Images.xcassets */,
AC3C4A611A11F47200143C57 /* Supporting Files */, AC3C4A611A11F47200143C57 /* Supporting Files */,
9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */, 9B92C87F1BC17D3000EE46B2 /* SupplementaryNode.h */,
@@ -125,7 +130,6 @@
AC3C4A5B1A11F47200143C57 /* Frameworks */, AC3C4A5B1A11F47200143C57 /* Frameworks */,
AC3C4A5C1A11F47200143C57 /* Resources */, AC3C4A5C1A11F47200143C57 /* Resources */,
A6902C454C7661D0D277AC62 /* Copy Pods Resources */, A6902C454C7661D0D277AC62 /* Copy Pods Resources */,
EC37EEC9933F5786936BFE7C /* Embed Pods Frameworks */,
); );
buildRules = ( buildRules = (
); );
@@ -196,21 +200,6 @@
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
EC37EEC9933F5786936BFE7C /* Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */ = { F868CFBB21824CC9521B6588 /* Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@@ -236,6 +225,7 @@
25FDEC921BF31EE700CEB123 /* ItemNode.m in Sources */, 25FDEC921BF31EE700CEB123 /* ItemNode.m in Sources */,
AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */, AC3C4A6A1A11F47200143C57 /* ViewController.m in Sources */,
9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */, 9B92C8811BC17D3000EE46B2 /* SupplementaryNode.m in Sources */,
FC3FCA801C2B1564009F6D6D /* PresentingViewController.m in Sources */,
AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */, AC3C4A671A11F47200143C57 /* AppDelegate.m in Sources */,
AC3C4A641A11F47200143C57 /* main.m in Sources */, AC3C4A641A11F47200143C57 /* main.m in Sources */,
); );

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Sample.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -11,6 +11,8 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#define SIMULATE_WEB_RESPONSE 0
@interface AppDelegate : UIResponder <UIApplicationDelegate> @interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window; @property (strong, nonatomic) UIWindow *window;

View File

@@ -11,6 +11,7 @@
#import "AppDelegate.h" #import "AppDelegate.h"
#import "PresentingViewController.h"
#import "ViewController.h" #import "ViewController.h"
@implementation AppDelegate @implementation AppDelegate
@@ -31,10 +32,14 @@
- (void)pushNewViewControllerAnimated:(BOOL)animated - (void)pushNewViewControllerAnimated:(BOOL)animated
{ {
UINavigationController *navController = (UINavigationController *)self.window.rootViewController; UINavigationController *navController = (UINavigationController *)self.window.rootViewController;
#if SIMULATE_WEB_RESPONSE
UIViewController *viewController = [[PresentingViewController alloc] init];
#else
UIViewController *viewController = [[ViewController alloc] init]; UIViewController *viewController = [[ViewController alloc] init];
viewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Push Another Copy" style:UIBarButtonItemStylePlain target:self action:@selector(pushNewViewController)]; viewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Push Another Copy" style:UIBarButtonItemStylePlain target:self action:@selector(pushNewViewController)];
#endif
[navController pushViewController:viewController animated:animated]; [navController pushViewController:viewController animated:animated];
} }

View File

@@ -0,0 +1,13 @@
//
// PresentingViewController.h
// Sample
//
// Created by Tom King on 12/23/15.
// Copyright © 2015 Facebook. All rights reserved.
//
#import <UIKit/UIKit.h>
@interface PresentingViewController : UIViewController
@end

View File

@@ -0,0 +1,30 @@
//
// PresentingViewController.m
// Sample
//
// Created by Tom King on 12/23/15.
// Copyright © 2015 Facebook. All rights reserved.
//
#import "PresentingViewController.h"
#import "ViewController.h"
@interface PresentingViewController ()
@end
@implementation PresentingViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Push Details" style:UIBarButtonItemStylePlain target:self action:@selector(pushNewViewController)];
}
- (void)pushNewViewController
{
ViewController *controller = [[ViewController alloc] init];
[self.navigationController pushViewController:controller animated:true];
}
@end

View File

@@ -18,6 +18,7 @@
@interface ViewController () <ASCollectionViewDataSource, ASCollectionViewDelegateFlowLayout> @interface ViewController () <ASCollectionViewDataSource, ASCollectionViewDelegateFlowLayout>
{ {
ASCollectionView *_collectionView; ASCollectionView *_collectionView;
NSArray *_data;
} }
@end @end
@@ -37,7 +38,7 @@
layout.headerReferenceSize = CGSizeMake(50.0, 50.0); layout.headerReferenceSize = CGSizeMake(50.0, 50.0);
layout.footerReferenceSize = CGSizeMake(50.0, 50.0); layout.footerReferenceSize = CGSizeMake(50.0, 50.0);
_collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout asyncDataFetching:YES]; _collectionView = [[ASCollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout];
_collectionView.asyncDataSource = self; _collectionView.asyncDataSource = self;
_collectionView.asyncDelegate = self; _collectionView.asyncDelegate = self;
_collectionView.backgroundColor = [UIColor whiteColor]; _collectionView.backgroundColor = [UIColor whiteColor];
@@ -45,8 +46,10 @@
[_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; [_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader];
[_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter]; [_collectionView registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter];
#if !SIMULATE_WEB_RESPONSE
self.navigationItem.leftItemsSupplementBackButton = YES; self.navigationItem.leftItemsSupplementBackButton = YES;
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(reloadTapped)]; self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(reloadTapped)];
#endif
return self; return self;
} }
@@ -56,6 +59,31 @@
[super viewDidLoad]; [super viewDidLoad];
[self.view addSubview:_collectionView]; [self.view addSubview:_collectionView];
#if SIMULATE_WEB_RESPONSE
__weak typeof(self) weakSelf = self;
void(^mockWebService)() = ^{
NSLog(@"ViewController \"got data from a web service\"");
ViewController *strongSelf = weakSelf;
if (strongSelf != nil)
{
NSLog(@"ViewController is not nil");
strongSelf->_data = [[NSArray alloc] init];
[strongSelf->_collectionView performBatchUpdates:^{
[strongSelf->_collectionView insertSections:[[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, 100)]];
} completion:nil];
NSLog(@"ViewController finished updating collectionView");
}
else {
NSLog(@"ViewController is nil - won't update collectionView");
}
};
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), mockWebService);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.navigationController popViewControllerAnimated:YES];
});
#endif
} }
- (void)viewWillLayoutSubviews - (void)viewWillLayoutSubviews
@@ -101,7 +129,11 @@
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{ {
#if SIMULATE_WEB_RESPONSE
return _data == nil ? 0 : 100;
#else
return 100; return 100;
#endif
} }
- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView - (void)collectionViewLockDataSource:(ASCollectionView *)collectionView
@@ -125,4 +157,11 @@
return UIEdgeInsetsMake(20.0, 20.0, 20.0, 20.0); return UIEdgeInsetsMake(20.0, 20.0, 20.0, 20.0);
} }
#if SIMULATE_WEB_RESPONSE
-(void)dealloc
{
NSLog(@"ViewController is deallocing");
}
#endif
@end @end