diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 33b7608f..9b74e892 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -341,6 +341,8 @@ CC58AA4B1E398E1D002C8CB4 /* ASBlockTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = CC58AA4A1E398E1D002C8CB4 /* ASBlockTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC6AA2DA1E9F03B900978E87 /* ASDisplayNode+Ancestry.h in Headers */ = {isa = PBXBuildFile; fileRef = CC6AA2D81E9F03B900978E87 /* ASDisplayNode+Ancestry.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC6AA2DB1E9F03B900978E87 /* ASDisplayNode+Ancestry.m in Sources */ = {isa = PBXBuildFile; fileRef = CC6AA2D91E9F03B900978E87 /* ASDisplayNode+Ancestry.m */; }; + CC7382C11ECBDAE60055FC22 /* ASObjectBasedCollectionDataSource.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7382BF1ECBDAE60055FC22 /* ASObjectBasedCollectionDataSource.h */; }; + CC7382C21ECBDAE60055FC22 /* ASObjectBasedCollectionDataSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC7382C01ECBDAE60055FC22 /* ASObjectBasedCollectionDataSource.mm */; }; CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */; }; CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC87BB951DA8193C0090E380 /* ASCellNode+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = CC87BB941DA8193C0090E380 /* ASCellNode+Internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -790,6 +792,8 @@ CC58AA4A1E398E1D002C8CB4 /* ASBlockTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBlockTypes.h; sourceTree = ""; }; CC6AA2D81E9F03B900978E87 /* ASDisplayNode+Ancestry.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "ASDisplayNode+Ancestry.h"; path = "Base/ASDisplayNode+Ancestry.h"; sourceTree = ""; }; CC6AA2D91E9F03B900978E87 /* ASDisplayNode+Ancestry.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "ASDisplayNode+Ancestry.m"; path = "Base/ASDisplayNode+Ancestry.m"; sourceTree = ""; }; + CC7382BF1ECBDAE60055FC22 /* ASObjectBasedCollectionDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASObjectBasedCollectionDataSource.h; sourceTree = ""; }; + CC7382C01ECBDAE60055FC22 /* ASObjectBasedCollectionDataSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASObjectBasedCollectionDataSource.mm; sourceTree = ""; }; CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosFrameworkImageRequest.h; sourceTree = ""; }; CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequest.m; sourceTree = ""; }; CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPhotosFrameworkImageRequestTests.m; sourceTree = ""; }; @@ -985,6 +989,8 @@ 058D09B1195D04C000B7D73C /* Source */ = { isa = PBXGroup; children = ( + CC7382BF1ECBDAE60055FC22 /* ASObjectBasedCollectionDataSource.h */, + CC7382C01ECBDAE60055FC22 /* ASObjectBasedCollectionDataSource.mm */, CCE04B1D1E313E99006AEBBB /* Collection Data Adapter */, CC58AA4A1E398E1D002C8CB4 /* ASBlockTypes.h */, DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */, @@ -1725,6 +1731,7 @@ 692BE8D71E36B65B00C86D87 /* ASLayoutSpecPrivate.h in Headers */, 34EFC75D1B701BE900AD841F /* ASInternalHelpers.h in Headers */, E5B5B9D11E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h in Headers */, + CC7382C11ECBDAE60055FC22 /* ASObjectBasedCollectionDataSource.h in Headers */, DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */, 68B8A4E21CBDB958007E4543 /* ASWeakProxy.h in Headers */, 9F98C0271DBE29FC00476D92 /* ASControlTargetAction.h in Headers */, @@ -2202,6 +2209,7 @@ 34566CB31BC1213700715E6B /* ASPhotosFrameworkImageRequest.m in Sources */, 254C6B831BF94F8A003EC431 /* ASTextKitCoreTextAdditions.m in Sources */, CCCCCCE21EC3EF060087FE10 /* ASTextUtilities.m in Sources */, + CC7382C21ECBDAE60055FC22 /* ASObjectBasedCollectionDataSource.mm in Sources */, CC55A70E1E529FA200594372 /* UIResponder+AsyncDisplayKit.m in Sources */, 697796611D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */, B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */, diff --git a/Source/ASObjectBasedCollectionDataSource.h b/Source/ASObjectBasedCollectionDataSource.h new file mode 100644 index 00000000..857e1ee0 --- /dev/null +++ b/Source/ASObjectBasedCollectionDataSource.h @@ -0,0 +1,125 @@ +// +// ASObjectBasedCollectionDataSource.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 5/16/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import +#import +#import + +@class ASDisplayNode; + +NS_ASSUME_NONNULL_BEGIN + +@protocol IGListDiffable +@end + +@protocol ASCopyDiffable +@end + +AS_SUBCLASSING_RESTRICTED +@interface ASCollectionData, SectionType: id, ItemType: id> : NSObject + +@property (atomic, copy, readonly) ObjectType rootObject; + +@property (atomic, copy, readonly) NSArray *sections; + +- (NSArray *)itemsInSectionWithID:(id)sectionID; + +@end + +@interface ASMutableCollectionData, SectionType: id, ItemType: id> : NSObject + +@end + +@interface ASSectionController2, ItemType : id> : NSObject + +@property (atomic, copy, readonly) NSArray *items; + +- (void)didUpdateToObject:(ObjectType)object; + +@end + +@interface ASCellNode (ObjectHandling) + +- (BOOL)canUpdateToObject:(id)object; + +- (void)didUpdateToObject:(id)object; + +@end + +AS_SUBCLASSING_RESTRICTED +@interface ASCollectionDataRequest> : NSObject + +@property (atomic, readonly, getter=isCancelled) BOOL cancelled; + +@end + +typedef ASDisplayNode *_Nullable(^ASObjectRenderBlock)(id object); + +AS_SUBCLASSING_RESTRICTED +@interface ASObjectRenderer : NSObject + +@property (class, atomic, strong, readonly) ASObjectRenderer *sharedRenderer; + +/** + * You should call this in the `load` method of your display node subclass. + * + * @param c The class of objects you can render. + * @param block The block that attempts to render the object into a node. + */ +- (void)addRenderMethodForObjectClass:(nullable Class)c block:(ASObjectRenderBlock)block; + +/** + */ +- (nullable ASDisplayNode *)renderObject:(id)object; + +@end + +AS_SUBCLASSING_RESTRICTED +@interface ASSectionRenderer : NSObject + +@property (class, atomic, strong, readonly) ASObjectRenderer *sharedRenderer; + +/** + * You should call this in the `load` method of your display node subclass. + * + * @param c The class of objects you can render. + * @param block The block that attempts to render the object into a node. + */ +- (void)addRenderMethodForObjectClass:(nullable Class)c block:(ASObjectRenderBlock)block; + +/** + */ +- (nullable NSArray *)renderObject:(id)object; +@end + +AS_SUBCLASSING_RESTRICTED +@interface ASObjectBasedCollectionDataSource, SectionType: id, ItemType: id> : NSObject + +- (instancetype)initWithCollectionNode:(ASCollectionNode *)collectionNode sourceQueue:(nullable dispatch_queue_t)sourceQueue; + +@property (nonatomic, weak, readonly) ASCollectionNode *collectionNode; + +@property (nonatomic, weak) id scrollViewDelegate; + +@property (nonatomic, copy, nullable) ASCollectionData *(^dataBlock)(ASCollectionDataRequest *request); + +/** + * If this is the main queue (default) the method will be called on the main thread. + */ +@property (nonatomic, strong, readonly) dispatch_queue_t sourceQueue; + +/** + * @note You can call this inside `+performWithoutAnimation:`. + * @note You can call this method from any thread. + */ +- (void)invalidateWithAnimationCompletion:(void(^)(BOOL finished))animationCompletion; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Source/ASObjectBasedCollectionDataSource.mm b/Source/ASObjectBasedCollectionDataSource.mm new file mode 100644 index 00000000..c78f6525 --- /dev/null +++ b/Source/ASObjectBasedCollectionDataSource.mm @@ -0,0 +1,321 @@ +// +// ASObjectBasedCollectionDataSource.m +// AsyncDisplayKit +// +// Created by Adlai Holler on 5/16/17. +// Copyright © 2017 Facebook. All rights reserved. +// + +#import "ASObjectBasedCollectionDataSource.h" +#import +#import +#import +#import +#import +#import +#import + +@implementation ASObjectRenderer { + NSMapTable *> *_classToRenderBlocksMap; + std::atomic _mapIsReadOnly; +} + ++ (ASObjectRenderer *)sharedRenderer +{ + static ASObjectRenderer *sharedRenderer; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedRenderer = [[ASObjectRenderer alloc] init]; + }); + return sharedRenderer; +} + +- (instancetype)init +{ + if (self = [super init]) { + _classToRenderBlocksMap = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality valueOptions:NSMapTableStrongMemory]; + } + return self; +} + +- (void)addRenderMethodForObjectClass:(Class)c block:(ASDisplayNode * _Nonnull (^)(id _Nonnull))block +{ + ASDisplayNodeAssertMainThread(); + if (_mapIsReadOnly.load()) { + ASDisplayNodeFailAssert(@"Cannot add renderers after first render."); + return; + } + + c = c ?: [NSObject class]; + auto blocks = [_classToRenderBlocksMap objectForKey:c]; + if (blocks == nil) { + blocks = [NSMutableArray array]; + [_classToRenderBlocksMap setObject:blocks forKey:c]; + } + [blocks insertObject:block atIndex:0]; +} + +- (ASDisplayNode *)renderObject:(id)object +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _mapIsReadOnly = YES; + }); + + NSParameterAssert(object != nil); + Class c = object_getClass(object); + while (c != nil) { + for (ASObjectRenderBlock block in [_classToRenderBlocksMap objectForKey:c]) { + auto node = block(object); + if (node != nil) { + return node; + } + } + c = class_getSuperclass(c); + } + + ASDisplayNodeFailAssert(@"Failed to render node for object: %@", object); + return [[ASDisplayNode alloc] init]; +} + +@end + +@interface ASObjectBasedInvalidationContextImpl : NSObject +@end + +@interface ASCollectionDataInvalidation : NSObject +@property (nonatomic, copy, nullable) void (^animationCompletion)(BOOL finished); + +@end + +@interface ASCollectionDataRequest () +@property (nonatomic, copy) asdisplaynode_iscancelled_block_t cancelledBlock; +@property (nonatomic, strong) ASCollectionData *capturedData; + +@property (nonatomic, strong) id diffResult; +@end + +@implementation ASCollectionDataRequest + +- (BOOL)isCancelled +{ + return self.cancelledBlock ? self.cancelledBlock() : NO; +} + +@end + +@interface ASObjectBasedCollectionDataSource () { + std::atomic _sourceDataVersion; +} +@property (atomic, copy) ASCollectionData *dataCommittedToNode; + +@property (nonatomic, readonly) dispatch_queue_t privateQueue; + +@property (nonatomic, strong, readonly) ASMainSerialQueue *mainSerialQueue; +@end + + + +@implementation ASCollectionDataInvalidation + +@end + +/** + * + */ +@implementation ASObjectBasedCollectionDataSource { + std::atomic _updateScheduled; + char _sourceQueueKey; + char _privateQueueKey; + + ASAtomicArrayType(ASCollectionDataInvalidation) *_invalidations; + + // Only written to on sourceQueue, read from multiple queues. + std::atomic _sourceDataIsFullyValid; +} + +- (instancetype)initWithCollectionNode:(ASCollectionNode *)collectionNode sourceQueue:(nullable dispatch_queue_t)sourceQueue +{ + if (self = [super init]) { + _privateQueue = dispatch_queue_create("com.texture.objectBasedCollectionDataSource.privateQueue", DISPATCH_QUEUE_SERIAL); + dispatch_queue_set_specific(_privateQueue, &_privateQueueKey, (void *)kCFNull, NULL); + _mainSerialQueue = [[ASMainSerialQueue alloc] init]; + if (sourceQueue) { + _sourceQueue = sourceQueue; + dispatch_queue_set_specific(_sourceQueue, &_sourceQueueKey, (void *)kCFNull, NULL); + } + } + return self; +} + +- (BOOL)isOnPrivateQueue +{ + return dispatch_get_specific(&_privateQueueKey) != NULL; +} + +- (BOOL)isOnSourceQueue +{ + if (_sourceQueue) { + return dispatch_get_specific(&_sourceQueueKey) != NULL; + } else { + return ASDisplayNodeThreadIsMain(); + } +} + +- (void)dealloc +{ + if (_sourceQueue) { + dispatch_queue_set_specific(_sourceQueue, &_sourceQueueKey, NULL, NULL); + } +} + +- (void)performOnSourceQueue:(void(^)())block +{ + if ([self isOnSourceQueue]) { + block(); + } else { + if (_sourceQueue) { + dispatch_async(_sourceQueue, block); + } else { + [_mainSerialQueue performBlockOnMainThread:block]; + } + } +} + +- (void)invalidateWithAnimationCompletion:(void (^)(BOOL))animationCompletion +{ + ASCollectionDataInvalidation *invalidation = [[ASCollectionDataInvalidation alloc] init]; + invalidation.animationCompletion = animationCompletion; + [_invalidations accessWithBlock:^(NSMutableArray * _Nonnull mutableValue) { + [mutableValue addObject:invalidation]; + }]; + + if (!_updateScheduled.exchange(YES)) { + dispatch_async(_privateQueue, ^{ + _updateScheduled = NO; + [self privateQueue_runUpdateFromTopLevel]; + }); + } +} + +- (void)privateQueue_runUpdateFromTopLevel +{ + NSParameterAssert([self isOnPrivateQueue]); + auto group = dispatch_group_create(); + auto request = [[ASCollectionDataRequest alloc] init]; + + // On source queue, latch in a new copy of data. + dispatch_group_enter(group); + [self performOnSourceQueue:^{ + [self sourceQueue_captureDataWithRequest:request]; + dispatch_group_leave(group); + }]; + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + + // On private queue (here), compute the diff. + [self privateQueue_processDataFromRequest:request]; + + // On main thread, apply the update to the collection node. + dispatch_group_enter(group); + [_mainSerialQueue performBlockOnMainThread:^{ + [self mainThread_applyDiffWithRequest:request]; + dispatch_group_leave(group); + }]; + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); +} + +- (void)sourceQueue_captureDataWithRequest:(ASCollectionDataRequest *)request +{ + NSParameterAssert([self isOnSourceQueue]); + + request.capturedInvalidationCount = ASAtomicAccessOneProperty(_invalidations, count); + request.capturedData = self.dataBlock(request); + request.cancelledBlock = ^BOOL{ + return !_sourceDataIsFullyValid; + }; +} + +- (void)privateQueue_processDataFromRequest:(ASCollectionDataRequest *)request +{ + NSParameterAssert([self isOnPrivateQueue]); + if (request.cancelled) { + return; + } + request.diffResult = nil; + + auto capturedData = request.capturedData; + // Gather new array of sections and perform full hierarchical diff. + NSArray *oldSections = self.dataCommittedToNode.sections; + NSArray *newSections = capturedData.sections; + for (id section in newSections) { + + } +} + +- (void)mainThread_applyDiffWithRequest:(ASCollectionDataRequest *)request +{ + ASDisplayNodeAssertMainThread(); + if (request.cancelled) { + return; + } + + // Now that we're committed, grab the first chunk of animation completion blocks. + __block NSArray *blocksToRun; + NSRange range = NSMakeRange(0, request.animationCompletionBlockCount); + [_animationCompletionBlocks accessWithBlock:^(NSMutableArray * _Nonnull mutableValue) { + blocksToRun = [mutableValue subarrayWithRange:range]; + [mutableValue removeObjectsInRange:range]; + }]; + + [self.collectionNode performBatchUpdates:^{ + self.dataCommittedToNode = request.capturedData; + // Apply diff. + + } completion:^(BOOL finished) { + for (void(^block)(BOOL) in blocksToRun) { + block(finished); + } + }]; +} + +#pragma mark - ASCollectionDataSource + +- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath +{ + id object = [self.dataCommittedToNode itemAtIndexPath:indexPath]; + return ^{ + auto node = [ASObjectRenderer.sharedRenderer renderObject:object]; + if ([node isKindOfClass:[ASCellNode class]]) { + return (ASCellNode *)node; + } else { + ASCellNode *cellNode = [[ASCellNode alloc] init]; + [cellNode addSubnode:node]; + cellNode.layoutSpecBlock = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { + return [[ASWrapperLayoutSpec alloc] initWithLayoutElement:node]; + }; + return cellNode; + } + }; +} + +- (NSInteger)numberOfSectionsInCollectionNode:(ASCollectionNode *)collectionNode +{ + return self.dataCommittedToNode.sections.count; +} + +- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section +{ + return [self.dataCommittedToNode itemsInSectionAtIndex:section].count; +} + +#pragma mark - UIScrollViewDelegate + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView +{ + // TODO: Handle cases where the delegate doesn't implement this. + return [self.scrollViewDelegate scrollViewDidScroll:scrollView]; +} + +// TODO: Other UIScrollViewDelegate methods. + +@end diff --git a/Source/Details/ASMainSerialQueue.h b/Source/Details/ASMainSerialQueue.h index ef939eff..68e45c04 100644 --- a/Source/Details/ASMainSerialQueue.h +++ b/Source/Details/ASMainSerialQueue.h @@ -18,9 +18,47 @@ #import #import +NS_ASSUME_NONNULL_BEGIN + AS_SUBCLASSING_RESTRICTED @interface ASMainSerialQueue : NSObject - (void)performBlockOnMainThread:(dispatch_block_t)block; +- (void)flushAllBlocks; + @end + + +/** + * A shortcut to read one property from an atomic without copying the whole value. + * + * For example, `NSUInteger c = ASAtomicAccessOneProperty(myAtomicArray, count);` + * The slower form would be `NSUInteger c = myAtomicArray.value.count;` + */ +#define ASAtomicAccessOneProperty(instance, getterName) ({ \ + __block __typeof([instance.value getterName]) __value; \ + [instance accessWithBlock:^(id mutableValue) { \ + __value = [mutableValue getterName]; \ + }]; \ + __value; \ +}) + +#define ASAtomicArrayType(T) ASAtomic *, NSMutableArray *> + +/** + * An atomic object container designed for classes that have mutable instances. + */ +@interface ASAtomic, MutableType : id> : NSObject + ++ (instancetype)atomicWithValue:(MutableType)value; + +- (void)accessWithBlock:(AS_NOESCAPE void (^)(MutableType mutableValue))block; + +- (CopiedType)readAndUpdate:(nullable AS_NOESCAPE void (^)(MutableType mutableValue))block; + +@property (atomic, copy, readonly) CopiedType value; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASMainSerialQueue.mm b/Source/Details/ASMainSerialQueue.mm index e79481fa..fe1a8948 100644 --- a/Source/Details/ASMainSerialQueue.mm +++ b/Source/Details/ASMainSerialQueue.mm @@ -22,8 +22,8 @@ @interface ASMainSerialQueue () { - ASDN::Mutex _serialQueueLock; - NSMutableArray *_blocks; + ASAtomic *_blocks; + std::atomic _scheduledOnMainQueue; } @end @@ -36,40 +36,43 @@ return nil; } - _blocks = [[NSMutableArray alloc] init]; + _blocks = [ASAtomic atomicWithValue:[NSMutableArray array]]; return self; } - (void)performBlockOnMainThread:(dispatch_block_t)block { - ASDN::MutexLocker l(_serialQueueLock); - [_blocks addObject:block]; - { - ASDN::MutexUnlocker u(_serialQueueLock); - [self runBlocks]; + // If we're already on the main thread, just run now and return. + if (ASDisplayNodeThreadIsMain()) { + block(); + return; + } + + // If we're off-main, add the block to `_blocks` and ensure that we're scheduled to run `_blocks` ASAP. + [_blocks accessWithBlock:^(NSMutableArray * _Nonnull mutableValue) { + [mutableValue addObject:block]; + }]; + + if (!_scheduledOnMainQueue.exchange(YES)) { + dispatch_async(dispatch_get_main_queue(), ^{ + _scheduledOnMainQueue = NO; + [self flushAllBlocks]; + }); } } -- (void)runBlocks +- (void)flushAllBlocks { - dispatch_block_t mainThread = ^{ - do { - ASDN::MutexLocker l(_serialQueueLock); - dispatch_block_t block; - if (_blocks.count > 0) { - block = _blocks[0]; - [_blocks removeObjectAtIndex:0]; - } else { - break; - } - { - ASDN::MutexUnlocker u(_serialQueueLock); - block(); - } - } while (true); - }; - - ASPerformBlockOnMainThread(mainThread); + ASDisplayNodeAssertMainThread(); + NSArray *batch; + do { + batch = [_blocks readAndUpdate:^(NSMutableArray * _Nonnull mutableValue) { + [mutableValue removeAllObjects]; + }]; + for (dispatch_block_t block in batch) { + block(); + } + } while (batch.count > 0); } - (NSString *)description @@ -78,3 +81,54 @@ } @end + +@implementation ASAtomic { + ASDN::Mutex _mutex; + id _value; +} + ++ (instancetype)atomicWithValue:(id)value +{ + ASAtomic *atomic = [[ASAtomic alloc] init]; + atomic->_value = value; + return atomic; +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wmismatched-parameter-types" + +- (void)accessWithBlock:(__kindof id _Nonnull (^)(__kindof id _Nonnull))block +{ + ASDN::MutexLocker locker (_mutex); + block(_value); +} + +- (id)readAndUpdate:(void(^)(__kindof id _Nonnull))block +#pragma clang diagnostic pop +{ + id oldValue; + + { + ASDN::MutexLocker locker (_mutex); + oldValue = [_value copy]; + if (block) { + block(_value); + } + } + + return oldValue; +} + +- (id)value +{ + ASDN::MutexLocker locker(_mutex); + return [_value copy]; +} + +- (void)setValue:(id)value +{ + ASDN::MutexLocker locker(_mutex); + _value = value; +} + +@end