diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index 3c7559f85f..c9a6955c6d 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -110,6 +110,8 @@ static const CGFloat kFontSize = 18.0f; { CGSize availableSize = CGSizeMake(constrainedSize.width - 2 * kHorizontalPadding, constrainedSize.height - 2 * kVerticalPadding); + availableSize.width = MAX(0, availableSize.width); + availableSize.height = MAX(0, availableSize.height); CGSize textNodeSize = [_textNode measure:availableSize]; return CGSizeMake(ceilf(2 * kHorizontalPadding + textNodeSize.width), diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 2288958d3c..080fe4f570 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -125,7 +125,7 @@ static BOOL _isInterceptedSelector(SEL sel) ASBatchContext *_batchContext; - NSIndexPath *_pendingVisibleIndexPath; + NSMutableSet *_pendingVisibleIndexPaths; } @property (atomic, assign) BOOL asyncDataSourceLocked; @@ -174,6 +174,8 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { _leadingScreensForBatching = 1.0; _batchContext = [[ASBatchContext alloc] init]; + + _pendingVisibleIndexPaths = [[NSMutableSet alloc] init]; } - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style @@ -431,7 +433,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { - _pendingVisibleIndexPath = indexPath; + [_pendingVisibleIndexPaths addObject:indexPath]; [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; @@ -442,8 +444,8 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath { - if ([_pendingVisibleIndexPath isEqual:indexPath]) { - _pendingVisibleIndexPath = nil; + if ([_pendingVisibleIndexPaths containsObject:indexPath]) { + [_pendingVisibleIndexPaths removeObject:indexPath]; } [_rangeController visibleNodeIndexPathsDidChangeWithScrollDirection:self.scrollDirection]; @@ -517,20 +519,15 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { { ASDisplayNodeAssertMainThread(); - NSArray *visibleIndexPaths = self.indexPathsForVisibleRows; - - if ( _pendingVisibleIndexPath ) { - NSMutableSet *indexPaths = [NSMutableSet setWithArray:self.indexPathsForVisibleRows]; - - if ( [indexPaths containsObject:_pendingVisibleIndexPath]) { - _pendingVisibleIndexPath = nil; // once it has shown up in visibleIndexPaths, we can stop tracking it - } else { - [indexPaths addObject:_pendingVisibleIndexPath]; - visibleIndexPaths = indexPaths.allObjects; - } - } - - return visibleIndexPaths; + NSMutableSet *visibleIndexPaths = [NSMutableSet setWithArray:[self indexPathsForVisibleRows]]; + + // First, remove any index paths we're tracking that UIKit has now proven it can remember :) + [_pendingVisibleIndexPaths minusSet:visibleIndexPaths]; + + // Next, add all remaining index paths that we know should be visible (from willDisplayCell) but are not in the set. + [visibleIndexPaths unionSet:_pendingVisibleIndexPaths]; + + return visibleIndexPaths.allObjects; } - (NSArray *)rangeController:(ASRangeController *)rangeController nodesAtIndexPaths:(NSArray *)indexPaths @@ -547,7 +544,7 @@ void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { - (void)rangeController:(ASRangeController *)rangeController didInsertNodesAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption { ASDisplayNodeAssertMainThread(); - + BOOL preventAnimation = animationOption == UITableViewRowAnimationNone; ASPerformBlockWithoutAnimation(preventAnimation, ^{ [super insertRowsAtIndexPaths:indexPaths withRowAnimation:(UITableViewRowAnimation)animationOption]; diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index f8f8fca202..8926b47598 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -120,7 +120,7 @@ typedef NSUInteger ASDataControllerAnimationOptions; * @discussion If enabled, we will fetch data through `dataController:nodeAtIndexPath:` and `dataController:rowsInSection:` in background thread. * Otherwise, the methods will be invoked synchronically in calling thread. Enabling data fetching in async mode could avoid blocking main thread - * while allocating cell on main thread, which is frequently reported issue for handing large scale data. On another hand, the application code + * while allocating cell on main thread, which is frequently reported issue for handling large scale data. On another hand, the application code * will take the responsibility to avoid data inconsistence. Specifically, we will lock the data source through `dataControllerLockDataSource`, * and unlock it by `dataControllerUnlockDataSource` after the data fetching. The application should not update the data source while * the data source is locked. diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index f9fe17d5b6..5a1e561911 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -113,7 +113,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; return sizingQueue; } -+ (BOOL)isSizingQueue { ++ (BOOL)executingOnSizingQueue +{ return kASSizingQueueContext == dispatch_get_specific(kASSizingQueueContext); } @@ -281,6 +282,9 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; } }]; + // Dispatch to sizing queue in order to guarantee that any in-progress sizing operations from prior edits have completed. + // For example, if an initial -reloadData call is quickly followed by -reloadSections, sizing the initial set may not be done + // at this time. Thus _nodes could be empty and crash in ASIndexPathsForMultidimensional[...] dispatch_async([ASDataController sizingQueue], ^{ [self syncUpdateDataWithBlock:^{ // remove elements @@ -329,6 +333,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; for (NSUInteger j = 0; j < nodes.count && j < indexPaths.count; j += kASDataControllerSizingCountPerProcessor) { NSArray *subIndexPaths = [indexPaths subarrayWithRange:NSMakeRange(j, MIN(kASDataControllerSizingCountPerProcessor, indexPaths.count - j))]; + // TODO: The current implementation does not make use of different constrained sizes per node. + // There should be a fast-path that avoids all of this object creation. NSMutableArray *nodeBoundSizes = [[NSMutableArray alloc] initWithCapacity:kASDataControllerSizingCountPerProcessor]; [subIndexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { [nodeBoundSizes addObject:[NSValue valueWithCGSize:[_dataSource dataController:self constrainedSizeForNodeAtIndexPath:indexPath]]]; @@ -347,12 +353,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; dispatch_group_wait(layoutGroup, DISPATCH_TIME_FOREVER); [self asyncUpdateDataWithBlock:^{ - // updating the cells + // Insert finished nodes into data storage INSERT_NODES(_nodes, indexPaths, nodes, animationOption); }]; }; - if ([ASDataController isSizingQueue]) { + if ([ASDataController executingOnSizingQueue]) { block(); } else { dispatch_async([ASDataController sizingQueue], block); @@ -403,9 +409,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withAnimationOption:(ASDataControllerAnimationOptions)animationOption { + // Reloading requires re-fetching the data. Load it on the current calling thread, locking the data source. [self performDataFetchingWithBlock:^{ - // The reloading operation required reloading the data - // Loading data in the calling thread NSMutableArray *nodes = [[NSMutableArray alloc] initWithCapacity:indexPaths.count]; [indexPaths sortedArrayUsingSelector:@selector(compare:)]; [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { @@ -458,16 +463,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; dispatch_async([ASDataController sizingQueue], ^{ [self syncUpdateDataWithBlock:^{ - + // Remove everything that existed before the reload, now that we're ready to insert replacements NSArray *indexPaths = ASIndexPathsForMultidimensionalArray(_nodes); DELETE_NODES(_nodes, indexPaths, animationOption); NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, _nodes.count)]; DELETE_SECTIONS(_nodes, indexSet, animationOption); - - // Insert section - + // Insert each section NSMutableArray *sections = [[NSMutableArray alloc] initWithCapacity:sectionNum]; for (int i = 0; i < sectionNum; i++) { [sections addObject:[[NSMutableArray alloc] init]]; @@ -509,6 +512,15 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (NSArray *)nodesAtIndexPaths:(NSArray *)indexPaths { ASDisplayNodeAssertMainThread(); + + // Make sure that any asynchronous layout operations have finished so that those nodes are present. + // Otherwise a failure case could be: + // - Reload section 2, deleting all current nodes in that section. + // - New nodes are created and sizing is triggered, but they are not yet added to _nodes. + // - This method is called and includes an indexPath in section 2. + // - Unless we wait for the layout group to finish, we will crash with array out of bounds looking for the index in _nodes. + // FIXME: Seralization is required here. Diff in progress to resolve. + return ASFindElementsInMultidimensionalArrayAtIndexPaths(_nodes, [indexPaths sortedArrayUsingSelector:@selector(compare:)]); } diff --git a/AsyncDisplayKit/Details/ASRangeController.h b/AsyncDisplayKit/Details/ASRangeController.h index e319ea5d30..e78a2d3d22 100644 --- a/AsyncDisplayKit/Details/ASRangeController.h +++ b/AsyncDisplayKit/Details/ASRangeController.h @@ -19,14 +19,16 @@ /** * Working range controller. * - * Used internally by ASTableView and potentially by a future ASCollectionView. Observes the visible range, maintains - * a working range, and is responsible for handling AsyncDisplayKit machinery (sizing cell nodes, enqueueing and - * cancelling their asynchronous layout and display, and so on). + * Used internally by ASTableView and ASCollectionView. It is paired with ASDataController. + * It is designed to support custom scrolling containers as well. Observes the visible range, maintains + * "working ranges" to trigger network calls and rendering, and is responsible for driving asynchronous layout of cells. + * This includes cancelling those asynchronous operations as cells fall outside of the working ranges. */ @interface ASRangeController : ASDealloc2MainObject /** - * Notify the receiver that the visible range has been updated. + * Notify the range controller that the visible range has been updated. + * This is the primary input call that drives updating the working ranges, and triggering their actions. * * @see [ASRangeControllerDelegate rangeControllerVisibleNodeIndexPaths:] */ diff --git a/AsyncDisplayKit/Details/ASRangeController.mm b/AsyncDisplayKit/Details/ASRangeController.mm index 14924c2192..2a30ab362e 100644 --- a/AsyncDisplayKit/Details/ASRangeController.mm +++ b/AsyncDisplayKit/Details/ASRangeController.mm @@ -75,6 +75,7 @@ // coalesce these events -- handling them multiple times per runloop is noisy and expensive _queuedRangeUpdate = YES; + [self performSelector:@selector(updateVisibleNodeIndexPaths) withObject:nil afterDelay:0 @@ -200,8 +201,8 @@ if ([_layoutController respondsToSelector:@selector(insertNodesAtIndexPaths:withSizes:)]) { [_layoutController insertNodesAtIndexPaths:indexPaths withSizes:nodeSizes]; } - [_delegate rangeController:self didInsertNodesAtIndexPaths:indexPaths withAnimationOption:animationOption]; _rangeIsValid = NO; + [_delegate rangeController:self didInsertNodesAtIndexPaths:indexPaths withAnimationOption:animationOption]; }); } @@ -218,8 +219,8 @@ if ([_layoutController respondsToSelector:@selector(deleteNodesAtIndexPaths:)]) { [_layoutController deleteNodesAtIndexPaths:indexPaths]; } - [_delegate rangeController:self didDeleteNodesAtIndexPaths:indexPaths withAnimationOption:animationOption]; _rangeIsValid = NO; + [_delegate rangeController:self didDeleteNodesAtIndexPaths:indexPaths withAnimationOption:animationOption]; }); } @@ -248,8 +249,8 @@ if ([_layoutController respondsToSelector:@selector(insertSections:atIndexSet:)]) { [_layoutController insertSections:sectionNodeSizes atIndexSet:indexSet]; } - [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOption:animationOption]; _rangeIsValid = NO; + [_delegate rangeController:self didInsertSectionsAtIndexSet:indexSet withAnimationOption:animationOption]; }); } @@ -266,8 +267,8 @@ if ([_layoutController respondsToSelector:@selector(deleteSectionsAtIndexSet:)]) { [_layoutController deleteSectionsAtIndexSet:indexSet]; } - [_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOption:animationOption]; _rangeIsValid = NO; + [_delegate rangeController:self didDeleteSectionsAtIndexSet:indexSet withAnimationOption:animationOption]; }); } diff --git a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm index 9d39bf069d..b9cf41c87a 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm @@ -19,14 +19,15 @@ ASDisplayNodeAssertMainThread(); // we add nodes' views to this invisible window to start async rendering + // TODO: Replace this with directly triggering display https://github.com/facebook/AsyncDisplayKit/issues/315 static UIWindow *workingWindow = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ workingWindow = [[UIWindow alloc] initWithFrame:CGRectZero]; workingWindow.windowLevel = UIWindowLevelNormal - 1000; workingWindow.userInteractionEnabled = NO; - workingWindow.clipsToBounds = YES; workingWindow.hidden = YES; + workingWindow.alpha = 0.0; }); return workingWindow; } diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index 6275fc3370..84f7f29eee 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -10,6 +10,10 @@ #import "ASTableView.h" +#define NumberOfSections 10 +#define NumberOfRowsPerSection 20 +#define NumberOfReloadIterations 50 + @interface ASTestTableView : ASTableView @property (atomic, copy) void (^willDeallocBlock)(ASTableView *tableView); @end @@ -52,6 +56,32 @@ @end +@interface ASTableViewFilledDataSource : NSObject + +@end + +@implementation ASTableViewFilledDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return NumberOfSections; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return NumberOfRowsPerSection; +} + +- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath +{ + ASTextCellNode *textCellNode = [ASTextCellNode new]; + textCellNode.text = indexPath.description; + + return textCellNode; +} + +@end + @interface ASTableViewTests : XCTestCase @end @@ -60,27 +90,60 @@ - (void)DISABLED_testTableViewDoesNotRetainItselfAndDelegate { ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain]; - + __block BOOL tableViewDidDealloc = NO; tableView.willDeallocBlock = ^(ASTableView *v){ tableViewDidDealloc = YES; }; - + ASTableViewTestDelegate *delegate = [[ASTableViewTestDelegate alloc] init]; - + __block BOOL delegateDidDealloc = NO; delegate.willDeallocBlock = ^(ASTableViewTestDelegate *d){ delegateDidDealloc = YES; }; - + tableView.asyncDataSource = delegate; tableView.asyncDelegate = delegate; - + [delegate release]; XCTAssertTrue(delegateDidDealloc, @"unexpected delegate lifetime:%@", delegate); - + XCTAssertNoThrow([tableView release], @"unexpected exception when deallocating table view:%@", tableView); XCTAssertTrue(tableViewDidDealloc, @"unexpected table view lifetime:%@", tableView); } +- (void)testReloadData +{ + // Keep the viewport moderately sized so that new cells are loaded on scrolling + ASTableView *tableView = [[ASTableView alloc] initWithFrame:CGRectMake(0, 0, 100, 500) + style:UITableViewStylePlain + asyncDataFetching:YES]; + + ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; + + tableView.asyncDelegate = dataSource; + tableView.asyncDataSource = dataSource; + + [tableView reloadData]; + + [tableView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1,2)] withRowAnimation:UITableViewRowAnimationNone]; + + // FIXME: Early return because we can't currently pass this test :). Diff is in progress to resolve. + return; + + for (int i = 0; i < NumberOfReloadIterations; ++i) { + NSInteger randA = arc4random_uniform(NumberOfSections - 1); + NSInteger randB = arc4random_uniform(NumberOfSections - 1); + + [tableView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(MIN(randA, randB), MAX(randA, randB) - MIN(randA, randB))] withRowAnimation:UITableViewRowAnimationNone]; + + BOOL animated = (arc4random_uniform(1) == 0 ? YES : NO); + + [tableView setContentOffset:CGPointMake(0, arc4random_uniform(tableView.contentSize.height - tableView.bounds.size.height)) animated:animated]; + + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]; + } +} + @end diff --git a/examples/ASTableViewStressTest/Default-568h@2x.png b/examples/ASTableViewStressTest/Default-568h@2x.png new file mode 100644 index 0000000000..6ee80b9393 Binary files /dev/null and b/examples/ASTableViewStressTest/Default-568h@2x.png differ diff --git a/examples/ASTableViewStressTest/Default-667h@2x.png b/examples/ASTableViewStressTest/Default-667h@2x.png new file mode 100644 index 0000000000..e7b975e21b Binary files /dev/null and b/examples/ASTableViewStressTest/Default-667h@2x.png differ diff --git a/examples/ASTableViewStressTest/Default-736h@3x.png b/examples/ASTableViewStressTest/Default-736h@3x.png new file mode 100644 index 0000000000..c8949cae16 Binary files /dev/null and b/examples/ASTableViewStressTest/Default-736h@3x.png differ diff --git a/examples/ASTableViewStressTest/Podfile b/examples/ASTableViewStressTest/Podfile new file mode 100644 index 0000000000..6c012e3c04 --- /dev/null +++ b/examples/ASTableViewStressTest/Podfile @@ -0,0 +1,3 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '8.0' +pod 'AsyncDisplayKit', :path => '../..' diff --git a/examples/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj b/examples/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..7ae0001200 --- /dev/null +++ b/examples/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,344 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */; }; + 05E2128719D4DB510098F589 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128619D4DB510098F589 /* main.m */; }; + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128919D4DB510098F589 /* AppDelegate.m */; }; + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 05E2128C19D4DB510098F589 /* ViewController.m */; }; + 3EC0CDCBA10D483D9F386E5E /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */; }; + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AA19EE274300767484 /* Default-667h@2x.png */; }; + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6C2C82AB19EE274300767484 /* Default-736h@3x.png */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Default-568h@2x.png"; path = "../Default-568h@2x.png"; sourceTree = ""; }; + 05E2128119D4DB510098F589 /* Sample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Sample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 05E2128519D4DB510098F589 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 05E2128619D4DB510098F589 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 05E2128819D4DB510098F589 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 05E2128919D4DB510098F589 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 05E2128B19D4DB510098F589 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; + 05E2128C19D4DB510098F589 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; + 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; + 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-667h@2x.png"; sourceTree = SOURCE_ROOT; }; + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-736h@3x.png"; sourceTree = SOURCE_ROOT; }; + C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 05E2127E19D4DB510098F589 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3EC0CDCBA10D483D9F386E5E /* libPods.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 05E2127819D4DB510098F589 = { + isa = PBXGroup; + children = ( + 05E2128319D4DB510098F589 /* Sample */, + 05E2128219D4DB510098F589 /* Products */, + 1A943BF0259746F18D6E423F /* Frameworks */, + 1AE410B73DA5C3BD087ACDD7 /* Pods */, + ); + sourceTree = ""; + }; + 05E2128219D4DB510098F589 /* Products */ = { + isa = PBXGroup; + children = ( + 05E2128119D4DB510098F589 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 05E2128319D4DB510098F589 /* Sample */ = { + isa = PBXGroup; + children = ( + 05E2128819D4DB510098F589 /* AppDelegate.h */, + 05E2128919D4DB510098F589 /* AppDelegate.m */, + 05E2128B19D4DB510098F589 /* ViewController.h */, + 05E2128C19D4DB510098F589 /* ViewController.m */, + 05E2128419D4DB510098F589 /* Supporting Files */, + ); + path = Sample; + sourceTree = ""; + }; + 05E2128419D4DB510098F589 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 0585427F19D4DBE100606EA6 /* Default-568h@2x.png */, + 6C2C82AA19EE274300767484 /* Default-667h@2x.png */, + 6C2C82AB19EE274300767484 /* Default-736h@3x.png */, + 05E2128519D4DB510098F589 /* Info.plist */, + 05E2128619D4DB510098F589 /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 1A943BF0259746F18D6E423F /* Frameworks */ = { + isa = PBXGroup; + children = ( + 3D24B17D1E4A4E7A9566C5E9 /* libPods.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1AE410B73DA5C3BD087ACDD7 /* Pods */ = { + isa = PBXGroup; + children = ( + C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */, + 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 05E2128019D4DB510098F589 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + 05E2127D19D4DB510098F589 /* Sources */, + 05E2127E19D4DB510098F589 /* Frameworks */, + 05E2127F19D4DB510098F589 /* Resources */, + F012A6F39E0149F18F564F50 /* Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Sample; + productName = Sample; + productReference = 05E2128119D4DB510098F589 /* Sample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 05E2127919D4DB510098F589 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0600; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 05E2128019D4DB510098F589 = { + CreatedOnToolsVersion = 6.0.1; + }; + }; + }; + buildConfigurationList = 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 05E2127819D4DB510098F589; + productRefGroup = 05E2128219D4DB510098F589 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 05E2128019D4DB510098F589 /* Sample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 05E2127F19D4DB510098F589 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0585428019D4DBE100606EA6 /* Default-568h@2x.png in Resources */, + 6C2C82AC19EE274300767484 /* Default-667h@2x.png in Resources */, + 6C2C82AD19EE274300767484 /* Default-736h@3x.png in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 05E2127D19D4DB510098F589 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */, + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, + 05E2128719D4DB510098F589 /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 05E212A219D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 05E212A319D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 05E212A519D4DB510098F589 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C068F1D3F0CC317E895FCDAB /* Pods.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 05E212A619D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 088AA6578212BE9BFBB07B70 /* Pods.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 7.1; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 05E2127C19D4DB510098F589 /* Build configuration list for PBXProject "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A219D4DB510098F589 /* Debug */, + 05E212A319D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 05E212A519D4DB510098F589 /* Debug */, + 05E212A619D4DB510098F589 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 05E2127919D4DB510098F589 /* Project object */; +} diff --git a/examples/ASTableViewStressTest/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples/ASTableViewStressTest/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..a80c038249 --- /dev/null +++ b/examples/ASTableViewStressTest/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/ASTableViewStressTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples/ASTableViewStressTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 0000000000..1e14aa0329 --- /dev/null +++ b/examples/ASTableViewStressTest/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata b/examples/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..d98549fd35 --- /dev/null +++ b/examples/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/ASTableViewStressTest/Sample/AppDelegate.h b/examples/ASTableViewStressTest/Sample/AppDelegate.h new file mode 100644 index 0000000000..2aa29369b4 --- /dev/null +++ b/examples/ASTableViewStressTest/Sample/AppDelegate.h @@ -0,0 +1,18 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/examples/ASTableViewStressTest/Sample/AppDelegate.m b/examples/ASTableViewStressTest/Sample/AppDelegate.m new file mode 100644 index 0000000000..a8e5594780 --- /dev/null +++ b/examples/ASTableViewStressTest/Sample/AppDelegate.m @@ -0,0 +1,27 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import "AppDelegate.h" + +#import "ViewController.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.backgroundColor = [UIColor whiteColor]; + self.window.rootViewController = [[ViewController alloc] init]; + [self.window makeKeyAndVisible]; + return YES; +} + +@end diff --git a/examples/ASTableViewStressTest/Sample/Info.plist b/examples/ASTableViewStressTest/Sample/Info.plist new file mode 100644 index 0000000000..35d842827b --- /dev/null +++ b/examples/ASTableViewStressTest/Sample/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/examples/ASTableViewStressTest/Sample/ViewController.h b/examples/ASTableViewStressTest/Sample/ViewController.h new file mode 100644 index 0000000000..d0e9200d88 --- /dev/null +++ b/examples/ASTableViewStressTest/Sample/ViewController.h @@ -0,0 +1,16 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +@interface ViewController : UIViewController + +@end diff --git a/examples/ASTableViewStressTest/Sample/ViewController.m b/examples/ASTableViewStressTest/Sample/ViewController.m new file mode 100644 index 0000000000..ee67521ee2 --- /dev/null +++ b/examples/ASTableViewStressTest/Sample/ViewController.m @@ -0,0 +1,112 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import "ViewController.h" + +#import +#import + +#define NumberOfSections 10 +#define NumberOfRowsPerSection 20 +#define NumberOfReloadIterations 50 + +@interface ViewController () +{ + ASTableView *_tableView; +} + +@end + + +@implementation ViewController + +#pragma mark - +#pragma mark UIViewController. + +- (instancetype)init +{ + if (!(self = [super init])) + return nil; + + _tableView = [[ASTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain asyncDataFetching:YES]; + _tableView.separatorStyle = UITableViewCellSeparatorStyleNone; // KittenNode has its own separator + _tableView.asyncDataSource = self; + _tableView.asyncDelegate = self; + + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + + [self.view addSubview:_tableView]; +} + +- (void)viewWillLayoutSubviews +{ + _tableView.frame = self.view.bounds; +} + +- (void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + + [self thrashTableView]; +} + +- (void)thrashTableView +{ + // Keep the viewport moderately sized so that new cells are loaded on scrolling + ASTableView *tableView = [[ASTableView alloc] initWithFrame:CGRectMake(0, 0, 100, 500) + style:UITableViewStylePlain + asyncDataFetching:NO]; + + tableView.asyncDelegate = self; + tableView.asyncDataSource = self; + + [tableView reloadData]; + + [tableView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1,2)] withRowAnimation:UITableViewRowAnimationNone]; + + for (int i = 0; i < NumberOfReloadIterations; ++i) { + NSInteger randA = arc4random_uniform(NumberOfSections - 1); + NSInteger randB = arc4random_uniform(NumberOfSections - 1); + + [tableView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(MIN(randA, randB), MAX(randA, randB) - MIN(randA, randB))] withRowAnimation:UITableViewRowAnimationNone]; + + BOOL animated = (arc4random_uniform(1) == 0 ? YES : NO); + + [tableView setContentOffset:CGPointMake(0, arc4random_uniform(tableView.contentSize.height - tableView.bounds.size.height)) animated:animated]; + + [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]; + } +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView +{ + return NumberOfSections; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section +{ + return NumberOfRowsPerSection; +} + +- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath +{ + ASTextCellNode *textCellNode = [ASTextCellNode new]; + textCellNode.text = indexPath.description; + + return textCellNode; +} + +@end diff --git a/examples/ASTableViewStressTest/Sample/main.m b/examples/ASTableViewStressTest/Sample/main.m new file mode 100644 index 0000000000..ae9488711c --- /dev/null +++ b/examples/ASTableViewStressTest/Sample/main.m @@ -0,0 +1,20 @@ +/* This file provided by Facebook is for non-commercial testing and evaluation + * purposes only. Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +}