Add support for run loop queues with no handler, optimize

This commit is contained in:
Adlai Holler
2016-11-20 15:55:18 +09:00
parent 6d01bbeb19
commit 887f48cbda
3 changed files with 30 additions and 15 deletions

View File

@@ -464,7 +464,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
static ASRunLoopQueue *queue; static ASRunLoopQueue *queue;
static dispatch_once_t onceToken; static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ dispatch_once(&onceToken, ^{
queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() andHandler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) { }]; queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() andHandler:nil];
queue.batchSize = 10; queue.batchSize = 10;
}); });

View File

@@ -16,8 +16,19 @@ NS_ASSUME_NONNULL_BEGIN
@interface ASRunLoopQueue<ObjectType> : NSObject @interface ASRunLoopQueue<ObjectType> : NSObject
/**
* Create a new queue with the given run loop and handler.
*
* @param runloop The run loop that will drive this queue.
* @param handlerBlock An optional block to be run for each enqueued object.
*
* @discussion You may pass @c nil for the handler if you simply want the objects to
* be retained at enqueue time, and released during the run loop step. This is useful
* for creating a "main deallocation queue", as @c ASDeallocQueue creates its own
* worker thread with its own run loop.
*/
- (instancetype)initWithRunLoop:(CFRunLoopRef)runloop - (instancetype)initWithRunLoop:(CFRunLoopRef)runloop
andHandler:(void(^)(ObjectType dequeuedItem, BOOL isQueueDrained))handlerBlock; andHandler:(nullable void(^)(ObjectType dequeuedItem, BOOL isQueueDrained))handlerBlock;
- (void)enqueue:(ObjectType)object; - (void)enqueue:(ObjectType)object;

View File

@@ -16,6 +16,7 @@
#import <cstdlib> #import <cstdlib>
#import <deque> #import <deque>
#import <vector>
#define ASRunLoopQueueLoggingEnabled 0 #define ASRunLoopQueueLoggingEnabled 0
@@ -222,7 +223,12 @@ static void runLoopSourceCallback(void *info) {
- (void)processQueue - (void)processQueue
{ {
std::deque<id> itemsToProcess = std::deque<id>(); BOOL hasExecutionBlock = (_queueConsumer != nil);
// If we have an execution block, this vector will be populated, otherwise remains empty.
// This is to avoid needlessly retaining/releasing the objects if we don't have a block.
std::vector<id> itemsToProcess;
BOOL isQueueDrained = NO; BOOL isQueueDrained = NO;
{ {
ASDN::MutexLocker l(_internalQueueLock); ASDN::MutexLocker l(_internalQueueLock);
@@ -235,25 +241,23 @@ static void runLoopSourceCallback(void *info) {
ASProfilingSignpostStart(0, self); ASProfilingSignpostStart(0, self);
// Snatch the next batch of items. // Snatch the next batch of items.
NSUInteger totalNodeCount = _internalQueue.size(); auto firstItemToProcess = _internalQueue.cbegin();
for (int i = 0; i < MIN(self.batchSize, totalNodeCount); i++) { auto lastItemToProcess = MIN(_internalQueue.cend(), firstItemToProcess + self.batchSize);
id node = _internalQueue[0];
itemsToProcess.push_back(node); if (hasExecutionBlock) {
_internalQueue.pop_front(); itemsToProcess = std::vector<id>(firstItemToProcess, lastItemToProcess);
} }
_internalQueue.erase(firstItemToProcess, lastItemToProcess);
if (_internalQueue.empty()) { if (_internalQueue.empty()) {
isQueueDrained = YES; isQueueDrained = YES;
} }
} }
unsigned long numberOfItems = itemsToProcess.size(); // itemsToProcess will be empty if _queueConsumer == nil so no need to check again.
for (int i = 0; i < numberOfItems; i++) { auto itemsEnd = itemsToProcess.cend();
if (isQueueDrained && i == numberOfItems - 1) { for (auto iterator = itemsToProcess.begin(); iterator < itemsEnd; iterator++) {
_queueConsumer(itemsToProcess[i], YES); _queueConsumer(*iterator, isQueueDrained && iterator == itemsEnd - 1);
} else {
_queueConsumer(itemsToProcess[i], isQueueDrained);
}
} }
// If the queue is not fully drained yet force another run loop to process next batch of items // If the queue is not fully drained yet force another run loop to process next batch of items