Merge pull request #312 from facebook/batch-api

Batch Fetching API
This commit is contained in:
Nadine Salter
2015-02-26 22:54:43 -08:00
14 changed files with 607 additions and 23 deletions

View File

@@ -144,6 +144,11 @@
292C59A21A956527007E5DD6 /* ASRangeHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C599C1A956527007E5DD6 /* ASRangeHandler.h */; settings = {ATTRIBUTES = (Public, ); }; };
292C59A31A956527007E5DD6 /* ASRangeHandlerRender.h in Headers */ = {isa = PBXBuildFile; fileRef = 292C599D1A956527007E5DD6 /* ASRangeHandlerRender.h */; settings = {ATTRIBUTES = (Public, ); }; };
292C59A41A956527007E5DD6 /* ASRangeHandlerRender.mm in Sources */ = {isa = PBXBuildFile; fileRef = 292C599E1A956527007E5DD6 /* ASRangeHandlerRender.mm */; };
296A0A2E1A9516B2005ACEAA /* ASBatchFetching.h in Headers */ = {isa = PBXBuildFile; fileRef = 296A0A2C1A9516B2005ACEAA /* ASBatchFetching.h */; settings = {ATTRIBUTES = (Private, ); }; };
296A0A2F1A9516B2005ACEAA /* ASBatchFetching.m in Sources */ = {isa = PBXBuildFile; fileRef = 296A0A2D1A9516B2005ACEAA /* ASBatchFetching.m */; };
296A0A351A951ABF005ACEAA /* ASBatchFetchingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.m */; };
299DA1A91A828D2900162D41 /* ASBatchContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 299DA1A71A828D2900162D41 /* ASBatchContext.h */; settings = {ATTRIBUTES = (Public, ); }; };
299DA1AA1A828D2900162D41 /* ASBatchContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 299DA1A81A828D2900162D41 /* ASBatchContext.mm */; };
3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
464052201A3F83C40061C0BA /* ASDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 464052191A3F83C40061C0BA /* ASDataController.h */; settings = {ATTRIBUTES = (Public, ); }; };
464052211A3F83C40061C0BA /* ASDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521A1A3F83C40061C0BA /* ASDataController.mm */; };
@@ -296,6 +301,12 @@
292C599C1A956527007E5DD6 /* ASRangeHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandler.h; sourceTree = "<group>"; };
292C599D1A956527007E5DD6 /* ASRangeHandlerRender.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeHandlerRender.h; sourceTree = "<group>"; };
292C599E1A956527007E5DD6 /* ASRangeHandlerRender.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRangeHandlerRender.mm; sourceTree = "<group>"; };
296A0A2C1A9516B2005ACEAA /* ASBatchFetching.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASBatchFetching.h; path = ../Details/ASBatchFetching.h; sourceTree = "<group>"; };
296A0A2D1A9516B2005ACEAA /* ASBatchFetching.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASBatchFetching.m; path = ../Details/ASBatchFetching.m; sourceTree = "<group>"; };
296A0A311A951715005ACEAA /* ASScrollDirection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASScrollDirection.h; path = AsyncDisplayKit/Details/ASScrollDirection.h; sourceTree = SOURCE_ROOT; };
296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBatchFetchingTests.m; sourceTree = "<group>"; };
299DA1A71A828D2900162D41 /* ASBatchContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBatchContext.h; sourceTree = "<group>"; };
299DA1A81A828D2900162D41 /* ASBatchContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBatchContext.mm; sourceTree = "<group>"; };
3C9C128419E616EF00E942A0 /* ASTableViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableViewTests.m; sourceTree = "<group>"; };
464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDataController.h; sourceTree = "<group>"; };
4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDataController.mm; sourceTree = "<group>"; };
@@ -427,6 +438,7 @@
058D09C5195D04C000B7D73C /* AsyncDisplayKitTests */ = {
isa = PBXGroup;
children = (
296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.m */,
2911485B1A77147A005D0878 /* ASControlNodeTests.m */,
058D0A2D195D057000B7D73C /* ASDisplayLayerTests.m */,
058D0A2E195D057000B7D73C /* ASDisplayNodeAppearanceTests.m */,
@@ -465,6 +477,8 @@
058D09E5195D050800B7D73C /* _ASDisplayView.mm */,
054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */,
054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */,
299DA1A71A828D2900162D41 /* ASBatchContext.h */,
299DA1A81A828D2900162D41 /* ASBatchContext.mm */,
464052191A3F83C40061C0BA /* ASDataController.h */,
4640521A1A3F83C40061C0BA /* ASDataController.mm */,
05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */,
@@ -490,6 +504,7 @@
292C599C1A956527007E5DD6 /* ASRangeHandler.h */,
292C599D1A956527007E5DD6 /* ASRangeHandlerRender.h */,
292C599E1A956527007E5DD6 /* ASRangeHandlerRender.mm */,
296A0A311A951715005ACEAA /* ASScrollDirection.h */,
058D09EA195D050800B7D73C /* ASTextNodeCoreTextAdditions.h */,
058D09EB195D050800B7D73C /* ASTextNodeCoreTextAdditions.m */,
058D09EC195D050800B7D73C /* ASTextNodeRenderer.h */,
@@ -528,6 +543,8 @@
058D0A01195D050800B7D73C /* Private */ = {
isa = PBXGroup;
children = (
296A0A2C1A9516B2005ACEAA /* ASBatchFetching.h */,
296A0A2D1A9516B2005ACEAA /* ASBatchFetching.m */,
058D0A02195D050800B7D73C /* _AS-objc-internal.h */,
058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */,
058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */,
@@ -593,6 +610,7 @@
058D0A4F195D05CB00B7D73C /* ASImageNode.h in Headers */,
058D0A50195D05CB00B7D73C /* ASImageNode.mm in Headers */,
058D0A51195D05CB00B7D73C /* ASTextNode.h in Headers */,
296A0A2E1A9516B2005ACEAA /* ASBatchFetching.h in Headers */,
058D0A52195D05CB00B7D73C /* ASTextNode.mm in Headers */,
055F1A3819ABD413004DAFF1 /* ASRangeController.h in Headers */,
292C59A31A956527007E5DD6 /* ASRangeHandlerRender.h in Headers */,
@@ -619,6 +637,7 @@
292C599F1A956527007E5DD6 /* ASLayoutRangeType.h in Headers */,
464052251A3F83C40061C0BA /* ASMultidimensionalArrayUtils.h in Headers */,
058D0A64195D05DC00B7D73C /* ASTextNodeWordKerner.h in Headers */,
299DA1A91A828D2900162D41 /* ASBatchContext.h in Headers */,
058D0A65195D05DC00B7D73C /* ASTextNodeWordKerner.m in Headers */,
058D0A66195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.h in Headers */,
058D0A67195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Headers */,
@@ -789,6 +808,7 @@
058D0A18195D050800B7D73C /* _ASDisplayLayer.mm in Sources */,
058D0A2C195D050800B7D73C /* ASSentinel.m in Sources */,
464052211A3F83C40061C0BA /* ASDataController.mm in Sources */,
299DA1AA1A828D2900162D41 /* ASBatchContext.mm in Sources */,
058D0A15195D050800B7D73C /* ASDisplayNodeExtras.mm in Sources */,
058D0A1F195D050800B7D73C /* ASTextNodeTextKitHelpers.mm in Sources */,
055F1A3519ABD3E3004DAFF1 /* ASTableView.mm in Sources */,
@@ -823,6 +843,7 @@
058D0A29195D050800B7D73C /* ASDisplayNode+DebugTiming.mm in Sources */,
058D0A22195D050800B7D73C /* _ASAsyncTransaction.m in Sources */,
055F1A3919ABD413004DAFF1 /* ASRangeController.mm in Sources */,
296A0A2F1A9516B2005ACEAA /* ASBatchFetching.m in Sources */,
D785F6631A74327E00291744 /* ASScrollNode.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -832,6 +853,7 @@
buildActionMask = 2147483647;
files = (
2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */,
296A0A351A951ABF005ACEAA /* ASBatchFetchingTests.m in Sources */,
058D0A3E195D057000B7D73C /* ASTextNodeRendererTests.m in Sources */,
058D0A3D195D057000B7D73C /* ASTextNodeCoreTextAdditionsTests.m in Sources */,
058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */,
@@ -839,6 +861,7 @@
058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.m in Sources */,
058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */,
052EE0661A159FEF002C6279 /* ASMultiplexImageNodeTests.m in Sources */,
291D92411A9D537B008286B8 /* ASBatchFetching.m in Sources */,
058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.m in Sources */,
058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */,
058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */,

View File

@@ -11,6 +11,7 @@
#import <AsyncDisplayKit/ASRangeController.h>
#import <AsyncDisplayKit/ASCollectionViewProtocols.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASBatchContext.h>
@class ASCellNode;
@@ -62,6 +63,13 @@
*/
- (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout asyncDataFetching:(BOOL)asyncDataFetchingEnabled;
/**
* The number of screens left to scroll before the delegate -collectionView:beginBatchFetchingWithContext: is called.
*
* Defaults to one screenful.
*/
@property (nonatomic, assign) CGFloat leadingScreensForBatching;
/**
* Reload everything from scratch, destroying the working range and all cached nodes.
*
@@ -166,6 +174,34 @@
- (void)collectionView:(ASCollectionView *)collectionView willDisplayNodeForItemAtIndexPath:(NSIndexPath *)indexPath;
- (void)collectionView:(ASCollectionView *)collectionView didEndDisplayingNodeForItemAtIndexPath:(NSIndexPath*)indexPath;
/**
* Receive a message that the collectionView is near the end of its data set and more data should be fetched if
* necessary.
*
* @param tableView The sender.
* @param context A context object that must be notified when the batch fetch is completed.
*
* @discussion You must eventually call -completeBatchFetching: with an argument of YES in order to receive future
* notifications to do batch fetches. This method is called on a background queue.
*
* UICollectionView currently only supports batch events for tail loads. If you require a head load, consider
* implementing a UIRefreshControl.
*/
- (void)collectionView:(ASCollectionView *)collectionView willBeginBatchFetchWithContext:(ASBatchContext *)context;
/**
* Tell the collectionView if batch fetching should begin.
*
* @param collectionView The sender.
*
* @discussion Use this method to conditionally fetch batches. Example use cases are: limiting the total number of
* objects that can be fetched or no network connection.
*
* If not implemented, the collectionView assumes that it should notify its asyncDelegate when batch fetching
* should occur.
*/
- (BOOL)shouldBatchFetchForCollectionView:(ASCollectionView *)collectionView;
@end
@interface ASCollectionView (Deprecated)

View File

@@ -13,6 +13,7 @@
#import "ASRangeController.h"
#import "ASDataController.h"
#import "ASDisplayNodeInternal.h"
#import "ASBatchFetching.h"
const static NSUInteger kASCollectionViewAnimationNone = 0;
@@ -38,7 +39,10 @@ static BOOL _isInterceptedSelector(SEL sel)
// used for ASRangeController visibility updates
sel == @selector(collectionView:willDisplayCell:forItemAtIndexPath:) ||
sel == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:)
sel == @selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:) ||
// used for batch fetching API
sel == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)
);
}
@@ -103,6 +107,8 @@ static BOOL _isInterceptedSelector(SEL sel)
NSMutableArray *_batchUpdateBlocks;
BOOL _asyncDataFetchingEnabled;
ASBatchContext *_batchContext;
}
@property (atomic, assign) BOOL asyncDataSourceLocked;
@@ -137,6 +143,10 @@ static BOOL _isInterceptedSelector(SEL sel)
_dataController.delegate = _rangeController;
_dataController.dataSource = self;
_batchContext = [[ASBatchContext alloc] init];
_leadingScreensForBatching = 1.0;
_proxyDelegate = [[_ASCollectionViewProxy alloc] initWithTarget:nil interceptor:self];
super.delegate = (id<UICollectionViewDelegate>)_proxyDelegate;
@@ -362,6 +372,46 @@ static BOOL _isInterceptedSelector(SEL sel)
}
#pragma mark -
#pragma mark Batch Fetching
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
[self handleBatchFetchScrollingToOffset:*targetContentOffset];
if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
[_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
}
}
- (BOOL)shouldBatchFetch
{
// if the delegate does not respond to this method, there is no point in starting to fetch
BOOL canFetch = [_asyncDelegate respondsToSelector:@selector(collectionView:willBeginBatchFetchWithContext:)];
if (canFetch && [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForCollectionView:)]) {
return [_asyncDelegate shouldBatchFetchForCollectionView:self];
} else {
return canFetch;
}
}
- (void)handleBatchFetchScrollingToOffset:(CGPoint)targetOffset
{
ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist");
if (![self shouldBatchFetch]) {
return;
}
if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) {
[_batchContext beginBatchFetching];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_asyncDelegate collectionView:self willBeginBatchFetchWithContext:_batchContext];
});
}
}
#pragma mark - ASDataControllerSource
- (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPath:(NSIndexPath *)indexPath

View File

@@ -11,6 +11,7 @@
#import <AsyncDisplayKit/ASRangeController.h>
#import <AsyncDisplayKit/ASTableViewProtocols.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASBatchContext.h>
@class ASCellNode;
@@ -62,6 +63,13 @@
*/
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style asyncDataFetching:(BOOL)asyncDataFetchingEnabled;
/**
* The number of screens left to scroll before the delegate -tableView:beginBatchFetchingWithContext: is called.
*
* Defaults to one screenful.
*/
@property (nonatomic, assign) CGFloat leadingScreensForBatching;
/**
* Reload everything from scratch, destroying the working range and all cached nodes.
*
@@ -171,6 +179,33 @@
- (void)tableView:(ASTableView *)tableView willDisplayNodeForRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(ASTableView *)tableView didEndDisplayingNodeForRowAtIndexPath:(NSIndexPath*)indexPath;
/**
* Receive a message that the tableView is near the end of its data set and more data should be fetched if necessary.
*
* @param tableView The sender.
* @param context A context object that must be notified when the batch fetch is completed.
*
* @discussion You must eventually call -completeBatchFetching: with an argument of YES in order to receive future
* notifications to do batch fetches. This method is called on a background queue.
*
* ASTableView currently only supports batch events for tail loads. If you require a head load, consider implementing a
* UIRefreshControl.
*/
- (void)tableView:(ASTableView *)tableView willBeginBatchFetchWithContext:(ASBatchContext *)context;
/**
* Tell the tableView if batch fetching should begin.
*
* @param tableView The sender.
*
* @discussion Use this method to conditionally fetch batches. Example use cases are: limiting the total number of
* objects that can be fetched or no network connection.
*
* If not implemented, the tableView assumes that it should notify its asyncDelegate when batch fetching
* should occur.
*/
- (BOOL)shouldBatchFetchForTableView:(ASTableView *)tableView;
@end
@interface ASTableView (Deprecated)

View File

@@ -14,7 +14,7 @@
#import "ASLayoutController.h"
#import "ASRangeController.h"
#import "ASDisplayNodeInternal.h"
#import "ASBatchFetching.h"
#pragma mark -
@@ -38,7 +38,10 @@ static BOOL _isInterceptedSelector(SEL sel)
// used for ASRangeController visibility updates
sel == @selector(tableView:willDisplayCell:forRowAtIndexPath:) ||
sel == @selector(tableView:didEndDisplayingCell:forRowAtIndexPath:)
sel == @selector(tableView:didEndDisplayingCell:forRowAtIndexPath:) ||
// used for batch fetching API
sel == @selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)
);
}
@@ -112,6 +115,8 @@ static BOOL _isInterceptedSelector(SEL sel)
ASRangeController *_rangeController;
BOOL _asyncDataFetchingEnabled;
ASBatchContext *_batchContext;
}
@property (atomic, assign) BOOL asyncDataSourceLocked;
@@ -150,6 +155,9 @@ static BOOL _isInterceptedSelector(SEL sel)
_asyncDataFetchingEnabled = asyncDataFetchingEnabled;
_asyncDataSourceLocked = NO;
_leadingScreensForBatching = 1.0;
_batchContext = [[ASBatchContext alloc] init];
return self;
}
@@ -370,6 +378,46 @@ static BOOL _isInterceptedSelector(SEL sel)
}
#pragma mark -
#pragma mark Batch Fetching
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
[self handleBatchFetchScrollingToOffset:*targetContentOffset];
if ([_asyncDelegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
[_asyncDelegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
}
}
- (BOOL)shouldBatchFetch
{
// if the delegate does not respond to this method, there is no point in starting to fetch
BOOL canFetch = [_asyncDelegate respondsToSelector:@selector(tableView:willBeginBatchFetchWithContext:)];
if (canFetch && [_asyncDelegate respondsToSelector:@selector(shouldBatchFetchForTableView:)]) {
return [_asyncDelegate shouldBatchFetchForTableView:self];
} else {
return canFetch;
}
}
- (void)handleBatchFetchScrollingToOffset:(CGPoint)targetOffset
{
ASDisplayNodeAssert(_batchContext != nil, @"Batch context should exist");
if (![self shouldBatchFetch]) {
return;
}
if (ASDisplayShouldFetchBatchForContext(_batchContext, [self scrollDirection], self.bounds, self.contentSize, targetOffset, _leadingScreensForBatching)) {
[_batchContext beginBatchFetching];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_asyncDelegate tableView:self willBeginBatchFetchWithContext:_batchContext];
});
}
}
#pragma mark -
#pragma mark ASRangeControllerDelegate

View File

@@ -0,0 +1,60 @@
/* Copyright (c) 2014-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>
/**
* @abstract A context object to notify when batch fetches are finished or cancelled.
*/
@interface ASBatchContext : NSObject
/**
* Retreive the state of the current batch process.
*
* @returns A boolean reflecting if the owner of the context object is fetching another batch.
*/
- (BOOL)isFetching;
/**
* Let the context object know that a batch fetch was completed.
*
* @param didComplete A boolean that states whether or not the batch fetch completed.
*
* @discussion Only by passing YES will the owner of the context know to attempt another batch update when necessary.
* For instance, when a table has reached the end of its data, a batch fetch will be attempted unless the context
* object thinks that it is still fetching.
*/
- (void)completeBatchFetching:(BOOL)didComplete;
/**
* Ask the context object if the batch fetching process was cancelled by the context owner.
*
* @discussion If an error occurs in the context owner, the batch fetching may become out of sync and need to be
* cancelled. For best practices, pass the return value of -batchWasCancelled to -completeBatchFetch:.
*
* @returns A boolean reflecting if the context object owner had to cancel the batch process.
*/
- (BOOL)batchFetchingWasCancelled;
/**
* Notify the context object that something has interupted the batch fetching process.
*
* @discussion Call this method only when something has corrupted the batch fetching process. Calling this method should
* be left to the owner of the batch process unless there is a specific purpose.
*/
- (void)cancelBatchFetching;
/**
* Notify the context object that fetching has started.
*
* @discussion Call this method only when you are beginning a fetch process. This should really only be called by the
* context object's owner. Calling this method should be paired with -completeBatchFetching:.
*/
- (void)beginBatchFetching;
@end

View File

@@ -0,0 +1,68 @@
/* Copyright (c) 2014-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 "ASBatchContext.h"
#import "ASThread.h"
typedef NS_ENUM(NSInteger, ASBatchContextState) {
ASBatchContextStateFetching,
ASBatchContextStateCancelled,
ASBatchContextStateCompleted
};
@interface ASBatchContext ()
{
ASBatchContextState _state;
ASDN::RecursiveMutex _propertyLock;
}
@end
@implementation ASBatchContext
- (instancetype)init
{
if (self = [super init]) {
_state = ASBatchContextStateCompleted;
}
return self;
}
- (BOOL)isFetching
{
ASDN::MutexLocker l(_propertyLock);
return _state == ASBatchContextStateFetching;
}
- (BOOL)batchFetchingWasCancelled
{
ASDN::MutexLocker l(_propertyLock);
return _state == ASBatchContextStateCancelled;
}
- (void)completeBatchFetching:(BOOL)didComplete
{
if (didComplete) {
ASDN::MutexLocker l(_propertyLock);
_state = ASBatchContextStateCompleted;
}
}
- (void)beginBatchFetching
{
ASDN::MutexLocker l(_propertyLock);
_state = ASBatchContextStateFetching;
}
- (void)cancelBatchFetching
{
ASDN::MutexLocker l(_propertyLock);
_state = ASBatchContextStateCancelled;
}
@end

View File

@@ -0,0 +1,36 @@
/* Copyright (c) 2014-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 <UIKit/UIKit.h>
#import "ASBatchContext.h"
#import "ASScrollDirection.h"
#import "ASBaseDefines.h"
ASDISPLAYNODE_EXTERN_C_BEGIN
/**
@abstract Determine if batch fetching should begin based on the state of the parameters.
@param context The batch fetching context that contains knowledge about in-flight fetches.
@param scrollDirection The current scrolling direction of the scroll view.
@param bounds The bounds of the scrollview.
@param contentSize The content size of the scrollview.
@param targetOffset The offset that the scrollview will scroll to.
@param leadingScreens How many screens in the remaining distance will trigger batch fetching.
@return Whether or not the current state should proceed with batch fetching.
@discussion This method is broken into a category for unit testing purposes and should be used with the ASTableView and
* ASCollectionView batch fetching API.
*/
extern BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context,
ASScrollDirection scrollDirection,
CGRect bounds,
CGSize contentSize,
CGPoint targetOffset,
CGFloat leadingScreens);
ASDISPLAYNODE_EXTERN_C_END

View File

@@ -0,0 +1,52 @@
/* Copyright (c) 2014-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 "ASBatchFetching.h"
BOOL ASDisplayShouldFetchBatchForContext(ASBatchContext *context,
ASScrollDirection scrollDirection,
CGRect bounds,
CGSize contentSize,
CGPoint targetOffset,
CGFloat leadingScreens) {
// do not allow fetching if a batch is already in-flight and hasn't been completed or cancelled
if ([context isFetching]) {
return NO;
}
// only Up and Left scrolls are currently supported (tail loading)
if (scrollDirection != ASScrollDirectionUp && scrollDirection != ASScrollDirectionLeft) {
return NO;
}
// no fetching for null states
if (leadingScreens <= 0.0 ||
CGRectEqualToRect(bounds, CGRectZero)) {
return NO;
}
CGFloat viewLength, offset, contentLength;
if (scrollDirection == ASScrollDirectionUp) {
viewLength = bounds.size.height;
offset = targetOffset.y;
contentLength = contentSize.height;
} else { // horizontal
viewLength = bounds.size.width;
offset = targetOffset.x;
contentLength = contentSize.width;
}
// target offset will always be 0 if the content size is smaller than the viewport
BOOL hasSmallContent = offset == 0.0 && contentLength < viewLength;
CGFloat triggerDistance = viewLength * leadingScreens;
CGFloat remainingDistance = contentLength - viewLength - offset;
return hasSmallContent || remainingDistance <= triggerDistance;
}

View File

@@ -10,20 +10,14 @@
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASLayoutRangeType.h>
#import "ASScrollDirection.h"
typedef struct {
CGFloat leadingBufferScreenfuls;
CGFloat trailingBufferScreenfuls;
} ASRangeTuningParameters;
typedef NS_ENUM(NSInteger, ASScrollDirection) {
ASScrollDirectionNone,
ASScrollDirectionRight,
ASScrollDirectionLeft,
ASScrollDirectionUp,
ASScrollDirectionDown,
};
@protocol ASLayoutController <NSObject>
/**

View File

@@ -0,0 +1,17 @@
/* Copyright (c) 2014-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>
typedef NS_ENUM(NSInteger, ASScrollDirection) {
ASScrollDirectionNone,
ASScrollDirectionRight,
ASScrollDirectionLeft,
ASScrollDirectionUp,
ASScrollDirectionDown,
};

View File

@@ -0,0 +1,118 @@
/* Copyright (c) 2014-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 <XCTest/XCTest.h>
#import "ASBatchFetching.h"
@interface ASBatchFetchingTests : XCTestCase
@end
@implementation ASBatchFetchingTests
#define PASSING_RECT (CGRect){0,0,1,1}
#define PASSING_SIZE (CGSize){1,1}
#define PASSING_POINT (CGPoint){1,1}
#define VERTICAL_RECT(h) (CGRect){0,0,0,h}
#define VERTICAL_SIZE(h) (CGSize){0,h}
#define VERTICAL_OFFSET(y) (CGPoint){0,y}
#define HORIZONTAL_RECT(w) (CGRect){0,0,w,0}
#define HORIZONTAL_SIZE(w) (CGSize){w,0}
#define HORIZONTAL_OFFSET(x) (CGPoint){x,0}
- (void)testBatchNullState {
ASBatchContext *context = [[ASBatchContext alloc] init];
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, CGRectZero, CGSizeZero, CGPointZero, 0.0);
XCTAssert(shouldFetch == NO, @"Should not fetch in the null state");
}
- (void)testBatchAlreadyFetching {
ASBatchContext *context = [[ASBatchContext alloc] init];
[context beginBatchFetching];
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0);
XCTAssert(shouldFetch == NO, @"Should not fetch when context is already fetching");
}
- (void)testUnsupportedScrollDirections {
ASBatchContext *context = [[ASBatchContext alloc] init];
BOOL fetchRight = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0);
XCTAssert(fetchRight == NO, @"Should not fetch for scrolling right");
BOOL fetchDown = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionDown, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0);
XCTAssert(fetchDown == NO, @"Should not fetch for scrolling down");
BOOL fetchUp = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0);
XCTAssert(fetchUp == YES, @"Should fetch for scrolling up");
BOOL fetchLeft = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, PASSING_RECT, PASSING_SIZE, PASSING_POINT, 1.0);
XCTAssert(fetchLeft == YES, @"Should fetch for scrolling left");
}
- (void)testVerticalScrollToExactLeading {
CGFloat screen = 1.0;
ASBatchContext *context = [[ASBatchContext alloc] init];
// scroll to 1-screen top offset, height is 1 screen, so bottom is 1 screen away from end of content
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 1.0), 1.0);
XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling to exactly 1 leading screen away");
}
- (void)testVerticalScrollToLessThanLeading {
CGFloat screen = 1.0;
ASBatchContext *context = [[ASBatchContext alloc] init];
// 3 screens of content, scroll only 1/2 of one screen
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 0.5), 1.0);
XCTAssert(shouldFetch == NO, @"Fetch should not begin when vertically scrolling less than the leading distance away");
}
- (void)testVerticalScrollingPastContentSize {
CGFloat screen = 1.0;
ASBatchContext *context = [[ASBatchContext alloc] init];
// 3 screens of content, top offset to 3-screens, height 1 screen, so its 1 screen past the leading
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 3.0), VERTICAL_OFFSET(screen * 3.0), 1.0);
XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling past the content size");
}
- (void)testHorizontalScrollToExactLeading {
CGFloat screen = 1.0;
ASBatchContext *context = [[ASBatchContext alloc] init];
// scroll to 1-screen left offset, width is 1 screen, so right is 1 screen away from end of content
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 1.0), 1.0);
XCTAssert(shouldFetch == YES, @"Fetch should begin when horizontally scrolling to exactly 1 leading screen away");
}
- (void)testHorizontalScrollToLessThanLeading {
CGFloat screen = 1.0;
ASBatchContext *context = [[ASBatchContext alloc] init];
// 3 screens of content, scroll only 1/2 of one screen
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionRight, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 0.5), 1.0);
XCTAssert(shouldFetch == NO, @"Fetch should not begin when horizontally scrolling less than the leading distance away");
}
- (void)testHorizontalScrollingPastContentSize {
CGFloat screen = 1.0;
ASBatchContext *context = [[ASBatchContext alloc] init];
// 3 screens of content, left offset to 3-screens, width 1 screen, so its 1 screen past the leading
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 3.0), HORIZONTAL_OFFSET(screen * 3.0), 1.0);
XCTAssert(shouldFetch == YES, @"Fetch should begin when vertically scrolling past the content size");
}
- (void)testVerticalScrollingSmallContentSize {
CGFloat screen = 1.0;
ASBatchContext *context = [[ASBatchContext alloc] init];
// when the content size is < screen size, the target offset will always be 0
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionUp, VERTICAL_RECT(screen), VERTICAL_SIZE(screen * 0.5), VERTICAL_OFFSET(0.0), 1.0);
XCTAssert(shouldFetch == YES, @"Fetch should begin when the target is 0 and the content size is smaller than the scree");
}
- (void)testHorizontalScrollingSmallContentSize {
CGFloat screen = 1.0;
ASBatchContext *context = [[ASBatchContext alloc] init];
// when the content size is < screen size, the target offset will always be 0
BOOL shouldFetch = ASDisplayShouldFetchBatchForContext(context, ASScrollDirectionLeft, HORIZONTAL_RECT(screen), HORIZONTAL_SIZE(screen * 0.5), HORIZONTAL_OFFSET(0.0), 1.0);
XCTAssert(shouldFetch == YES, @"Fetch should begin when the target is 0 and the content size is smaller than the scree");
}
@end

View File

@@ -65,7 +65,7 @@
- (ASCellNode *)collectionView:(ASCollectionView *)collectionView nodeForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSString *text = [NSString stringWithFormat:@"[%ld.%ld] says hi", indexPath.section, indexPath.item];
NSString *text = [NSString stringWithFormat:@"[%zd.%zd] says hi", indexPath.section, indexPath.item];
ASTextCellNode *node = [[ASTextCellNode alloc] init];
node.text = text;
node.backgroundColor = [UIColor lightGrayColor];
@@ -78,13 +78,21 @@
return 300;
}
- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView {
- (void)collectionViewLockDataSource:(ASCollectionView *)collectionView
{
// lock the data source
// The data source should not be change until it is unlocked.
}
- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView {
- (void)collectionViewUnlockDataSource:(ASCollectionView *)collectionView
{
// unlock the data source to enable data source updating.
}
- (void)collectionView:(UICollectionView *)collectionView willBeginBatchFetchWithContext:(ASBatchContext *)context
{
NSLog(@"fetch additional content");
[context completeBatchFetching:YES];
}
@end

View File

@@ -17,8 +17,10 @@
#import "BlurbNode.h"
#import "KittenNode.h"
static const NSInteger kLitterSize = 200;
static const NSInteger kLitterSize = 20;
static const NSInteger kLitterBatchSize = 10;
static const NSInteger kMaxLitterSize = 100;
@interface ViewController () <ASTableViewDataSource, ASTableViewDelegate>
{
@@ -52,17 +54,23 @@ static const NSInteger kLitterSize = 200;
_tableView.asyncDelegate = self;
// populate our "data source" with some random kittens
NSMutableArray *kittenDataSource = [NSMutableArray arrayWithCapacity:kLitterSize];
for (NSInteger i = 0; i < kLitterSize; i++) {
_kittenDataSource = [self createLitterWithSize:kLitterSize];
return self;
}
- (NSArray *)createLitterWithSize:(NSInteger)litterSize
{
NSMutableArray *kittens = [NSMutableArray arrayWithCapacity:litterSize];
for (NSInteger i = 0; i < litterSize; i++) {
u_int32_t deltaX = arc4random_uniform(10) - 5;
u_int32_t deltaY = arc4random_uniform(10) - 5;
CGSize size = CGSizeMake(350 + 2 * deltaX, 350 + 4 * deltaY);
[kittenDataSource addObject:[NSValue valueWithCGSize:size]];
[kittens addObject:[NSValue valueWithCGSize:size]];
}
_kittenDataSource = kittenDataSource;
return self;
return kittens;
}
- (void)setKittenDataSource:(NSArray *)kittenDataSource {
@@ -117,12 +125,43 @@ static const NSInteger kLitterSize = 200;
return NO;
}
- (void)tableViewLockDataSource:(ASTableView *)tableView {
- (void)tableViewLockDataSource:(ASTableView *)tableView
{
self.dataSourceLocked = YES;
}
- (void)tableViewUnlockDataSource:(ASTableView *)tableView {
- (void)tableViewUnlockDataSource:(ASTableView *)tableView
{
self.dataSourceLocked = NO;
}
- (BOOL)shouldBatchFetchForTableView:(UITableView *)tableView
{
return _kittenDataSource.count < kMaxLitterSize;
}
- (void)tableView:(UITableView *)tableView willBeginBatchFetchWithContext:(ASBatchContext *)context
{
NSLog(@"adding kitties");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
dispatch_async(dispatch_get_main_queue(), ^{
NSArray *moarKittens = [self createLitterWithSize:kLitterBatchSize];
NSMutableArray *indexPaths = [[NSMutableArray alloc] init];
NSInteger existingKittens = _kittenDataSource.count;
for (NSInteger i = 0; i < moarKittens.count; i++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:existingKittens + i inSection:0]];
}
_kittenDataSource = [_kittenDataSource arrayByAddingObjectsFromArray:moarKittens];
[tableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
[context completeBatchFetching:YES];
NSLog(@"kittens added");
});
});
}
@end