From 1e7d46196fb82b5a2afeccd9adbb9d5a1ae7ddac Mon Sep 17 00:00:00 2001 From: Mustafa Besnili Date: Tue, 17 Oct 2017 16:18:23 +0300 Subject: [PATCH 001/133] Fix "This block and function declaration is not a prototype" warning. (#619) --- Source/ASBlockTypes.h | 2 +- Source/ASCollectionNode.h | 8 ++++---- Source/ASCollectionView.h | 8 ++++---- Source/ASDisplayNode+Beta.h | 4 ++-- Source/ASDisplayNode.h | 10 +++++----- Source/ASDisplayNodeExtras.h | 4 ++-- Source/ASTableNode.h | 8 ++++---- Source/ASTableView.h | 4 ++-- Source/Base/ASAssert.h | 6 +++--- Source/Base/ASLog.h | 14 +++++++------- Source/Details/ASDataController.h | 2 +- Source/Details/ASTraitCollection.h | 2 +- Source/Layout/ASLayoutElementPrivate.h | 4 ++-- Source/Private/ASCollectionView+Undeprecated.h | 4 ++-- Source/Private/ASInternalHelpers.h | 8 ++++---- Source/Private/ASInternalHelpers.m | 4 ++-- .../TextExperiment/Utility/ASTextUtilities.h | 4 ++-- 17 files changed, 48 insertions(+), 48 deletions(-) diff --git a/Source/ASBlockTypes.h b/Source/ASBlockTypes.h index cf05a1937..f0e2875e1 100644 --- a/Source/ASBlockTypes.h +++ b/Source/ASBlockTypes.h @@ -22,7 +22,7 @@ /** * ASCellNode creation block. Used to lazily create the ASCellNode instance for a specified indexPath. */ -typedef ASCellNode * _Nonnull(^ASCellNodeBlock)(); +typedef ASCellNode * _Nonnull(^ASCellNodeBlock)(void); // Type for the cancellation checker block passed into the async display blocks. YES means the operation has been cancelled, NO means continue. typedef BOOL(^asdisplaynode_iscancelled_block_t)(void); diff --git a/Source/ASCollectionNode.h b/Source/ASCollectionNode.h index d60fd57ef..5d7740458 100644 --- a/Source/ASCollectionNode.h +++ b/Source/ASCollectionNode.h @@ -244,7 +244,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion; /** * Perform a batch of updates asynchronously, optionally disabling all animations in the batch. This method must be called from the main thread. @@ -255,7 +255,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion; /** * Returns YES if the ASCollectionNode is still processing changes from performBatchUpdates:. @@ -284,7 +284,7 @@ NS_ASSUME_NONNULL_BEGIN * * Calling -waitUntilAllUpdatesAreProcessed is one way to flush any pending update completion blocks. */ -- (void)onDidFinishProcessingUpdates:(nullable void (^)())didFinishProcessingUpdates; +- (void)onDidFinishProcessingUpdates:(nullable void (^)(void))didFinishProcessingUpdates; /** * Blocks execution of the main thread until all section and item updates are committed to the view. This method must be called from the main thread. @@ -382,7 +382,7 @@ NS_ASSUME_NONNULL_BEGIN * the main thread. * @warning This method is substantially more expensive than UICollectionView's version. */ -- (void)reloadDataWithCompletion:(nullable void (^)())completion; +- (void)reloadDataWithCompletion:(nullable void (^)(void))completion; /** diff --git a/Source/ASCollectionView.h b/Source/ASCollectionView.h index ba9dda296..9f552e44c 100644 --- a/Source/ASCollectionView.h +++ b/Source/ASCollectionView.h @@ -256,7 +256,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); +- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); /** * Perform a batch of updates asynchronously. This method must be called from the main thread. @@ -267,7 +267,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); +- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASCollectionNode method instead."); /** * Reload everything from scratch, destroying the working range and all cached nodes. @@ -276,7 +276,7 @@ NS_ASSUME_NONNULL_BEGIN * the main thread. * @warning This method is substantially more expensive than UICollectionView's version. */ -- (void)reloadDataWithCompletion:(nullable void (^)())completion AS_UNAVAILABLE("Use ASCollectionNode method instead."); +- (void)reloadDataWithCompletion:(nullable void (^)(void))completion AS_UNAVAILABLE("Use ASCollectionNode method instead."); /** * Reload everything from scratch, destroying the working range and all cached nodes. @@ -296,7 +296,7 @@ NS_ASSUME_NONNULL_BEGIN * See ASCollectionNode.h for full documentation of these methods. */ @property (nonatomic, readonly) BOOL isProcessingUpdates; -- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion; +- (void)onDidFinishProcessingUpdates:(nullable void (^)(void))completion; - (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use -[ASCollectionNode waitUntilAllUpdatesAreProcessed] instead."); /** diff --git a/Source/ASDisplayNode+Beta.h b/Source/ASDisplayNode+Beta.h index ea241bd16..fff4ee975 100644 --- a/Source/ASDisplayNode+Beta.h +++ b/Source/ASDisplayNode+Beta.h @@ -28,8 +28,8 @@ NS_ASSUME_NONNULL_BEGIN ASDISPLAYNODE_EXTERN_C_BEGIN -void ASPerformBlockOnMainThread(void (^block)()); -void ASPerformBlockOnBackgroundThread(void (^block)()); // DISPATCH_QUEUE_PRIORITY_DEFAULT +void ASPerformBlockOnMainThread(void (^block)(void)); +void ASPerformBlockOnBackgroundThread(void (^block)(void)); // DISPATCH_QUEUE_PRIORITY_DEFAULT ASDISPLAYNODE_EXTERN_C_END #if ASEVENTLOG_ENABLE diff --git a/Source/ASDisplayNode.h b/Source/ASDisplayNode.h index 84da628b5..bb81387b3 100644 --- a/Source/ASDisplayNode.h +++ b/Source/ASDisplayNode.h @@ -36,17 +36,17 @@ NS_ASSUME_NONNULL_BEGIN /** * UIView creation block. Used to create the backing view of a new display node. */ -typedef UIView * _Nonnull(^ASDisplayNodeViewBlock)(); +typedef UIView * _Nonnull(^ASDisplayNodeViewBlock)(void); /** * UIView creation block. Used to create the backing view of a new display node. */ -typedef UIViewController * _Nonnull(^ASDisplayNodeViewControllerBlock)(); +typedef UIViewController * _Nonnull(^ASDisplayNodeViewControllerBlock)(void); /** * CALayer creation block. Used to create the backing layer of a new display node. */ -typedef CALayer * _Nonnull(^ASDisplayNodeLayerBlock)(); +typedef CALayer * _Nonnull(^ASDisplayNodeLayerBlock)(void); /** * ASDisplayNode loaded callback block. This block is called BEFORE the -didLoad method and is always called on the main thread. @@ -880,7 +880,7 @@ extern NSInteger const ASDefaultDrawingPriority; - (void)transitionLayoutWithSizeRange:(ASSizeRange)constrainedSize animated:(BOOL)animated shouldMeasureAsync:(BOOL)shouldMeasureAsync - measurementCompletion:(nullable void(^)())completion; + measurementCompletion:(nullable void(^)(void))completion; /** @@ -897,7 +897,7 @@ extern NSInteger const ASDefaultDrawingPriority; */ - (void)transitionLayoutWithAnimation:(BOOL)animated shouldMeasureAsync:(BOOL)shouldMeasureAsync - measurementCompletion:(nullable void(^)())completion; + measurementCompletion:(nullable void(^)(void))completion; /** * @abstract Cancels all performing layout transitions. Can be called on any thread. diff --git a/Source/ASDisplayNodeExtras.h b/Source/ASDisplayNodeExtras.h index aefb445f9..923f8b72f 100644 --- a/Source/ASDisplayNodeExtras.h +++ b/Source/ASDisplayNodeExtras.h @@ -208,8 +208,8 @@ extern __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstSubnode(ASDispla */ extern __kindof ASDisplayNode * _Nullable ASDisplayNodeFindFirstSubnodeOfClass(ASDisplayNode *start, Class c) AS_WARN_UNUSED_RESULT; -extern UIColor *ASDisplayNodeDefaultPlaceholderColor() AS_WARN_UNUSED_RESULT; -extern UIColor *ASDisplayNodeDefaultTintColor() AS_WARN_UNUSED_RESULT; +extern UIColor *ASDisplayNodeDefaultPlaceholderColor(void) AS_WARN_UNUSED_RESULT; +extern UIColor *ASDisplayNodeDefaultTintColor(void) AS_WARN_UNUSED_RESULT; /** Disable willAppear / didAppear / didDisappear notifications for a sub-hierarchy, then re-enable when done. Nested calls are supported. diff --git a/Source/ASTableNode.h b/Source/ASTableNode.h index 8322faaf4..aae4f375d 100644 --- a/Source/ASTableNode.h +++ b/Source/ASTableNode.h @@ -172,7 +172,7 @@ NS_ASSUME_NONNULL_BEGIN * the main thread. * @warning This method is substantially more expensive than UITableView's version. */ -- (void)reloadDataWithCompletion:(nullable void (^)())completion; +- (void)reloadDataWithCompletion:(nullable void (^)(void))completion; /** * Reload everything from scratch, destroying the working range and all cached nodes. @@ -198,7 +198,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion; /** * Perform a batch of updates asynchronously with animations in the batch. This method must be called from the main thread. @@ -209,7 +209,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion; /** * Returns YES if the ASCollectionNode is still processing changes from performBatchUpdates:. @@ -238,7 +238,7 @@ NS_ASSUME_NONNULL_BEGIN * * Calling -waitUntilAllUpdatesAreProcessed is one way to flush any pending update completion blocks. */ -- (void)onDidFinishProcessingUpdates:(nullable void (^)())didFinishProcessingUpdates; +- (void)onDidFinishProcessingUpdates:(nullable void (^)(void))didFinishProcessingUpdates; /** * Blocks execution of the main thread until all section and item updates are committed to the view. This method must be called from the main thread. diff --git a/Source/ASTableView.h b/Source/ASTableView.h index ba6736eba..d99ae5b21 100644 --- a/Source/ASTableView.h +++ b/Source/ASTableView.h @@ -180,7 +180,7 @@ NS_ASSUME_NONNULL_BEGIN * the main thread. * @warning This method is substantially more expensive than UITableView's version. */ --(void)reloadDataWithCompletion:(void (^ _Nullable)())completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); +-(void)reloadDataWithCompletion:(void (^ _Nullable)(void))completion ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); /** * Reload everything from scratch, destroying the working range and all cached nodes. @@ -219,7 +219,7 @@ NS_ASSUME_NONNULL_BEGIN * See ASTableNode.h for full documentation of these methods. */ @property (nonatomic, readonly) BOOL isProcessingUpdates; -- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion; +- (void)onDidFinishProcessingUpdates:(nullable void (^)(void))completion; - (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use -[ASTableNode waitUntilAllUpdatesAreProcessed] instead."); - (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); diff --git a/Source/Base/ASAssert.h b/Source/Base/ASAssert.h index 21bdcf2da..336ac3d0e 100644 --- a/Source/Base/ASAssert.h +++ b/Source/Base/ASAssert.h @@ -78,11 +78,11 @@ #pragma mark - Main Thread Assertions Disabling ASDISPLAYNODE_EXTERN_C_BEGIN -BOOL ASMainThreadAssertionsAreDisabled(); +BOOL ASMainThreadAssertionsAreDisabled(void); -void ASPushMainThreadAssertionsDisabled(); +void ASPushMainThreadAssertionsDisabled(void); -void ASPopMainThreadAssertionsDisabled(); +void ASPopMainThreadAssertionsDisabled(void); ASDISPLAYNODE_EXTERN_C_END #pragma mark - Non-Fatal Assertions diff --git a/Source/Base/ASLog.h b/Source/Base/ASLog.h index e4f54fd81..5de8ab939 100644 --- a/Source/Base/ASLog.h +++ b/Source/Base/ASLog.h @@ -40,31 +40,31 @@ ASDISPLAYNODE_EXTERN_C_BEGIN * are at the `debug` log level, which the system * disables in production. */ -void ASDisableLogging(); +void ASDisableLogging(void); /// Log for general node events e.g. interfaceState, didLoad. #define ASNodeLogEnabled 1 -os_log_t ASNodeLog(); +os_log_t ASNodeLog(void); /// Log for layout-specific events e.g. calculateLayout. #define ASLayoutLogEnabled 1 -os_log_t ASLayoutLog(); +os_log_t ASLayoutLog(void); /// Log for display-specific events e.g. display queue batches. #define ASDisplayLogEnabled 1 -os_log_t ASDisplayLog(); +os_log_t ASDisplayLog(void); /// Log for collection events e.g. reloadData, performBatchUpdates. #define ASCollectionLogEnabled 1 -os_log_t ASCollectionLog(); +os_log_t ASCollectionLog(void); /// Log for ASNetworkImageNode and ASMultiplexImageNode events. #define ASImageLoadingLogEnabled 1 -os_log_t ASImageLoadingLog(); +os_log_t ASImageLoadingLog(void); /// Specialized log for our main thread deallocation trampoline. #define ASMainThreadDeallocationLogEnabled 0 -os_log_t ASMainThreadDeallocationLog(); +os_log_t ASMainThreadDeallocationLog(void); ASDISPLAYNODE_EXTERN_C_END diff --git a/Source/Details/ASDataController.h b/Source/Details/ASDataController.h index b2d2f3ab8..7590700e4 100644 --- a/Source/Details/ASDataController.h +++ b/Source/Details/ASDataController.h @@ -258,7 +258,7 @@ extern NSString * const ASCollectionInvalidUpdateException; * See ASCollectionNode.h for full documentation of these methods. */ @property (nonatomic, readonly) BOOL isProcessingUpdates; -- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion; +- (void)onDidFinishProcessingUpdates:(nullable void (^)(void))completion; - (void)waitUntilAllUpdatesAreProcessed; /** diff --git a/Source/Details/ASTraitCollection.h b/Source/Details/ASTraitCollection.h index fdff5c0b1..26714aa64 100644 --- a/Source/Details/ASTraitCollection.h +++ b/Source/Details/ASTraitCollection.h @@ -42,7 +42,7 @@ typedef struct ASPrimitiveTraitCollection { /** * Creates ASPrimitiveTraitCollection with default values. */ -extern ASPrimitiveTraitCollection ASPrimitiveTraitCollectionMakeDefault(); +extern ASPrimitiveTraitCollection ASPrimitiveTraitCollectionMakeDefault(void); /** * Creates a ASPrimitiveTraitCollection from a given UITraitCollection. diff --git a/Source/Layout/ASLayoutElementPrivate.h b/Source/Layout/ASLayoutElementPrivate.h index 9bc3101b0..96c520e5d 100644 --- a/Source/Layout/ASLayoutElementPrivate.h +++ b/Source/Layout/ASLayoutElementPrivate.h @@ -37,9 +37,9 @@ extern int32_t const ASLayoutElementContextDefaultTransitionID; // Does not currently support nesting – there must be no current context. extern void ASLayoutElementPushContext(ASLayoutElementContext * context); -extern ASLayoutElementContext * _Nullable ASLayoutElementGetCurrentContext(); +extern ASLayoutElementContext * _Nullable ASLayoutElementGetCurrentContext(void); -extern void ASLayoutElementPopContext(); +extern void ASLayoutElementPopContext(void); NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASCollectionView+Undeprecated.h b/Source/Private/ASCollectionView+Undeprecated.h index bc03fc6aa..bf037b16a 100644 --- a/Source/Private/ASCollectionView+Undeprecated.h +++ b/Source/Private/ASCollectionView+Undeprecated.h @@ -154,7 +154,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchAnimated:(BOOL)animated updates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion; /** * Perform a batch of updates asynchronously. This method must be called from the main thread. @@ -165,7 +165,7 @@ NS_ASSUME_NONNULL_BEGIN * Boolean parameter that contains the value YES if all of the related animations completed successfully or * NO if they were interrupted. This parameter may be nil. If supplied, the block is run on the main thread. */ -- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)())updates completion:(nullable void (^)(BOOL finished))completion; +- (void)performBatchUpdates:(nullable AS_NOESCAPE void (^)(void))updates completion:(nullable void (^)(BOOL finished))completion; /** * Triggers a relayout of all nodes. diff --git a/Source/Private/ASInternalHelpers.h b/Source/Private/ASInternalHelpers.h index 31766b366..90a525ef8 100644 --- a/Source/Private/ASInternalHelpers.h +++ b/Source/Private/ASInternalHelpers.h @@ -32,15 +32,15 @@ BOOL ASSubclassOverridesClassSelector(Class superclass, Class subclass, SEL sele IMP ASReplaceMethodWithBlock(Class c, SEL origSEL, id block); /// Dispatches the given block to the main queue if not already running on the main thread -void ASPerformBlockOnMainThread(void (^block)()); +void ASPerformBlockOnMainThread(void (^block)(void)); /// Dispatches the given block to a background queue with priority of DISPATCH_QUEUE_PRIORITY_DEFAULT if not already run on a background queue -void ASPerformBlockOnBackgroundThread(void (^block)()); // DISPATCH_QUEUE_PRIORITY_DEFAULT +void ASPerformBlockOnBackgroundThread(void (^block)(void)); // DISPATCH_QUEUE_PRIORITY_DEFAULT /// For deallocation of objects on a background thread without GCD overhead / thread explosion void ASPerformBackgroundDeallocation(id object); -CGFloat ASScreenScale(); +CGFloat ASScreenScale(void); CGSize ASFloorSizeValues(CGSize s); @@ -80,7 +80,7 @@ ASDISPLAYNODE_INLINE BOOL ASImageAlphaInfoIsOpaque(CGImageAlphaInfo info) { @param withoutAnimation Set to `YES` to perform given block without animation @param block Perform UIView geometry changes within the passed block */ -ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)()) { +ASDISPLAYNODE_INLINE void ASPerformBlockWithoutAnimation(BOOL withoutAnimation, void (^block)(void)) { if (withoutAnimation) { [UIView performWithoutAnimation:block]; } else { diff --git a/Source/Private/ASInternalHelpers.m b/Source/Private/ASInternalHelpers.m index e0d757101..8bc865eb3 100644 --- a/Source/Private/ASInternalHelpers.m +++ b/Source/Private/ASInternalHelpers.m @@ -60,7 +60,7 @@ IMP ASReplaceMethodWithBlock(Class c, SEL origSEL, id block) } } -void ASPerformBlockOnMainThread(void (^block)()) +void ASPerformBlockOnMainThread(void (^block)(void)) { if (block == nil){ return; @@ -72,7 +72,7 @@ void ASPerformBlockOnMainThread(void (^block)()) } } -void ASPerformBlockOnBackgroundThread(void (^block)()) +void ASPerformBlockOnBackgroundThread(void (^block)(void)) { if (block == nil){ return; diff --git a/Source/Private/TextExperiment/Utility/ASTextUtilities.h b/Source/Private/TextExperiment/Utility/ASTextUtilities.h index c434cc901..024fb269c 100755 --- a/Source/Private/TextExperiment/Utility/ASTextUtilities.h +++ b/Source/Private/TextExperiment/Utility/ASTextUtilities.h @@ -164,13 +164,13 @@ static inline CGRect ASTextEmojiGetGlyphBoundingRectWithFontSize(CGFloat fontSiz Get the character set which should rotate in vertical form. @return The shared character set. */ -NSCharacterSet *ASTextVerticalFormRotateCharacterSet(); +NSCharacterSet *ASTextVerticalFormRotateCharacterSet(void); /** Get the character set which should rotate and move in vertical form. @return The shared character set. */ -NSCharacterSet *ASTextVerticalFormRotateAndMoveCharacterSet(); +NSCharacterSet *ASTextVerticalFormRotateAndMoveCharacterSet(void); /// Get the transform rotation. From c12509e67af00e85ee01740ad4ff52de06deefa0 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Tue, 17 Oct 2017 14:20:20 +0100 Subject: [PATCH 002/133] [ASTextKitComponents] Make sure Main Thread Checker isn't triggered during background calculations #trivial (#612) * Add unit test * Make sure TextKit components can calculate size in background without upsetting Main Thread Checker - Add a new thread-safe text view bounds. - Temporary components stack doesn't have a text view so it can be safely deallocated off main. * Add ASTextKitComponentsTextView * Remove unnecessary change * Fix minor mistake * ASTextKitComponentsTextView has only 1 initializer * Minor change * Switch to atomic property * Remove manual synthesization --- Source/ASEditableTextNode.mm | 4 +-- Source/TextKit/ASTextKitComponents.h | 9 +++-- Source/TextKit/ASTextKitComponents.mm | 48 +++++++++++++++++++-------- Tests/ASTextKitTests.mm | 29 +++++++++++++++- 4 files changed, 71 insertions(+), 19 deletions(-) diff --git a/Source/ASEditableTextNode.mm b/Source/ASEditableTextNode.mm index 98351f076..404049fcf 100644 --- a/Source/ASEditableTextNode.mm +++ b/Source/ASEditableTextNode.mm @@ -75,7 +75,7 @@ other content (setting scrollEnabled = NO on the UITextView itself, See issue: https://github.com/facebook/AsyncDisplayKit/issues/1063 */ -@interface ASPanningOverriddenUITextView : UITextView +@interface ASPanningOverriddenUITextView : ASTextKitComponentsTextView { BOOL _shouldBlockPanGesture; } @@ -215,7 +215,7 @@ - (void)didLoad ASDN::MutexLocker l(_textKitLock); // Create and configure the placeholder text view. - _placeholderTextKitComponents.textView = [[UITextView alloc] initWithFrame:CGRectZero textContainer:_placeholderTextKitComponents.textContainer]; + _placeholderTextKitComponents.textView = [[ASTextKitComponentsTextView alloc] initWithFrame:CGRectZero textContainer:_placeholderTextKitComponents.textContainer]; _placeholderTextKitComponents.textView.userInteractionEnabled = NO; _placeholderTextKitComponents.textView.accessibilityElementsHidden = YES; configureTextView(_placeholderTextKitComponents.textView); diff --git a/Source/TextKit/ASTextKitComponents.h b/Source/TextKit/ASTextKitComponents.h index 223abf5c1..20acc2c47 100644 --- a/Source/TextKit/ASTextKitComponents.h +++ b/Source/TextKit/ASTextKitComponents.h @@ -20,6 +20,12 @@ NS_ASSUME_NONNULL_BEGIN +@interface ASTextKitComponentsTextView : UITextView +- (instancetype)initWithFrame:(CGRect)frame textContainer:(nullable NSTextContainer *)textContainer NS_DESIGNATED_INITIALIZER; +- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder __unavailable; +- (instancetype)init __unavailable; +@end + AS_SUBCLASSING_RESTRICTED @interface ASTextKitComponents : NSObject @@ -52,14 +58,13 @@ AS_SUBCLASSING_RESTRICTED */ - (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth; - - (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth forMaxNumberOfLines:(NSInteger)numberOfLines; @property (nonatomic, strong, readonly) NSTextStorage *textStorage; @property (nonatomic, strong, readonly) NSTextContainer *textContainer; @property (nonatomic, strong, readonly) NSLayoutManager *layoutManager; -@property (nonatomic, strong, nullable) UITextView *textView; +@property (nonatomic, strong, nullable) ASTextKitComponentsTextView *textView; @end diff --git a/Source/TextKit/ASTextKitComponents.mm b/Source/TextKit/ASTextKitComponents.mm index 6b3955eb7..7e4a7ae68 100644 --- a/Source/TextKit/ASTextKitComponents.mm +++ b/Source/TextKit/ASTextKitComponents.mm @@ -20,6 +20,37 @@ #import +@interface ASTextKitComponentsTextView () +@property (atomic, assign) CGRect threadSafeBounds; +@end + +@implementation ASTextKitComponentsTextView + +- (instancetype)initWithFrame:(CGRect)frame textContainer:(NSTextContainer *)textContainer +{ + self = [super initWithFrame:frame textContainer:textContainer]; + if (self) { + _threadSafeBounds = self.bounds; + } + return self; +} + +- (void)setFrame:(CGRect)frame +{ + ASDisplayNodeAssertMainThread(); + [super setFrame:frame]; + self.threadSafeBounds = self.bounds; +} + +- (void)setBounds:(CGRect)bounds +{ + ASDisplayNodeAssertMainThread(); + [super setBounds:bounds]; + self.threadSafeBounds = bounds; +} + +@end + @interface ASTextKitComponents () // read-write redeclarations @@ -27,9 +58,6 @@ @interface ASTextKitComponents () @property (nonatomic, strong, readwrite) NSTextContainer *textContainer; @property (nonatomic, strong, readwrite) NSLayoutManager *layoutManager; -// Indicates whether or not this object must be deallocated on main thread. Defaults to YES. -@property (nonatomic, assign) BOOL requiresMainThreadDeallocation; - @end @implementation ASTextKitComponents @@ -61,8 +89,6 @@ + (instancetype)componentsWithTextStorage:(NSTextStorage *)textStorage components.textContainer.lineFragmentPadding = 0.0; // We want the text laid out up to the very edges of the text-view. [components.layoutManager addTextContainer:components.textContainer]; - components.requiresMainThreadDeallocation = YES; - return components; } @@ -70,11 +96,9 @@ + (instancetype)componentsWithTextStorage:(NSTextStorage *)textStorage - (void)dealloc { - if (_requiresMainThreadDeallocation) { - ASDisplayNodeAssertMainThread(); - } // Nil out all delegates to prevent crash if (_textView) { + ASDisplayNodeAssertMainThread(); _textView.delegate = nil; } _layoutManager.delegate = nil; @@ -88,10 +112,8 @@ - (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth // If our text-view's width is already the constrained width, we can use our existing TextKit stack for this sizing calculation. // Otherwise, we create a temporary stack to size for `constrainedWidth`. - if (CGRectGetWidth(components.textView.bounds) != constrainedWidth) { + if (CGRectGetWidth(components.textView.threadSafeBounds) != constrainedWidth) { components = [ASTextKitComponents componentsWithAttributedSeedString:components.textStorage textContainerSize:CGSizeMake(constrainedWidth, CGFLOAT_MAX)]; - // The temporary stack can be deallocated off main - components.requiresMainThreadDeallocation = NO; } // Force glyph generation and layout, which may not have happened yet (and isn't triggered by -usedRectForTextContainer:). @@ -112,9 +134,7 @@ - (CGSize)sizeForConstrainedWidth:(CGFloat)constrainedWidth // Always use temporary stack in case of threading issues components = [ASTextKitComponents componentsWithAttributedSeedString:components.textStorage textContainerSize:CGSizeMake(constrainedWidth, CGFLOAT_MAX)]; - // The temporary stack can be deallocated off main - components.requiresMainThreadDeallocation = NO; - + // Force glyph generation and layout, which may not have happened yet (and isn't triggered by - usedRectForTextContainer:). [components.layoutManager ensureLayoutForTextContainer:components.textContainer]; diff --git a/Tests/ASTextKitTests.mm b/Tests/ASTextKitTests.mm index a39a333b1..687206178 100644 --- a/Tests/ASTextKitTests.mm +++ b/Tests/ASTextKitTests.mm @@ -20,11 +20,14 @@ #import -#import #import +#import +#import #import #import +#import + @interface ASTextKitTests : XCTestCase @end @@ -201,4 +204,28 @@ - (void)testRectsForRangeBeyondTruncationSizeReturnsNonZeroNumberOfRects XCTAssert([renderer rectsForTextRange:NSMakeRange(0, attributedString.length) measureOption:ASTextKitRendererMeasureOptionBlock].count > 0); } +- (void)testTextKitComponentsCanCalculateSizeInBackground +{ + NSAttributedString *attributedString = + [[NSAttributedString alloc] + initWithString:@"90's cray photo booth tote bag bespoke Carles. Plaid wayfarers Odd Future master cleanse tattooed four dollar toast small batch kale chips leggings meh photo booth occupy irony. " attributes:@{ASTextKitEntityAttributeName : [[ASTextKitEntityAttribute alloc] initWithEntity:@"entity"]}]; + ASTextKitComponents *components = [ASTextKitComponents componentsWithAttributedSeedString:attributedString textContainerSize:CGSizeZero]; + components.textView = [[ASTextKitComponentsTextView alloc] initWithFrame:CGRectZero textContainer:components.textContainer]; + components.textView.frame = CGRectMake(0, 0, 20, 1000); + + XCTestExpectation *expectation = [self expectationWithDescription:@"Components deallocated in background"]; + + ASPerformBlockOnBackgroundThread(^{ + // Use an autorelease pool here to ensure temporary components are (and can be) released in background + @autoreleasepool { + [components sizeForConstrainedWidth:100]; + [components sizeForConstrainedWidth:50 forMaxNumberOfLines:5]; + } + + [expectation fulfill]; + }); + + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + @end From dce7ab3a9b17e46beaa1b5204c7479075ed77a30 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Tue, 17 Oct 2017 07:03:53 -0700 Subject: [PATCH 003/133] [ASTextNode] Implement an example comparing ASTextNode 1 & 2 behavior. (#570) * fix SIMULATE_WEB_RESPONSE not imported #449 * add constraint to using catching for layout * Add TextNode example to test Yoga Layout * update Yoga version * add debugging log * fix lisence * clean up * clean up * fix lisence warning * add shared scheme * change sdk version * revert some metadata * Merge FlexLayoutExample to TextStressText. Add flags to control different TextNode used. * clean up * fix lisence and syntax * clean up * remove xcworkspacedata * Tiny coding style changes * Another tiny change related to code style --- examples_extra/TextStressTest/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 23 +++- .../TextStressTest/Sample/AppDelegate.m | 36 +++++-- .../Sample/CollectionViewController.h | 17 +++ .../Sample/CollectionViewController.m | 67 ++++++++++++ .../TextStressTest/Sample/TabBarController.h | 16 +++ .../TextStressTest/Sample/TabBarController.m | 19 ++++ .../TextStressTest/Sample/TextCellNode.h | 17 +++ .../TextStressTest/Sample/TextCellNode.m | 100 ++++++++++++++++++ 9 files changed, 284 insertions(+), 13 deletions(-) create mode 100644 examples_extra/TextStressTest/Sample/CollectionViewController.h create mode 100644 examples_extra/TextStressTest/Sample/CollectionViewController.m create mode 100644 examples_extra/TextStressTest/Sample/TabBarController.h create mode 100644 examples_extra/TextStressTest/Sample/TabBarController.m create mode 100644 examples_extra/TextStressTest/Sample/TextCellNode.h create mode 100644 examples_extra/TextStressTest/Sample/TextCellNode.m diff --git a/examples_extra/TextStressTest/Podfile b/examples_extra/TextStressTest/Podfile index 922ff50ec..667002269 100644 --- a/examples_extra/TextStressTest/Podfile +++ b/examples_extra/TextStressTest/Podfile @@ -1,6 +1,6 @@ source 'https://github.com/CocoaPods/Specs.git' platform :ios, '8.0' target 'Sample' do - pod 'Texture', :path => '../..' + pod 'Texture/Yoga', :path => '../..' end diff --git a/examples_extra/TextStressTest/Sample.xcodeproj/project.pbxproj b/examples_extra/TextStressTest/Sample.xcodeproj/project.pbxproj index 3453463d2..86a3f35f7 100644 --- a/examples_extra/TextStressTest/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/TextStressTest/Sample.xcodeproj/project.pbxproj @@ -14,6 +14,9 @@ 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 */; }; 92F1263CECFE3FFCC7A5F936 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E8EC8300ABAAEA079224272A /* libPods-Sample.a */; }; + C081EE8D1F85AEEC00F0B5F1 /* TabBarController.m in Sources */ = {isa = PBXBuildFile; fileRef = C081EE8C1F85AEEC00F0B5F1 /* TabBarController.m */; }; + C081EE921F85AFB800F0B5F1 /* CollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C081EE8F1F85AFB800F0B5F1 /* CollectionViewController.m */; }; + C081EE931F85AFB800F0B5F1 /* TextCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = C081EE911F85AFB800F0B5F1 /* TextCellNode.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -29,6 +32,12 @@ 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; }; A950870A2154F92D5DC91F1A /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; + C081EE8B1F85AEEC00F0B5F1 /* TabBarController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TabBarController.h; sourceTree = ""; }; + C081EE8C1F85AEEC00F0B5F1 /* TabBarController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TabBarController.m; sourceTree = ""; }; + C081EE8E1F85AFB800F0B5F1 /* CollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CollectionViewController.h; sourceTree = ""; }; + C081EE8F1F85AFB800F0B5F1 /* CollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CollectionViewController.m; sourceTree = ""; }; + C081EE901F85AFB800F0B5F1 /* TextCellNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextCellNode.h; sourceTree = ""; }; + C081EE911F85AFB800F0B5F1 /* TextCellNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TextCellNode.m; sourceTree = ""; }; E8EC8300ABAAEA079224272A /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ @@ -68,11 +77,17 @@ 05E2128319D4DB510098F589 /* Sample */ = { isa = PBXGroup; children = ( + C081EE8E1F85AFB800F0B5F1 /* CollectionViewController.h */, + C081EE8F1F85AFB800F0B5F1 /* CollectionViewController.m */, + C081EE901F85AFB800F0B5F1 /* TextCellNode.h */, + C081EE911F85AFB800F0B5F1 /* TextCellNode.m */, 05E2128819D4DB510098F589 /* AppDelegate.h */, 05E2128919D4DB510098F589 /* AppDelegate.m */, 05E2128B19D4DB510098F589 /* ViewController.h */, 05E2128C19D4DB510098F589 /* ViewController.m */, 05E2128419D4DB510098F589 /* Supporting Files */, + C081EE8B1F85AEEC00F0B5F1 /* TabBarController.h */, + C081EE8C1F85AEEC00F0B5F1 /* TabBarController.m */, ); path = Sample; sourceTree = ""; @@ -181,13 +196,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); 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"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 96436DA0C1AFF84D8041B522 /* [CP] Embed Pods Frameworks */ = { @@ -227,8 +245,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C081EE931F85AFB800F0B5F1 /* TextCellNode.m in Sources */, 05E2128D19D4DB510098F589 /* ViewController.m in Sources */, + C081EE8D1F85AEEC00F0B5F1 /* TabBarController.m in Sources */, 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, + C081EE921F85AFB800F0B5F1 /* CollectionViewController.m in Sources */, 05E2128719D4DB510098F589 /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/examples_extra/TextStressTest/Sample/AppDelegate.m b/examples_extra/TextStressTest/Sample/AppDelegate.m index a8e559478..5be641c02 100644 --- a/examples_extra/TextStressTest/Sample/AppDelegate.m +++ b/examples_extra/TextStressTest/Sample/AppDelegate.m @@ -1,25 +1,39 @@ -/* 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. - */ +// +// AppDelegate.m +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// #import "AppDelegate.h" +#import "TabBarController.h" +#import "CollectionViewController.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]; + + ViewController *viewController = [[ViewController alloc] init]; + viewController.tabBarItem.title = @"TextStress"; + + CollectionViewController *cvc = [[CollectionViewController alloc] init]; + cvc.tabBarItem.title = @"Flexbox"; + + TabBarController *tabBarController = [[TabBarController alloc] init]; + tabBarController.viewControllers = @[cvc, viewController]; + + self.window.rootViewController = tabBarController; [self.window makeKeyAndVisible]; return YES; } diff --git a/examples_extra/TextStressTest/Sample/CollectionViewController.h b/examples_extra/TextStressTest/Sample/CollectionViewController.h new file mode 100644 index 000000000..159b2fa1c --- /dev/null +++ b/examples_extra/TextStressTest/Sample/CollectionViewController.h @@ -0,0 +1,17 @@ +// +// CollectionViewController.h +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import + +@interface CollectionViewController : ASViewController +@end diff --git a/examples_extra/TextStressTest/Sample/CollectionViewController.m b/examples_extra/TextStressTest/Sample/CollectionViewController.m new file mode 100644 index 000000000..d93005236 --- /dev/null +++ b/examples_extra/TextStressTest/Sample/CollectionViewController.m @@ -0,0 +1,67 @@ +// +// CollectionViewController.m +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import "CollectionViewController.h" +#import "TextCellNode.h" + +@interface CollectionViewController() +{ + ASCollectionNode *_collectionNode; + NSArray *_labels; + TextCellNode *_cellNode; +} + +@end + +@implementation CollectionViewController + +- (instancetype)init +{ + UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init]; + _collectionNode = [[ASCollectionNode alloc] initWithCollectionViewLayout:flowLayout]; + CGRect rect = [[UIApplication sharedApplication] statusBarFrame]; + _collectionNode.contentInset = UIEdgeInsetsMake(rect.size.height, 0, 0, 0); + self = [super initWithNode:_collectionNode]; + if (self) { + _collectionNode.delegate = self; + _collectionNode.dataSource = self; + } + return self; +} + +- (void)viewDidLoad +{ + [super viewDidLoad]; + _collectionNode.backgroundColor = [UIColor whiteColor]; + _labels = @[@"Fight of the Living Dead: Experiment Fight of the Living Dead: Experiment", @"S1 • E1"]; +} + +- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section +{ + return 1; +} + +- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath +{ + return ^{ + _cellNode = [[TextCellNode alloc] initWithText1:_labels[0] text2:_labels[1]]; + return _cellNode; + }; +} + +- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode constrainedSizeForItemAtIndexPath:(NSIndexPath *)indexPath +{ + CGFloat width = collectionNode.view.bounds.size.width; + return ASSizeRangeMake(CGSizeMake(width, 0.0f), CGSizeMake(width, CGFLOAT_MAX)); +} + +@end diff --git a/examples_extra/TextStressTest/Sample/TabBarController.h b/examples_extra/TextStressTest/Sample/TabBarController.h new file mode 100644 index 000000000..5a25bb04a --- /dev/null +++ b/examples_extra/TextStressTest/Sample/TabBarController.h @@ -0,0 +1,16 @@ +// +// TabBarController.h +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +@interface TabBarController : ASTabBarController +@end diff --git a/examples_extra/TextStressTest/Sample/TabBarController.m b/examples_extra/TextStressTest/Sample/TabBarController.m new file mode 100644 index 000000000..a7905cb23 --- /dev/null +++ b/examples_extra/TextStressTest/Sample/TabBarController.m @@ -0,0 +1,19 @@ +// +// TabBarController.m +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import "TabBarController.h" + +@interface TabBarController () +@end + +@implementation TabBarController +@end diff --git a/examples_extra/TextStressTest/Sample/TextCellNode.h b/examples_extra/TextStressTest/Sample/TextCellNode.h new file mode 100644 index 000000000..c4b35cfaf --- /dev/null +++ b/examples_extra/TextStressTest/Sample/TextCellNode.h @@ -0,0 +1,17 @@ +// +// TextCellNode.h +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +@interface TextCellNode : ASCellNode +- (instancetype)initWithText1:(NSString *)text1 text2:(NSString *)text2; +@end diff --git a/examples_extra/TextStressTest/Sample/TextCellNode.m b/examples_extra/TextStressTest/Sample/TextCellNode.m new file mode 100644 index 000000000..13bb7694c --- /dev/null +++ b/examples_extra/TextStressTest/Sample/TextCellNode.m @@ -0,0 +1,100 @@ +// +// TextCellNode.m +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import "TextCellNode.h" +#import +#import + +#ifndef USE_ASTEXTNODE_2 +#define USE_ASTEXTNODE_2 1 +#endif + +@interface TextCellNode() +{ +#if USE_ASTEXTNODE_2 + ASTextNode2 *_label1; + ASTextNode2 *_label2; +#else + ASTextNode *_label1; + ASTextNode *_label2; +#endif +} +@end + +@implementation TextCellNode + +- (instancetype)initWithText1:(NSString *)text1 text2:(NSString *)text2 +{ + self = [super init]; + if (self) { + self.automaticallyManagesSubnodes = YES; + self.clipsToBounds = YES; +#if USE_ASTEXTNODE_2 + _label1 = [[ASTextNode2 alloc] init]; + _label2 = [[ASTextNode2 alloc] init]; +#else + _label1 = [[ASTextNode alloc] init]; + _label2 = [[ASTextNode alloc] init]; +#endif + + _label1.attributedText = [[NSAttributedString alloc] initWithString:text1]; + _label2.attributedText = [[NSAttributedString alloc] initWithString:text2]; + + _label1.maximumNumberOfLines = 1; + _label1.truncationMode = NSLineBreakByTruncatingTail; + _label2.maximumNumberOfLines = 1; + _label2.truncationMode = NSLineBreakByTruncatingTail; + + [self simpleSetupYogaLayout]; + } + return self; +} + +/** + This is to text a row with two labels, the first should be truncated with "...". + Layout is like: [l1Container[_label1], label2]. + This shows a bug of ASTextNode2. + */ +- (void)simpleSetupYogaLayout +{ + [self.style yogaNodeCreateIfNeeded]; + [_label1.style yogaNodeCreateIfNeeded]; + [_label2.style yogaNodeCreateIfNeeded]; + + _label1.style.flexGrow = 0; + _label1.style.flexShrink = 1; + _label1.backgroundColor = [UIColor lightGrayColor]; + + _label2.style.flexGrow = 0; + _label2.style.flexShrink = 0; + _label2.backgroundColor = [UIColor greenColor]; + + ASDisplayNode *l1Container = [ASDisplayNode yogaVerticalStack]; + + // TODO(fix ASTextNode2): next two line will show the bug of TextNode2 + // which works for ASTextNode though + // see discussion here: https://github.com/TextureGroup/Texture/pull/553 + l1Container.style.alignItems = ASStackLayoutAlignItemsCenter; + _label1.style.alignSelf = ASStackLayoutAlignSelfStart; + + l1Container.style.flexGrow = 0; + l1Container.style.flexShrink = 1; + + l1Container.yogaChildren = @[_label1]; + + self.style.justifyContent = ASStackLayoutJustifyContentSpaceBetween; + self.style.alignItems = ASStackLayoutAlignItemsStart; + self.style.flexDirection = ASStackLayoutDirectionHorizontal; + self.yogaChildren = @[l1Container, _label2]; +} + +@end From 526a7cfb5338a6fcbd96cae6e20dfd5a89cb82fa Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 18 Oct 2017 15:49:58 -0700 Subject: [PATCH 004/133] Fix name clash with YYText (#623) --- .../TextExperiment/Component/ASTextDebugOption.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Private/TextExperiment/Component/ASTextDebugOption.m b/Source/Private/TextExperiment/Component/ASTextDebugOption.m index e823d328b..2e0a706ea 100755 --- a/Source/Private/TextExperiment/Component/ASTextDebugOption.m +++ b/Source/Private/TextExperiment/Component/ASTextDebugOption.m @@ -16,14 +16,14 @@ static CFMutableSetRef _sharedDebugTargets = nil; static ASTextDebugOption *_sharedDebugOption = nil; -static const void* _sharedDebugSetRetain(CFAllocatorRef allocator, const void *value) { +static const void* _as_sharedDebugSetRetain(CFAllocatorRef allocator, const void *value) { return value; } -static void _sharedDebugSetRelease(CFAllocatorRef allocator, const void *value) { +static void _as_sharedDebugSetRelease(CFAllocatorRef allocator, const void *value) { } -void _sharedDebugSetFunction(const void *value, void *context) { +void _as_sharedDebugSetFunction(const void *value, void *context) { id target = (__bridge id)(value); [target setDebugOption:_sharedDebugOption]; } @@ -33,8 +33,8 @@ static void _initSharedDebug() { dispatch_once(&onceToken, ^{ pthread_mutex_init(&_sharedDebugLock, NULL); CFSetCallBacks callbacks = kCFTypeSetCallBacks; - callbacks.retain = _sharedDebugSetRetain; - callbacks.release = _sharedDebugSetRelease; + callbacks.retain = _as_sharedDebugSetRetain; + callbacks.release = _as_sharedDebugSetRelease; _sharedDebugTargets = CFSetCreateMutable(CFAllocatorGetDefault(), 0, &callbacks); }); } @@ -43,7 +43,7 @@ static void _setSharedDebugOption(ASTextDebugOption *option) { _initSharedDebug(); pthread_mutex_lock(&_sharedDebugLock); _sharedDebugOption = option.copy; - CFSetApplyFunction(_sharedDebugTargets, _sharedDebugSetFunction, NULL); + CFSetApplyFunction(_sharedDebugTargets, _as_sharedDebugSetFunction, NULL); pthread_mutex_unlock(&_sharedDebugLock); } From 919ec8d32b31000de0c310815dee96c8849002f6 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Fri, 20 Oct 2017 09:23:07 -0700 Subject: [PATCH 005/133] Check if we need to do a batch update (#624) * Check if we need to do a batch update If we've changed our leading screens for batch fetching, we may need to batch fetch. * Add CHANGELOG entry --- CHANGELOG.md | 1 + Source/ASCollectionView.mm | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbb407e1b..f6d33cd93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - [ASCornerRounding] Introduce .cornerRoundingType: CALayer, Precomposited, or Clip Corners. [Scott Goodson](https://github.com/appleguy) [#465](https://github.com/TextureGroup/Texture/pull/465) - [Yoga] Add insertYogaNode:atIndex: method. Improve handling of relayouts. [Scott Goodson](https://github.com/appleguy) - [Animated Image] Adds support for animated WebP as well as improves GIF handling. [#605](https://github.com/TextureGroup/Texture/pull/605) [Garrett Moon](https://github.com/garrettmoon) +- [ASCollectionView] Check if batch fetching is needed if batch fetching parameter has been changed. [#624](https://github.com/TextureGroup/Texture/pull/624) [Garrett Moon](https://github.com/garrettmoon) ## 2.5 diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 0c706faa4..70dff45aa 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -1585,7 +1585,10 @@ - (void)setInverted:(BOOL)inverted - (void)setLeadingScreensForBatching:(CGFloat)leadingScreensForBatching { - _leadingScreensForBatching = leadingScreensForBatching; + if (_leadingScreensForBatching != leadingScreensForBatching) { + _leadingScreensForBatching = leadingScreensForBatching; + [self _checkForBatchFetching]; + } } - (CGFloat)leadingScreensForBatching From d31af734df4beeae83dda7d6083c5febf4ba27e5 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Fri, 20 Oct 2017 15:01:38 -0700 Subject: [PATCH 006/133] Dispatch batch update to main #trivial (#626) * Dispatch batch update to main * TableView too --- Source/ASCollectionView.mm | 4 +++- Source/ASTableView.mm | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 70dff45aa..ef9df71a5 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -1587,7 +1587,9 @@ - (void)setLeadingScreensForBatching:(CGFloat)leadingScreensForBatching { if (_leadingScreensForBatching != leadingScreensForBatching) { _leadingScreensForBatching = leadingScreensForBatching; - [self _checkForBatchFetching]; + ASPerformBlockOnMainThread(^{ + [self _checkForBatchFetching]; + }); } } diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 4606726f2..1458a4781 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -1318,7 +1318,12 @@ - (CGFloat)leadingScreensForBatching - (void)setLeadingScreensForBatching:(CGFloat)leadingScreensForBatching { - _leadingScreensForBatching = leadingScreensForBatching; + if (_leadingScreensForBatching != leadingScreensForBatching) { + _leadingScreensForBatching = leadingScreensForBatching; + ASPerformBlockOnMainThread(^{ + [self _checkForBatchFetching]; + }); + } } - (BOOL)automaticallyAdjustsContentOffset From 53147f0ce6c2ac27db858331cd355149072f30b3 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Sun, 22 Oct 2017 17:36:52 -0700 Subject: [PATCH 007/133] Updating podspec to 2.5.1 --- Texture.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Texture.podspec b/Texture.podspec index 6bc7c1621..ec743417d 100644 --- a/Texture.podspec +++ b/Texture.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'Texture' - spec.version = '2.5' + spec.version = '2.5.1' spec.license = { :type => 'BSD and Apache 2', } spec.homepage = 'http://texturegroup.org' spec.authors = { 'Huy Nguyen' => 'huy@pinterest.com', 'Garrett Moon' => 'garrett@excitedpixel.com', 'Scott Goodson' => 'scottgoodson@gmail.com', 'Michael Schneider' => 'schneider@pinterest.com', 'Adlai Holler' => 'adlai@pinterest.com' } From 77795318101af180efed8cc1b3514cd210072f63 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Sun, 22 Oct 2017 17:38:31 -0700 Subject: [PATCH 008/133] Update CHANGELOG for 2.5.1 --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6d33cd93..de2296dec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ ## master - * Add your own contributions to the next release on the line below this with your name. - [ASCollectionView] Improve index space translation of Flow Layout Delegate methods. [Scott Goodson](https://github.com/appleguy) + +## 2.5.1 - [ASVideoNode] Fix unreleased time observer. [Flo Vouin](https://github.com/flovouin) - [PINCache] Set a default .byteLimit to reduce disk usage and startup time. [#595](https://github.com/TextureGroup/Texture/pull/595) [Scott Goodson](https://github.com/appleguy) - [ASNetworkImageNode] Fix deadlock in GIF handling. [#582](https://github.com/TextureGroup/Texture/pull/582) [Garrett Moon](https://github.com/garrettmoon) From 63842e1a39ee988b03206393f9c17fef428d14fb Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Sun, 22 Oct 2017 17:42:57 -0700 Subject: [PATCH 009/133] Fixing changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de2296dec..3a79c97d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## master * Add your own contributions to the next release on the line below this with your name. - [ASCollectionView] Improve index space translation of Flow Layout Delegate methods. [Scott Goodson](https://github.com/appleguy) +- [Animated Image] Adds support for animated WebP as well as improves GIF handling. [#605](https://github.com/TextureGroup/Texture/pull/605) [Garrett Moon](https://github.com/garrettmoon) +- [ASCollectionView] Check if batch fetching is needed if batch fetching parameter has been changed. [#624](https://github.com/TextureGroup/Texture/pull/624) [Garrett Moon](https://github.com/garrettmoon) ## 2.5.1 - [ASVideoNode] Fix unreleased time observer. [Flo Vouin](https://github.com/flovouin) @@ -9,8 +11,6 @@ - [ASDisplayNode] Add attributed versions of a11y label, hint and value. [#554](https://github.com/TextureGroup/Texture/pull/554) [Alexander Hüllmandel](https://github.com/fruitcoder) - [ASCornerRounding] Introduce .cornerRoundingType: CALayer, Precomposited, or Clip Corners. [Scott Goodson](https://github.com/appleguy) [#465](https://github.com/TextureGroup/Texture/pull/465) - [Yoga] Add insertYogaNode:atIndex: method. Improve handling of relayouts. [Scott Goodson](https://github.com/appleguy) -- [Animated Image] Adds support for animated WebP as well as improves GIF handling. [#605](https://github.com/TextureGroup/Texture/pull/605) [Garrett Moon](https://github.com/garrettmoon) -- [ASCollectionView] Check if batch fetching is needed if batch fetching parameter has been changed. [#624](https://github.com/TextureGroup/Texture/pull/624) [Garrett Moon](https://github.com/garrettmoon) ## 2.5 From 68f3468d919f6b876e24c24b57468c0860300bf3 Mon Sep 17 00:00:00 2001 From: appleguy Date: Tue, 24 Oct 2017 05:12:52 -0700 Subject: [PATCH 010/133] [ASCollectionView] Improve performance and behavior of rotation / bounds changes. (#431) * [ASCollectionView] Improve performance and behavior of rotation / bounds changes. See #430 for details. * Edit CHANGELOG.md * [ASDataController] Implement -relayoutAllNodesWithInvalidationBlock:, to flush the ASMainSerialQueue before -invalidateLayout is called. * Don't set download results if no longer in preload range. (#606) Good catch by @djblake, if you scroll fast enough, you leave images set on the image node because didExitPreloadRange (which would have cleared it) was already called. * Animated WebP support (#605) * Updating to support animated WebP * Fix a deadlock with display link * Fix playhead issue. * Fix up timing on iOS 10 and above * Don't redraw the same frame over and over * Clear out layer contents if we're an animated GIF on exit range * Clear out cover image on exit of visible range * Don't set cover image if we're no longer in display range. * Don't clear out image if we're not an animated image * Only set image if we're not already animating * Get rid of changes to podfile * Add CHANGELOG entry * Update license * Update PINRemoteImage * Remove commented out lines in example * [ASDataController] Add nullable specifier to invalidationBlock for relayout of nodes. --- CHANGELOG.md | 1 + Source/ASCollectionView.mm | 11 ++++++----- Source/ASTableView.mm | 4 ++-- Source/Details/ASDataController.h | 5 ++++- Source/Details/ASDataController.mm | 9 ++++++++- Tests/ASTableViewTests.mm | 4 ++-- 6 files changed, 23 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a79c97d9..b4bb3a65e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASCollectionView] Improve performance and behavior of rotation / bounds changes. [Scott Goodson](https://github.com/appleguy) [#431](https://github.com/TextureGroup/Texture/pull/431) - [ASCollectionView] Improve index space translation of Flow Layout Delegate methods. [Scott Goodson](https://github.com/appleguy) - [Animated Image] Adds support for animated WebP as well as improves GIF handling. [#605](https://github.com/TextureGroup/Texture/pull/605) [Garrett Moon](https://github.com/garrettmoon) - [ASCollectionView] Check if batch fetching is needed if batch fetching parameter has been changed. [#624](https://github.com/TextureGroup/Texture/pull/624) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index ef9df71a5..da31222ec 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -367,7 +367,9 @@ - (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UICol - (void)relayoutItems { - [_dataController relayoutAllNodes]; + [_dataController relayoutAllNodesWithInvalidationBlock:^{ + [self.collectionViewLayout invalidateLayout]; + }]; } - (BOOL)isProcessingUpdates @@ -2276,10 +2278,9 @@ - (void)layer:(CALayer *)layer didChangeBoundsWithOldValue:(CGRect)oldBounds new BOOL changedInNonScrollingDirection = (fixedHorizontally && newBounds.size.width != lastUsedSize.width) || (fixedVertically && newBounds.size.height != lastUsedSize.height); if (changedInNonScrollingDirection) { - [_dataController relayoutAllNodes]; - [_dataController waitUntilAllUpdatesAreProcessed]; - // We need to ensure the size requery is done before we update our layout. - [self.collectionViewLayout invalidateLayout]; + [_dataController relayoutAllNodesWithInvalidationBlock:^{ + [self.collectionViewLayout invalidateLayout]; + }]; } } diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 1458a4781..32e497977 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -553,7 +553,7 @@ - (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITabl - (void)relayoutItems { - [_dataController relayoutAllNodes]; + [_dataController relayoutAllNodesWithInvalidationBlock:nil]; } - (void)setTuningParameters:(ASRangeTuningParameters)tuningParameters forRangeType:(ASLayoutRangeType)rangeType @@ -760,7 +760,7 @@ - (void)layoutSubviews _nodesConstrainedWidth = constrainedWidth; [self beginUpdates]; - [_dataController relayoutAllNodes]; + [_dataController relayoutAllNodesWithInvalidationBlock:nil]; [self endUpdatesAnimated:(ASDisplayNodeLayerHasAnimations(self.layer) == NO) completion:nil]; } else { if (_cellsForLayoutUpdates.count > 0) { diff --git a/Source/Details/ASDataController.h b/Source/Details/ASDataController.h index 7590700e4..29a13f846 100644 --- a/Source/Details/ASDataController.h +++ b/Source/Details/ASDataController.h @@ -244,8 +244,11 @@ extern NSString * const ASCollectionInvalidUpdateException; * * @discussion Used to respond to a change in size of the containing view * (e.g. ASTableView or ASCollectionView after an orientation change). + * + * The invalidationBlock is called after flushing the ASMainSerialQueue, which ensures that any in-progress + * layout calculations have been applied. The block will not be called if data hasn't been loaded. */ -- (void)relayoutAllNodes; +- (void)relayoutAllNodesWithInvalidationBlock:(nullable void (^)())invalidationBlock; /** * Re-measures given nodes in the backing store. diff --git a/Source/Details/ASDataController.mm b/Source/Details/ASDataController.mm index f42d01306..c8b489e29 100644 --- a/Source/Details/ASDataController.mm +++ b/Source/Details/ASDataController.mm @@ -759,7 +759,7 @@ - (void)relayoutNodes:(id)nodes nodesSizeChanged:(NSMutableAr } } -- (void)relayoutAllNodes +- (void)relayoutAllNodesWithInvalidationBlock:(nullable void (^)())invalidationBlock { ASDisplayNodeAssertMainThread(); if (!_initialReloadDataHasBeenCalled) { @@ -770,6 +770,13 @@ - (void)relayoutAllNodes // i.e there might be some nodes that were measured using the old constrained size but haven't been added to _visibleMap LOG(@"Edit Command - relayoutRows"); [self _scheduleBlockOnMainSerialQueue:^{ + // Because -invalidateLayout doesn't trigger any operations by itself, and we answer queries from UICollectionView using layoutThatFits:, + // we invalidate the layout before we have updated all of the cells. Any cells that the collection needs the size of immediately will get + // -layoutThatFits: with a new constraint, on the main thread, and synchronously calculate them. Meanwhile, relayoutAllNodes will update + // the layout of any remaining nodes on background threads (and fast-return for any nodes that the UICV got to first). + if (invalidationBlock) { + invalidationBlock(); + } [self _relayoutAllNodes]; }]; } diff --git a/Tests/ASTableViewTests.mm b/Tests/ASTableViewTests.mm index f26d96b11..939c910fb 100644 --- a/Tests/ASTableViewTests.mm +++ b/Tests/ASTableViewTests.mm @@ -37,10 +37,10 @@ @interface ASTestDataController : ASDataController @implementation ASTestDataController -- (void)relayoutAllNodes +- (void)relayoutAllNodesWithInvalidationBlock:(nullable void (^)())invalidationBlock { _numberOfAllNodesRelayouts++; - [super relayoutAllNodes]; + [super relayoutAllNodesWithInvalidationBlock:invalidationBlock]; } @end From 7fffebe293540599748a1ecf96a90fc802e8294e Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Tue, 24 Oct 2017 08:15:40 -0700 Subject: [PATCH 011/133] Update to 2.6 to indicate Xcode upgrade required. --- CHANGELOG.md | 1 + Texture.podspec | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4bb3a65e..e42ea465c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) - [ASCollectionView] Improve performance and behavior of rotation / bounds changes. [Scott Goodson](https://github.com/appleguy) [#431](https://github.com/TextureGroup/Texture/pull/431) - [ASCollectionView] Improve index space translation of Flow Layout Delegate methods. [Scott Goodson](https://github.com/appleguy) - [Animated Image] Adds support for animated WebP as well as improves GIF handling. [#605](https://github.com/TextureGroup/Texture/pull/605) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Texture.podspec b/Texture.podspec index ec743417d..73e904772 100644 --- a/Texture.podspec +++ b/Texture.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'Texture' - spec.version = '2.5.1' + spec.version = '2.6' spec.license = { :type => 'BSD and Apache 2', } spec.homepage = 'http://texturegroup.org' spec.authors = { 'Huy Nguyen' => 'huy@pinterest.com', 'Garrett Moon' => 'garrett@excitedpixel.com', 'Scott Goodson' => 'scottgoodson@gmail.com', 'Michael Schneider' => 'schneider@pinterest.com', 'Adlai Holler' => 'adlai@pinterest.com' } From d00a58580accaacd9cff9f52f0a4f3650d5765d7 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Tue, 24 Oct 2017 08:20:41 -0700 Subject: [PATCH 012/133] Xcode update line should be in 2.6 --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e42ea465c..d1386609b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,13 @@ ## master * Add your own contributions to the next release on the line below this with your name. -- [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) - [ASCollectionView] Improve performance and behavior of rotation / bounds changes. [Scott Goodson](https://github.com/appleguy) [#431](https://github.com/TextureGroup/Texture/pull/431) - [ASCollectionView] Improve index space translation of Flow Layout Delegate methods. [Scott Goodson](https://github.com/appleguy) - [Animated Image] Adds support for animated WebP as well as improves GIF handling. [#605](https://github.com/TextureGroup/Texture/pull/605) [Garrett Moon](https://github.com/garrettmoon) - [ASCollectionView] Check if batch fetching is needed if batch fetching parameter has been changed. [#624](https://github.com/TextureGroup/Texture/pull/624) [Garrett Moon](https://github.com/garrettmoon) +## 2.6 +- [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) + ## 2.5.1 - [ASVideoNode] Fix unreleased time observer. [Flo Vouin](https://github.com/flovouin) - [PINCache] Set a default .byteLimit to reduce disk usage and startup time. [#595](https://github.com/TextureGroup/Texture/pull/595) [Scott Goodson](https://github.com/appleguy) From 3b91fbaab238b7380e7a2067ba9dcb1e4efb089e Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Tue, 24 Oct 2017 16:50:50 +0100 Subject: [PATCH 013/133] Update "Getting Started" page (#633) --- docs/_docs/getting-started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_docs/getting-started.md b/docs/_docs/getting-started.md index 8b11e57cf..ae96e6cfb 100755 --- a/docs/_docs/getting-started.md +++ b/docs/_docs/getting-started.md @@ -42,7 +42,7 @@ Texture's layout engine is both one of its most powerful and one of its most uni

Advanced Developer Features

-Texture offers a variety of advanced developer features that cannot be found in UIKit or Foundation. Our developers have found that AsyncDisplyKit allows simplifications in their architecture and improves developer velocity. +Texture offers a variety of advanced developer features that cannot be found in UIKit or Foundation. Our developers have found that Texture allows simplifications in their architecture and improves developer velocity. (Full list coming soon!) From baf1ea2db449c9dbee5051a6bfb42cb8ad78effd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?r=20=C3=A9=20m=20i=20=EF=A3=BF?= Date: Tue, 24 Oct 2017 20:40:28 +0200 Subject: [PATCH 014/133] introduce tests for the ASNavigationViewController (#627) --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 ++ Tests/ASNavigationControllerTests.m | 56 +++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 Tests/ASNavigationControllerTests.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 068938a4d..15867e86e 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -307,6 +307,7 @@ B350625C1B010F070018CF92 /* ASLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3B1A15563400B4EBED /* ASLog.h */; settings = {ATTRIBUTES = (Public, ); }; }; B350625D1B0111740018CF92 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + BB5FC3CE1F9BA689007F191E /* ASNavigationControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BB5FC3CD1F9BA688007F191E /* ASNavigationControllerTests.m */; }; C78F7E2B1BF7809800CDEAFC /* ASTableNode.h in Headers */ = {isa = PBXBuildFile; fileRef = B0F880581BEAEC7500D17647 /* ASTableNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */ = {isa = PBXBuildFile; fileRef = CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC034A0A1E60BEB400626263 /* ASDisplayNode+Convenience.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */; }; @@ -783,6 +784,7 @@ B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutManager.h; path = TextKit/ASLayoutManager.h; sourceTree = ""; }; B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASLayoutManager.m; path = TextKit/ASLayoutManager.m; sourceTree = ""; }; B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BB5FC3CD1F9BA688007F191E /* ASNavigationControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASNavigationControllerTests.m; sourceTree = ""; }; BDC2D162BD55A807C1475DA5 /* Pods-AsyncDisplayKitTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.profile.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.profile.xcconfig"; sourceTree = ""; }; CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Convenience.h"; sourceTree = ""; }; CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASDisplayNode+Convenience.m"; sourceTree = ""; }; @@ -1157,6 +1159,7 @@ children = ( CC583ABF1EF9BAB400134156 /* Common */, CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */, + BB5FC3CD1F9BA688007F191E /* ASNavigationControllerTests.m */, CC034A0F1E60C9BF00626263 /* ASRectTableTests.m */, CC11F9791DB181180024D77B /* ASNetworkImageNodeTests.m */, CC051F1E1D7A286A006434CB /* ASCALayerTests.m */, @@ -2130,6 +2133,7 @@ CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.m in Sources */, CC034A101E60C9BF00626263 /* ASRectTableTests.m in Sources */, F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.m in Sources */, + BB5FC3CE1F9BA689007F191E /* ASNavigationControllerTests.m in Sources */, ACF6ED5D1B178DC700DA7C62 /* ASDimensionTests.mm in Sources */, 695BE2551DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm in Sources */, CCA221D31D6FA7EF00AF6A0F /* ASViewControllerTests.m in Sources */, diff --git a/Tests/ASNavigationControllerTests.m b/Tests/ASNavigationControllerTests.m new file mode 100644 index 000000000..80f6ba87f --- /dev/null +++ b/Tests/ASNavigationControllerTests.m @@ -0,0 +1,56 @@ +// +// ASNavigationControllerTests.m +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import +#import "ASNavigationController.h" + +@interface ASNavigationControllerTests : XCTestCase +@end + +@implementation ASNavigationControllerTests + +- (void)testSetViewControllers { + ASViewController *firstController = [ASViewController new]; + ASViewController *secondController = [ASViewController new]; + NSArray *expectedViewControllerStack = @[firstController, secondController]; + ASNavigationController *navigationController = [ASNavigationController new]; + [navigationController setViewControllers:@[firstController, secondController]]; + XCTAssertEqual(navigationController.topViewController, secondController); + XCTAssertEqual(navigationController.visibleViewController, secondController); + XCTAssertTrue([navigationController.viewControllers isEqualToArray:expectedViewControllerStack]); +} + +- (void)testPopViewController { + ASViewController *firstController = [ASViewController new]; + ASViewController *secondController = [ASViewController new]; + NSArray *expectedViewControllerStack = @[firstController]; + ASNavigationController *navigationController = [ASNavigationController new]; + [navigationController setViewControllers:@[firstController, secondController]]; + [navigationController popViewControllerAnimated:false]; + XCTAssertEqual(navigationController.topViewController, firstController); + XCTAssertEqual(navigationController.visibleViewController, firstController); + XCTAssertTrue([navigationController.viewControllers isEqualToArray:expectedViewControllerStack]); +} + +- (void)testPushViewController { + ASViewController *firstController = [ASViewController new]; + ASViewController *secondController = [ASViewController new]; + NSArray *expectedViewControllerStack = @[firstController, secondController]; + ASNavigationController *navigationController = [[ASNavigationController new] initWithRootViewController:firstController]; + [navigationController pushViewController:secondController animated:false]; + XCTAssertEqual(navigationController.topViewController, secondController); + XCTAssertEqual(navigationController.visibleViewController, secondController); + XCTAssertTrue([navigationController.viewControllers isEqualToArray:expectedViewControllerStack]); +} + +@end From 128700f82d857858fd1f30c7fc441e649e7bf613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?r=20=C3=A9=20m=20i=20=EF=A3=BF?= Date: Tue, 24 Oct 2017 20:40:48 +0200 Subject: [PATCH 015/133] [Tests] Add test scrollToPageAtIndex ASPagerNode (#629) * add scrollToPageAtIndex for the ASPagerNode * update convention code --- Tests/ASPagerNodeTests.m | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Tests/ASPagerNodeTests.m b/Tests/ASPagerNodeTests.m index 05396387b..6f7157768 100644 --- a/Tests/ASPagerNodeTests.m +++ b/Tests/ASPagerNodeTests.m @@ -50,7 +50,8 @@ @interface ASPagerNodeTestController: UIViewController @implementation ASPagerNodeTestController -- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { // Populate these immediately so that they're not unexpectedly nil during tests. @@ -74,7 +75,8 @@ @interface ASPagerNodeTests : XCTestCase @implementation ASPagerNodeTests -- (void)testPagerReturnsIndexOfPages { +- (void)testPagerReturnsIndexOfPages +{ ASPagerNodeTestController *testController = [self testController]; ASCellNode *cellNode = [testController.pagerNode nodeForPageAtIndex:0]; @@ -82,7 +84,8 @@ - (void)testPagerReturnsIndexOfPages { XCTAssertEqual([testController.pagerNode indexOfPageWithNode:cellNode], 0); } -- (void)testPagerReturnsNotFoundForCellThatDontExistInPager { +- (void)testPagerReturnsNotFoundForCellThatDontExistInPager +{ ASPagerNodeTestController *testController = [self testController]; ASCellNode *badNode = [[ASCellNode alloc] init]; @@ -90,7 +93,17 @@ - (void)testPagerReturnsNotFoundForCellThatDontExistInPager { XCTAssertEqual([testController.pagerNode indexOfPageWithNode:badNode], NSNotFound); } -- (ASPagerNodeTestController *)testController { +- (void)testScrollPageToIndex +{ + ASPagerNodeTestController *testController = [self testController]; + testController.pagerNode.frame = CGRectMake(0, 0, 500, 500); + [testController.pagerNode scrollToPageAtIndex:1 animated:false]; + + XCTAssertEqual(testController.pagerNode.currentPageIndex, 1); +} + +- (ASPagerNodeTestController *)testController +{ ASPagerNodeTestController *testController = [[ASPagerNodeTestController alloc] initWithNibName:nil bundle:nil]; UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; [window makeKeyAndVisible]; From 8317c11e42a9310679cfedd09c34484d78c50b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?r=20=C3=A9=20m=20i=20=EF=A3=BF?= Date: Wed, 25 Oct 2017 11:53:37 +0200 Subject: [PATCH 016/133] [Tests] Introducing tests for the ASTabBarController (#628) * introducing tests for the ASTabBarController * Update ASTabBarControllerTests.m --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 ++ Tests/ASTabBarControllerTests.m | 45 +++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 Tests/ASTabBarControllerTests.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 15867e86e..cefcdea8e 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -307,6 +307,7 @@ B350625C1B010F070018CF92 /* ASLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3B1A15563400B4EBED /* ASLog.h */; settings = {ATTRIBUTES = (Public, ); }; }; B350625D1B0111740018CF92 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + BB5FC3D11F9C9389007F191E /* ASTabBarControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BB5FC3D01F9C9389007F191E /* ASTabBarControllerTests.m */; }; BB5FC3CE1F9BA689007F191E /* ASNavigationControllerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = BB5FC3CD1F9BA688007F191E /* ASNavigationControllerTests.m */; }; C78F7E2B1BF7809800CDEAFC /* ASTableNode.h in Headers */ = {isa = PBXBuildFile; fileRef = B0F880581BEAEC7500D17647 /* ASTableNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */ = {isa = PBXBuildFile; fileRef = CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -784,6 +785,7 @@ B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASLayoutManager.h; path = TextKit/ASLayoutManager.h; sourceTree = ""; }; B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASLayoutManager.m; path = TextKit/ASLayoutManager.m; sourceTree = ""; }; B35061DA1B010EDF0018CF92 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BB5FC3D01F9C9389007F191E /* ASTabBarControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASTabBarControllerTests.m; sourceTree = ""; }; BB5FC3CD1F9BA688007F191E /* ASNavigationControllerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASNavigationControllerTests.m; sourceTree = ""; }; BDC2D162BD55A807C1475DA5 /* Pods-AsyncDisplayKitTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.profile.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.profile.xcconfig"; sourceTree = ""; }; CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Convenience.h"; sourceTree = ""; }; @@ -1161,6 +1163,7 @@ CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */, BB5FC3CD1F9BA688007F191E /* ASNavigationControllerTests.m */, CC034A0F1E60C9BF00626263 /* ASRectTableTests.m */, + BB5FC3D01F9C9389007F191E /* ASTabBarControllerTests.m */, CC11F9791DB181180024D77B /* ASNetworkImageNodeTests.m */, CC051F1E1D7A286A006434CB /* ASCALayerTests.m */, CCE4F9B21F0D60AC00062E4E /* ASIntegerMapTests.m */, @@ -2135,6 +2138,7 @@ F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.m in Sources */, BB5FC3CE1F9BA689007F191E /* ASNavigationControllerTests.m in Sources */, ACF6ED5D1B178DC700DA7C62 /* ASDimensionTests.mm in Sources */, + BB5FC3D11F9C9389007F191E /* ASTabBarControllerTests.m in Sources */, 695BE2551DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm in Sources */, CCA221D31D6FA7EF00AF6A0F /* ASViewControllerTests.m in Sources */, 058D0A38195D057000B7D73C /* ASDisplayLayerTests.m in Sources */, diff --git a/Tests/ASTabBarControllerTests.m b/Tests/ASTabBarControllerTests.m new file mode 100644 index 000000000..0e6d9b3d3 --- /dev/null +++ b/Tests/ASTabBarControllerTests.m @@ -0,0 +1,45 @@ +// +// ASTabBarControllerTests.m +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import "ASTabBarController.h" +#import "ASViewController.h" + +@interface ASTabBarControllerTests: XCTestCase + +@end + +@implementation ASTabBarControllerTests + +- (void)testTabBarControllerSelectIndex { + ASViewController *firstViewController = [ASViewController new]; + ASViewController *secondViewController = [ASViewController new]; + NSArray *viewControllers = @[firstViewController, secondViewController]; + ASTabBarController *tabBarController = [ASTabBarController new]; + [tabBarController setViewControllers:viewControllers]; + [tabBarController setSelectedIndex:1]; + XCTAssertTrue([tabBarController.viewControllers isEqualToArray:viewControllers]); + XCTAssertEqual(tabBarController.selectedViewController, secondViewController); +} + +- (void)testTabBarControllerSelectViewController { + ASViewController *firstViewController = [ASViewController new]; + ASViewController *secondViewController = [ASViewController new]; + NSArray *viewControllers = @[firstViewController, secondViewController]; + ASTabBarController *tabBarController = [ASTabBarController new]; + [tabBarController setViewControllers:viewControllers]; + [tabBarController setSelectedViewController:secondViewController]; + XCTAssertTrue([tabBarController.viewControllers isEqualToArray:viewControllers]); + XCTAssertEqual(tabBarController.selectedViewController, secondViewController); +} + +@end From af99ff5ef24a9d5ffaaaa83195434b0f0fb3df49 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 25 Oct 2017 15:57:30 -0700 Subject: [PATCH 017/133] Have ASNetworkImageNode report whether images were cached or not (#639) * Have ASNetworkImageNode report whether images were cached or not. * Update changelog * Add fileURL case --- CHANGELOG.md | 1 + Source/ASNetworkImageNode.h | 27 +++++ Source/ASNetworkImageNode.mm | 40 +++++-- Source/Details/ASPINRemoteImageDownloader.m | 109 +++++++++++--------- 4 files changed, 119 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1386609b..ed264d511 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - [ASCollectionView] Improve index space translation of Flow Layout Delegate methods. [Scott Goodson](https://github.com/appleguy) - [Animated Image] Adds support for animated WebP as well as improves GIF handling. [#605](https://github.com/TextureGroup/Texture/pull/605) [Garrett Moon](https://github.com/garrettmoon) - [ASCollectionView] Check if batch fetching is needed if batch fetching parameter has been changed. [#624](https://github.com/TextureGroup/Texture/pull/624) [Garrett Moon](https://github.com/garrettmoon) +- [ASNetworkImageNode] New delegate callback to tell the consumer whether the image was loaded from cache or download. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASNetworkImageNode.h b/Source/ASNetworkImageNode.h index dc911bede..455806cd2 100644 --- a/Source/ASNetworkImageNode.h +++ b/Source/ASNetworkImageNode.h @@ -130,6 +130,21 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - + +typedef NS_ENUM(NSInteger, ASNetworkImageSource) { + ASNetworkImageSourceUnspecified = 0, + ASNetworkImageSourceSynchronousCache, + ASNetworkImageSourceAsynchronousCache, + ASNetworkImageSourceFileURL, + ASNetworkImageSourceDownload, +}; + +/// A struct that carries details about ASNetworkImageNode's image loads. +typedef struct { + /// The source from which the image was loaded. + ASNetworkImageSource imageSource; +} ASNetworkImageNodeDidLoadInfo; + /** * The methods declared by the ASNetworkImageNodeDelegate protocol allow the adopting delegate to respond to * notifications such as finished decoding and downloading an image. @@ -137,6 +152,18 @@ NS_ASSUME_NONNULL_BEGIN @protocol ASNetworkImageNodeDelegate @optional +/** + * Notification that the image node finished downloading an image, with additional info. + * If implemented, this method will be called instead of `imageNode:didLoadImage:`. + * + * @param imageNode The sender. + * @param image The newly-loaded image. + * @param info Misc information about the image load. + * + * @discussion Called on a background queue. + */ +- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image info:(ASNetworkImageNodeDidLoadInfo)info; + /** * Notification that the image node finished downloading an image. * diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index 2b22ea526..0a7ee0fbd 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -56,6 +56,7 @@ @interface ASNetworkImageNode () unsigned int delegateDidFailWithError:1; unsigned int delegateDidFinishDecoding:1; unsigned int delegateDidLoadImage:1; + unsigned int delegateDidLoadImageWithInfo:1; } _delegateFlags; @@ -305,6 +306,7 @@ - (void)setDelegate:(id)delegate _delegateFlags.delegateDidFailWithError = [delegate respondsToSelector:@selector(imageNode:didFailWithError:)]; _delegateFlags.delegateDidFinishDecoding = [delegate respondsToSelector:@selector(imageNodeDidFinishDecoding:)]; _delegateFlags.delegateDidLoadImage = [delegate respondsToSelector:@selector(imageNode:didLoadImage:)]; + _delegateFlags.delegateDidLoadImageWithInfo = [delegate respondsToSelector:@selector(imageNode:didLoadImage:info:)]; } - (id)delegate @@ -353,8 +355,18 @@ - (void)displayWillStartAsynchronously:(BOOL)asynchronously if (result) { [self _locked_setCurrentImageQuality:1.0]; [self _locked__setImage:result]; - _imageLoaded = YES; + + // Call out to the delegate. + if (_delegateFlags.delegateDidLoadImageWithInfo) { + ASDN::MutexUnlocker l(__instanceLock__); + ASNetworkImageNodeDidLoadInfo info = {}; + info.imageSource = ASNetworkImageSourceSynchronousCache; + [_delegate imageNode:self didLoadImage:result info:info]; + } else if (_delegateFlags.delegateDidLoadImage) { + ASDN::MutexUnlocker l(__instanceLock__); + [_delegate imageNode:self didLoadImage:result]; + } break; } } @@ -688,14 +700,19 @@ - (void)_lazilyLoadImageIfNecessary [self _locked_setCurrentImageQuality:1.0]; - if (_delegateFlags.delegateDidLoadImage) { + if (_delegateFlags.delegateDidLoadImageWithInfo) { + ASDN::MutexUnlocker u(__instanceLock__); + ASNetworkImageNodeDidLoadInfo info = {}; + info.imageSource = ASNetworkImageSourceFileURL; + [delegate imageNode:self didLoadImage:self.image info:info]; + } else if (_delegateFlags.delegateDidLoadImage) { ASDN::MutexUnlocker u(__instanceLock__); [delegate imageNode:self didLoadImage:self.image]; } }); } else { __weak __typeof__(self) weakSelf = self; - auto finished = ^(id imageContainer, NSError *error, id downloadIdentifier) { + auto finished = ^(id imageContainer, NSError *error, id downloadIdentifier, ASNetworkImageSource imageSource) { __typeof__(self) strongSelf = weakSelf; if (strongSelf == nil) { @@ -732,7 +749,12 @@ - (void)_lazilyLoadImageIfNecessary strongSelf->_cacheUUID = nil; if (imageContainer != nil) { - if (strongSelf->_delegateFlags.delegateDidLoadImage) { + if (strongSelf->_delegateFlags.delegateDidLoadImageWithInfo) { + ASDN::MutexUnlocker u(strongSelf->__instanceLock__); + ASNetworkImageNodeDidLoadInfo info = {}; + info.imageSource = imageSource; + [delegate imageNode:strongSelf didLoadImage:strongSelf.image info:info]; + } else if (strongSelf->_delegateFlags.delegateDidLoadImage) { ASDN::MutexUnlocker u(strongSelf->__instanceLock__); [delegate imageNode:strongSelf didLoadImage:strongSelf.image]; } @@ -763,10 +785,12 @@ - (void)_lazilyLoadImageIfNecessary } if ([imageContainer asdk_image] == nil && _downloader != nil) { - [self _downloadImageWithCompletion:finished]; + [self _downloadImageWithCompletion:^(id imageContainer, NSError *error, id downloadIdentifier) { + finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload); + }]; } else { as_log_verbose(ASImageLoadingLog(), "Decached image for %@ img: %@ urls: %@", self, [imageContainer asdk_image], URLs); - finished(imageContainer, nil, nil); + finished(imageContainer, nil, nil, ASNetworkImageSourceAsynchronousCache); } }; @@ -780,7 +804,9 @@ - (void)_lazilyLoadImageIfNecessary completion:completion]; } } else { - [self _downloadImageWithCompletion:finished]; + [self _downloadImageWithCompletion:^(id imageContainer, NSError *error, id downloadIdentifier) { + finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload); + }]; } } } diff --git a/Source/Details/ASPINRemoteImageDownloader.m b/Source/Details/ASPINRemoteImageDownloader.m index 05107f8e0..b57b72102 100644 --- a/Source/Details/ASPINRemoteImageDownloader.m +++ b/Source/Details/ASPINRemoteImageDownloader.m @@ -202,15 +202,11 @@ - (void)cachedImageWithURL:(NSURL *)URL callbackQueue:(dispatch_queue_t)callbackQueue completion:(ASImageCacherCompletion)completion { - // We do not check the cache here and instead check it in downloadImageWithURL to avoid checking the cache twice. - // If we're targeting the main queue and we're on the main thread, complete immediately. - if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) { - completion(nil); - } else { - dispatch_async(callbackQueue, ^{ - completion(nil); - }); - } + [[self sharedPINRemoteImageManager] imageFromCacheWithURL:URL processorKey:nil options:PINRemoteImageManagerDownloadOptionsSkipDecode completion:^(PINRemoteImageManagerResult * _Nonnull result) { + [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{ + completion(result.image); + }]; + }]; } - (void)cachedImageWithURLs:(NSArray *)URLs @@ -256,51 +252,38 @@ - (nullable id)downloadImageWithURLs:(NSArray *)URLs downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress completion:(ASImageDownloaderCompletion)completion { - PINRemoteImageManagerProgressDownload progressDownload = ^(int64_t completedBytes, int64_t totalBytes) { - if (downloadProgress == nil) { return; } - - /// If we're targeting the main queue and we're on the main thread, call immediately. - if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) { - downloadProgress(completedBytes / (CGFloat)totalBytes); - } else { - dispatch_async(callbackQueue, ^{ - downloadProgress(completedBytes / (CGFloat)totalBytes); - }); - } - }; - - PINRemoteImageManagerImageCompletion imageCompletion = ^(PINRemoteImageManagerResult * _Nonnull result) { - /// If we're targeting the main queue and we're on the main thread, complete immediately. - if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) { -#if PIN_ANIMATED_AVAILABLE - if (result.alternativeRepresentation) { - completion(result.alternativeRepresentation, result.error, result.UUID); - } else { - completion(result.image, result.error, result.UUID); - } -#else - completion(result.image, result.error, result.UUID); -#endif - } else { - dispatch_async(callbackQueue, ^{ + PINRemoteImageManagerProgressDownload progressDownload = ^(int64_t completedBytes, int64_t totalBytes) { + if (downloadProgress == nil) { return; } + + [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{ + downloadProgress(completedBytes / (CGFloat)totalBytes); + }]; + }; + + PINRemoteImageManagerImageCompletion imageCompletion = ^(PINRemoteImageManagerResult * _Nonnull result) { + [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{ #if PIN_ANIMATED_AVAILABLE - if (result.alternativeRepresentation) { - completion(result.alternativeRepresentation, result.error, result.UUID); - } else { - completion(result.image, result.error, result.UUID); - } + if (result.alternativeRepresentation) { + completion(result.alternativeRepresentation, result.error, result.UUID); + } else { + completion(result.image, result.error, result.UUID); + } #else - completion(result.image, result.error, result.UUID); + completion(result.image, result.error, result.UUID); #endif - }); - } - }; - - return [[self sharedPINRemoteImageManager] downloadImageWithURLs:URLs - options:PINRemoteImageManagerDownloadOptionsSkipDecode - progressImage:nil - progressDownload:progressDownload - completion:imageCompletion]; + }]; + }; + + // add "IgnoreCache" option since we have a caching API so we already checked it, not worth checking again. + // PINRemoteImage is responsible for coalescing downloads, and even if it wasn't, the tiny probability of + // extra downloads isn't worth the effort of rechecking caches every single time. In order to provide + // feedback to the consumer about whether images are cached, we can't simply make the cache a no-op and + // check the cache as part of this download. + return [[self sharedPINRemoteImageManager] downloadImageWithURLs:URLs + options:PINRemoteImageManagerDownloadOptionsSkipDecode | PINRemoteImageManagerDownloadOptionsIgnoreCache + progressImage:nil + progressDownload:progressDownload + completion:imageCompletion]; } - (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier @@ -369,5 +352,29 @@ - (id)alternateRepresentationWithData:(NSData *)data options:(PINRemoteImageMana return nil; } +#pragma mark - Private + +/** + * If on main thread and queue is main, perform now. + * If queue is nil, assert and perform now. + * Otherwise, dispatch async to queue. + */ ++ (void)_performWithCallbackQueue:(dispatch_queue_t)queue work:(void (^)())work +{ + if (work == nil) { + // No need to assert here, really. We aren't expecting any feedback from this method. + return; + } + + if (ASDisplayNodeThreadIsMain() && queue == dispatch_get_main_queue()) { + work(); + } else if (queue == nil) { + ASDisplayNodeFailAssert(@"Callback queue should not be nil."); + work(); + } else { + dispatch_async(queue, work); + } +} + @end #endif From 63efdbde8f958fcf5cf09a8df19e96cb90843a54 Mon Sep 17 00:00:00 2001 From: appleguy Date: Tue, 31 Oct 2017 06:20:58 -0700 Subject: [PATCH 018/133] [ASCollectionView] Call -invalidateFlowLayoutDelegateMetrics when rotating. #trivial (#616) * [ASCollectionView] Ensure -invalidateFlowLayoutDelegateMetrics is called for UIKit passthrough cells. This allows rotation to work properly when rotating UIKit passthrough cells that need to change width. * [ASCollectionView] No need to verify node is still in model to handle view-only notifications. --- Source/ASCollectionView.mm | 49 +++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index da31222ec..339728d83 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -369,6 +369,7 @@ - (void)relayoutItems { [_dataController relayoutAllNodesWithInvalidationBlock:^{ [self.collectionViewLayout invalidateLayout]; + [self invalidateFlowLayoutDelegateMetrics]; }]; } @@ -1165,9 +1166,8 @@ - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICol { if (_asyncDelegateFlags.interopWillDisplayCell) { ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; - NSIndexPath *modelIndexPath = [self indexPathForNode:node]; - if (modelIndexPath && node.shouldUseUIKitCell) { - [(id )_asyncDelegate collectionView:collectionView willDisplayCell:rawCell forItemAtIndexPath:modelIndexPath]; + if (node.shouldUseUIKitCell) { + [(id )_asyncDelegate collectionView:collectionView willDisplayCell:rawCell forItemAtIndexPath:indexPath]; } } @@ -1226,9 +1226,8 @@ - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:( { if (_asyncDelegateFlags.interopDidEndDisplayingCell) { ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; - NSIndexPath *modelIndexPath = [self indexPathForNode:node]; - if (modelIndexPath && node.shouldUseUIKitCell) { - [(id )_asyncDelegate collectionView:collectionView didEndDisplayingCell:rawCell forItemAtIndexPath:modelIndexPath]; + if (node.shouldUseUIKitCell) { + [(id )_asyncDelegate collectionView:collectionView didEndDisplayingCell:rawCell forItemAtIndexPath:indexPath]; } } @@ -1271,10 +1270,9 @@ - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:( - (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)rawView forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath { if (_asyncDelegateFlags.interopWillDisplaySupplementaryView) { - ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; - NSIndexPath *modelIndexPath = [self indexPathForNode:node]; - if (modelIndexPath && node.shouldUseUIKitCell) { - [(id )_asyncDelegate collectionView:collectionView willDisplaySupplementaryView:rawView forElementKind:elementKind atIndexPath:modelIndexPath]; + ASCellNode *node = [self supplementaryNodeForElementKind:elementKind atIndexPath:indexPath]; + if (node.shouldUseUIKitCell) { + [(id )_asyncDelegate collectionView:collectionView willDisplaySupplementaryView:rawView forElementKind:elementKind atIndexPath:indexPath]; } } @@ -1312,10 +1310,9 @@ - (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementa - (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)rawView forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath { if (_asyncDelegateFlags.interopdidEndDisplayingSupplementaryView) { - ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; - NSIndexPath *modelIndexPath = [self indexPathForNode:node]; - if (modelIndexPath && node.shouldUseUIKitCell) { - [(id )_asyncDelegate collectionView:collectionView didEndDisplayingSupplementaryView:rawView forElementOfKind:elementKind atIndexPath:modelIndexPath]; + ASCellNode *node = [self supplementaryNodeForElementKind:elementKind atIndexPath:indexPath]; + if (node.shouldUseUIKitCell) { + [(id )_asyncDelegate collectionView:collectionView didEndDisplayingSupplementaryView:rawView forElementOfKind:elementKind atIndexPath:indexPath]; } } @@ -2253,18 +2250,17 @@ - (void)didMoveToWindow */ - (void)layer:(CALayer *)layer didChangeBoundsWithOldValue:(CGRect)oldBounds newValue:(CGRect)newBounds { - if (_hasDataControllerLayoutDelegate) { - // Let the layout delegate handle bounds changes if it's available. - return; - } - if (self.collectionViewLayout == nil) { + CGSize newSize = newBounds.size; + CGSize lastUsedSize = _lastBoundsSizeUsedForMeasuringNodes; + if (CGSizeEqualToSize(lastUsedSize, newSize)) { return; } - CGSize lastUsedSize = _lastBoundsSizeUsedForMeasuringNodes; - if (CGSizeEqualToSize(lastUsedSize, newBounds.size)) { + if (_hasDataControllerLayoutDelegate || self.collectionViewLayout == nil) { + // Let the layout delegate handle bounds changes if it's available. If no layout, it will init in the new state. return; } - _lastBoundsSizeUsedForMeasuringNodes = newBounds.size; + + _lastBoundsSizeUsedForMeasuringNodes = newSize; // Laying out all nodes is expensive. // We only need to do this if the bounds changed in the non-scrollable direction. @@ -2272,15 +2268,14 @@ - (void)layer:(CALayer *)layer didChangeBoundsWithOldValue:(CGRect)oldBounds new // appearance update, we do not need to relayout all nodes. // For a more permanent fix to the unsafety mentioned above, see https://github.com/facebook/AsyncDisplayKit/pull/2182 ASScrollDirection scrollDirection = self.scrollableDirections; - BOOL fixedVertically = (ASScrollDirectionContainsVerticalDirection(scrollDirection) == NO); + BOOL fixedVertically = (ASScrollDirectionContainsVerticalDirection (scrollDirection) == NO); BOOL fixedHorizontally = (ASScrollDirectionContainsHorizontalDirection(scrollDirection) == NO); - BOOL changedInNonScrollingDirection = (fixedHorizontally && newBounds.size.width != lastUsedSize.width) || (fixedVertically && newBounds.size.height != lastUsedSize.height); + BOOL changedInNonScrollingDirection = (fixedHorizontally && newSize.width != lastUsedSize.width) || + (fixedVertically && newSize.height != lastUsedSize.height); if (changedInNonScrollingDirection) { - [_dataController relayoutAllNodesWithInvalidationBlock:^{ - [self.collectionViewLayout invalidateLayout]; - }]; + [self relayoutItems]; } } From d8c2a8edd2536a51e0aa0624e9aea53e3ed50599 Mon Sep 17 00:00:00 2001 From: Ha Hyun soo Date: Tue, 31 Oct 2017 22:25:37 +0900 Subject: [PATCH 019/133] [Documentation] Update Inversion Docs (#647) * Update docs append inversion example about swift language * apply github comment --- docs/_docs/inversion.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/_docs/inversion.md b/docs/_docs/inversion.md index 2231c20b9..5dd18140d 100755 --- a/docs/_docs/inversion.md +++ b/docs/_docs/inversion.md @@ -23,8 +23,10 @@ When this is enabled, developers only have to take one more step to have full in self.tableNode.view.scrollIndicatorInsets = UIEdgeInsetsMake(0, 0, inset, 0); - + + ### Example using `ASDimension` @@ -68,8 +70,10 @@ self.rightStack.style.flexBasis = ASDimensionMake(@"60%"); self.leftStack.style.flexBasis = ASDimensionMake("40%") self.rightStack.style.flexBasis = ASDimensionMake("60%") -horizontalStack.children = [self.leftStack, self.rightStack]] +horizontalStack.children = [self.leftStack, self.rightStack] + + ## Sizes (`CGSize`, `ASLayoutSize`) @@ -187,5 +191,4 @@ func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec -
The `constrainedSize` passed to an `ASDisplayNode` subclass' `layoutSpecThatFits:` method is the minimum and maximum sizes that the node should fit in. The minimum and maximum `CGSize`s contained in `constrainedSize` can be used to size the node's layout elements. From d8cda8d74afba5c6ad6d98b4f33e048040966552 Mon Sep 17 00:00:00 2001 From: Erekle Date: Thu, 30 Nov 2017 16:25:06 +0400 Subject: [PATCH 034/133] [iOS11] Update project settings and fix errors (#676) * [iOS11] Update project settings and fix errors * update changelog * resolve comments --- AsyncDisplayKit.xcodeproj/project.pbxproj | 18 ++++++ CHANGELOG.md | 1 + Source/ASCollectionView.mm | 4 +- Source/ASDisplayNode.h | 2 +- Source/ASImageNode+AnimatedImage.mm | 2 +- Source/ASTextNode.mm | 2 +- Source/Base/ASLog.h | 43 ++++++++++-- Source/Details/ASDataController.h | 2 +- Source/Details/ASPINRemoteImageDownloader.m | 2 +- Source/Details/ASTraitCollection.m | 40 ++++++++---- Source/Details/UIView+ASConvenience.h | 2 +- Source/Private/ASTableView+Undeprecated.h | 2 +- Source/Private/_ASPendingState.mm | 18 ++++-- Tests/ASDisplayLayerTests.m | 4 +- Tests/ASDisplayNodeTests.mm | 72 ++++++++++++++------- Tests/ASLayoutFlatteningTests.m | 14 ++-- 16 files changed, 162 insertions(+), 66 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 28ee692cc..fb9888c5f 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -2443,14 +2443,20 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = 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_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -2488,14 +2494,20 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = 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_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -2631,14 +2643,20 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = 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_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; diff --git a/CHANGELOG.md b/CHANGELOG.md index fa04a6c77..472f12134 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [iOS11] Update project settings and fix errors [Eke](https://github.com/Eke) [#676](https://github.com/TextureGroup/Texture/pull/676) - [ASCollectionView] Improve performance and behavior of rotation / bounds changes. [Scott Goodson](https://github.com/appleguy) [#431](https://github.com/TextureGroup/Texture/pull/431) - [ASCollectionView] Improve index space translation of Flow Layout Delegate methods. [Scott Goodson](https://github.com/appleguy) - [Animated Image] Adds support for animated WebP as well as improves GIF handling. [#605](https://github.com/TextureGroup/Texture/pull/605) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index c1751baf7..2811f6271 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -273,7 +273,7 @@ - (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionV // Experiments done by Instagram show that this option being YES (default) // when unused causes a significant hit to scroll performance. // https://github.com/Instagram/IGListKit/issues/318 - if (AS_AT_LEAST_IOS10) { + if (AS_AVAILABLE_IOS(10)) { super.prefetchingEnabled = NO; } @@ -1972,7 +1972,7 @@ - (ASRangeController *)rangeController /// The UIKit version of this method is only available on iOS >= 9 - (NSArray *)asdk_indexPathsForVisibleSupplementaryElementsOfKind:(NSString *)kind { - if (NSFoundationVersionNumber >= NSFoundationVersionNumber_iOS_9_0) { + if (AS_AVAILABLE_IOS(9)) { return [self indexPathsForVisibleSupplementaryElementsOfKind:kind]; } diff --git a/Source/ASDisplayNode.h b/Source/ASDisplayNode.h index bb81387b3..feecc3ad9 100644 --- a/Source/ASDisplayNode.h +++ b/Source/ASDisplayNode.h @@ -702,7 +702,7 @@ extern NSInteger const ASDefaultDrawingPriority; */ @property (nonatomic, assign) UIViewContentMode contentMode; // default=UIViewContentModeScaleToFill @property (nonatomic, copy) NSString *contentsGravity; // Use .contentMode in preference when possible. -@property (nonatomic, assign) UISemanticContentAttribute semanticContentAttribute; // default=Unspecified +@property (nonatomic, assign) UISemanticContentAttribute semanticContentAttribute API_AVAILABLE(ios(9.0), tvos(9.0)); // default=Unspecified @property (nonatomic, nullable) CGColorRef shadowColor; // default=opaque rgb black @property (nonatomic, assign) CGFloat shadowOpacity; // default=0.0 diff --git a/Source/ASImageNode+AnimatedImage.mm b/Source/ASImageNode+AnimatedImage.mm index 75e5d5f65..355ab472e 100644 --- a/Source/ASImageNode+AnimatedImage.mm +++ b/Source/ASImageNode+AnimatedImage.mm @@ -316,7 +316,7 @@ - (void)displayLinkFired:(CADisplayLink *)displayLink CFTimeInterval timeBetweenLastFire; if (self.lastDisplayLinkFire == 0) { timeBetweenLastFire = 0; - } else if (AS_AT_LEAST_IOS10){ + } else if (AS_AVAILABLE_IOS(10)){ timeBetweenLastFire = displayLink.targetTimestamp - displayLink.timestamp; } else { timeBetweenLastFire = CACurrentMediaTime() - self.lastDisplayLinkFire; diff --git a/Source/ASTextNode.mm b/Source/ASTextNode.mm index 6caaec8cd..e5338f521 100644 --- a/Source/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -539,7 +539,7 @@ - (id)linkAttributeValueAtPoint:(CGPoint)point } - (id)_linkAttributeValueAtPoint:(CGPoint)point - attributeName:(out NSString **)attributeNameOut + attributeName:(out NSString * __autoreleasing *)attributeNameOut range:(out NSRange *)rangeOut inAdditionalTruncationMessage:(out BOOL *)inAdditionalTruncationMessageOut forHighlighting:(BOOL)highlighting diff --git a/Source/Base/ASLog.h b/Source/Base/ASLog.h index 5de8ab939..be4bff368 100644 --- a/Source/Base/ASLog.h +++ b/Source/Base/ASLog.h @@ -119,11 +119,44 @@ ASDISPLAYNODE_EXTERN_C_END * The logging macros are not guarded by deployment-target checks like the activity macros are, but they are * only available on iOS >= 9 at runtime, so just make them conditional. */ -#define as_log_create(subsystem, category) (AS_AT_LEAST_IOS9 ? os_log_create(subsystem, category) : (os_log_t)0) -#define as_log_debug(log, format, ...) (AS_AT_LEAST_IOS9 ? os_log_debug(log, format, ##__VA_ARGS__) : (void)0) -#define as_log_info(log, format, ...) (AS_AT_LEAST_IOS9 ? os_log_info(log, format, ##__VA_ARGS__) : (void)0) -#define as_log_error(log, format, ...) (AS_AT_LEAST_IOS9 ? os_log_error(log, format, ##__VA_ARGS__) : (void)0) -#define as_log_fault(log, format, ...) (AS_AT_LEAST_IOS9 ? os_log_fault(log, format, ##__VA_ARGS__) : (void)0) + +#define as_log_create(subsystem, category) ({ \ +os_log_t __val; \ +if (AS_AVAILABLE_IOS(9)) { \ + __val = os_log_create(subsystem, category); \ +} else { \ + __val = (os_log_t)0; \ +} \ +__val; \ +}) + +#define as_log_debug(log, format, ...) \ +if (AS_AVAILABLE_IOS(9)) { \ + os_log_debug(log, format, ##__VA_ARGS__); \ +} else { \ + (void)0; \ +} \ + +#define as_log_info(log, format, ...) \ +if (AS_AVAILABLE_IOS(9)) { \ + os_log_info(log, format, ##__VA_ARGS__); \ +} else { \ + (void)0; \ +} \ + +#define as_log_error(log, format, ...) \ +if (AS_AVAILABLE_IOS(9)) { \ + os_log_error(log, format, ##__VA_ARGS__); \ +} else { \ + (void)0; \ +} \ + +#define as_log_fault(log, format, ...) \ +if (AS_AVAILABLE_IOS(9)) { \ + os_log_fault(log, format, ##__VA_ARGS__); \ +} else { \ + (void)0; \ +} \ #if ASEnableVerboseLogging #define as_log_verbose(log, format, ...) as_log_debug(log, format, ##__VA_ARGS__) diff --git a/Source/Details/ASDataController.h b/Source/Details/ASDataController.h index 29a13f846..acc98bcee 100644 --- a/Source/Details/ASDataController.h +++ b/Source/Details/ASDataController.h @@ -248,7 +248,7 @@ extern NSString * const ASCollectionInvalidUpdateException; * The invalidationBlock is called after flushing the ASMainSerialQueue, which ensures that any in-progress * layout calculations have been applied. The block will not be called if data hasn't been loaded. */ -- (void)relayoutAllNodesWithInvalidationBlock:(nullable void (^)())invalidationBlock; +- (void)relayoutAllNodesWithInvalidationBlock:(nullable void (^)(void))invalidationBlock; /** * Re-measures given nodes in the backing store. diff --git a/Source/Details/ASPINRemoteImageDownloader.m b/Source/Details/ASPINRemoteImageDownloader.m index b57b72102..34cd1c7b7 100644 --- a/Source/Details/ASPINRemoteImageDownloader.m +++ b/Source/Details/ASPINRemoteImageDownloader.m @@ -359,7 +359,7 @@ - (id)alternateRepresentationWithData:(NSData *)data options:(PINRemoteImageMana * If queue is nil, assert and perform now. * Otherwise, dispatch async to queue. */ -+ (void)_performWithCallbackQueue:(dispatch_queue_t)queue work:(void (^)())work ++ (void)_performWithCallbackQueue:(dispatch_queue_t)queue work:(void (^)(void))work { if (work == nil) { // No need to assert here, really. We aren't expecting any feedback from this method. diff --git a/Source/Details/ASTraitCollection.m b/Source/Details/ASTraitCollection.m index 0c845e9f6..04eaea608 100644 --- a/Source/Details/ASTraitCollection.m +++ b/Source/Details/ASTraitCollection.m @@ -48,7 +48,7 @@ ASPrimitiveTraitCollection ASPrimitiveTraitCollectionFromUITraitCollection(UITra environmentTraitCollection.horizontalSizeClass = traitCollection.horizontalSizeClass; environmentTraitCollection.verticalSizeClass = traitCollection.verticalSizeClass; environmentTraitCollection.userInterfaceIdiom = traitCollection.userInterfaceIdiom; - if (AS_AT_LEAST_IOS9) { + if (AS_AVAILABLE_IOS(9)) { environmentTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability; } return environmentTraitCollection; @@ -67,17 +67,28 @@ BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTr // Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceIdiom(UIUserInterfaceIdiom idiom) { - switch (idiom) { - case UIUserInterfaceIdiomTV: - return @"TV"; - case UIUserInterfaceIdiomPad: - return @"Pad"; - case UIUserInterfaceIdiomPhone: - return @"Phone"; - case UIUserInterfaceIdiomCarPlay: - return @"CarPlay"; - default: - return @"Unspecified"; + if (AS_AVAILABLE_IOS(9)) { + switch (idiom) { + case UIUserInterfaceIdiomTV: + return @"TV"; + case UIUserInterfaceIdiomPad: + return @"Pad"; + case UIUserInterfaceIdiomPhone: + return @"Phone"; + case UIUserInterfaceIdiomCarPlay: + return @"CarPlay"; + default: + return @"Unspecified"; + } + } else { + switch (idiom) { + case UIUserInterfaceIdiomPad: + return @"Pad"; + case UIUserInterfaceIdiomPhone: + return @"Phone"; + default: + return @"Unspecified"; + } } } @@ -167,7 +178,10 @@ + (instancetype)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitC + (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection containerSize:(CGSize)windowSize { - UIForceTouchCapability forceTouch = AS_AT_LEAST_IOS9 ? traitCollection.forceTouchCapability : UIForceTouchCapabilityUnknown; + UIForceTouchCapability forceTouch = UIForceTouchCapabilityUnknown; + if(AS_AVAILABLE_IOS(9)) { + forceTouch = traitCollection.forceTouchCapability; + } return [self traitCollectionWithDisplayScale:traitCollection.displayScale userInterfaceIdiom:traitCollection.userInterfaceIdiom horizontalSizeClass:traitCollection.horizontalSizeClass diff --git a/Source/Details/UIView+ASConvenience.h b/Source/Details/UIView+ASConvenience.h index 109bf7812..452dcee6e 100644 --- a/Source/Details/UIView+ASConvenience.h +++ b/Source/Details/UIView+ASConvenience.h @@ -71,7 +71,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) CGRect bounds; @property (nonatomic, assign) CGRect frame; // Only for use with nodes wrapping synchronous views @property (nonatomic, assign) UIViewContentMode contentMode; -@property (nonatomic, assign) UISemanticContentAttribute semanticContentAttribute; +@property (nonatomic, assign) UISemanticContentAttribute semanticContentAttribute API_AVAILABLE(ios(9.0), tvos(9.0)); @property (nonatomic, assign, getter=isUserInteractionEnabled) BOOL userInteractionEnabled; @property (nonatomic, assign, getter=isExclusiveTouch) BOOL exclusiveTouch; @property (nonatomic, assign, getter=asyncdisplaykit_isAsyncTransactionContainer, setter = asyncdisplaykit_setAsyncTransactionContainer:) BOOL asyncdisplaykit_asyncTransactionContainer; diff --git a/Source/Private/ASTableView+Undeprecated.h b/Source/Private/ASTableView+Undeprecated.h index bab43adf0..7ddc9c28e 100644 --- a/Source/Private/ASTableView+Undeprecated.h +++ b/Source/Private/ASTableView+Undeprecated.h @@ -146,7 +146,7 @@ NS_ASSUME_NONNULL_BEGIN * the main thread. * @warning This method is substantially more expensive than UITableView's version. */ --(void)reloadDataWithCompletion:(void (^ _Nullable)())completion; +-(void)reloadDataWithCompletion:(void (^ _Nullable)(void))completion; /** * Reload everything from scratch, destroying the working range and all cached nodes. diff --git a/Source/Private/_ASPendingState.mm b/Source/Private/_ASPendingState.mm index 9bb4dc1a6..4374401d8 100644 --- a/Source/Private/_ASPendingState.mm +++ b/Source/Private/_ASPendingState.mm @@ -141,7 +141,7 @@ @implementation _ASPendingState NSArray *accessibilityHeaderElements; CGPoint accessibilityActivationPoint; UIBezierPath *accessibilityPath; - UISemanticContentAttribute semanticContentAttribute; + UISemanticContentAttribute semanticContentAttribute API_AVAILABLE(ios(9.0), tvos(9.0)); ASPendingStateFlags _flags; } @@ -295,7 +295,9 @@ - (instancetype)init accessibilityActivationPoint = CGPointZero; accessibilityPath = nil; edgeAntialiasingMask = (kCALayerLeftEdge | kCALayerRightEdge | kCALayerTopEdge | kCALayerBottomEdge); - semanticContentAttribute = UISemanticContentAttributeUnspecified; + if (AS_AVAILABLE_IOS(9)) { + semanticContentAttribute = UISemanticContentAttributeUnspecified; + } return self; } @@ -573,7 +575,7 @@ - (void)asyncdisplaykit_setAsyncTransactionContainer:(BOOL)flag _flags.setAsyncTransactionContainer = YES; } -- (void)setSemanticContentAttribute:(UISemanticContentAttribute)attribute { +- (void)setSemanticContentAttribute:(UISemanticContentAttribute)attribute API_AVAILABLE(ios(9.0), tvos(9.0)) { semanticContentAttribute = attribute; _flags.setSemanticContentAttribute = YES; } @@ -1049,8 +1051,10 @@ - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPr if (flags.setOpaque) ASDisplayNodeAssert(layer.opaque == opaque, @"Didn't set opaque as desired"); - if (flags.setSemanticContentAttribute) { - view.semanticContentAttribute = semanticContentAttribute; + if (AS_AVAILABLE_IOS(9)) { + if (flags.setSemanticContentAttribute) { + view.semanticContentAttribute = semanticContentAttribute; + } } if (flags.setIsAccessibilityElement) @@ -1211,7 +1215,9 @@ + (_ASPendingState *)pendingViewStateFromView:(UIView *)view pendingState.allowsGroupOpacity = layer.allowsGroupOpacity; pendingState.allowsEdgeAntialiasing = layer.allowsEdgeAntialiasing; pendingState.edgeAntialiasingMask = layer.edgeAntialiasingMask; - pendingState.semanticContentAttribute = view.semanticContentAttribute; + if (AS_AVAILABLE_IOS(9)) { + pendingState.semanticContentAttribute = view.semanticContentAttribute; + } pendingState.isAccessibilityElement = view.isAccessibilityElement; pendingState.accessibilityLabel = view.accessibilityLabel; pendingState.accessibilityHint = view.accessibilityHint; diff --git a/Tests/ASDisplayLayerTests.m b/Tests/ASDisplayLayerTests.m index 1abcc7154..1e5b19aba 100644 --- a/Tests/ASDisplayLayerTests.m +++ b/Tests/ASDisplayLayerTests.m @@ -148,7 +148,7 @@ @interface _ASDisplayLayerTestDelegate : ASDisplayNode <_ASDisplayLayerDelegate> // for _ASDisplayLayerTestDelegateModeClassDisplay @property (nonatomic, assign) NSUInteger displayCount; -@property (nonatomic, copy) UIImage *(^displayLayerBlock)(); +@property (nonatomic, copy) UIImage *(^displayLayerBlock)(void); // for _ASDisplayLayerTestDelegateModeClassDrawInContext @property (nonatomic, assign) NSUInteger drawRectCount; @@ -472,7 +472,7 @@ - (void)DISABLED_testTransaction layer1.displaysAsynchronously = YES; dispatch_semaphore_t displayAsyncLayer1Sema = dispatch_semaphore_create(0); - layer1Delegate.displayLayerBlock = ^(_ASDisplayLayer *asyncLayer) { + layer1Delegate.displayLayerBlock = ^UIImage *{ dispatch_semaphore_wait(displayAsyncLayer1Sema, DISPATCH_TIME_FOREVER); return bogusImage(); }; diff --git a/Tests/ASDisplayNodeTests.mm b/Tests/ASDisplayNodeTests.mm index 75346ff85..df4adbec6 100644 --- a/Tests/ASDisplayNodeTests.mm +++ b/Tests/ASDisplayNodeTests.mm @@ -711,46 +711,54 @@ - (void)testDisplayNodePointConversionWithFrames // Setup CGPoint originalPoint = CGPointZero, convertedPoint = CGPointZero, correctPoint = CGPointZero; - node = [[ASDisplayNode alloc] init], innerNode = [[ASDisplayNode alloc] init]; + node = [[ASDisplayNode alloc] init]; + innerNode = [[ASDisplayNode alloc] init]; [node addSubnode:innerNode]; // Convert point *FROM* outer node's coordinate space to inner node's coordinate space node.frame = CGRectMake(100, 100, 100, 100); innerNode.frame = CGRectMake(10, 10, 20, 20); - originalPoint = CGPointMake(105, 105), correctPoint = CGPointMake(95, 95); + originalPoint = CGPointMake(105, 105); + correctPoint = CGPointMake(95, 95); convertedPoint = [self checkConvertPoint:originalPoint fromNode:node selfNode:innerNode]; XCTAssertTrue(CGPointEqualToPoint(convertedPoint, correctPoint), @"Unexpected point conversion result. Point: %@ Expected conversion: %@ Actual conversion: %@", NSStringFromCGPoint(originalPoint), NSStringFromCGPoint(correctPoint), NSStringFromCGPoint(convertedPoint)); // Setup - node = [[ASDisplayNode alloc] init], innerNode = [[ASDisplayNode alloc] init]; + node = [[ASDisplayNode alloc] init]; + innerNode = [[ASDisplayNode alloc] init]; [node addSubnode:innerNode]; // Convert point *FROM* inner node's coordinate space to outer node's coordinate space node.frame = CGRectMake(100, 100, 100, 100); innerNode.frame = CGRectMake(10, 10, 20, 20); - originalPoint = CGPointMake(5, 5), correctPoint = CGPointMake(15, 15); + originalPoint = CGPointMake(5, 5); + correctPoint = CGPointMake(15, 15); convertedPoint = [self checkConvertPoint:originalPoint fromNode:innerNode selfNode:node]; XCTAssertTrue(CGPointEqualToPoint(convertedPoint, correctPoint), @"Unexpected point conversion result. Point: %@ Expected conversion: %@ Actual conversion: %@", NSStringFromCGPoint(originalPoint), NSStringFromCGPoint(correctPoint), NSStringFromCGPoint(convertedPoint)); // Setup - node = [[ASDisplayNode alloc] init], innerNode = [[ASDisplayNode alloc] init]; + node = [[ASDisplayNode alloc] init]; + innerNode = [[ASDisplayNode alloc] init]; [node addSubnode:innerNode]; // Convert point in inner node's coordinate space *TO* outer node's coordinate space node.frame = CGRectMake(100, 100, 100, 100); innerNode.frame = CGRectMake(10, 10, 20, 20); - originalPoint = CGPointMake(95, 95), correctPoint = CGPointMake(105, 105); + originalPoint = CGPointMake(95, 95); + correctPoint = CGPointMake(105, 105); convertedPoint = [self checkConvertPoint:originalPoint toNode:node selfNode:innerNode]; XCTAssertTrue(CGPointEqualToPoint(convertedPoint, correctPoint), @"Unexpected point conversion result. Point: %@ Expected conversion: %@ Actual conversion: %@", NSStringFromCGPoint(originalPoint), NSStringFromCGPoint(correctPoint), NSStringFromCGPoint(convertedPoint)); // Setup - node = [[ASDisplayNode alloc] init], innerNode = [[ASDisplayNode alloc] init]; + node = [[ASDisplayNode alloc] init]; + innerNode = [[ASDisplayNode alloc] init]; [node addSubnode:innerNode]; // Convert point in outer node's coordinate space *TO* inner node's coordinate space node.frame = CGRectMake(0, 0, 100, 100); innerNode.frame = CGRectMake(10, 10, 20, 20); - originalPoint = CGPointMake(5, 5), correctPoint = CGPointMake(-5, -5); + originalPoint = CGPointMake(5, 5); + correctPoint = CGPointMake(-5, -5); convertedPoint = [self checkConvertPoint:originalPoint toNode:innerNode selfNode:node]; XCTAssertTrue(CGPointEqualToPoint(convertedPoint, correctPoint), @"Unexpected point conversion result. Point: %@ Expected conversion: %@ Actual conversion: %@", NSStringFromCGPoint(originalPoint), NSStringFromCGPoint(correctPoint), NSStringFromCGPoint(convertedPoint)); } @@ -764,7 +772,8 @@ - (void)testDisplayNodePointConversionWithNonZeroBounds // Setup CGPoint originalPoint = CGPointZero, convertedPoint = CGPointZero, correctPoint = CGPointZero; - node = [[ASDisplayNode alloc] init], innerNode = [[ASDisplayNode alloc] init]; + node = [[ASDisplayNode alloc] init]; + innerNode = [[ASDisplayNode alloc] init]; [node addSubnode:innerNode]; // Convert point *FROM* outer node's coordinate space to inner node's coordinate space @@ -773,12 +782,14 @@ - (void)testDisplayNodePointConversionWithNonZeroBounds node.bounds = CGRectMake(20, 20, 100, 100); innerNode.position = CGPointMake(23, 23); innerNode.bounds = CGRectMake(17, 17, 20, 20); - originalPoint = CGPointMake(42, 42), correctPoint = CGPointMake(36, 36); + originalPoint = CGPointMake(42, 42); + correctPoint = CGPointMake(36, 36); convertedPoint = [self checkConvertPoint:originalPoint fromNode:node selfNode:innerNode]; XCTAssertTrue(CGPointEqualToPoint(convertedPoint, correctPoint), @"Unexpected point conversion result. Point: %@ Expected conversion: %@ Actual conversion: %@", NSStringFromCGPoint(originalPoint), NSStringFromCGPoint(correctPoint), NSStringFromCGPoint(convertedPoint)); // Setup - node = [[ASDisplayNode alloc] init], innerNode = [[ASDisplayNode alloc] init]; + node = [[ASDisplayNode alloc] init]; + innerNode = [[ASDisplayNode alloc] init]; [node addSubnode:innerNode]; // Convert point *FROM* inner node's coordinate space to outer node's coordinate space @@ -787,12 +798,14 @@ - (void)testDisplayNodePointConversionWithNonZeroBounds node.bounds = CGRectMake(-1000, -1000, 1337, 1337); innerNode.position = CGPointMake(23, 23); innerNode.bounds = CGRectMake(17, 17, 200, 200); - originalPoint = CGPointMake(5, 5), correctPoint = CGPointMake(11, 11); + originalPoint = CGPointMake(5, 5); + correctPoint = CGPointMake(11, 11); convertedPoint = [self checkConvertPoint:originalPoint fromNode:innerNode selfNode:node]; XCTAssertTrue(CGPointEqualToPoint(convertedPoint, correctPoint), @"Unexpected point conversion result. Point: %@ Expected conversion: %@ Actual conversion: %@", NSStringFromCGPoint(originalPoint), NSStringFromCGPoint(correctPoint), NSStringFromCGPoint(convertedPoint)); // Setup - node = [[ASDisplayNode alloc] init], innerNode = [[ASDisplayNode alloc] init]; + node = [[ASDisplayNode alloc] init]; + innerNode = [[ASDisplayNode alloc] init]; [node addSubnode:innerNode]; // Convert point in inner node's coordinate space *TO* outer node's coordinate space @@ -801,12 +814,14 @@ - (void)testDisplayNodePointConversionWithNonZeroBounds node.bounds = CGRectMake(20, 20, 100, 100); innerNode.position = CGPointMake(23, 23); innerNode.bounds = CGRectMake(17, 17, 20, 20); - originalPoint = CGPointMake(36, 36), correctPoint = CGPointMake(42, 42); + originalPoint = CGPointMake(36, 36); + correctPoint = CGPointMake(42, 42); convertedPoint = [self checkConvertPoint:originalPoint toNode:node selfNode:innerNode]; XCTAssertTrue(CGPointEqualToPoint(convertedPoint, correctPoint), @"Unexpected point conversion result. Point: %@ Expected conversion: %@ Actual conversion: %@", NSStringFromCGPoint(originalPoint), NSStringFromCGPoint(correctPoint), NSStringFromCGPoint(convertedPoint)); // Setup - node = [[ASDisplayNode alloc] init], innerNode = [[ASDisplayNode alloc] init]; + node = [[ASDisplayNode alloc] init]; + innerNode = [[ASDisplayNode alloc] init]; [node addSubnode:innerNode]; // Convert point in outer node's coordinate space *TO* inner node's coordinate space @@ -815,7 +830,8 @@ - (void)testDisplayNodePointConversionWithNonZeroBounds node.bounds = CGRectMake(-1000, -1000, 1337, 1337); innerNode.position = CGPointMake(23, 23); innerNode.bounds = CGRectMake(17, 17, 200, 200); - originalPoint = CGPointMake(11, 11), correctPoint = CGPointMake(5, 5); + originalPoint = CGPointMake(11, 11); + correctPoint = CGPointMake(5, 5); convertedPoint = [self checkConvertPoint:originalPoint toNode:innerNode selfNode:node]; XCTAssertTrue(CGPointEqualToPoint(convertedPoint, correctPoint), @"Unexpected point conversion result. Point: %@ Expected conversion: %@ Actual conversion: %@", NSStringFromCGPoint(originalPoint), NSStringFromCGPoint(correctPoint), NSStringFromCGPoint(convertedPoint)); } @@ -828,7 +844,8 @@ - (void)testDisplayNodePointConversionWithNonZeroAnchorPoint // Setup CGPoint originalPoint = CGPointZero, convertedPoint = CGPointZero, correctPoint = CGPointZero; - node = [[ASDisplayNode alloc] init], innerNode = [[ASDisplayNode alloc] init]; + node = [[ASDisplayNode alloc] init]; + innerNode = [[ASDisplayNode alloc] init]; [node addSubnode:innerNode]; // Convert point *FROM* outer node's coordinate space to inner node's coordinate space @@ -836,12 +853,14 @@ - (void)testDisplayNodePointConversionWithNonZeroAnchorPoint innerNode.anchorPoint = CGPointMake(0.75, 1); innerNode.position = CGPointMake(23, 23); innerNode.bounds = CGRectMake(17, 17, 20, 20); - originalPoint = CGPointMake(42, 42), correctPoint = CGPointMake(51, 56); + originalPoint = CGPointMake(42, 42); + correctPoint = CGPointMake(51, 56); convertedPoint = [self checkConvertPoint:originalPoint fromNode:node selfNode:innerNode]; XCTAssertTrue(_CGPointEqualToPointWithEpsilon(convertedPoint, correctPoint, 0.001), @"Unexpected point conversion result. Point: %@ Expected conversion: %@ Actual conversion: %@", NSStringFromCGPoint(originalPoint), NSStringFromCGPoint(correctPoint), NSStringFromCGPoint(convertedPoint)); // Setup - node = [[ASDisplayNode alloc] init], innerNode = [[ASDisplayNode alloc] init]; + node = [[ASDisplayNode alloc] init]; + innerNode = [[ASDisplayNode alloc] init]; [node addSubnode:innerNode]; // Convert point *FROM* inner node's coordinate space to outer node's coordinate space @@ -849,12 +868,14 @@ - (void)testDisplayNodePointConversionWithNonZeroAnchorPoint innerNode.anchorPoint = CGPointMake(0.3, 0.3); innerNode.position = CGPointMake(23, 23); innerNode.bounds = CGRectMake(17, 17, 200, 200); - originalPoint = CGPointMake(55, 55), correctPoint = CGPointMake(1, 1); + originalPoint = CGPointMake(55, 55); + correctPoint = CGPointMake(1, 1); convertedPoint = [self checkConvertPoint:originalPoint fromNode:innerNode selfNode:node]; XCTAssertTrue(_CGPointEqualToPointWithEpsilon(convertedPoint, correctPoint, 0.001), @"Unexpected point conversion result. Point: %@ Expected conversion: %@ Actual conversion: %@", NSStringFromCGPoint(originalPoint), NSStringFromCGPoint(correctPoint), NSStringFromCGPoint(convertedPoint)); // Setup - node = [[ASDisplayNode alloc] init], innerNode = [[ASDisplayNode alloc] init]; + node = [[ASDisplayNode alloc] init]; + innerNode = [[ASDisplayNode alloc] init]; [node addSubnode:innerNode]; // Convert point in inner node's coordinate space *TO* outer node's coordinate space @@ -862,12 +883,14 @@ - (void)testDisplayNodePointConversionWithNonZeroAnchorPoint innerNode.anchorPoint = CGPointMake(0.75, 1); innerNode.position = CGPointMake(23, 23); innerNode.bounds = CGRectMake(17, 17, 20, 20); - originalPoint = CGPointMake(51, 56), correctPoint = CGPointMake(42, 42); + originalPoint = CGPointMake(51, 56); + correctPoint = CGPointMake(42, 42); convertedPoint = [self checkConvertPoint:originalPoint toNode:node selfNode:innerNode]; XCTAssertTrue(_CGPointEqualToPointWithEpsilon(convertedPoint, correctPoint, 0.001), @"Unexpected point conversion result. Point: %@ Expected conversion: %@ Actual conversion: %@", NSStringFromCGPoint(originalPoint), NSStringFromCGPoint(correctPoint), NSStringFromCGPoint(convertedPoint)); // Setup - node = [[ASDisplayNode alloc] init], innerNode = [[ASDisplayNode alloc] init]; + node = [[ASDisplayNode alloc] init]; + innerNode = [[ASDisplayNode alloc] init]; [node addSubnode:innerNode]; // Convert point in outer node's coordinate space *TO* inner node's coordinate space @@ -875,7 +898,8 @@ - (void)testDisplayNodePointConversionWithNonZeroAnchorPoint innerNode.anchorPoint = CGPointMake(0.3, 0.3); innerNode.position = CGPointMake(23, 23); innerNode.bounds = CGRectMake(17, 17, 200, 200); - originalPoint = CGPointMake(1, 1), correctPoint = CGPointMake(55, 55); + originalPoint = CGPointMake(1, 1); + correctPoint = CGPointMake(55, 55); convertedPoint = [self checkConvertPoint:originalPoint toNode:innerNode selfNode:node]; XCTAssertTrue(_CGPointEqualToPointWithEpsilon(convertedPoint, correctPoint, 0.001), @"Unexpected point conversion result. Point: %@ Expected conversion: %@ Actual conversion: %@", NSStringFromCGPoint(originalPoint), NSStringFromCGPoint(correctPoint), NSStringFromCGPoint(convertedPoint)); } diff --git a/Tests/ASLayoutFlatteningTests.m b/Tests/ASLayoutFlatteningTests.m index 56f76c5d4..fb04a9509 100644 --- a/Tests/ASLayoutFlatteningTests.m +++ b/Tests/ASLayoutFlatteningTests.m @@ -42,9 +42,9 @@ - (void)testThatFlattenedLayoutContainsOnlyDirectSubnodesInValidOrder NSMutableArray *layoutSpecs = [NSMutableArray array]; NSMutableArray *indirectSubnodes = [NSMutableArray array]; - ASDisplayNode *(^subnode)() = ^ASDisplayNode *() { [subnodes addObject:[[ASDisplayNode alloc] init]]; return [subnodes lastObject]; }; - ASLayoutSpec *(^layoutSpec)() = ^ASLayoutSpec *() { [layoutSpecs addObject:[[ASLayoutSpec alloc] init]]; return [layoutSpecs lastObject]; }; - ASDisplayNode *(^indirectSubnode)() = ^ASDisplayNode *() { [indirectSubnodes addObject:[[ASDisplayNode alloc] init]]; return [indirectSubnodes lastObject]; }; + ASDisplayNode *(^subnode)(void) = ^ASDisplayNode *() { [subnodes addObject:[[ASDisplayNode alloc] init]]; return [subnodes lastObject]; }; + ASLayoutSpec *(^layoutSpec)(void) = ^ASLayoutSpec *() { [layoutSpecs addObject:[[ASLayoutSpec alloc] init]]; return [layoutSpecs lastObject]; }; + ASDisplayNode *(^indirectSubnode)(void) = ^ASDisplayNode *() { [indirectSubnodes addObject:[[ASDisplayNode alloc] init]]; return [indirectSubnodes lastObject]; }; NSArray *sublayouts = @[ layout(subnode(), @[ @@ -118,7 +118,7 @@ - (void)testThatLayoutWithNullPositionAndFlattenedNodeSublayoutsIsReused @autoreleasepool { ASDisplayNode *rootNode = [[ASDisplayNode alloc] init]; NSMutableArray *subnodes = [NSMutableArray array]; - ASDisplayNode *(^subnode)() = ^ASDisplayNode *() { [subnodes addObject:[[ASDisplayNode alloc] init]]; return [subnodes lastObject]; }; + ASDisplayNode *(^subnode)(void) = ^ASDisplayNode *() { [subnodes addObject:[[ASDisplayNode alloc] init]]; return [subnodes lastObject]; }; ASLayout *originalLayout = layoutWithCustomPosition(ASPointNull, rootNode, @[ @@ -148,9 +148,9 @@ - (void)testThatLayoutWithNullPositionAndUnflattenedSublayoutsIsNotReused NSMutableArray *indirectSubnodes = [NSMutableArray array]; NSMutableArray *reusedLayouts = [NSMutableArray array]; - ASDisplayNode *(^subnode)() = ^ASDisplayNode *() { [subnodes addObject:[[ASDisplayNode alloc] init]]; return [subnodes lastObject]; }; - ASLayoutSpec *(^layoutSpec)() = ^ASLayoutSpec *() { [layoutSpecs addObject:[[ASLayoutSpec alloc] init]]; return [layoutSpecs lastObject]; }; - ASDisplayNode *(^indirectSubnode)() = ^ASDisplayNode *() { [indirectSubnodes addObject:[[ASDisplayNode alloc] init]]; return [indirectSubnodes lastObject]; }; + ASDisplayNode *(^subnode)(void) = ^ASDisplayNode *() { [subnodes addObject:[[ASDisplayNode alloc] init]]; return [subnodes lastObject]; }; + ASLayoutSpec *(^layoutSpec)(void) = ^ASLayoutSpec *() { [layoutSpecs addObject:[[ASLayoutSpec alloc] init]]; return [layoutSpecs lastObject]; }; + ASDisplayNode *(^indirectSubnode)(void) = ^ASDisplayNode *() { [indirectSubnodes addObject:[[ASDisplayNode alloc] init]]; return [indirectSubnodes lastObject]; }; ASLayout *(^reusedLayout)(ASDisplayNode *) = ^ASLayout *(ASDisplayNode *subnode) { [reusedLayouts addObject:layout(subnode, @[])]; return [reusedLayouts lastObject]; }; /* From b01fac35b6a35de2871accd579ac44ada429d811 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Fri, 1 Dec 2017 13:11:17 +0000 Subject: [PATCH 035/133] [ASDisplayNode+Layout] Ensure a pending layout is applied once (#695) Before: - Even if a pending layout was applied before, it'll be unnecessarily applied again in next layout passes and cause `-calculatedLayoutDidChange` being called multiple times. After: - If a pending layout was applied, the calculated layout will not be ignored but reused, if possible, in next layout passes. Test plan: testSetNeedsLayoutAndNormalLayoutPass in #424. --- CHANGELOG.md | 1 + Source/ASDisplayNode+Layout.mm | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 472f12134..99f695457 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - [API CHANGES] `ASPerformMainThreadDeallocation` and `ASPerformBackgroundDeallocation` functions take `id *` instead of `id` and they're now more reliable. Also, in Swift, `ASDeallocQueue.sharedDeallocationQueue() -> ASDeallocQueue.sharedDeallocationQueue`. [Adlai Holler](https://github.com/Adlai-Holler) [#651](https://github.com/TextureGroup/Texture/pull/651) - [Collection/Table] Added direct support for mapping section indexes between data spaces. [Adlai Holler](https://github.com/Adlai-Holler) [#651](https://github.com/TextureGroup/Texture/pull/660) - [ASCornerLayoutSpec] New layout spec class for declarative corner element layout. [#657](https://github.com/TextureGroup/Texture/pull/657) [huangkun](https://github.com/huang-kun) +- [Layout] Fix an issue that causes a pending layout to be applied multiple times. [Huy Nguyen](https://github.com/nguyenhuy) [#695](https://github.com/TextureGroup/Texture/pull/695) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASDisplayNode+Layout.mm b/Source/ASDisplayNode+Layout.mm index f91016ac4..6d291c830 100644 --- a/Source/ASDisplayNode+Layout.mm +++ b/Source/ASDisplayNode+Layout.mm @@ -300,14 +300,16 @@ - (void)_u_measureNodeWithBoundsIfNecessary:(CGRect)bounds CGSize boundsSizeForLayout = ASCeilSizeValues(bounds.size); - // Prefer _pendingDisplayNodeLayout over _calculatedDisplayNodeLayout (if exists, it's the newest) - // If there is no _pending, check if _calculated is valid to reuse (avoiding recalculation below). - if (_pendingDisplayNodeLayout == nullptr || _pendingDisplayNodeLayout->version < _layoutVersion) { - if (_calculatedDisplayNodeLayout->version >= _layoutVersion - && (_calculatedDisplayNodeLayout->requestedLayoutFromAbove == YES - || CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))) { - return; - } + // Prefer a newer and not yet applied _pendingDisplayNodeLayout over _calculatedDisplayNodeLayout + // If there is no such _pending, check if _calculated is valid to reuse (avoiding recalculation below). + BOOL pendingLayoutIsPreferred = (_pendingDisplayNodeLayout != nullptr + && _pendingDisplayNodeLayout->version >= _layoutVersion + && _pendingDisplayNodeLayout->version > _calculatedDisplayNodeLayout->version); // _pending is not yet applied + BOOL calculatedLayoutIsReusable = (_calculatedDisplayNodeLayout->version >= _layoutVersion + && (_calculatedDisplayNodeLayout->requestedLayoutFromAbove + || CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))); + if (!pendingLayoutIsPreferred && calculatedLayoutIsReusable) { + return; } as_activity_create_for_scope("Update node layout for current bounds"); From bccde6cf0f26d71931f91d6d10fdd7a195a89669 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Fri, 1 Dec 2017 15:41:06 +0000 Subject: [PATCH 036/133] [ASScrollNode] Fix small bugs and add unit tests (#637) * Add unit tests for ASScrollNode * Make sure ASScrollNode's size is clamped against its size range * Invalidate ASScrollNode's calculated layout if its scrollable directions changed * Update comment * Update CHANGELOG * Address Adlai's comments --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 + CHANGELOG.md | 2 + Source/ASScrollNode.mm | 11 +- Tests/ASScrollNodeTests.m | 139 ++++++++++++++++++++++ 4 files changed, 153 insertions(+), 3 deletions(-) create mode 100644 Tests/ASScrollNodeTests.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index fb9888c5f..025344d7e 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -443,6 +443,7 @@ E5775B041F16759F00CAC9BC /* ASCollectionLayoutCache.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5775B031F16759F00CAC9BC /* ASCollectionLayoutCache.mm */; }; E5855DEF1EBB4D83003639AE /* ASCollectionLayoutDefines.m in Sources */ = {isa = PBXBuildFile; fileRef = E5855DED1EBB4D83003639AE /* ASCollectionLayoutDefines.m */; }; E5855DF01EBB4D83003639AE /* ASCollectionLayoutDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = E5855DEE1EBB4D83003639AE /* ASCollectionLayoutDefines.h */; settings = {ATTRIBUTES = (Private, ); }; }; + E586F96C1F9F9E2900ECE00E /* ASScrollNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E586F96B1F9F9E2900ECE00E /* ASScrollNodeTests.m */; }; E58E9E421E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E58E9E3D1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; E58E9E431E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E58E9E3E1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m */; }; E58E9E441E941D74004CFC59 /* ASCollectionLayoutContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E58E9E3F1E941D74004CFC59 /* ASCollectionLayoutContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -936,6 +937,7 @@ E5775B031F16759F00CAC9BC /* ASCollectionLayoutCache.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionLayoutCache.mm; sourceTree = ""; }; E5855DED1EBB4D83003639AE /* ASCollectionLayoutDefines.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionLayoutDefines.m; sourceTree = ""; }; E5855DEE1EBB4D83003639AE /* ASCollectionLayoutDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutDefines.h; sourceTree = ""; }; + E586F96B1F9F9E2900ECE00E /* ASScrollNodeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASScrollNodeTests.m; sourceTree = ""; }; E58E9E3D1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionFlowLayoutDelegate.h; sourceTree = ""; }; E58E9E3E1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionFlowLayoutDelegate.m; sourceTree = ""; }; E58E9E3F1E941D74004CFC59 /* ASCollectionLayoutContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutContext.h; sourceTree = ""; }; @@ -1219,6 +1221,7 @@ E51B78BD1F01A0EE00E32604 /* ASLayoutFlatteningTests.m */, 052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */, 058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m */, + E586F96B1F9F9E2900ECE00E /* ASScrollNodeTests.m */, 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */, CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */, 058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m */, @@ -2177,6 +2180,7 @@ CC7FD9E11BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m in Sources */, 052EE0661A159FEF002C6279 /* ASMultiplexImageNodeTests.m in Sources */, 058D0A3C195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m in Sources */, + E586F96C1F9F9E2900ECE00E /* ASScrollNodeTests.m in Sources */, CC8B05D81D73979700F54286 /* ASTextNodePerformanceTests.m in Sources */, CC583AD91EF9BDC600134156 /* ASDisplayNode+OCMock.m in Sources */, 697B315A1CFE4B410049936F /* ASEditableTextNodeTests.m in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 99f695457..3263e31d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ - [Collection/Table] Added direct support for mapping section indexes between data spaces. [Adlai Holler](https://github.com/Adlai-Holler) [#651](https://github.com/TextureGroup/Texture/pull/660) - [ASCornerLayoutSpec] New layout spec class for declarative corner element layout. [#657](https://github.com/TextureGroup/Texture/pull/657) [huangkun](https://github.com/huang-kun) - [Layout] Fix an issue that causes a pending layout to be applied multiple times. [Huy Nguyen](https://github.com/nguyenhuy) [#695](https://github.com/TextureGroup/Texture/pull/695) +- [ASScrollNode] Ensure the node respects the given size range while calculating its layout. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) +- [ASScrollNode] Invalidate the node's calculated layout if its scrollable directions changed. Also add unit tests for the class. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASScrollNode.mm b/Source/ASScrollNode.mm index 7d1beaa3d..3ff008809 100644 --- a/Source/ASScrollNode.mm +++ b/Source/ASScrollNode.mm @@ -99,10 +99,12 @@ - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize // To understand this code, imagine we're containing a horizontal stack set within a vertical table node. // Our parentSize is fixed ~375pt width, but 0 - INF height. Our stack measures 1000pt width, 50pt height. // In this case, we want our scrollNode.bounds to be 375pt wide, and 50pt high. ContentSize 1000pt, 50pt. - // We can achieve this behavior by: 1. Always set contentSize to layout.size. 2. Set bounds to parentSize, + // We can achieve this behavior by: + // 1. Always set contentSize to layout.size. + // 2. Set bounds to a size that is calculated by clamping parentSize against constrained size, // unless one dimension is not defined, in which case adopt the contentSize for that dimension. _contentCalculatedSizeFromLayout = layout.size; - CGSize selfSize = parentSize; + CGSize selfSize = ASSizeRangeClamp(constrainedSize, parentSize); if (ASPointsValidForLayout(selfSize.width) == NO) { selfSize.width = _contentCalculatedSizeFromLayout.width; } @@ -161,7 +163,10 @@ - (ASScrollDirection)scrollableDirections - (void)setScrollableDirections:(ASScrollDirection)scrollableDirections { ASDN::MutexLocker l(__instanceLock__); - _scrollableDirections = scrollableDirections; + if (_scrollableDirections != scrollableDirections) { + _scrollableDirections = scrollableDirections; + [self setNeedsLayout]; + } } @end diff --git a/Tests/ASScrollNodeTests.m b/Tests/ASScrollNodeTests.m new file mode 100644 index 000000000..56b3fd731 --- /dev/null +++ b/Tests/ASScrollNodeTests.m @@ -0,0 +1,139 @@ +// +// ASScrollNodeTests.m +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import +#import +#import "ASXCTExtensions.h" + +@interface ASScrollNodeTests : XCTestCase + +@property (nonatomic) ASScrollNode *scrollNode; +@property (nonatomic) ASDisplayNode *subnode; + +@end + +@implementation ASScrollNodeTests + +- (void)setUp +{ + ASDisplayNode *subnode = [[ASDisplayNode alloc] init]; + self.subnode = subnode; + + self.scrollNode = [[ASScrollNode alloc] init]; + self.scrollNode.scrollableDirections = ASScrollDirectionVerticalDirections; + self.scrollNode.automaticallyManagesContentSize = YES; + self.scrollNode.automaticallyManagesSubnodes = YES; + self.scrollNode.layoutSpecBlock = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { + return [[ASWrapperLayoutSpec alloc] initWithLayoutElement:subnode]; + }; + [self.scrollNode view]; +} + +- (void)testSubnodeLayoutCalculatedWithUnconstrainedMaxSizeInScrollableDirection +{ + CGSize parentSize = CGSizeMake(100, 100); + ASSizeRange sizeRange = ASSizeRangeMake(parentSize); + + [self.scrollNode layoutThatFits:sizeRange parentSize:parentSize]; + + ASSizeRange subnodeSizeRange = sizeRange; + subnodeSizeRange.max.height = CGFLOAT_MAX; + XCTAssertEqual(self.scrollNode.scrollableDirections, ASScrollDirectionVerticalDirections); + ASXCTAssertEqualSizeRanges(self.subnode.constrainedSizeForCalculatedLayout, subnodeSizeRange); + + // Same test for horizontal scrollable directions + self.scrollNode.scrollableDirections = ASScrollDirectionHorizontalDirections; + [self.scrollNode layoutThatFits:sizeRange parentSize:parentSize]; + + subnodeSizeRange = sizeRange; + subnodeSizeRange.max.width = CGFLOAT_MAX; + + ASXCTAssertEqualSizeRanges(self.subnode.constrainedSizeForCalculatedLayout, subnodeSizeRange); +} + +- (void)testAutomaticallyManagesContentSizeUnderflow +{ + CGSize subnodeSize = CGSizeMake(100, 100); + CGSize parentSize = CGSizeMake(100, 200); + ASSizeRange sizeRange = ASSizeRangeUnconstrained; + + self.subnode.style.preferredSize = subnodeSize; + + [self.scrollNode layoutThatFits:sizeRange parentSize:parentSize]; + [self.scrollNode layout]; + + ASXCTAssertEqualSizes(self.scrollNode.calculatedSize, parentSize); + ASXCTAssertEqualSizes(self.scrollNode.view.contentSize, subnodeSize); +} + +- (void)testAutomaticallyManagesContentSizeOverflow +{ + CGSize subnodeSize = CGSizeMake(100, 500); + CGSize parentSize = CGSizeMake(100, 200); + ASSizeRange sizeRange = ASSizeRangeUnconstrained; + + self.subnode.style.preferredSize = subnodeSize; + + [self.scrollNode layoutThatFits:sizeRange parentSize:parentSize]; + [self.scrollNode layout]; + + ASXCTAssertEqualSizes(self.scrollNode.calculatedSize, parentSize); + ASXCTAssertEqualSizes(self.scrollNode.view.contentSize, subnodeSize); +} + +- (void)testAutomaticallyManagesContentSizeWithSizeRangeSmallerThanParentSize +{ + CGSize subnodeSize = CGSizeMake(100, 100); + CGSize parentSize = CGSizeMake(100, 500); + ASSizeRange sizeRange = ASSizeRangeMake(CGSizeMake(100, 100), CGSizeMake(100, 200)); + + self.subnode.style.preferredSize = subnodeSize; + + [self.scrollNode layoutThatFits:sizeRange parentSize:parentSize]; + [self.scrollNode layout]; + + ASXCTAssertEqualSizes(self.scrollNode.calculatedSize, sizeRange.max); + ASXCTAssertEqualSizes(self.scrollNode.view.contentSize, subnodeSize); +} + +- (void)testAutomaticallyManagesContentSizeWithSizeRangeBiggerThanParentSize +{ + CGSize subnodeSize = CGSizeMake(100, 200); + CGSize parentSize = CGSizeMake(100, 100); + ASSizeRange sizeRange = ASSizeRangeMake(CGSizeMake(100, 150)); + + self.subnode.style.preferredSize = subnodeSize; + + [self.scrollNode layoutThatFits:sizeRange parentSize:parentSize]; + [self.scrollNode layout]; + + ASXCTAssertEqualSizes(self.scrollNode.calculatedSize, sizeRange.min); + ASXCTAssertEqualSizes(self.scrollNode.view.contentSize, subnodeSize); +} + +- (void)testAutomaticallyManagesContentSizeWithInvalidCalculatedSizeForLayout +{ + CGSize subnodeSize = CGSizeMake(100, 200); + CGSize parentSize = CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX); + ASSizeRange sizeRange = ASSizeRangeUnconstrained; + + self.subnode.style.preferredSize = subnodeSize; + + [self.scrollNode layoutThatFits:sizeRange parentSize:parentSize]; + [self.scrollNode layout]; + + ASXCTAssertEqualSizes(self.scrollNode.calculatedSize, subnodeSize); + ASXCTAssertEqualSizes(self.scrollNode.view.contentSize, subnodeSize); +} + +@end From 0dc7002f0bd74642aa06be449742729bc2044251 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 1 Dec 2017 09:05:47 -0800 Subject: [PATCH 037/133] Add unit tests for the layout engine (#424) * Build testing platform & tests for the layout engine * Add our license header to debugbreak. * Remove thing * Address review comments * Beef up the logging * Update -[ASLayout isEqual:] * testLayoutTransitionWithAsyncMeasurement passes now * Disable testASetNeedsLayoutInterferingWithTheCurrentTransition * Fix build errors --- AsyncDisplayKit.xcodeproj/project.pbxproj | 18 + CHANGELOG.md | 1 + Source/ASDisplayNode+Layout.mm | 1 + Source/ASDisplayNode.mm | 3 +- Source/Details/_ASDisplayLayer.mm | 2 + Source/Layout/ASLayout.mm | 8 +- .../Private/ASDisplayNode+FrameworkPrivate.h | 23 + Tests/ASLayoutEngineTests.mm | 517 ++++++++++++++++++ Tests/ASLayoutTestNode.h | 42 ++ Tests/ASLayoutTestNode.mm | 92 ++++ Tests/ASTLayoutFixture.h | 61 +++ Tests/ASTLayoutFixture.mm | 134 +++++ Tests/Common/ASTestCase.h | 5 + Tests/Common/OCMockObject+ASAdditions.h | 29 +- Tests/Common/OCMockObject+ASAdditions.m | 102 +++- Tests/Common/debugbreak.h | 146 +++++ 16 files changed, 1176 insertions(+), 8 deletions(-) create mode 100644 Tests/ASLayoutEngineTests.mm create mode 100644 Tests/ASLayoutTestNode.h create mode 100644 Tests/ASLayoutTestNode.mm create mode 100644 Tests/ASTLayoutFixture.h create mode 100644 Tests/ASTLayoutFixture.mm create mode 100644 Tests/Common/debugbreak.h diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 025344d7e..f8d60fb32 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -405,6 +405,9 @@ CCCCCCE81EC3F0FC0087FE10 /* NSAttributedString+ASText.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.m */; }; CCDD148B1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */; }; CCE4F9B31F0D60AC00062E4E /* ASIntegerMapTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B21F0D60AC00062E4E /* ASIntegerMapTests.m */; }; + CCE4F9B51F0DA4F300062E4E /* ASLayoutEngineTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B41F0DA4F300062E4E /* ASLayoutEngineTests.mm */; }; + CCE4F9BA1F0DBB5000062E4E /* ASLayoutTestNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B71F0DBA5000062E4E /* ASLayoutTestNode.mm */; }; + CCE4F9BE1F0ECE5200062E4E /* ASTLayoutFixture.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9BD1F0ECE5200062E4E /* ASTLayoutFixture.mm */; }; CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; settings = {ATTRIBUTES = (Private, ); }; }; DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; @@ -899,6 +902,12 @@ CCE04B211E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "IGListAdapter+AsyncDisplayKit.m"; sourceTree = ""; }; CCE04B2B1E314A32006AEBBB /* ASSupplementaryNodeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASSupplementaryNodeSource.h; sourceTree = ""; }; CCE4F9B21F0D60AC00062E4E /* ASIntegerMapTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIntegerMapTests.m; sourceTree = ""; }; + CCE4F9B41F0DA4F300062E4E /* ASLayoutEngineTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutEngineTests.mm; sourceTree = ""; }; + CCE4F9B61F0DBA5000062E4E /* ASLayoutTestNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutTestNode.h; sourceTree = ""; }; + CCE4F9B71F0DBA5000062E4E /* ASLayoutTestNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutTestNode.mm; sourceTree = ""; }; + CCE4F9BB1F0EA67F00062E4E /* debugbreak.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = debugbreak.h; sourceTree = ""; }; + CCE4F9BC1F0ECE5200062E4E /* ASTLayoutFixture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTLayoutFixture.h; sourceTree = ""; }; + CCE4F9BD1F0ECE5200062E4E /* ASTLayoutFixture.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTLayoutFixture.mm; sourceTree = ""; }; D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = ""; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASScrollNode.mm; sourceTree = ""; }; @@ -1175,6 +1184,11 @@ CC11F9791DB181180024D77B /* ASNetworkImageNodeTests.m */, CC051F1E1D7A286A006434CB /* ASCALayerTests.m */, CCE4F9B21F0D60AC00062E4E /* ASIntegerMapTests.m */, + CCE4F9B41F0DA4F300062E4E /* ASLayoutEngineTests.mm */, + CCE4F9B61F0DBA5000062E4E /* ASLayoutTestNode.h */, + CCE4F9B71F0DBA5000062E4E /* ASLayoutTestNode.mm */, + CCE4F9BC1F0ECE5200062E4E /* ASTLayoutFixture.h */, + CCE4F9BD1F0ECE5200062E4E /* ASTLayoutFixture.mm */, CC8B05D71D73979700F54286 /* ASTextNodePerformanceTests.m */, CC8B05D41D73836400F54286 /* ASPerformanceTestContext.h */, CC8B05D51D73836400F54286 /* ASPerformanceTestContext.m */, @@ -1573,6 +1587,7 @@ CC583ABF1EF9BAB400134156 /* Common */ = { isa = PBXGroup; children = ( + CCE4F9BB1F0EA67F00062E4E /* debugbreak.h */, CC583AC01EF9BAB400134156 /* ASDisplayNode+OCMock.m */, CC583AC11EF9BAB400134156 /* ASTestCase.h */, CC583AC21EF9BAB400134156 /* ASTestCase.m */, @@ -2187,6 +2202,7 @@ ACF6ED611B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm in Sources */, CC8B05D61D73836400F54286 /* ASPerformanceTestContext.m in Sources */, CC0AEEA41D66316E005D1C78 /* ASUICollectionViewTests.m in Sources */, + CCE4F9B51F0DA4F300062E4E /* ASLayoutEngineTests.mm in Sources */, 69B225671D72535E00B25B22 /* ASDisplayNodeLayoutTests.mm in Sources */, ACF6ED621B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm in Sources */, 7AB338691C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm in Sources */, @@ -2195,12 +2211,14 @@ 254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */, 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.m in Sources */, ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */, + CCE4F9BA1F0DBB5000062E4E /* ASLayoutTestNode.mm in Sources */, 81E95C141D62639600336598 /* ASTextNodeSnapshotTests.m in Sources */, 3C9C128519E616EF00E942A0 /* ASTableViewTests.mm in Sources */, AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.m in Sources */, 254C6B521BF8FE6D003EC431 /* ASTextKitTruncationTests.mm in Sources */, 058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m in Sources */, CC3B20901C3F892D00798563 /* ASBridgedPropertiesTests.mm in Sources */, + CCE4F9BE1F0ECE5200062E4E /* ASTLayoutFixture.mm in Sources */, 058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */, DBC453221C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m in Sources */, 058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 3263e31d2..430bc73e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - [Layout] Fix an issue that causes a pending layout to be applied multiple times. [Huy Nguyen](https://github.com/nguyenhuy) [#695](https://github.com/TextureGroup/Texture/pull/695) - [ASScrollNode] Ensure the node respects the given size range while calculating its layout. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - [ASScrollNode] Invalidate the node's calculated layout if its scrollable directions changed. Also add unit tests for the class. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) +- Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASDisplayNode+Layout.mm b/Source/ASDisplayNode+Layout.mm index 6d291c830..dd0a2e2ad 100644 --- a/Source/ASDisplayNode+Layout.mm +++ b/Source/ASDisplayNode+Layout.mm @@ -85,6 +85,7 @@ - (ASLayout *)layoutThatFits:(ASSizeRange)constrainedSize parentSize:(CGSize)par layout = [self calculateLayoutThatFits:constrainedSize restrictedToSize:self.style.size relativeToParentSize:parentSize]; + as_log_verbose(ASLayoutLog(), "Established pending layout for %@ in %s", self, sel_getName(_cmd)); _pendingDisplayNodeLayout = std::make_shared(layout, constrainedSize, parentSize, version); ASDisplayNodeAssertNotNil(layout, @"-[ASDisplayNode layoutThatFits:parentSize:] newly calculated layout should not be nil! %@", self); } diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index c5bdd8832..95e927667 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -2762,7 +2762,8 @@ - (void)setHierarchyState:(ASHierarchyState)newState } } - ASDisplayNodeLogEvent(self, @"setHierarchyState: oldState = %@, newState = %@", NSStringFromASHierarchyState(oldState), NSStringFromASHierarchyState(newState)); + ASDisplayNodeLogEvent(self, @"setHierarchyState: %@", NSStringFromASHierarchyStateChange(oldState, newState)); + as_log_verbose(ASNodeLog(), "%s%@ %@", sel_getName(_cmd), NSStringFromASHierarchyStateChange(oldState, newState), self); } - (void)willEnterHierarchy diff --git a/Source/Details/_ASDisplayLayer.mm b/Source/Details/_ASDisplayLayer.mm index 7ceba1c79..ba8b02ab4 100644 --- a/Source/Details/_ASDisplayLayer.mm +++ b/Source/Details/_ASDisplayLayer.mm @@ -25,6 +25,7 @@ #import #import #import +#import @implementation _ASDisplayLayer { @@ -93,6 +94,7 @@ - (void)setContents:(id)contents - (void)setNeedsLayout { ASDisplayNodeAssertMainThread(); + as_log_verbose(ASNodeLog(), "%s on %@", sel_getName(_cmd), self); [super setNeedsLayout]; } #endif diff --git a/Source/Layout/ASLayout.mm b/Source/Layout/ASLayout.mm index 4e8d2f502..919a520c9 100644 --- a/Source/Layout/ASLayout.mm +++ b/Source/Layout/ASLayout.mm @@ -23,6 +23,7 @@ #import #import +#import #import #import #import @@ -281,11 +282,12 @@ - (BOOL)isEqual:(id)object } if (!CGSizeEqualToSize(_size, layout.size)) return NO; - if (!CGPointEqualToPoint(_position, layout.position)) return NO; + + if (!((ASPointIsNull(self.position) && ASPointIsNull(layout.position)) + || CGPointEqualToPoint(self.position, layout.position))) return NO; if (_layoutElement != layout.layoutElement) return NO; - NSArray *sublayouts = layout.sublayouts; - if (sublayouts != _sublayouts && (sublayouts == nil || _sublayouts == nil || ![_sublayouts isEqual:sublayouts])) { + if (!ASObjectIsEqual(_sublayouts, layout.sublayouts)) { return NO; } diff --git a/Source/Private/ASDisplayNode+FrameworkPrivate.h b/Source/Private/ASDisplayNode+FrameworkPrivate.h index d4c18a6e9..dbb520787 100644 --- a/Source/Private/ASDisplayNode+FrameworkPrivate.h +++ b/Source/Private/ASDisplayNode+FrameworkPrivate.h @@ -99,6 +99,29 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyStat return [NSString stringWithFormat:@"{ %@ }", [states componentsJoinedByString:@" | "]]; } +#define HIERARCHY_STATE_DELTA(Name) ({ \ + if ((oldState & ASHierarchyState##Name) != (newState & ASHierarchyState##Name)) { \ + [changes appendFormat:@"%c%s ", (newState & ASHierarchyState##Name ? '+' : '-'), #Name]; \ + } \ +}) + +__unused static NSString * _Nonnull NSStringFromASHierarchyStateChange(ASHierarchyState oldState, ASHierarchyState newState) +{ + if (oldState == newState) { + return @"{ }"; + } + + NSMutableString *changes = [NSMutableString stringWithString:@"{ "]; + HIERARCHY_STATE_DELTA(Rasterized); + HIERARCHY_STATE_DELTA(RangeManaged); + HIERARCHY_STATE_DELTA(TransitioningSupernodes); + HIERARCHY_STATE_DELTA(LayoutPending); + [changes appendString:@"}"]; + return changes; +} + +#undef HIERARCHY_STATE_DELTA + @interface ASDisplayNode () { @protected diff --git a/Tests/ASLayoutEngineTests.mm b/Tests/ASLayoutEngineTests.mm new file mode 100644 index 000000000..550dffa47 --- /dev/null +++ b/Tests/ASLayoutEngineTests.mm @@ -0,0 +1,517 @@ +// +// ASLayoutEngineTests.mm +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import "ASTestCase.h" +#import "ASLayoutTestNode.h" +#import "ASXCTExtensions.h" +#import "ASTLayoutFixture.h" + +@interface ASLayoutEngineTests : ASTestCase + +@end + +@implementation ASLayoutEngineTests { + ASLayoutTestNode *nodeA; + ASLayoutTestNode *nodeB; + ASLayoutTestNode *nodeC; + ASLayoutTestNode *nodeD; + ASLayoutTestNode *nodeE; + ASTLayoutFixture *fixture1; + ASTLayoutFixture *fixture2; + ASTLayoutFixture *fixture3; + ASTLayoutFixture *fixture4; + + // fixtures 1 and 3 share the same exact node A layout spec block. + // we don't want the infra to call -setNeedsLayout when we switch fixtures + // so we need to use the same exact block. + ASLayoutSpecBlock fixture1and3NodeALayoutSpecBlock; + + UIWindow *window; + UIViewController *vc; + NSArray *allNodes; + NSTimeInterval verifyDelay; + // See -stubCalculatedLayoutDidChange. + BOOL stubbedCalculatedLayoutDidChange; +} + +- (void)setUp +{ + [super setUp]; + verifyDelay = 3; + window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 10, 1)]; + vc = [[UIViewController alloc] init]; + nodeA = [ASLayoutTestNode new]; + nodeA.backgroundColor = [UIColor redColor]; + + // NOTE: nodeB has flexShrink, the others don't + nodeB = [ASLayoutTestNode new]; + nodeB.style.flexShrink = 1; + nodeB.backgroundColor = [UIColor orangeColor]; + + nodeC = [ASLayoutTestNode new]; + nodeC.backgroundColor = [UIColor yellowColor]; + nodeD = [ASLayoutTestNode new]; + nodeD.backgroundColor = [UIColor greenColor]; + nodeE = [ASLayoutTestNode new]; + nodeE.backgroundColor = [UIColor blueColor]; + allNodes = @[ nodeA, nodeB, nodeC, nodeD, nodeE ]; + ASSetDebugNames(nodeA, nodeB, nodeC, nodeD, nodeE); + ASLayoutSpecBlock b = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { + return [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:0 justifyContent:ASStackLayoutJustifyContentSpaceBetween alignItems:ASStackLayoutAlignItemsStart children:@[ nodeB, nodeC, nodeD ]]; + }; + fixture1and3NodeALayoutSpecBlock = b; + fixture1 = [self createFixture1]; + fixture2 = [self createFixture2]; + fixture3 = [self createFixture3]; + fixture4 = [self createFixture4]; + + nodeA.frame = vc.view.bounds; + nodeA.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [vc.view addSubnode:nodeA]; + + window.rootViewController = vc; + [window makeKeyAndVisible]; +} + +- (void)tearDown +{ + nodeA.layoutSpecBlock = nil; + for (ASLayoutTestNode *node in allNodes) { + OCMVerifyAllWithDelay(node.mock, verifyDelay); + } + [super tearDown]; +} + +- (void)testFirstLayoutPassWhenInWindow +{ + [self runFirstLayoutPassWithFixture:fixture1]; +} + +- (void)testSetNeedsLayoutAndNormalLayoutPass +{ + [self runFirstLayoutPassWithFixture:fixture1]; + + [fixture2 apply]; + + // skip nodeB because its layout doesn't change. + for (ASLayoutTestNode *node in @[ nodeA, nodeC, nodeE ]) { + [fixture2 withSizeRangesForNode:node block:^(ASSizeRange sizeRange) { + OCMExpect([node.mock calculateLayoutThatFits:sizeRange]).onMainThread(); + }]; + OCMExpect([node.mock calculatedLayoutDidChange]).onMainThread(); + } + + [window layoutIfNeeded]; + [self verifyFixture:fixture2]; +} + +/** + * Transition from fixture1 to Fixture2 on node A. + * + * Expect A and D to calculate once off main, and + * to receive calculatedLayoutDidChange on main, + * then to get the measurement completion call on main, + * then to get animateLayoutTransition: and didCompleteLayoutTransition: on main. + */ +- (void)testLayoutTransitionWithAsyncMeasurement +{ + [self stubCalculatedLayoutDidChange]; + [self runFirstLayoutPassWithFixture:fixture1]; + + [fixture2 apply]; + + // Expect A, C, E to calculate new layouts off-main + // dispatch_once onto main to run our injectedMainThread work while the transition calculates. + __block dispatch_block_t injectedMainThreadWork = nil; + for (ASLayoutTestNode *node in @[ nodeA, nodeC, nodeE ]) { + [fixture2 withSizeRangesForNode:node block:^(ASSizeRange sizeRange) { + OCMExpect([node.mock calculateLayoutThatFits:sizeRange]) + .offMainThread() + .andDo(^(NSInvocation *inv) { + // On first calculateLayoutThatFits, schedule our injected main thread work. + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + injectedMainThreadWork(); + }); + }); + }); + }]; + } + + // The code in this section is designed to move in time order, all on the main thread: + + OCMExpect([nodeA.mock animateLayoutTransition:OCMOCK_ANY]).onMainThread(); + OCMExpect([nodeA.mock didCompleteLayoutTransition:OCMOCK_ANY]).onMainThread(); + + // Trigger the layout transition. + __block dispatch_block_t measurementCompletionBlock = nil; + [nodeA transitionLayoutWithAnimation:NO shouldMeasureAsync:YES measurementCompletion:^{ + measurementCompletionBlock(); + }]; + + // This block will get run after bg layout calculate starts, but before measurementCompletion + __block BOOL injectedMainThreadWorkDone = NO; + injectedMainThreadWork = ^{ + injectedMainThreadWorkDone = YES; + + [window layoutIfNeeded]; + + // Ensure we're still on the old layout. We should stay on this until the transition completes. + [self verifyFixture:fixture1]; + }; + + measurementCompletionBlock = ^{ + XCTAssert(injectedMainThreadWorkDone, @"We hoped to get onto the main thread before the measurementCompletion callback ran."); + }; + + for (ASLayoutTestNode *node in allNodes) { + OCMVerifyAllWithDelay(node.mock, verifyDelay); + } + + [self verifyFixture:fixture2]; +} + +/** + * Start at fixture 1. + * Trigger an async transition to fixture 2. + * While it's measuring, on main switch to fixture 4 (setNeedsLayout A, D) and run a CA layout pass. + * + * Correct behavior, we end up at fixture 4 since it's newer. + * Current incorrect behavior, we end up at fixture 2 and we remeasure surviving node C. + * Note: incorrect behavior likely introduced by the early check in __layout added in + * https://github.com/facebookarchive/AsyncDisplayKit/pull/2657 + */ +- (void)DISABLE_testASetNeedsLayoutInterferingWithTheCurrentTransition +{ + static BOOL enforceCorrectBehavior = NO; + + [self stubCalculatedLayoutDidChange]; + [self runFirstLayoutPassWithFixture:fixture1]; + + [fixture2 apply]; + + // Expect A, C, E to calculate new layouts off-main + // dispatch_once onto main to run our injectedMainThread work while the transition calculates. + __block dispatch_block_t injectedMainThreadWork = nil; + for (ASLayoutTestNode *node in @[ nodeA, nodeC, nodeE ]) { + [fixture2 withSizeRangesForNode:node block:^(ASSizeRange sizeRange) { + OCMExpect([node.mock calculateLayoutThatFits:sizeRange]) + .offMainThread() + .andDo(^(NSInvocation *inv) { + // On first calculateLayoutThatFits, schedule our injected main thread work. + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + dispatch_async(dispatch_get_main_queue(), ^{ + injectedMainThreadWork(); + }); + }); + }); + }]; + } + + // The code in this section is designed to move in time order, all on the main thread: + + // With the current behavior, the transition will continue and complete. + if (!enforceCorrectBehavior) { + OCMExpect([nodeA.mock animateLayoutTransition:OCMOCK_ANY]).onMainThread(); + OCMExpect([nodeA.mock didCompleteLayoutTransition:OCMOCK_ANY]).onMainThread(); + } + + // Trigger the layout transition. + __block dispatch_block_t measurementCompletionBlock = nil; + [nodeA transitionLayoutWithAnimation:NO shouldMeasureAsync:YES measurementCompletion:^{ + measurementCompletionBlock(); + }]; + + // Injected block will get run on main after bg layout calculate starts, but before measurementCompletion + __block BOOL injectedMainThreadWorkDone = NO; + injectedMainThreadWork = ^{ + as_log_verbose(OS_LOG_DEFAULT, "Begin injectedMainThreadWork"); + injectedMainThreadWorkDone = YES; + + [fixture4 apply]; + as_log_verbose(OS_LOG_DEFAULT, "Did apply new fixture"); + + if (enforceCorrectBehavior) { + // Correct measurement behavior here is unclear, may depend on whether the layouts which + // are common to both fixture2 and fixture4 are available from the cache. + } else { + // Incorrect behavior: nodeC will get measured against its new bounds on main. + auto cPendingSize = [fixture2 layoutForNode:nodeC].size; + OCMExpect([nodeC.mock calculateLayoutThatFits:ASSizeRangeMake(cPendingSize)]).onMainThread(); + } + [window layoutIfNeeded]; + as_log_verbose(OS_LOG_DEFAULT, "End injectedMainThreadWork"); + }; + + measurementCompletionBlock = ^{ + XCTAssert(injectedMainThreadWorkDone, @"We hoped to get onto the main thread before the measurementCompletion callback ran."); + }; + + for (ASLayoutTestNode *node in allNodes) { + OCMVerifyAllWithDelay(node.mock, verifyDelay); + } + + // Incorrect behavior: The transition will "win" even though its transitioning to stale data. + if (enforceCorrectBehavior) { + [self verifyFixture:fixture4]; + } else { + [self verifyFixture:fixture2]; + } +} + +/** + * Start on fixture 3 where nodeB is force-shrunk via multipass layout. + * Apply fixture 1, which just changes nodeB's size and calls -setNeedsLayout on it. + * + * This behavior is currently broken. See implementation for correct behavior and incorrect behavior. + */ +- (void)testCallingSetNeedsLayoutOnANodeThatWasSubjectToMultipassLayout +{ + static BOOL const enforceCorrectBehavior = NO; + [self stubCalculatedLayoutDidChange]; + [self runFirstLayoutPassWithFixture:fixture3]; + + // Switch to fixture 1, updating nodeB's desired size and calling -setNeedsLayout + // Now nodeB will fit happily into the stack. + [fixture1 apply]; + + if (enforceCorrectBehavior) { + /* + * Correct behavior: nodeB is remeasured against the first (unconstrained) size + * and when it's discovered that now nodeB fits, nodeA will re-layout and we'll + * end up correctly at fixture1. + */ + OCMExpect([nodeB.mock calculateLayoutThatFits:[fixture3 firstSizeRangeForNode:nodeB]]); + + [fixture1 withSizeRangesForNode:nodeA block:^(ASSizeRange sizeRange) { + OCMExpect([nodeA.mock calculateLayoutThatFits:sizeRange]); + }]; + + [window layoutIfNeeded]; + [self verifyFixture:fixture1]; + } else { + /* + * Incorrect behavior: nodeB is remeasured against the second (fixed-width) constraint. + * The returned value (8) is clamped to the fixed with (7), and then compared to the previous + * width (7) and we decide not to propagate up the invalidation, and we stay stuck on the old + * layout (fixture3). + */ + OCMExpect([nodeB.mock calculateLayoutThatFits:nodeB.constrainedSizeForCalculatedLayout]); + [window layoutIfNeeded]; + [self verifyFixture:fixture3]; + } +} + +#pragma mark - Helpers + +- (void)verifyFixture:(ASTLayoutFixture *)fixture +{ + auto expected = fixture.layout; + + // Ensure expected == frames + auto frames = [fixture.rootNode currentLayoutBasedOnFrames]; + if (![expected isEqual:frames]) { + XCTFail(@"\n*** Layout verification failed – frames don't match expected. ***\nGot:\n%@\nExpected:\n%@", [frames recursiveDescription], [expected recursiveDescription]); + } + + // Ensure expected == calculatedLayout + auto calculated = fixture.rootNode.calculatedLayout; + if (![expected isEqual:calculated]) { + XCTFail(@"\n*** Layout verification failed – calculated layout doesn't match expected. ***\nGot:\n%@\nExpected:\n%@", [calculated recursiveDescription], [expected recursiveDescription]); + } +} + +/** + * Stubs calculatedLayoutDidChange for all nodes. + * + * It's not really a core layout engine method, and it's also + * currently bugged and gets called a lot so for most + * tests its better not to have expectations about it littered around. + * https://github.com/TextureGroup/Texture/issues/422 + */ +- (void)stubCalculatedLayoutDidChange +{ + stubbedCalculatedLayoutDidChange = YES; + for (ASLayoutTestNode *node in allNodes) { + OCMStub([node.mock calculatedLayoutDidChange]); + } +} + +/** + * Fixture 1: A basic horizontal stack, all single-pass. + * + * [A: HorizStack([B, C, D])]. B is (1x1), C is (2x1), D is (1x1) + */ +- (ASTLayoutFixture *)createFixture1 +{ + auto fixture = [[ASTLayoutFixture alloc] init]; + + // nodeB + [fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB]; + auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{1,1} position:{0,0} sublayouts:nil]; + + // nodeC + [fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeC]; + auto layoutC = [ASLayout layoutWithLayoutElement:nodeC size:{2,1} position:{4,0} sublayouts:nil]; + + // nodeD + [fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeD]; + auto layoutD = [ASLayout layoutWithLayoutElement:nodeD size:{1,1} position:{9,0} sublayouts:nil]; + + [fixture addSizeRange:{{10, 1}, {10, 1}} forNode:nodeA]; + auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{10,1} position:ASPointNull sublayouts:@[ layoutB, layoutC, layoutD ]]; + fixture.layout = layoutA; + + [fixture.layoutSpecBlocks setObject:fixture1and3NodeALayoutSpecBlock forKey:nodeA]; + return fixture; +} + +/** + * Fixture 2: A simple transition away from fixture 1. + * + * [A: HorizStack([B, C, E])]. B is (1x1), C is (4x1), E is (1x1) + * + * From fixture 1: + * B survives with same layout + * C survives with new layout + * D is removed + * E joins with first layout + */ +- (ASTLayoutFixture *)createFixture2 +{ + auto fixture = [[ASTLayoutFixture alloc] init]; + + // nodeB + [fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB]; + auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{1,1} position:{0,0} sublayouts:nil]; + + // nodeC + [fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeC]; + auto layoutC = [ASLayout layoutWithLayoutElement:nodeC size:{4,1} position:{3,0} sublayouts:nil]; + + // nodeE + [fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeE]; + auto layoutE = [ASLayout layoutWithLayoutElement:nodeE size:{1,1} position:{9,0} sublayouts:nil]; + + [fixture addSizeRange:{{10, 1}, {10, 1}} forNode:nodeA]; + auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{10,1} position:ASPointNull sublayouts:@[ layoutB, layoutC, layoutE ]]; + fixture.layout = layoutA; + + ASLayoutSpecBlock specBlockA = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { + return [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:0 justifyContent:ASStackLayoutJustifyContentSpaceBetween alignItems:ASStackLayoutAlignItemsStart children:@[ nodeB, nodeC, nodeE ]]; + }; + [fixture.layoutSpecBlocks setObject:specBlockA forKey:nodeA]; + return fixture; +} + +/** + * Fixture 3: Multipass stack layout + * + * [A: HorizStack([B, C, D])]. B is (7x1), C is (2x1), D is (1x1) + * + * nodeB (which has flexShrink=1) will return 8x1 for its size during the first + * stack pass, and it'll be subject to a second pass where it returns 7x1. + * + */ +- (ASTLayoutFixture *)createFixture3 +{ + auto fixture = [[ASTLayoutFixture alloc] init]; + + // nodeB wants 8,1 but it will settle for 7,1 + [fixture setReturnedSize:{8,1} forNode:nodeB]; + [fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB]; + [fixture addSizeRange:{{7, 0}, {7, 1}} forNode:nodeB]; + auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{7,1} position:{0,0} sublayouts:nil]; + + // nodeC + [fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeC]; + auto layoutC = [ASLayout layoutWithLayoutElement:nodeC size:{2,1} position:{7,0} sublayouts:nil]; + + // nodeD + [fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeD]; + auto layoutD = [ASLayout layoutWithLayoutElement:nodeD size:{1,1} position:{9,0} sublayouts:nil]; + + [fixture addSizeRange:{{10, 1}, {10, 1}} forNode:nodeA]; + auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{10,1} position:ASPointNull sublayouts:@[ layoutB, layoutC, layoutD ]]; + fixture.layout = layoutA; + + [fixture.layoutSpecBlocks setObject:fixture1and3NodeALayoutSpecBlock forKey:nodeA]; + return fixture; +} + +/** + * Fixture 4: A different simple transition away from fixture 1. + * + * [A: HorizStack([B, D, E])]. B is (1x1), D is (2x1), E is (1x1) + * + * From fixture 1: + * B survives with same layout + * C is removed + * D survives with new layout + * E joins with first layout + * + * From fixture 2: + * B survives with same layout + * C is removed + * D joins with first layout + * E survives with same layout + */ +- (ASTLayoutFixture *)createFixture4 +{ + auto fixture = [[ASTLayoutFixture alloc] init]; + + // nodeB + [fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeB]; + auto layoutB = [ASLayout layoutWithLayoutElement:nodeB size:{1,1} position:{0,0} sublayouts:nil]; + + // nodeD + [fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeD]; + auto layoutD = [ASLayout layoutWithLayoutElement:nodeD size:{2,1} position:{4,0} sublayouts:nil]; + + // nodeE + [fixture addSizeRange:{{0, 0}, {INFINITY, 1}} forNode:nodeE]; + auto layoutE = [ASLayout layoutWithLayoutElement:nodeE size:{1,1} position:{9,0} sublayouts:nil]; + + [fixture addSizeRange:{{10, 1}, {10, 1}} forNode:nodeA]; + auto layoutA = [ASLayout layoutWithLayoutElement:nodeA size:{10,1} position:ASPointNull sublayouts:@[ layoutB, layoutD, layoutE ]]; + fixture.layout = layoutA; + + ASLayoutSpecBlock specBlockA = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { + return [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal spacing:0 justifyContent:ASStackLayoutJustifyContentSpaceBetween alignItems:ASStackLayoutAlignItemsStart children:@[ nodeB, nodeD, nodeE ]]; + }; + [fixture.layoutSpecBlocks setObject:specBlockA forKey:nodeA]; + return fixture; +} + +- (void)runFirstLayoutPassWithFixture:(ASTLayoutFixture *)fixture +{ + [fixture apply]; + for (ASLayoutTestNode *node in fixture.allNodes) { + [fixture withSizeRangesForNode:node block:^(ASSizeRange sizeRange) { + OCMExpect([node.mock calculateLayoutThatFits:sizeRange]).onMainThread(); + }]; + + if (!stubbedCalculatedLayoutDidChange) { + OCMExpect([node.mock calculatedLayoutDidChange]).onMainThread(); + } + } + + // Trigger CA layout pass. + [window layoutIfNeeded]; + + // Make sure it went through. + [self verifyFixture:fixture]; +} + +@end diff --git a/Tests/ASLayoutTestNode.h b/Tests/ASLayoutTestNode.h new file mode 100644 index 000000000..66fafee14 --- /dev/null +++ b/Tests/ASLayoutTestNode.h @@ -0,0 +1,42 @@ +// +// ASLayoutTestNode.h +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import + +@interface ASLayoutTestNode : ASDisplayNode + +/** + * Mocking ASDisplayNodes directly isn't very safe because when you pump mock objects + * into the guts of the framework, bad things happen e.g. direct-ivar-access on mock + * objects will return garbage data. + * + * Instead we create a strict mock for each node, and forward a selected set of calls to it. + */ +@property (nonatomic, strong, readonly) id mock; + +/** + * The size that this node will return in calculateLayoutThatFits (if it doesn't have a layoutSpecBlock). + * + * Changing this value will call -setNeedsLayout on the node. + */ +@property (nonatomic) CGSize testSize; + +/** + * Generate a layout based on the frame of this node and its subtree. + * + * The root layout will be unpositioned. This is so that the returned layout can be directly + * compared to `calculatedLayout` + */ +- (ASLayout *)currentLayoutBasedOnFrames; + +@end diff --git a/Tests/ASLayoutTestNode.mm b/Tests/ASLayoutTestNode.mm new file mode 100644 index 000000000..3a112422a --- /dev/null +++ b/Tests/ASLayoutTestNode.mm @@ -0,0 +1,92 @@ +// +// ASLayoutTestNode.mm +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import "ASLayoutTestNode.h" +#import +#import "OCMockObject+ASAdditions.h" + +@implementation ASLayoutTestNode + +- (instancetype)init +{ + if (self = [super init]) { + _mock = OCMStrictClassMock([ASDisplayNode class]); + + // If errors occur (e.g. unexpected method) we need to quickly figure out + // which node is at fault, so we inject the node name into the mock instance + // description. + __weak __typeof(self) weakSelf = self; + [_mock setModifyDescriptionBlock:^(id mock, NSString *baseDescription){ + return [NSString stringWithFormat:@"Mock(%@)", weakSelf.description]; + }]; + } + return self; +} + +- (ASLayout *)currentLayoutBasedOnFrames +{ + return [self _currentLayoutBasedOnFramesForRootNode:YES]; +} + +- (ASLayout *)_currentLayoutBasedOnFramesForRootNode:(BOOL)isRootNode +{ + auto sublayouts = [NSMutableArray array]; + for (ASLayoutTestNode *subnode in self.subnodes) { + [sublayouts addObject:[subnode _currentLayoutBasedOnFramesForRootNode:NO]]; + } + CGPoint rootPosition = isRootNode ? ASPointNull : self.frame.origin; + return [ASLayout layoutWithLayoutElement:self size:self.frame.size position:rootPosition sublayouts:sublayouts]; +} + +- (void)setTestSize:(CGSize)testSize +{ + if (!CGSizeEqualToSize(testSize, _testSize)) { + _testSize = testSize; + [self setNeedsLayout]; + } +} + +- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize +{ + [_mock calculateLayoutThatFits:constrainedSize]; + + // If we have a layout spec block, or no test size, return super. + if (self.layoutSpecBlock || CGSizeEqualToSize(self.testSize, CGSizeZero)) { + return [super calculateLayoutThatFits:constrainedSize]; + } else { + // Interestingly, the infra will auto-clamp sizes from calculateSizeThatFits, but not from calculateLayoutThatFits. + auto size = ASSizeRangeClamp(constrainedSize, self.testSize); + return [ASLayout layoutWithLayoutElement:self size:size]; + } +} + +#pragma mark - Forwarding to mock + +- (void)calculatedLayoutDidChange +{ + [_mock calculatedLayoutDidChange]; + [super calculatedLayoutDidChange]; +} + +- (void)didCompleteLayoutTransition:(id)context +{ + [_mock didCompleteLayoutTransition:context]; + [super didCompleteLayoutTransition:context]; +} + +- (void)animateLayoutTransition:(id)context +{ + [_mock animateLayoutTransition:context]; + [super animateLayoutTransition:context]; +} + +@end diff --git a/Tests/ASTLayoutFixture.h b/Tests/ASTLayoutFixture.h new file mode 100644 index 000000000..ef590220a --- /dev/null +++ b/Tests/ASTLayoutFixture.h @@ -0,0 +1,61 @@ +// +// ASTLayoutFixture.h +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import +#import "ASTestCase.h" +#import "ASLayoutTestNode.h" + +NS_ASSUME_NONNULL_BEGIN + +AS_SUBCLASSING_RESTRICTED +@interface ASTLayoutFixture : NSObject + +/// The correct layout. The root should be unpositioned (same as -calculatedLayout). +@property (nonatomic, strong, nullable) ASLayout *layout; + +/// The layoutSpecBlocks for non-leaf nodes. +@property (nonatomic, strong, readonly) NSMapTable *layoutSpecBlocks; + +@property (nonatomic, strong, readonly) ASLayoutTestNode *rootNode; + +@property (nonatomic, strong, readonly) NSSet *allNodes; + +/// Get the (correct) layout for the specified node. +- (ASLayout *)layoutForNode:(ASLayoutTestNode *)node; + +/// Add this to the list of expected size ranges for the given node. +- (void)addSizeRange:(ASSizeRange)sizeRange forNode:(ASLayoutTestNode *)node; + +/// If you have a node that wants a size different than it gets, set it here. +/// For any leaf nodes that you don't call this on, the node will return the correct size +/// based on the fixture's layout. This is useful for triggering multipass stack layout. +- (void)setReturnedSize:(CGSize)size forNode:(ASLayoutTestNode *)node; + +/// Get the first expected size range for the node. +- (ASSizeRange)firstSizeRangeForNode:(ASLayoutTestNode *)node; + +/// Enumerate all the size ranges for the node. +- (void)withSizeRangesForNode:(ASLayoutTestNode *)node block:(void (^)(ASSizeRange sizeRange))block; + +/// Configure the nodes for this fixture. Set testSize on leaf nodes, layoutSpecBlock on container nodes. +- (void)apply; + +@end + +@interface ASLayout (TestHelpers) + +@property (nonatomic, readonly) NSArray *allNodes; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Tests/ASTLayoutFixture.mm b/Tests/ASTLayoutFixture.mm new file mode 100644 index 000000000..bdddbe5bf --- /dev/null +++ b/Tests/ASTLayoutFixture.mm @@ -0,0 +1,134 @@ +// +// ASTLayoutFixture.mm +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import "ASTLayoutFixture.h" + +@interface ASTLayoutFixture () + +/// The size ranges against which nodes are expected to be measured. +@property (nonatomic, strong, readonly) NSMapTable *> *sizeRanges; + +/// The overridden returned sizes for nodes where you want to trigger multipass layout. +@property (nonatomic, strong, readonly) NSMapTable *returnedSizes; + +@end + +@implementation ASTLayoutFixture + +- (instancetype)init +{ + if (self = [super init]) { + _sizeRanges = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality valueOptions:NSMapTableStrongMemory]; + _layoutSpecBlocks = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality valueOptions:NSMapTableStrongMemory]; + _returnedSizes = [NSMapTable mapTableWithKeyOptions:NSMapTableObjectPointerPersonality valueOptions:NSMapTableStrongMemory]; + + } + return self; +} + +- (void)addSizeRange:(ASSizeRange)sizeRange forNode:(ASLayoutTestNode *)node +{ + auto ranges = [_sizeRanges objectForKey:node]; + if (ranges == nil) { + ranges = [NSMutableArray array]; + [_sizeRanges setObject:ranges forKey:node]; + } + [ranges addObject:[NSValue valueWithBytes:&sizeRange objCType:@encode(ASSizeRange)]]; +} + +- (void)setReturnedSize:(CGSize)size forNode:(ASLayoutTestNode *)node +{ + [_returnedSizes setObject:[NSValue valueWithCGSize:size] forKey:node]; +} + +- (ASSizeRange)firstSizeRangeForNode:(ASLayoutTestNode *)node +{ + auto val = [_sizeRanges objectForKey:node].firstObject; + ASSizeRange r; + [val getValue:&r]; + return r; +} + +- (void)withSizeRangesForNode:(ASLayoutTestNode *)node block:(void (^)(ASSizeRange))block +{ + for (NSValue *value in [_sizeRanges objectForKey:node]) { + ASSizeRange r; + [value getValue:&r]; + block(r); + } +} + +- (ASLayout *)layoutForNode:(ASLayoutTestNode *)node +{ + NSMutableArray *allLayouts = [NSMutableArray array]; + [ASTLayoutFixture collectAllLayoutsFromLayout:self.layout array:allLayouts]; + for (ASLayout *layout in allLayouts) { + if (layout.layoutElement == node) { + return layout; + } + } + return nil; +} + +/// A very dumb tree iteration approach. NSEnumerator or something would be way better. ++ (void)collectAllLayoutsFromLayout:(ASLayout *)layout array:(NSMutableArray *)array +{ + [array addObject:layout]; + for (ASLayout *sublayout in layout.sublayouts) { + [self collectAllLayoutsFromLayout:sublayout array:array]; + } +} + +- (ASLayoutTestNode *)rootNode +{ + return (ASLayoutTestNode *)self.layout.layoutElement; +} + +- (NSSet *)allNodes +{ + auto allLayouts = [NSMutableArray array]; + [ASTLayoutFixture collectAllLayoutsFromLayout:self.layout array:allLayouts]; + return [NSSet setWithArray:[allLayouts valueForKey:@"layoutElement"]]; +} + +- (void)apply +{ + // Update layoutSpecBlock for parent nodes, set automatic subnode management + for (ASDisplayNode *node in _layoutSpecBlocks) { + auto block = [_layoutSpecBlocks objectForKey:node]; + if (node.layoutSpecBlock != block) { + node.automaticallyManagesSubnodes = YES; + node.layoutSpecBlock = block; + [node setNeedsLayout]; + } + } + + [self setTestSizesOfLeafNodesInLayout:self.layout]; +} + +/// Go through the given layout, and for all the leaf nodes, set their preferredSize +/// to the layout size if needed, then call -setNeedsLayout +- (void)setTestSizesOfLeafNodesInLayout:(ASLayout *)layout +{ + auto node = (ASLayoutTestNode *)layout.layoutElement; + if (layout.sublayouts.count == 0) { + auto override = [self.returnedSizes objectForKey:node]; + node.testSize = override ? override.CGSizeValue : layout.size; + } else { + node.testSize = CGSizeZero; + for (ASLayout *sublayout in layout.sublayouts) { + [self setTestSizesOfLeafNodesInLayout:sublayout]; + } + } +} + +@end diff --git a/Tests/Common/ASTestCase.h b/Tests/Common/ASTestCase.h index 4868f64d0..54231b64e 100644 --- a/Tests/Common/ASTestCase.h +++ b/Tests/Common/ASTestCase.h @@ -12,6 +12,11 @@ #import +// Not strictly necessary, but convenient +#import +#import +#import "OCMockObject+ASAdditions.h" + NS_ASSUME_NONNULL_BEGIN @interface ASTestCase : XCTestCase diff --git a/Tests/Common/OCMockObject+ASAdditions.h b/Tests/Common/OCMockObject+ASAdditions.h index a8e700491..f8617411b 100644 --- a/Tests/Common/OCMockObject+ASAdditions.h +++ b/Tests/Common/OCMockObject+ASAdditions.h @@ -10,7 +10,7 @@ // http://www.apache.org/licenses/LICENSE-2.0 // -#import +#import @interface OCMockObject (ASAdditions) @@ -30,4 +30,31 @@ */ - (void)addImplementedOptionalProtocolMethods:(SEL)aSelector, ... NS_REQUIRES_NIL_TERMINATION; +/// An optional block to modify description text. Only used in OCClassMockObject currently. +@property (atomic) NSString *(^modifyDescriptionBlock)(OCMockObject *object, NSString *baseDescription); + +@end + +/** + * Additional stub recorders useful in ASDK. + */ +@interface OCMStubRecorder (ASProperties) + +/** + * Add a debug-break side effect to this stub/expectation. + * + * You will usually need to jump to frame 12 "fr s 12" + */ +#define andDebugBreak() _andDebugBreak() +@property (nonatomic, readonly) OCMStubRecorder *(^ _andDebugBreak)(void); + +#define ignoringNonObjectArgs() _ignoringNonObjectArgs() +@property (nonatomic, readonly) OCMStubRecorder *(^ _ignoringNonObjectArgs)(void); + +#define onMainThread() _onMainThread() +@property (nonatomic, readonly) OCMStubRecorder *(^ _onMainThread)(void); + +#define offMainThread() _offMainThread() +@property (nonatomic, readonly) OCMStubRecorder *(^ _offMainThread)(void); + @end diff --git a/Tests/Common/OCMockObject+ASAdditions.m b/Tests/Common/OCMockObject+ASAdditions.m index 86dcdbf9d..50fabd5eb 100644 --- a/Tests/Common/OCMockObject+ASAdditions.m +++ b/Tests/Common/OCMockObject+ASAdditions.m @@ -15,6 +15,8 @@ #import #import #import "ASTestCase.h" +#import +#import "debugbreak.h" @interface ASTestCase (OCMockObjectRegistering) @@ -32,9 +34,18 @@ + (void)load method_exchangeImplementations(orig, new); // init <-> swizzled_init - Method origInit = class_getInstanceMethod([OCMockObject class], @selector(init)); - Method newInit = class_getInstanceMethod(self, @selector(swizzled_init)); - method_exchangeImplementations(origInit, newInit); + { + Method origInit = class_getInstanceMethod([OCMockObject class], @selector(init)); + Method newInit = class_getInstanceMethod(self, @selector(swizzled_init)); + method_exchangeImplementations(origInit, newInit); + } + + // (class mock) description <-> swizzled_classMockDescription + { + Method orig = class_getInstanceMethod(OCMockObject.classMockObjectClass, @selector(description)); + Method new = class_getInstanceMethod(self, @selector(swizzled_classMockDescription)); + method_exchangeImplementations(orig, new); + } } /// Since OCProtocolMockObject is private, use this method to get the class. @@ -49,6 +60,18 @@ + (Class)protocolMockObjectClass return c; } +/// Since OCClassMockObject is private, use this method to get the class. ++ (Class)classMockObjectClass +{ + static Class c; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + c = NSClassFromString(@"OCClassMockObject"); + NSAssert(c != Nil, nil); + }); + return c; +} + /// Whether the user has opted-in to specify which optional methods are implemented for this object. - (BOOL)hasSpecifiedOptionalProtocolMethods { @@ -142,4 +165,77 @@ - (instancetype)swizzled_init return self; } +- (NSString *)swizzled_classMockDescription +{ + NSString *orig = [self swizzled_classMockDescription]; + __auto_type block = self.modifyDescriptionBlock; + if (block) { + return block(self, orig); + } + return orig; +} + +- (void)setModifyDescriptionBlock:(NSString *(^)(OCMockObject *, NSString *))modifyDescriptionBlock +{ + objc_setAssociatedObject(self, @selector(modifyDescriptionBlock), modifyDescriptionBlock, OBJC_ASSOCIATION_COPY); +} + +- (NSString *(^)(OCMockObject *, NSString *))modifyDescriptionBlock +{ + return objc_getAssociatedObject(self, _cmd); +} + +@end + +@implementation OCMStubRecorder (ASProperties) + +@dynamic _ignoringNonObjectArgs; + +- (OCMStubRecorder *(^)(void))_ignoringNonObjectArgs +{ + id (^theBlock)(void) = ^ () + { + return [self ignoringNonObjectArgs]; + }; + return theBlock; +} + +@dynamic _onMainThread; + +- (OCMStubRecorder *(^)(void))_onMainThread +{ + id (^theBlock)(void) = ^ () + { + return [self andDo:^(NSInvocation *invocation) { + ASDisplayNodeAssertMainThread(); + }]; + }; + return theBlock; +} + +@dynamic _offMainThread; + +- (OCMStubRecorder *(^)(void))_offMainThread +{ + id (^theBlock)(void) = ^ () + { + return [self andDo:^(NSInvocation *invocation) { + ASDisplayNodeAssertNotMainThread(); + }]; + }; + return theBlock; +} + +@dynamic _andDebugBreak; + +- (OCMStubRecorder *(^)(void))_andDebugBreak +{ + id (^theBlock)(void) = ^ () + { + return [self andDo:^(NSInvocation *invocation) { + debug_break(); + }]; + }; + return theBlock; +} @end diff --git a/Tests/Common/debugbreak.h b/Tests/Common/debugbreak.h new file mode 100644 index 000000000..5405e40de --- /dev/null +++ b/Tests/Common/debugbreak.h @@ -0,0 +1,146 @@ +// +// debugbreak.h +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +/* Copyright (c) 2011-2015, Scott Tsai + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DEBUG_BREAK_H +#define DEBUG_BREAK_H + +#ifdef _MSC_VER + +#define debug_break __debugbreak + +#else + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + /* gcc optimizers consider code after __builtin_trap() dead. + * Making __builtin_trap() unsuitable for breaking into the debugger */ + DEBUG_BREAK_PREFER_BUILTIN_TRAP_TO_SIGTRAP = 0, +}; + +#if defined(__i386__) || defined(__x86_64__) +enum { HAVE_TRAP_INSTRUCTION = 1, }; +__attribute__((gnu_inline, always_inline)) +__inline__ static void trap_instruction(void) +{ + __asm__ volatile("int $0x03"); +} +#elif defined(__thumb__) +enum { HAVE_TRAP_INSTRUCTION = 1, }; +/* FIXME: handle __THUMB_INTERWORK__ */ +__attribute__((gnu_inline, always_inline)) +__inline__ static void trap_instruction(void) +{ + /* See 'arm-linux-tdep.c' in GDB source. + * Both instruction sequences below work. */ +#if 1 + /* 'eabi_linux_thumb_le_breakpoint' */ + __asm__ volatile(".inst 0xde01"); +#else + /* 'eabi_linux_thumb2_le_breakpoint' */ + __asm__ volatile(".inst.w 0xf7f0a000"); +#endif + + /* Known problem: + * After a breakpoint hit, can't stepi, step, or continue in GDB. + * 'step' stuck on the same instruction. + * + * Workaround: a new GDB command, + * 'debugbreak-step' is defined in debugbreak-gdb.py + * that does: + * (gdb) set $instruction_len = 2 + * (gdb) tbreak *($pc + $instruction_len) + * (gdb) jump *($pc + $instruction_len) + */ +} +#elif defined(__arm__) && !defined(__thumb__) +enum { HAVE_TRAP_INSTRUCTION = 1, }; +__attribute__((gnu_inline, always_inline)) +__inline__ static void trap_instruction(void) +{ + /* See 'arm-linux-tdep.c' in GDB source, + * 'eabi_linux_arm_le_breakpoint' */ + __asm__ volatile(".inst 0xe7f001f0"); + /* Has same known problem and workaround + * as Thumb mode */ +} +#elif defined(__aarch64__) +enum { HAVE_TRAP_INSTRUCTION = 1, }; +__attribute__((gnu_inline, always_inline)) +__inline__ static void trap_instruction(void) +{ + /* See 'aarch64-tdep.c' in GDB source, + * 'aarch64_default_breakpoint' */ + __asm__ volatile(".inst 0xd4200000"); +} +#else +enum { HAVE_TRAP_INSTRUCTION = 0, }; +#endif + +__attribute__((gnu_inline, always_inline)) +__inline__ static void debug_break(void) +{ + if (HAVE_TRAP_INSTRUCTION) { + trap_instruction(); + } else if (DEBUG_BREAK_PREFER_BUILTIN_TRAP_TO_SIGTRAP) { + /* raises SIGILL on Linux x86{,-64}, to continue in gdb: + * (gdb) handle SIGILL stop nopass + * */ + __builtin_trap(); + } else { + #ifdef _WIN32 + /* SIGTRAP available only on POSIX-compliant operating systems + * use builtin trap instead */ + __builtin_trap(); + #else + raise(SIGTRAP); + #endif + } +} + +#ifdef __cplusplus +} +#endif + +#endif + +#endif From c1f517a7eb9b0407b409995cfd63104da1412d16 Mon Sep 17 00:00:00 2001 From: Andrew Yates Date: Mon, 4 Dec 2017 10:41:00 -0800 Subject: [PATCH 038/133] Correct Synchronous Concurrency Talk Link (#698) Fixes #697. --- docs/_docs/synchronous-concurrency.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_docs/synchronous-concurrency.md b/docs/_docs/synchronous-concurrency.md index 68e9aa457..d4430b751 100755 --- a/docs/_docs/synchronous-concurrency.md +++ b/docs/_docs/synchronous-concurrency.md @@ -12,7 +12,7 @@ By setting this property to YES, the main thread will be blocked until display h Using this option does not eliminate all of the performance advantages of Texture. Normally, a given node has been preloading and is almost done when it reaches the screen, so the blocking time is very short. Even if the rangeTuningParameters are set to 0 this option outperforms UIKit. While the main thread is waiting, all subnode display executes concurrently, thus synchronous concurrency. -See the NSSpain 2015 talk video for a visual walkthrough of this behavior. +See the NSSpain 2015 talk video for a visual walkthrough of this behavior.
SwiftObjective-C From 0b6d41f8726c80653c36ea41bf6dd55d2a56efd3 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Mon, 4 Dec 2017 14:56:04 -0800 Subject: [PATCH 039/133] A couple performance tweaks for animated images #trivial (#634) * A couple performance tweaks for animated images * @nguyenhuy's comments * Avoid calling animatedImageData twice. Thanks @maicki. * Fix call to background deallocation * Good catch by @Adlai-Holler --- Source/ASImageNode+AnimatedImage.mm | 6 ++ Source/ASNetworkImageNode.mm | 104 ++++++++++++++++------------ 2 files changed, 64 insertions(+), 46 deletions(-) diff --git a/Source/ASImageNode+AnimatedImage.mm b/Source/ASImageNode+AnimatedImage.mm index 355ab472e..246ee49cc2 100644 --- a/Source/ASImageNode+AnimatedImage.mm +++ b/Source/ASImageNode+AnimatedImage.mm @@ -55,6 +55,7 @@ - (void)_locked_setAnimatedImage:(id )animatedImage } id previousAnimatedImage = _animatedImage; + _animatedImage = animatedImage; if (animatedImage != nil) { @@ -80,6 +81,11 @@ - (void)_locked_setAnimatedImage:(id )animatedImage } [self animatedImageSet:_animatedImage previousAnimatedImage:previousAnimatedImage]; + + // Animated image can take while to dealloc, do it off the main queue + if (previousAnimatedImage != nil) { + ASPerformBackgroundDeallocation(&previousAnimatedImage); + } } - (void)animatedImageSet:(id )newAnimatedImage previousAnimatedImage:(id )previousAnimatedImage diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index 0a7ee0fbd..6b4a40a89 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -713,55 +713,67 @@ - (void)_lazilyLoadImageIfNecessary } else { __weak __typeof__(self) weakSelf = self; auto finished = ^(id imageContainer, NSError *error, id downloadIdentifier, ASNetworkImageSource imageSource) { - - __typeof__(self) strongSelf = weakSelf; - if (strongSelf == nil) { - return; - } - - as_log_verbose(ASImageLoadingLog(), "Downloaded image for %@ img: %@ urls: %@", self, [imageContainer asdk_image], URLs); - - // Grab the lock for the rest of the block - ASDN::MutexLocker l(strongSelf->__instanceLock__); - - //Getting a result back for a different download identifier, download must not have been successfully canceled - if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { - return; - } + ASPerformBlockOnBackgroundThread(^{ + __typeof__(self) strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } - //No longer in preload range, no point in setting the results (they won't be cleared in exit preload range) - if (ASInterfaceStateIncludesPreload(self->_interfaceState) == NO) { - return; - } - - if (imageContainer != nil) { - [strongSelf _locked_setCurrentImageQuality:1.0]; - if ([imageContainer asdk_animatedImageData] && strongSelf->_downloaderFlags.downloaderImplementsAnimatedImage) { - id animatedImage = [strongSelf->_downloader animatedImageWithData:[imageContainer asdk_animatedImageData]]; - [strongSelf _locked_setAnimatedImage:animatedImage]; - } else { - [strongSelf _locked__setImage:[imageContainer asdk_image]]; + as_log_verbose(ASImageLoadingLog(), "Downloaded image for %@ img: %@ urls: %@", self, [imageContainer asdk_image], URLs); + + // Grab the lock for the rest of the block + ASDN::MutexLocker l(strongSelf->__instanceLock__); + + //Getting a result back for a different download identifier, download must not have been successfully canceled + if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { + return; } - strongSelf->_imageLoaded = YES; - } - - strongSelf->_downloadIdentifier = nil; - strongSelf->_cacheUUID = nil; - - if (imageContainer != nil) { - if (strongSelf->_delegateFlags.delegateDidLoadImageWithInfo) { - ASDN::MutexUnlocker u(strongSelf->__instanceLock__); - ASNetworkImageNodeDidLoadInfo info = {}; - info.imageSource = imageSource; - [delegate imageNode:strongSelf didLoadImage:strongSelf.image info:info]; - } else if (strongSelf->_delegateFlags.delegateDidLoadImage) { - ASDN::MutexUnlocker u(strongSelf->__instanceLock__); - [delegate imageNode:strongSelf didLoadImage:strongSelf.image]; + + //No longer in preload range, no point in setting the results (they won't be cleared in exit preload range) + if (ASInterfaceStateIncludesPreload(self->_interfaceState) == NO) { + return; } - } else if (error && strongSelf->_delegateFlags.delegateDidFailWithError) { - ASDN::MutexUnlocker u(strongSelf->__instanceLock__); - [delegate imageNode:strongSelf didFailWithError:error]; - } + + if (imageContainer != nil) { + [strongSelf _locked_setCurrentImageQuality:1.0]; + NSData *animatedImageData = [imageContainer asdk_animatedImageData]; + if (animatedImageData && strongSelf->_downloaderFlags.downloaderImplementsAnimatedImage) { + id animatedImage = [strongSelf->_downloader animatedImageWithData:animatedImageData]; + [strongSelf _locked_setAnimatedImage:animatedImage]; + } else { + [strongSelf _locked__setImage:[imageContainer asdk_image]]; + } + strongSelf->_imageLoaded = YES; + } + + strongSelf->_downloadIdentifier = nil; + strongSelf->_cacheUUID = nil; + + ASPerformBlockOnMainThread(^{ + __typeof__(self) strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } + + // Grab the lock for the rest of the block + ASDN::MutexLocker l(strongSelf->__instanceLock__); + + if (imageContainer != nil) { + if (strongSelf->_delegateFlags.delegateDidLoadImageWithInfo) { + ASDN::MutexUnlocker u(strongSelf->__instanceLock__); + ASNetworkImageNodeDidLoadInfo info = {}; + info.imageSource = imageSource; + [delegate imageNode:strongSelf didLoadImage:strongSelf.image info:info]; + } else if (strongSelf->_delegateFlags.delegateDidLoadImage) { + ASDN::MutexUnlocker u(strongSelf->__instanceLock__); + [delegate imageNode:strongSelf didLoadImage:strongSelf.image]; + } + } else if (error && strongSelf->_delegateFlags.delegateDidFailWithError) { + ASDN::MutexUnlocker u(strongSelf->__instanceLock__); + [delegate imageNode:strongSelf didFailWithError:error]; + } + }); + }); }; // As the _cache and _downloader is only set once in the intializer we don't have to use a From 008a1ce2084717f627e758988b565e2008c28452 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Tue, 5 Dec 2017 13:55:19 -0800 Subject: [PATCH 040/133] Revert Adds support for specifying a quality indexed array of URLs (#699) * Revert "Adds support for specifying a quality indexed array of URLs (#557)" This reverts commit 3c77d4a5da44c46c7b80b2a627c95389b7d6352d. * Add CHANGELOG entry --- CHANGELOG.md | 1 + Source/ASNetworkImageNode.h | 17 +- Source/ASNetworkImageNode.mm | 172 ++++++++------------ Source/Details/ASImageProtocols.h | 30 +--- Source/Details/ASPINRemoteImageDownloader.m | 39 +---- 5 files changed, 83 insertions(+), 176 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 430bc73e7..ab351bffe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASNetworkImageNode] Deprecates .URLs in favor of .URL [Garrett Moon](https://github.com/garrettmoon) [#699](https://github.com/TextureGroup/Texture/pull/699) - [iOS11] Update project settings and fix errors [Eke](https://github.com/Eke) [#676](https://github.com/TextureGroup/Texture/pull/676) - [ASCollectionView] Improve performance and behavior of rotation / bounds changes. [Scott Goodson](https://github.com/appleguy) [#431](https://github.com/TextureGroup/Texture/pull/431) - [ASCollectionView] Improve index space translation of Flow Layout Delegate methods. [Scott Goodson](https://github.com/appleguy) diff --git a/Source/ASNetworkImageNode.h b/Source/ASNetworkImageNode.h index 455806cd2..c4c83214b 100644 --- a/Source/ASNetworkImageNode.h +++ b/Source/ASNetworkImageNode.h @@ -83,13 +83,16 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, strong, readwrite) NSURL *URL; /** - * An array of URLs of increasing cost to download. - * - * @discussion By setting an array of URLs, the image property of this node will be managed internally. This means previously - * directly set images to the image property will be cleared out and replaced by the placeholder () image - * while loading and the final image after the new image data was downloaded and processed. - */ -@property (nullable, nonatomic, strong, readwrite) NSArray *URLs; + * An array of URLs of increasing cost to download. + * + * @discussion By setting an array of URLs, the image property of this node will be managed internally. This means previously + * directly set images to the image property will be cleared out and replaced by the placeholder () image + * while loading and the final image after the new image data was downloaded and processed. + * + * @deprecated This API has been removed for now due to the increased complexity to the class that it brought. + * Please use .URL instead. + */ +@property (nullable, nonatomic, strong, readwrite) NSArray *URLs ASDISPLAYNODE_DEPRECATED_MSG("Please use URL instead."); /** * Download and display a new image. diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index 6b4a40a89..d844b6646 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -37,7 +37,7 @@ @interface ASNetworkImageNode () // Only access any of these with __instanceLock__. __weak id _delegate; - NSArray *_URLs; + NSURL *_URL; UIImage *_defaultImage; NSUUID *_cacheUUID; @@ -67,7 +67,6 @@ @interface ASNetworkImageNode () unsigned int downloaderImplementsSetPriority:1; unsigned int downloaderImplementsAnimatedImage:1; unsigned int downloaderImplementsCancelWithResume:1; - unsigned int downloaderImplementsDownloadURLs:1; } _downloaderFlags; // Immutable and set on init only. We don't need to lock in this case. @@ -75,7 +74,6 @@ @interface ASNetworkImageNode () struct { unsigned int cacheSupportsClearing:1; unsigned int cacheSupportsSynchronousFetch:1; - unsigned int cacheSupportsCachedURLs:1; } _cacheFlags; } @@ -97,11 +95,9 @@ - (instancetype)initWithCache:(id)cache downloader:(id *)URLs { - if (URL) { - [self setURLs:@[URL] resetToDefault:reset]; - } else { - [self setURLs:nil resetToDefault:reset]; - } + [self setURL:[URLs firstObject]]; } -- (NSURL *)URL +// Deprecated +- (NSArray *)URLs { - return [self.URLs lastObject]; + return @[self.URL]; } -- (void)setURLs:(NSArray *)URLs +- (void)setURL:(NSURL *)URL { - [self setURLs:URLs resetToDefault:YES]; + [self setURL:URL resetToDefault:YES]; } -- (void)setURLs:(NSArray *)URLs resetToDefault:(BOOL)reset +- (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset { { ASDN::MutexLocker l(__instanceLock__); - if (ASObjectIsEqual(URLs, _URLs)) { + if (ASObjectIsEqual(URL, _URL)) { return; } @@ -201,25 +186,25 @@ - (void)setURLs:(NSArray *)URLs resetToDefault:(BOOL)reset _imageWasSetExternally = NO; [self _locked_cancelImageDownloadWithResumePossibility:NO]; - + _imageLoaded = NO; - _URLs = URLs; + _URL = URL; - BOOL hasURL = (_URLs.count == 0); + BOOL hasURL = (_URL == nil); if (reset || hasURL) { [self _locked_setCurrentImageQuality:(hasURL ? 0.0 : 1.0)]; [self _locked__setImage:_defaultImage]; } } - + [self setNeedsPreload]; } -- (NSArray *)URLs +- (NSURL *)URL { ASDN::MutexLocker l(__instanceLock__); - return _URLs; + return _URL; } - (void)setDefaultImage:(UIImage *)defaultImage @@ -238,7 +223,7 @@ - (void)_locked_setDefaultImage:(UIImage *)defaultImage _defaultImage = defaultImage; if (!_imageLoaded) { - [self _locked_setCurrentImageQuality:((_URLs.count == 0) ? 0.0 : 1.0)]; + [self _locked_setCurrentImageQuality:((_URL == nil) ? 0.0 : 1.0)]; [self _locked__setImage:defaultImage]; } @@ -337,7 +322,7 @@ - (BOOL)shouldRenderProgressImages - (BOOL)placeholderShouldPersist { ASDN::MutexLocker l(__instanceLock__); - return (self.image == nil && self.animatedImage == nil && _URLs.count != 0); + return (self.image == nil && self.animatedImage == nil && _URL != nil); } /* displayWillStartAsynchronously: in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary @@ -349,25 +334,22 @@ - (void)displayWillStartAsynchronously:(BOOL)asynchronously if (asynchronously == NO && _cacheFlags.cacheSupportsSynchronousFetch) { ASDN::MutexLocker l(__instanceLock__); - if (_imageLoaded == NO && _URLs.count > 0 && _downloadIdentifier == nil) { - for (NSURL *url in [_URLs reverseObjectEnumerator]) { - UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:url] asdk_image]; - if (result) { - [self _locked_setCurrentImageQuality:1.0]; - [self _locked__setImage:result]; - _imageLoaded = YES; - - // Call out to the delegate. - if (_delegateFlags.delegateDidLoadImageWithInfo) { - ASDN::MutexUnlocker l(__instanceLock__); - ASNetworkImageNodeDidLoadInfo info = {}; - info.imageSource = ASNetworkImageSourceSynchronousCache; - [_delegate imageNode:self didLoadImage:result info:info]; - } else if (_delegateFlags.delegateDidLoadImage) { - ASDN::MutexUnlocker l(__instanceLock__); - [_delegate imageNode:self didLoadImage:result]; - } - break; + if (_imageLoaded == NO && _URL && _downloadIdentifier == nil) { + UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:_URL] asdk_image]; + if (result) { + [self _locked_setCurrentImageQuality:1.0]; + [self _locked__setImage:result]; + _imageLoaded = YES; + + // Call out to the delegate. + if (_delegateFlags.delegateDidLoadImageWithInfo) { + ASDN::MutexUnlocker l(__instanceLock__); + ASNetworkImageNodeDidLoadInfo info = {}; + info.imageSource = ASNetworkImageSourceSynchronousCache; + [_delegate imageNode:self didLoadImage:result info:info]; + } else if (_delegateFlags.delegateDidLoadImage) { + ASDN::MutexUnlocker l(__instanceLock__); + [_delegate imageNode:self didLoadImage:result]; } } } @@ -538,11 +520,9 @@ - (void)_locked_cancelDownloadAndClearImageWithResumePossibility:(BOOL)storeResu _imageLoaded = NO; if (_cacheFlags.cacheSupportsClearing) { - if (_URLs.count != 0) { - as_log_verbose(ASImageLoadingLog(), "Clearing cached image for %@ url: %@", self, _URLs); - for (NSURL *url in _URLs) { - [_cache clearFetchedImageFromCacheWithURL:url]; - } + if (_URL != nil) { + as_log_verbose(ASImageLoadingLog(), "Clearing cached image for %@ url: %@", self, _URL); + [_cache clearFetchedImageFromCacheWithURL:_URL]; } } } @@ -576,7 +556,7 @@ - (void)_locked_cancelImageDownloadWithResumePossibility:(BOOL)storeResume - (void)_downloadImageWithCompletion:(void (^)(id imageContainer, NSError*, id downloadIdentifier))finished { ASPerformBlockOnBackgroundThread(^{ - NSArray *urls; + NSURL *url; id downloadIdentifier; BOOL cancelAndReattempt = NO; @@ -585,34 +565,23 @@ - (void)_downloadImageWithCompletion:(void (^)(id ima // it and try again. { ASDN::MutexLocker l(__instanceLock__); - urls = _URLs; + url = _URL; } - if (_downloaderFlags.downloaderImplementsDownloadURLs) { - downloadIdentifier = [_downloader downloadImageWithURLs:urls - callbackQueue:dispatch_get_main_queue() - downloadProgress:NULL - completion:^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) { - if (finished != NULL) { - finished(imageContainer, error, downloadIdentifier); - } - }]; - } else { - downloadIdentifier = [_downloader downloadImageWithURL:[urls lastObject] - callbackQueue:dispatch_get_main_queue() - downloadProgress:NULL - completion:^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) { - if (finished != NULL) { - finished(imageContainer, error, downloadIdentifier); - } - }]; - } - + + downloadIdentifier = [_downloader downloadImageWithURL:url + callbackQueue:dispatch_get_main_queue() + downloadProgress:NULL + completion:^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) { + if (finished != NULL) { + finished(imageContainer, error, downloadIdentifier); + } + }]; as_log_verbose(ASImageLoadingLog(), "Downloading image for %@ url: %@", self, url); { ASDN::MutexLocker l(__instanceLock__); - if (ASObjectIsEqual(_URLs, urls)) { + if (ASObjectIsEqual(_URL, url)) { // The download we kicked off is correct, no need to do any more work. _downloadIdentifier = downloadIdentifier; } else { @@ -641,36 +610,34 @@ - (void)_lazilyLoadImageIfNecessary __weak id delegate = _delegate; BOOL delegateDidStartFetchingData = _delegateFlags.delegateDidStartFetchingData; BOOL isImageLoaded = _imageLoaded; - NSArray *URLs = _URLs; + NSURL *URL = _URL; id currentDownloadIdentifier = _downloadIdentifier; __instanceLock__.unlock(); - if (!isImageLoaded && URLs.count > 0 && currentDownloadIdentifier == nil) { + if (!isImageLoaded && URL != nil && currentDownloadIdentifier == nil) { if (delegateDidStartFetchingData) { [delegate imageNodeDidStartFetchingData:self]; } - // We only support file URLs if there is one URL currently - if (URLs.count == 1 && [URLs lastObject].isFileURL) { + if (URL.isFileURL) { dispatch_async(dispatch_get_main_queue(), ^{ ASDN::MutexLocker l(__instanceLock__); // Bail out if not the same URL anymore - if (!ASObjectIsEqual(URLs, _URLs)) { + if (!ASObjectIsEqual(URL, _URL)) { return; } - NSURL *URL = [URLs lastObject]; if (_shouldCacheImage) { - [self _locked__setImage:[UIImage imageNamed:URL.path.lastPathComponent]]; + [self _locked__setImage:[UIImage imageNamed:_URL.path.lastPathComponent]]; } else { // First try to load the path directly, for efficiency assuming a developer who // doesn't want caching is trying to be as minimal as possible. - UIImage *nonAnimatedImage = [UIImage imageWithContentsOfFile:URL.path]; + UIImage *nonAnimatedImage = [UIImage imageWithContentsOfFile:_URL.path]; if (nonAnimatedImage == nil) { // If we couldn't find it, execute an -imageNamed:-like search so we can find resources even if the // extension is not provided in the path. This allows the same path to work regardless of shouldCacheImage. - NSString *filename = [[NSBundle mainBundle] pathForResource:URL.path.lastPathComponent ofType:nil]; + NSString *filename = [[NSBundle mainBundle] pathForResource:_URL.path.lastPathComponent ofType:nil]; if (filename != nil) { nonAnimatedImage = [UIImage imageWithContentsOfFile:filename]; } @@ -679,7 +646,7 @@ - (void)_lazilyLoadImageIfNecessary // If the file may be an animated gif and then created an animated image. id animatedImage = nil; if (_downloaderFlags.downloaderImplementsAnimatedImage) { - NSData *data = [NSData dataWithContentsOfURL:URL]; + NSData *data = [NSData dataWithContentsOfURL:_URL]; if (data != nil) { animatedImage = [_downloader animatedImageWithData:data]; @@ -719,7 +686,7 @@ - (void)_lazilyLoadImageIfNecessary return; } - as_log_verbose(ASImageLoadingLog(), "Downloaded image for %@ img: %@ urls: %@", self, [imageContainer asdk_image], URLs); + as_log_verbose(ASImageLoadingLog(), "Downloaded image for %@ img: %@ url: %@", self, [imageContainer asdk_image], URL); // Grab the lock for the rest of the block ASDN::MutexLocker l(strongSelf->__instanceLock__); @@ -784,7 +751,7 @@ - (void)_lazilyLoadImageIfNecessary _cacheUUID = cacheUUID; __instanceLock__.unlock(); - as_log_verbose(ASImageLoadingLog(), "Decaching image for %@ urls: %@", self, URLs); + as_log_verbose(ASImageLoadingLog(), "Decaching image for %@ url: %@", self, URL); ASImageCacherCompletion completion = ^(id imageContainer) { // If the cache UUID changed, that means this request was cancelled. @@ -801,20 +768,13 @@ - (void)_lazilyLoadImageIfNecessary finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload); }]; } else { - as_log_verbose(ASImageLoadingLog(), "Decached image for %@ img: %@ urls: %@", self, [imageContainer asdk_image], URLs); + as_log_verbose(ASImageLoadingLog(), "Decached image for %@ img: %@ url: %@", self, [imageContainer asdk_image], URL); finished(imageContainer, nil, nil, ASNetworkImageSourceAsynchronousCache); } }; - - if (_cacheFlags.cacheSupportsCachedURLs) { - [_cache cachedImageWithURLs:URLs - callbackQueue:dispatch_get_main_queue() - completion:completion]; - } else { - [_cache cachedImageWithURL:[URLs lastObject] - callbackQueue:dispatch_get_main_queue() - completion:completion]; - } + [_cache cachedImageWithURL:URL + callbackQueue:dispatch_get_main_queue() + completion:completion]; } else { [self _downloadImageWithCompletion:^(id imageContainer, NSError *error, id downloadIdentifier) { finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload); diff --git a/Source/Details/ASImageProtocols.h b/Source/Details/ASImageProtocols.h index bc671866a..3fdf321e4 100644 --- a/Source/Details/ASImageProtocols.h +++ b/Source/Details/ASImageProtocols.h @@ -37,7 +37,7 @@ typedef void(^ASImageCacherCompletion)(id _Nullable i @param URL The URL of the image to retrieve from the cache. @param callbackQueue The queue to call `completion` on. @param completion The block to be called when the cache has either hit or missed. - @discussion If `URL` is nil, `completion` should be invoked immediately with a nil image. This method should not block + @discussion If `URL` is nil, `completion` will be invoked immediately with a nil image. This method should not block the calling thread as it is likely to be called from the main thread. */ - (void)cachedImageWithURL:(NSURL *)URL @@ -66,19 +66,6 @@ typedef void(^ASImageCacherCompletion)(id _Nullable i */ - (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL; -/** - @abstract Attempts to fetch an image with the given URLs from the cache in reverse order. - @param URLs The URLs of the image to retrieve from the cache. - @param callbackQueue The queue to call `completion` on. - @param completion The block to be called when the cache has either hit or missed. - @discussion If `URLs` is nil or empty, `completion` should be invoked immediately with a nil image. This method should not block - the calling thread as it is likely to be called from the main thread. - @see downloadImageWithURLs:callbackQueue:downloadProgress:completion: - */ -- (void)cachedImageWithURLs:(NSArray *)URLs - callbackQueue:(dispatch_queue_t)callbackQueue - completion:(ASImageCacherCompletion)completion; - @end /** @@ -167,21 +154,6 @@ typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { - (void)setPriority:(ASImageDownloaderPriority)priority withDownloadIdentifier:(id)downloadIdentifier; -/** - @abstract Downloads an image from a list of URLs depending on previously observed network speed conditions. - @param URLs An array of URLs ordered by the cost of downloading them, the URL at index 0 being the lowest cost. - @param callbackQueue The queue to call `downloadProgressBlock` and `completion` on. - @param downloadProgress The block to be invoked when the download of `URL` progresses. - @param completion The block to be invoked when the download has completed, or has failed. - @discussion This method is likely to be called on the main thread, so any custom implementations should make sure to background any expensive download operations. - @result An opaque identifier to be used in canceling the download, via `cancelImageDownloadForIdentifier:`. You must - retain the identifier if you wish to use it later. - */ -- (nullable id)downloadImageWithURLs:(NSArray *)URLs - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion; - @end @protocol ASAnimatedImageProtocol diff --git a/Source/Details/ASPINRemoteImageDownloader.m b/Source/Details/ASPINRemoteImageDownloader.m index 34cd1c7b7..3b46fc314 100644 --- a/Source/Details/ASPINRemoteImageDownloader.m +++ b/Source/Details/ASPINRemoteImageDownloader.m @@ -209,23 +209,6 @@ - (void)cachedImageWithURL:(NSURL *)URL }]; } -- (void)cachedImageWithURLs:(NSArray *)URLs - callbackQueue:(dispatch_queue_t)callbackQueue - completion:(ASImageCacherCompletion)completion -{ - [self cachedImageWithURL:[URLs lastObject] - callbackQueue:callbackQueue - completion:^(id _Nullable imageFromCache) { - if (imageFromCache.asdk_image == nil && URLs.count > 1) { - [self cachedImageWithURLs:[URLs subarrayWithRange:NSMakeRange(0, URLs.count - 1)] - callbackQueue:callbackQueue - completion:completion]; - } else { - completion(imageFromCache); - } - }]; -} - - (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL { if ([self sharedImageManagerSupportsMemoryRemoval]) { @@ -239,18 +222,6 @@ - (nullable id)downloadImageWithURL:(NSURL *)URL callbackQueue:(dispatch_queue_t)callbackQueue downloadProgress:(ASImageDownloaderProgress)downloadProgress completion:(ASImageDownloaderCompletion)completion; -{ - NSArray *URLs = nil; - if (URL) { - URLs = @[URL]; - } - return [self downloadImageWithURLs:URLs callbackQueue:callbackQueue downloadProgress:downloadProgress completion:completion]; -} - -- (nullable id)downloadImageWithURLs:(NSArray *)URLs - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion { PINRemoteImageManagerProgressDownload progressDownload = ^(int64_t completedBytes, int64_t totalBytes) { if (downloadProgress == nil) { return; } @@ -279,11 +250,11 @@ - (nullable id)downloadImageWithURLs:(NSArray *)URLs // extra downloads isn't worth the effort of rechecking caches every single time. In order to provide // feedback to the consumer about whether images are cached, we can't simply make the cache a no-op and // check the cache as part of this download. - return [[self sharedPINRemoteImageManager] downloadImageWithURLs:URLs - options:PINRemoteImageManagerDownloadOptionsSkipDecode | PINRemoteImageManagerDownloadOptionsIgnoreCache - progressImage:nil - progressDownload:progressDownload - completion:imageCompletion]; + return [[self sharedPINRemoteImageManager] downloadImageWithURL:URL + options:PINRemoteImageManagerDownloadOptionsSkipDecode | PINRemoteImageManagerDownloadOptionsIgnoreCache + progressImage:nil + progressDownload:progressDownload + completion:imageCompletion]; } - (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier From 5a4d569c56f8bf871ccb1e60a1976df4e9bae945 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Fri, 8 Dec 2017 18:30:26 +0000 Subject: [PATCH 041/133] Ensure an ASM enabled node applies its pending layout when enters preload state (#706) This makes sure subnodes are inserted and start preloading right away, instead of waiting until the next layout pass of the supernode. Fixes #693. --- CHANGELOG.md | 1 + Source/ASDisplayNode.mm | 14 +++++++ Tests/ASDisplayNodeImplicitHierarchyTests.m | 44 ++++++++++++++++++++- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab351bffe..40c83558f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - [ASScrollNode] Ensure the node respects the given size range while calculating its layout. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - [ASScrollNode] Invalidate the node's calculated layout if its scrollable directions changed. Also add unit tests for the class. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424) +- [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so the subnodes can preload too. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 95e927667..3a537ca4b 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -3079,6 +3079,20 @@ - (void)didEnterPreloadState { ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); + + if (self.automaticallyManagesSubnodes) { + // Tell the node to apply its applicable pending layout, if any, so that its subnodes are inserted/deleted + // and start preloading right away. + // + // If this node has an up-to-date layout (and subnodes), calling layoutIfNeeded will be fast. + // + // If this node doesn't have a calculated or pending layout that fits its current bounds, a measurement pass will occur + // (see __layout and _u_measureNodeWithBoundsIfNecessary:). + // This scenario should be uncommon, and running a measurement pass here is a fine trade-off because preloading + // any time after this point would be late. + [self layoutIfNeeded]; + } + [_interfaceStateDelegate didEnterPreloadState]; } diff --git a/Tests/ASDisplayNodeImplicitHierarchyTests.m b/Tests/ASDisplayNodeImplicitHierarchyTests.m index a3a7e160f..11e2ce4af 100644 --- a/Tests/ASDisplayNodeImplicitHierarchyTests.m +++ b/Tests/ASDisplayNodeImplicitHierarchyTests.m @@ -2,8 +2,6 @@ // ASDisplayNodeImplicitHierarchyTests.m // Texture // -// Created by Levi McCallum on 2/1/16. -// // 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 /ASDK-Licenses directory of this source tree. An additional @@ -20,6 +18,7 @@ #import #import +#import #import "ASDisplayNodeTestsHelper.h" @interface ASSpecTestDisplayNode : ASDisplayNode @@ -101,6 +100,47 @@ - (void)testInitialNodeInsertionWithOrdering XCTAssertEqual(node.subnodes[4], node5); } +- (void)testInitialNodeInsertionWhenEnterPreloadState +{ + static CGSize kSize = {100, 100}; + + static NSInteger subnodeCount = 5; + NSMutableArray *subnodes = [NSMutableArray arrayWithCapacity:subnodeCount]; + for (NSInteger i = 0; i < subnodeCount; i++) { + ASDisplayNode *subnode = [[ASDisplayNode alloc] init]; + // As we will involve a stack spec we have to give the nodes an intrinsic content size + subnode.style.preferredSize = kSize; + [subnodes addObject:subnode]; + } + + ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; + node.automaticallyManagesSubnodes = YES; + node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) { + ASAbsoluteLayoutSpec *absoluteLayout = [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[subnodes[3]]]; + + ASStackLayoutSpec *stack1 = [[ASStackLayoutSpec alloc] init]; + [stack1 setChildren:@[subnodes[0], subnodes[1]]]; + + ASStackLayoutSpec *stack2 = [[ASStackLayoutSpec alloc] init]; + [stack2 setChildren:@[subnodes[2], absoluteLayout]]; + + return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[stack1, stack2, subnodes[4]]]; + }; + + ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))); + [node recursivelySetInterfaceState:ASInterfaceStatePreload]; + + // No premature view allocation + XCTAssertFalse(node.isNodeLoaded); + // Subnodes should be inserted, laid out and entered preload state + XCTAssertTrue([subnodes isEqualToArray:node.subnodes]); + for (NSInteger i = 0; i < subnodeCount; i++) { + ASDisplayNode *subnode = subnodes[i]; + XCTAssertTrue(CGSizeEqualToSize(kSize, subnode.bounds.size)); + XCTAssertTrue(ASInterfaceStateIncludesPreload(subnode.interfaceState)); + } +} + - (void)testCalculatedLayoutHierarchyTransitions { static CGSize kSize = {100, 100}; From 8300d8eb61e6794d38bb8d202c9ff59cbd89d40f Mon Sep 17 00:00:00 2001 From: Ha Hyun soo Date: Tue, 12 Dec 2017 12:47:41 +0900 Subject: [PATCH 042/133] Vingle very community - Update showcase (#711) --- docs/showcase.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/showcase.md b/docs/showcase.md index 3a5531509..64c8871a1 100755 --- a/docs/showcase.md +++ b/docs/showcase.md @@ -210,7 +210,13 @@ permalink: /showcase.html
Sorted: Master Your Day - + + + +
+ Vingle + + From 46d46fdf126a9b7ee14100701687ec41956bba1d Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Mon, 18 Dec 2017 15:15:29 -0800 Subject: [PATCH 043/133] #trivial Fixes image nodes being stuck not being able to download image (#720) * #trivial Fixes image nodes being stuck not being able to download image * Clear out the _cacheUUID too even though this is not strictly necessary. --- Source/ASNetworkImageNode.mm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index d844b6646..2240e3ae9 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -698,6 +698,8 @@ - (void)_lazilyLoadImageIfNecessary //No longer in preload range, no point in setting the results (they won't be cleared in exit preload range) if (ASInterfaceStateIncludesPreload(self->_interfaceState) == NO) { + self->_downloadIdentifier = nil; + self->_cacheUUID = nil; return; } From 8f194347887d01ff17d189ea60e8cb5c2a712fad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Est=C3=A9banez=20Tasc=C3=B3n?= Date: Tue, 19 Dec 2017 17:56:35 +0100 Subject: [PATCH 044/133] Check for nil elements on ASTableView as well #trivial (#710) --- Source/ASCollectionView.mm | 2 +- Source/ASTableView.mm | 29 ++++++++++++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 2811f6271..1c884c304 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -1174,7 +1174,7 @@ - (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICol ASCellNode *cellNode = element.node; cellNode.scrollView = collectionView; - // Update the selected background view in collectionView:willDisplayCell:forItemAtIndexPath: otherwise it could be to + // Update the selected background view in collectionView:willDisplayCell:forItemAtIndexPath: otherwise it could be too // early e.g. if the selectedBackgroundView was set in didLoad() cell.selectedBackgroundView = cellNode.selectedBackgroundView; diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 4d9d01f81..1cc369b07 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -130,6 +130,15 @@ - (void)setElement:(ASCollectionElement *)element [node __setHighlightedFromUIKit:self.highlighted]; } +- (BOOL)consumesCellNodeVisibilityEvents +{ + ASCellNode *node = self.node; + if (node == nil) { + return NO; + } + return ASSubclassOverridesSelector([ASCellNode class], [node class], @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:)); +} + - (void)setSelected:(BOOL)selected animated:(BOOL)animated { [super setSelected:selected animated:animated]; @@ -971,7 +980,14 @@ - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sou - (void)tableView:(UITableView *)tableView willDisplayCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { ASCollectionElement *element = cell.element; - [_visibleElements addObject:element]; + if (element) { + ASDisplayNodeAssertTrue([_dataController.visibleMap elementForItemAtIndexPath:indexPath] == element); + [_visibleElements addObject:element]; + } else { + ASDisplayNodeAssert(NO, @"Unexpected nil element for willDisplayCell: %@, %@, %@", cell, self, indexPath); + return; + } + ASCellNode *cellNode = element.node; cellNode.scrollView = tableView; @@ -991,15 +1007,22 @@ - (void)tableView:(UITableView *)tableView willDisplayCell:(_ASTableViewCell *)c [_rangeController setNeedsUpdate]; - if (ASSubclassOverridesSelector([ASCellNode class], [cellNode class], @selector(cellNodeVisibilityEvent:inScrollView:withCellFrame:))) { + if ([cell consumesCellNodeVisibilityEvents]) { [_cellsForVisibilityUpdates addObject:cell]; } } - (void)tableView:(UITableView *)tableView didEndDisplayingCell:(_ASTableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { + // Retrieve the element from cell instead of visible map because at this point visible map could have been updated and no longer holds the element. ASCollectionElement *element = cell.element; - [_visibleElements removeObject:element]; + if (element) { + [_visibleElements removeObject:element]; + } else { + ASDisplayNodeAssert(NO, @"Unexpected nil element for didEndDisplayingCell: %@, %@, %@", cell, self, indexPath); + return; + } + ASCellNode *cellNode = element.node; [_rangeController setNeedsUpdate]; From eab7bea48de9ceb8b57ff96f6ec5f9f827800384 Mon Sep 17 00:00:00 2001 From: Ilya Date: Tue, 19 Dec 2017 20:13:45 +0300 Subject: [PATCH 045/133] Add missing flags for ASCollectionDelegate (#718) * Add missing flags for ASCollectionDelegate * Update CHANGELOG.md --- CHANGELOG.md | 1 + Source/ASCollectionView.mm | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40c83558f..cf53b2a88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASCollectionView] Add missing flags for ASCollectionDelegate [Ilya Zheleznikov](https://github.com/ilyailya) [#718](https://github.com/TextureGroup/Texture/pull/718) - [ASNetworkImageNode] Deprecates .URLs in favor of .URL [Garrett Moon](https://github.com/garrettmoon) [#699](https://github.com/TextureGroup/Texture/pull/699) - [iOS11] Update project settings and fix errors [Eke](https://github.com/Eke) [#676](https://github.com/TextureGroup/Texture/pull/676) - [ASCollectionView] Improve performance and behavior of rotation / bounds changes. [Scott Goodson](https://github.com/appleguy) [#431](https://github.com/TextureGroup/Texture/pull/431) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 1c884c304..4efe9a75d 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -539,6 +539,8 @@ - (void)setAsyncDelegate:(id)asyncDelegate _asyncDelegateFlags.collectionNodeShouldShowMenuForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:shouldShowMenuForItemAtIndexPath:)]; _asyncDelegateFlags.collectionNodeCanPerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:canPerformAction:forItemAtIndexPath:sender:)]; _asyncDelegateFlags.collectionNodePerformActionForItem = [_asyncDelegate respondsToSelector:@selector(collectionNode:performAction:forItemAtIndexPath:sender:)]; + _asyncDelegateFlags.collectionNodeWillDisplaySupplementaryElement = [_asyncDelegate respondsToSelector:@selector(collectionNode:willDisplaySupplementaryElementWithNode:)]; + _asyncDelegateFlags.collectionNodeDidEndDisplayingSupplementaryElement = [_asyncDelegate respondsToSelector:@selector(collectionNode:didEndDisplayingSupplementaryElementWithNode:)]; _asyncDelegateFlags.interop = [_asyncDelegate conformsToProtocol:@protocol(ASCollectionDelegateInterop)]; if (_asyncDelegateFlags.interop) { id interopDelegate = (id)_asyncDelegate; From 7416d6a88a7319d9ee5770f9b7eeb2a2ac179f2a Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Tue, 19 Dec 2017 18:53:13 +0100 Subject: [PATCH 046/133] Add Blendle to our showcase page (#721) --- docs/showcase.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/showcase.md b/docs/showcase.md index 64c8871a1..4b3e68233 100755 --- a/docs/showcase.md +++ b/docs/showcase.md @@ -219,6 +219,16 @@ permalink: /showcase.html + + + + +
+ Blendle + + + +
From fff5aae0a5168dae66ac80e01d87cb17ad4020f6 Mon Sep 17 00:00:00 2001 From: John T McIntosh Date: Wed, 20 Dec 2017 06:49:09 -0600 Subject: [PATCH 047/133] Add support for toggling logs off and back on at runtime #trivial (#714) --- Source/Base/ASLog.h | 10 ++++++++++ Source/Base/ASLog.m | 9 +++++---- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Source/Base/ASLog.h b/Source/Base/ASLog.h index be4bff368..c6aba76ba 100644 --- a/Source/Base/ASLog.h +++ b/Source/Base/ASLog.h @@ -42,6 +42,16 @@ ASDISPLAYNODE_EXTERN_C_BEGIN */ void ASDisableLogging(void); +/** + * Restore logging that has been runtime-disabled via ASDisableLogging(). + * + * Logging can be disabled at runtime using the ASDisableLogging() function. + * This command restores logging to the level provided in the build + * configuration. This can be used in conjunction with ASDisableLogging() + * to allow logging to be toggled off and back on at runtime. + */ +void ASEnableLogging(void); + /// Log for general node events e.g. interfaceState, didLoad. #define ASNodeLogEnabled 1 os_log_t ASNodeLog(void); diff --git a/Source/Base/ASLog.m b/Source/Base/ASLog.m index 8e65c4b55..e1c42ea79 100644 --- a/Source/Base/ASLog.m +++ b/Source/Base/ASLog.m @@ -16,10 +16,11 @@ static atomic_bool __ASLogEnabled = ATOMIC_VAR_INIT(YES); void ASDisableLogging() { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - atomic_store(&__ASLogEnabled, NO); - }); + atomic_store(&__ASLogEnabled, NO); +} + +void ASEnableLogging() { + atomic_store(&__ASLogEnabled, YES); } ASDISPLAYNODE_INLINE BOOL ASLoggingIsEnabled() { From 4dec51ca37842be31c8e8dd97abbc206ef1fb0c2 Mon Sep 17 00:00:00 2001 From: Stephen Williams Date: Thu, 21 Dec 2017 01:53:13 +1300 Subject: [PATCH 048/133] Fix ASDKgram example #trivial (#700) - Fix an insta-crash that's caused by Webservice.load method to call its completion block off the main thread. - Fix incorrect http status code check. - Bump the deployment target to get the project compiling. --- .../ASDKgram-Swift.xcodeproj/project.pbxproj | 4 ++-- .../ASDKgram-Swift/Webservice.swift | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift.xcodeproj/project.pbxproj b/examples_extra/ASDKgram-Swift/ASDKgram-Swift.xcodeproj/project.pbxproj index 875331955..23ef18b40 100644 --- a/examples_extra/ASDKgram-Swift/ASDKgram-Swift.xcodeproj/project.pbxproj +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift.xcodeproj/project.pbxproj @@ -428,7 +428,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -478,7 +478,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Webservice.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Webservice.swift index 4021e53c5..830d78eb0 100644 --- a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Webservice.swift +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/Webservice.swift @@ -25,12 +25,13 @@ final class WebService { URLSession.shared.dataTask(with: resource.url) { data, response, error in // Check for errors in responses. let result = self.checkForNetworkErrors(data, response, error) - - switch result { - case .success(let data): - completion(resource.parse(data)) - case .failure(let error): - completion(.failure(error)) + DispatchQueue.main.async { + switch result { + case .success(let data): + completion(resource.parse(data)) + case .failure(let error): + completion(.failure(error)) + } } }.resume() } @@ -49,7 +50,7 @@ extension WebService { } } - if let response = response as? HTTPURLResponse, response.statusCode >= 200 && response.statusCode <= 299 { + if let response = response as? HTTPURLResponse, response.statusCode <= 200 && response.statusCode >= 299 { return .failure((.invalidStatusCode("Request returned status code other than 2xx \(response)"))) } From 131619de96190415aec89987d4493220c6d0305d Mon Sep 17 00:00:00 2001 From: appleguy Date: Thu, 21 Dec 2017 16:17:25 -0800 Subject: [PATCH 049/133] Reimplement ASRectTable using unordered_map to avoid obscure NSMapTable exception. (#719) * Reimplement ASRectTable using unordered_map to avoid obscure NSMapTable exception. The new class is called ASRectMap, which patterns alongside ASIntegerMap in both name and implementation. After some pretty detailed investigation, including study of open-source reimplementations of Foundation, the best lead I've found on the NSMapTable exception is that some NSPointerFunction types are not fully supported. Strangely, the ones being used do seem to work fine almost all of the time. The main concern is the Struct memory type, which is not officially re-declared in NSMapTable, and as such the documentation claims that there may exist some combinations of NSPointerFunction that are not supported. Because the exception is occurring frequently enough to be a concern (in the hundreds to low thousands, though only 50 a day) - I decided to replace NSMapTable entirely in order to ensure full correctness. "*** -[NSMapTable initWithKeyPointerFunctions:valuePointerFunctions:capacity:] Requested configuration not supported." * Fix Xcode project --- AsyncDisplayKit.xcodeproj/project.pbxproj | 24 +++--- CHANGELOG.md | 1 + Source/Layout/ASLayout.mm | 10 +-- Source/Private/ASRectMap.h | 52 ++++++++++++ Source/Private/ASRectMap.mm | 78 ++++++++++++++++++ Source/Private/ASRectTable.h | 73 ----------------- Source/Private/ASRectTable.m | 80 ------------------- .../{ASRectTableTests.m => ASRectMapTests.m} | 14 ++-- 8 files changed, 155 insertions(+), 177 deletions(-) create mode 100644 Source/Private/ASRectMap.h create mode 100644 Source/Private/ASRectMap.mm delete mode 100644 Source/Private/ASRectTable.h delete mode 100644 Source/Private/ASRectTable.m rename Tests/{ASRectTableTests.m => ASRectMapTests.m} (78%) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index f8d60fb32..3a4b1c534 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -315,7 +315,6 @@ C78F7E2B1BF7809800CDEAFC /* ASTableNode.h in Headers */ = {isa = PBXBuildFile; fileRef = B0F880581BEAEC7500D17647 /* ASTableNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */ = {isa = PBXBuildFile; fileRef = CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC034A0A1E60BEB400626263 /* ASDisplayNode+Convenience.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */; }; - CC034A101E60C9BF00626263 /* ASRectTableTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A0F1E60C9BF00626263 /* ASRectTableTests.m */; }; CC034A131E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h in Headers */ = {isa = PBXBuildFile; fileRef = CC034A111E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC034A141E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.m in Sources */ = {isa = PBXBuildFile; fileRef = CC034A121E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.m */; }; CC051F1F1D7A286A006434CB /* ASCALayerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC051F1E1D7A286A006434CB /* ASCALayerTests.m */; }; @@ -431,6 +430,9 @@ DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; DEFAD8131CC48914000527C4 /* ASVideoNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */; }; E51B78BF1F028ABF00E32604 /* ASLayoutFlatteningTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E51B78BD1F01A0EE00E32604 /* ASLayoutFlatteningTests.m */; }; + E52AC9BA1FEA90EB00AA4040 /* ASRectMap.mm in Sources */ = {isa = PBXBuildFile; fileRef = E52AC9B81FEA90EB00AA4040 /* ASRectMap.mm */; }; + E52AC9BB1FEA90EB00AA4040 /* ASRectMap.h in Headers */ = {isa = PBXBuildFile; fileRef = E52AC9B91FEA90EB00AA4040 /* ASRectMap.h */; settings = {ATTRIBUTES = (Private, ); }; }; + E52AC9C01FEA916C00AA4040 /* ASRectMapTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E52AC9BE1FEA915D00AA4040 /* ASRectMapTests.m */; }; E54E00721F1D3828000B30D7 /* ASPagerNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = E54E00711F1D3828000B30D7 /* ASPagerNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; E54E81FC1EB357BD00FFE8E1 /* ASPageTable.h in Headers */ = {isa = PBXBuildFile; fileRef = E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */; }; E54E81FD1EB357BD00FFE8E1 /* ASPageTable.m in Sources */ = {isa = PBXBuildFile; fileRef = E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */; }; @@ -454,8 +456,6 @@ E58E9E461E941D74004CFC59 /* ASCollectionLayoutDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E58E9E411E941D74004CFC59 /* ASCollectionLayoutDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; E58E9E491E941DA5004CFC59 /* ASCollectionLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = E58E9E471E941DA5004CFC59 /* ASCollectionLayout.h */; settings = {ATTRIBUTES = (Private, ); }; }; E58E9E4A1E941DA5004CFC59 /* ASCollectionLayout.mm in Sources */ = {isa = PBXBuildFile; fileRef = E58E9E481E941DA5004CFC59 /* ASCollectionLayout.mm */; }; - E5ABAC7B1E8564EE007AC15C /* ASRectTable.h in Headers */ = {isa = PBXBuildFile; fileRef = E5ABAC791E8564EE007AC15C /* ASRectTable.h */; }; - E5ABAC7C1E8564EE007AC15C /* ASRectTable.m in Sources */ = {isa = PBXBuildFile; fileRef = E5ABAC7A1E8564EE007AC15C /* ASRectTable.m */; }; E5B077FF1E69F4EB00C24B5B /* ASElementMap.h in Headers */ = {isa = PBXBuildFile; fileRef = E5B077FD1E69F4EB00C24B5B /* ASElementMap.h */; settings = {ATTRIBUTES = (Public, ); }; }; E5B078001E69F4EB00C24B5B /* ASElementMap.m in Sources */ = {isa = PBXBuildFile; fileRef = E5B077FE1E69F4EB00C24B5B /* ASElementMap.m */; }; E5B225281F1790D6001E1431 /* ASHashing.h in Headers */ = {isa = PBXBuildFile; fileRef = E5B225271F1790B5001E1431 /* ASHashing.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -800,7 +800,6 @@ BDC2D162BD55A807C1475DA5 /* Pods-AsyncDisplayKitTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.profile.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.profile.xcconfig"; sourceTree = ""; }; CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Convenience.h"; sourceTree = ""; }; CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASDisplayNode+Convenience.m"; sourceTree = ""; }; - CC034A0F1E60C9BF00626263 /* ASRectTableTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASRectTableTests.m; sourceTree = ""; }; CC034A111E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "AsyncDisplayKit+IGListKitMethods.h"; sourceTree = ""; }; CC034A121E649F1300626263 /* AsyncDisplayKit+IGListKitMethods.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "AsyncDisplayKit+IGListKitMethods.m"; sourceTree = ""; }; CC051F1E1D7A286A006434CB /* ASCALayerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCALayerTests.m; sourceTree = ""; }; @@ -931,6 +930,9 @@ E51B78BD1F01A0EE00E32604 /* ASLayoutFlatteningTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLayoutFlatteningTests.m; sourceTree = ""; }; E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutTransition.mm; sourceTree = ""; }; E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutTransition.h; sourceTree = ""; }; + E52AC9B81FEA90EB00AA4040 /* ASRectMap.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASRectMap.mm; sourceTree = ""; }; + E52AC9B91FEA90EB00AA4040 /* ASRectMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRectMap.h; sourceTree = ""; }; + E52AC9BE1FEA915D00AA4040 /* ASRectMapTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASRectMapTests.m; sourceTree = ""; }; E54E00711F1D3828000B30D7 /* ASPagerNode+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASPagerNode+Beta.h"; sourceTree = ""; }; E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPageTable.h; sourceTree = ""; }; E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPageTable.m; sourceTree = ""; }; @@ -954,8 +956,6 @@ E58E9E411E941D74004CFC59 /* ASCollectionLayoutDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayoutDelegate.h; sourceTree = ""; }; E58E9E471E941DA5004CFC59 /* ASCollectionLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionLayout.h; sourceTree = ""; }; E58E9E481E941DA5004CFC59 /* ASCollectionLayout.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionLayout.mm; sourceTree = ""; }; - E5ABAC791E8564EE007AC15C /* ASRectTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRectTable.h; sourceTree = ""; }; - E5ABAC7A1E8564EE007AC15C /* ASRectTable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASRectTable.m; sourceTree = ""; }; E5B077FD1E69F4EB00C24B5B /* ASElementMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASElementMap.h; sourceTree = ""; }; E5B077FE1E69F4EB00C24B5B /* ASElementMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASElementMap.m; sourceTree = ""; }; E5B225261F1790B5001E1431 /* ASHashing.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASHashing.m; sourceTree = ""; }; @@ -1179,7 +1179,6 @@ CC583ABF1EF9BAB400134156 /* Common */, CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */, BB5FC3CD1F9BA688007F191E /* ASNavigationControllerTests.m */, - CC034A0F1E60C9BF00626263 /* ASRectTableTests.m */, BB5FC3D01F9C9389007F191E /* ASTabBarControllerTests.m */, CC11F9791DB181180024D77B /* ASNetworkImageNodeTests.m */, CC051F1E1D7A286A006434CB /* ASCALayerTests.m */, @@ -1196,6 +1195,7 @@ CCA221D21D6FA7EF00AF6A0F /* ASViewControllerTests.m */, CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */, CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */, + E52AC9BE1FEA915D00AA4040 /* ASRectMapTests.m */, 83A7D95D1D446A6E00BF333E /* ASWeakMapTests.m */, DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */, DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */, @@ -1383,8 +1383,8 @@ CCA282B31E9EA7310037E8B7 /* ASTipsController.m */, CC2F65EC1E5FFB1600DA57C9 /* ASMutableElementMap.h */, CC2F65ED1E5FFB1600DA57C9 /* ASMutableElementMap.m */, - E5ABAC791E8564EE007AC15C /* ASRectTable.h */, - E5ABAC7A1E8564EE007AC15C /* ASRectTable.m */, + E52AC9B91FEA90EB00AA4040 /* ASRectMap.h */, + E52AC9B81FEA90EB00AA4040 /* ASRectMap.mm */, CC55A70F1E52A0F200594372 /* ASResponderChainEnumerator.h */, CC55A7101E52A0F200594372 /* ASResponderChainEnumerator.m */, 6947B0BB1E36B4E30007C478 /* Layout */, @@ -1857,6 +1857,7 @@ E5775B021F16759300CAC9BC /* ASCollectionLayoutCache.h in Headers */, E5775B001F13D25400CAC9BC /* ASCollectionLayoutState+Private.h in Headers */, E5667E8C1F33871300FA6FC0 /* _ASCollectionGalleryLayoutInfo.h in Headers */, + E52AC9BB1FEA90EB00AA4040 /* ASRectMap.h in Headers */, E5775AFC1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h in Headers */, E5855DF01EBB4D83003639AE /* ASCollectionLayoutDefines.h in Headers */, E5B5B9D11E9BAD9800A6B726 /* ASCollectionLayoutContext+Private.h in Headers */, @@ -1918,7 +1919,6 @@ B35062061B010EFD0018CF92 /* ASNetworkImageNode.h in Headers */, CCA282C81E9EB64B0037E8B7 /* ASDisplayNodeTipState.h in Headers */, 34EFC76C1B701CED00AD841F /* ASOverlayLayoutSpec.h in Headers */, - E5ABAC7B1E8564EE007AC15C /* ASRectTable.h in Headers */, B35062261B010EFD0018CF92 /* ASRangeController.h in Headers */, 34EFC76E1B701CF400AD841F /* ASRatioLayoutSpec.h in Headers */, DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */, @@ -2165,7 +2165,6 @@ 9F06E5CD1B4CAF4200F015D8 /* ASCollectionViewTests.mm in Sources */, 2911485C1A77147A005D0878 /* ASControlNodeTests.m in Sources */, CC3B208E1C3F7D0A00798563 /* ASWeakSetTests.m in Sources */, - CC034A101E60C9BF00626263 /* ASRectTableTests.m in Sources */, F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.m in Sources */, BB5FC3CE1F9BA689007F191E /* ASNavigationControllerTests.m in Sources */, ACF6ED5D1B178DC700DA7C62 /* ASDimensionTests.mm in Sources */, @@ -2211,6 +2210,7 @@ 254C6B541BF8FF2A003EC431 /* ASTextKitTests.mm in Sources */, 05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.m in Sources */, ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */, + E52AC9C01FEA916C00AA4040 /* ASRectMapTests.m in Sources */, CCE4F9BA1F0DBB5000062E4E /* ASLayoutTestNode.mm in Sources */, 81E95C141D62639600336598 /* ASTextNodeSnapshotTests.m in Sources */, 3C9C128519E616EF00E942A0 /* ASTableViewTests.mm in Sources */, @@ -2239,6 +2239,7 @@ 3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.m in Sources */, CCA282D11E9EBF6C0037E8B7 /* ASTipsWindow.m in Sources */, CCCCCCE41EC3EF060087FE10 /* NSParagraphStyle+ASText.m in Sources */, + E52AC9BA1FEA90EB00AA4040 /* ASRectMap.mm in Sources */, 8BBBAB8D1CEBAF1E00107FC6 /* ASDefaultPlaybackButton.m in Sources */, B30BF6541C59D889004FCD53 /* ASLayoutManager.m in Sources */, 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */, @@ -2351,7 +2352,6 @@ 254C6B8B1BF94F8A003EC431 /* ASTextKitShadower.mm in Sources */, 254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */, 90FC784F1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm in Sources */, - E5ABAC7C1E8564EE007AC15C /* ASRectTable.m in Sources */, CCA282C91E9EB64B0037E8B7 /* ASDisplayNodeTipState.m in Sources */, 509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */, B35062091B010EFD0018CF92 /* ASScrollNode.mm in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index cf53b2a88..35e4f360b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASRectMap] Replace implementation of ASRectTable with a simpler one based on unordered_map.[Scott Goodson](https://github.com/appleguy) [#719](https://github.com/TextureGroup/Texture/pull/719) - [ASCollectionView] Add missing flags for ASCollectionDelegate [Ilya Zheleznikov](https://github.com/ilyailya) [#718](https://github.com/TextureGroup/Texture/pull/718) - [ASNetworkImageNode] Deprecates .URLs in favor of .URL [Garrett Moon](https://github.com/garrettmoon) [#699](https://github.com/TextureGroup/Texture/pull/699) - [iOS11] Update project settings and fix errors [Eke](https://github.com/Eke) [#676](https://github.com/TextureGroup/Texture/pull/676) diff --git a/Source/Layout/ASLayout.mm b/Source/Layout/ASLayout.mm index 919a520c9..f6fbe9127 100644 --- a/Source/Layout/ASLayout.mm +++ b/Source/Layout/ASLayout.mm @@ -26,7 +26,7 @@ #import #import #import -#import +#import CGPoint const ASPointNull = {NAN, NAN}; @@ -86,7 +86,7 @@ @interface ASLayout () */ @property (nonatomic, strong) NSMutableArray> *sublayoutLayoutElements; -@property (nonatomic, strong, readonly) ASRectTable, id> *elementToRectTable; +@property (nonatomic, strong, readonly) ASRectMap *elementToRectMap; @end @@ -143,9 +143,9 @@ - (instancetype)initWithLayoutElement:(id)layoutElement _sublayouts = sublayouts != nil ? [sublayouts copy] : @[]; if (_sublayouts.count > 0) { - _elementToRectTable = [ASRectTable rectTableForWeakObjectPointers]; + _elementToRectMap = [ASRectMap rectMapForWeakObjectPointers]; for (ASLayout *layout in sublayouts) { - [_elementToRectTable setRect:layout.frame forKey:layout.layoutElement]; + [_elementToRectMap setRect:layout.frame forKey:layout.layoutElement]; } } @@ -303,7 +303,7 @@ - (ASLayoutElementType)type - (CGRect)frameForElement:(id)layoutElement { - return _elementToRectTable ? [_elementToRectTable rectForKey:layoutElement] : CGRectNull; + return _elementToRectMap ? [_elementToRectMap rectForKey:layoutElement] : CGRectNull; } - (CGRect)frame diff --git a/Source/Private/ASRectMap.h b/Source/Private/ASRectMap.h new file mode 100644 index 000000000..fd756b333 --- /dev/null +++ b/Source/Private/ASRectMap.h @@ -0,0 +1,52 @@ +// +// ASRectMap.h +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * A category for indexing weak pointers to CGRects. Similar to ASIntegerMap. + */ +@interface ASRectMap : NSObject + +/** + * Creates a new rect map. The keys are never retained. + */ ++ (ASRectMap *)rectMapForWeakObjectPointers; + +/** + * Retrieves the rect for a given key, or CGRectNull if the key is not found. + * + * @param key An object to lookup the rect for. + */ +- (CGRect)rectForKey:(id)key; + +/** + * Sets the given rect for the associated key. Key *will not be retained!* + * + * @param rect The rect to store as value. + * @param key The key to use for the rect. + */ +- (void)setRect:(CGRect)rect forKey:(id)key; + +/** + * Removes the rect for the given key, if one exists. + * + * @param key The key to remove. + */ +- (void)removeRectForKey:(id)key; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASRectMap.mm b/Source/Private/ASRectMap.mm new file mode 100644 index 000000000..cb7681080 --- /dev/null +++ b/Source/Private/ASRectMap.mm @@ -0,0 +1,78 @@ +// +// ASRectMap.mm +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import "ASRectMap.h" +#import "ASObjectDescriptionHelpers.h" +#import +#import + +@implementation ASRectMap { + std::unordered_map _map; +} + ++ (ASRectMap *)rectMapForWeakObjectPointers +{ + return [[self alloc] init]; +} + +- (CGRect)rectForKey:(id)key +{ + auto result = _map.find((__bridge void *)key); + if (result != _map.end()) { + // result->first is the key; result->second is the value, a CGRect. + return result->second; + } else { + return CGRectNull; + } +} + +- (void)setRect:(CGRect)rect forKey:(id)key +{ + if (key) { + _map[(__bridge void *)key] = rect; + } +} + +- (void)removeRectForKey:(id)key +{ + if (key) { + _map.erase((__bridge void *)key); + } +} + +- (id)copyWithZone:(NSZone *)zone +{ + ASRectMap *copy = [ASRectMap rectMapForWeakObjectPointers]; + copy->_map = _map; + return copy; +} + +- (NSMutableArray *)propertiesForDescription +{ + NSMutableArray *result = [NSMutableArray array]; + + // { ptr1->rect1 ptr2->rect2 ptr3->rect3 } + NSMutableString *str = [NSMutableString string]; + for (auto it = _map.begin(); it != _map.end(); it++) { + [str appendFormat:@" %@->%@", it->first, NSStringFromCGRect(it->second)]; + } + [result addObject:@{ @"ASRectMap": str }]; + + return result; +} + +- (NSString *)description +{ + return ASObjectDescriptionMakeWithoutObject([self propertiesForDescription]); +} + +@end diff --git a/Source/Private/ASRectTable.h b/Source/Private/ASRectTable.h deleted file mode 100644 index af47f59b6..000000000 --- a/Source/Private/ASRectTable.h +++ /dev/null @@ -1,73 +0,0 @@ -// -// ASRectTable.h -// Texture -// -// 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 /ASDK-Licenses directory of this source tree. An additional -// grant of patent rights can be found in the PATENTS file in the same directory. -// -// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, -// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -/** - * An alias for an NSMapTable created to store rects. - * - * You should not call -objectForKey:, -setObject:forKey:, or -allObjects - * on these objects. - */ -typedef NSMapTable ASRectTable; - -/** - * A category for creating & using map tables meant for storing CGRects. - * - * This category is private, so name collisions are not worth worrying about. - */ -@interface NSMapTable (ASRectTableMethods) - -/** - * Creates a new rect table with (NSMapTableStrongMemory | NSMapTableObjectPointerPersonality) for keys. - */ -+ (ASRectTable *)rectTableForStrongObjectPointers; - -/** - * Creates a new rect table with (NSMapTableWeakMemory | NSMapTableObjectPointerPersonality) for keys. - */ -+ (ASRectTable *)rectTableForWeakObjectPointers; - -/** - * Retrieves the rect for a given key, or CGRectNull if the key is not found. - * - * @param key An object to lookup the rect for. - */ -- (CGRect)rectForKey:(KeyType)key; - -/** - * Sets the given rect for the associated key. - * - * @param rect The rect to store as value. - * @param key The key to use for the rect. - */ -- (void)setRect:(CGRect)rect forKey:(KeyType)key; - -/** - * Removes the rect for the given key, if one exists. - * - * @param key The key to remove. - */ -- (void)removeRectForKey:(KeyType)key; - -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASRectTable.m b/Source/Private/ASRectTable.m deleted file mode 100644 index 5a98a3f70..000000000 --- a/Source/Private/ASRectTable.m +++ /dev/null @@ -1,80 +0,0 @@ -// -// ASRectTable.m -// Texture -// -// 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 /ASDK-Licenses directory of this source tree. An additional -// grant of patent rights can be found in the PATENTS file in the same directory. -// -// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, -// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// - -#import "ASRectTable.h" - -__attribute__((const)) -static NSUInteger ASRectSize(const void *ptr) -{ - return sizeof(CGRect); -} - -@implementation NSMapTable (ASRectTableMethods) - -+ (NSMapTable *)rectTableWithKeyPointerFunctions:(NSPointerFunctions *)keyFuncs -{ - static NSPointerFunctions *cgRectFuncs; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - cgRectFuncs = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStructPersonality | NSPointerFunctionsCopyIn | NSPointerFunctionsMallocMemory]; - cgRectFuncs.sizeFunction = &ASRectSize; - }); - - return [[NSMapTable alloc] initWithKeyPointerFunctions:keyFuncs valuePointerFunctions:cgRectFuncs capacity:0]; -} - -+ (NSMapTable *)rectTableForStrongObjectPointers -{ - static NSPointerFunctions *strongObjectPointerFuncs; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - strongObjectPointerFuncs = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsStrongMemory | NSPointerFunctionsObjectPointerPersonality]; - }); - return [self rectTableWithKeyPointerFunctions:strongObjectPointerFuncs]; -} - -+ (NSMapTable *)rectTableForWeakObjectPointers -{ - static NSPointerFunctions *weakObjectPointerFuncs; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - weakObjectPointerFuncs = [NSPointerFunctions pointerFunctionsWithOptions:NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPointerPersonality]; - }); - return [self rectTableWithKeyPointerFunctions:weakObjectPointerFuncs]; -} - -- (CGRect)rectForKey:(id)key -{ - CGRect *ptr = (__bridge CGRect *)[self objectForKey:key]; - if (ptr == NULL) { - return CGRectNull; - } - return *ptr; -} - -- (void)setRect:(CGRect)rect forKey:(id)key -{ - __unsafe_unretained id obj = (__bridge id)▭ - [self setObject:obj forKey:key]; -} - -- (void)removeRectForKey:(id)key -{ - [self removeObjectForKey:key]; -} - -@end diff --git a/Tests/ASRectTableTests.m b/Tests/ASRectMapTests.m similarity index 78% rename from Tests/ASRectTableTests.m rename to Tests/ASRectMapTests.m index ca897a357..357f3879a 100644 --- a/Tests/ASRectTableTests.m +++ b/Tests/ASRectMapTests.m @@ -1,5 +1,5 @@ // -// ASRectTableTests.m +// ASRectMapTests.m // Texture // // Created by Adlai Holler on 2/24/17. @@ -8,17 +8,17 @@ #import -#import "ASRectTable.h" +#import "ASRectMap.h" #import "ASXCTExtensions.h" -@interface ASRectTableTests : XCTestCase +@interface ASRectMapTests : XCTestCase @end -@implementation ASRectTableTests +@implementation ASRectMapTests - (void)testThatItStoresRects { - ASRectTable *table = [ASRectTable rectTableForWeakObjectPointers]; + ASRectMap *table = [ASRectMap rectMapForWeakObjectPointers]; NSObject *key0 = [[NSObject alloc] init]; NSObject *key1 = [[NSObject alloc] init]; ASXCTAssertEqualRects([table rectForKey:key0], CGRectNull); @@ -35,13 +35,13 @@ - (void)testThatItStoresRects - (void)testCopying { - ASRectTable *table = [ASRectTable rectTableForWeakObjectPointers]; + ASRectMap *table = [ASRectMap rectMapForWeakObjectPointers]; NSObject *key = [[NSObject alloc] init]; ASXCTAssertEqualRects([table rectForKey:key], CGRectNull); CGRect rect0 = CGRectMake(0, 0, 100, 100); CGRect rect1 = CGRectMake(0, 0, 50, 50); [table setRect:rect0 forKey:key]; - ASRectTable *copy = [table copy]; + ASRectMap *copy = [table copy]; [copy setRect:rect1 forKey:key]; ASXCTAssertEqualRects([table rectForKey:key], rect0); From 5e73396cde1e173aef0ba46873b928f50f6f3d01 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 9 Jan 2018 14:34:32 -0800 Subject: [PATCH 050/133] Enable collection node interactive moves (#735) * Add support for interactive moves * Enable drag & drop in collection view example * Update changelog * Change the gating logic to match UIKit * Add a warning when we prevent interactive movement due to async layout --- CHANGELOG.md | 1 + Source/ASCollectionNode.h | 26 ++++ Source/ASCollectionView.mm | 87 ++++++++++--- Source/Details/ASDataController.mm | 2 +- Source/Layout/ASLayoutElement.h | 2 +- Source/Private/ASCollectionLayout.mm | 15 +++ .../ASCollectionView/Sample/ViewController.m | 123 ++++++++++++------ 7 files changed, 195 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35e4f360b..c5281189d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ - [ASScrollNode] Invalidate the node's calculated layout if its scrollable directions changed. Also add unit tests for the class. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424) - [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so the subnodes can preload too. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706) +- [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASCollectionNode.h b/Source/ASCollectionNode.h index 5d7740458..4a56217cf 100644 --- a/Source/ASCollectionNode.h +++ b/Source/ASCollectionNode.h @@ -630,6 +630,32 @@ NS_ASSUME_NONNULL_BEGIN */ - (NSArray *)collectionNode:(ASCollectionNode *)collectionNode supplementaryElementKindsInSection:(NSInteger)section; +/** + * Asks the data source if it's possible to move the specified item interactively. + * + * See @p -[UICollectionViewDataSource collectionView:canMoveItemAtIndexPath:] @c. + * + * @param collectionNode The sender. + * @param node The display node for the item that may be moved. + * + * @return Whether the item represented by @p node may be moved. + */ +- (BOOL)collectionNode:(ASCollectionNode *)collectionNode canMoveItemWithNode:(ASCellNode *)node; + +/** + * Called when the user has interactively moved an item. The data source + * should update its internal data store to reflect the move. Note that you + * should not call [collectionNode moveItemAtIndexPath:toIndexPath:] – the + * collection node's internal state will be updated automatically. + * + * * See @p -[UICollectionViewDataSource collectionView:moveItemAtIndexPath:toIndexPath:] @c. + * + * @param collectionNode The sender. + * @param sourceIndexPath The original item index path. + * @param destinationIndexPath The new item index path. + */ +- (void)collectionNode:(ASCollectionNode *)collectionNode moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath; + /** * Similar to -collectionView:cellForItemAtIndexPath:. * diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 4efe9a75d..fda3ea803 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -101,6 +101,10 @@ @interface ASCollectionView () *_cellsForLayoutUpdates; id _layoutFacilitator; CGFloat _leadingScreensForBatching; + + // When we update our data controller in response to an interactive move, + // we don't want to tell the collection view about the change (it knows!) + BOOL _updatingInResponseToInteractiveMove; BOOL _inverted; NSUInteger _superBatchUpdateCount; @@ -218,6 +222,8 @@ @interface ASCollectionView () )asyncDataSource _asyncDataSourceFlags.collectionNodeNodeBlockForSupplementaryElement = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeBlockForSupplementaryElementOfKind:atIndexPath:)]; _asyncDataSourceFlags.collectionNodeSupplementaryElementKindsInSection = [_asyncDataSource respondsToSelector:@selector(collectionNode:supplementaryElementKindsInSection:)]; _asyncDataSourceFlags.nodeModelForItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:nodeModelForItemAtIndexPath:)]; + _asyncDataSourceFlags.collectionNodeCanMoveItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:canMoveItemWithNode:)]; + _asyncDataSourceFlags.collectionNodeMoveItem = [_asyncDataSource respondsToSelector:@selector(collectionNode:moveItemAtIndexPath:toIndexPath:)]; _asyncDataSourceFlags.interop = [_asyncDataSource conformsToProtocol:@protocol(ASCollectionDataSourceInterop)]; if (_asyncDataSourceFlags.interop) { @@ -1492,6 +1500,66 @@ - (void)collectionView:(UICollectionView *)collectionView performAction:(nonnull } } +- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath +{ + // Mimic UIKit's gating logic. + // If the data source doesn't support moving, then all bets are off. + if (!_asyncDataSourceFlags.collectionNodeMoveItem) { + return NO; + } + + // Currently we do not support interactive moves when using async layout. The reason is, we do not have a mechanism + // to propagate the "presentation data" element map (containing the speculative in-progress moves) to the layout delegate, + // and this can cause exceptions to be thrown from UICV. For example, if you drag an item out of a section, + // the element map will still contain N items in that section, even though there's only N-1 shown, and UICV will + // throw an exception that you specified an element that doesn't exist. + // + // In iOS >= 11, this is made much easier by the UIDataSourceTranslating API. In previous versions of iOS our best bet + // would be to capture the invalidation contexts that are sent during interactive moves and make our own data source translator. + if ([self.collectionViewLayout isKindOfClass:[ASCollectionLayout class]]) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + as_log_debug(ASCollectionLog(), "Collection node item interactive movement is not supported when using a layout delegate. This message will only be logged once. Node: %@", ASObjectDescriptionMakeTiny(self)); + }); + return NO; + } + + // If the data source implements canMoveItem, let them decide. + if (_asyncDataSourceFlags.collectionNodeCanMoveItem) { + if (auto cellNode = [self nodeForItemAtIndexPath:indexPath]) { + GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO); + return [_asyncDataSource collectionNode:collectionNode canMoveItemWithNode:cellNode]; + } + } + + // Otherwise allow the move for all items. + return YES; +} + +- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath +{ + ASDisplayNodeAssert(_asyncDataSourceFlags.collectionNodeMoveItem, @"Should not allow interactive collection item movement if data source does not support it."); + + // Inform the data source first, in case they call nodeForItemAtIndexPath:. + // We want to make sure we return them the node for the item they have in mind. + if (auto collectionNode = self.collectionNode) { + [_asyncDataSource collectionNode:collectionNode moveItemAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; + } + + // Now we update our data controller's store. + // Get up to date + [self waitUntilAllUpdatesAreCommitted]; + // Set our flag to suppress informing super about the change. + ASDisplayNodeAssertFalse(_updatingInResponseToInteractiveMove); + _updatingInResponseToInteractiveMove = YES; + // Submit the move + [self moveItemAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath]; + // Wait for it to finish – should be fast! + [self waitUntilAllUpdatesAreCommitted]; + // Clear the flag + _updatingInResponseToInteractiveMove = NO; +} + - (void)scrollViewDidScroll:(UIScrollView *)scrollView { // If a scroll happenes the current range mode needs to go to full @@ -2023,7 +2091,7 @@ - (NSString *)nameForRangeControllerDataSource - (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet updates:(dispatch_block_t)updates { ASDisplayNodeAssertMainThread(); - if (!self.asyncDataSource || _superIsPendingDataLoad) { + if (!self.asyncDataSource || _superIsPendingDataLoad || _updatingInResponseToInteractiveMove) { updates(); [changeSet executeCompletionHandlerWithFinished:NO]; return; // if the asyncDataSource has become invalid while we are processing, ignore this request to avoid crashes @@ -2278,21 +2346,4 @@ - (void)setPrefetchingEnabled:(BOOL)prefetchingEnabled return; } -#if ASDISPLAYNODE_ASSERTIONS_ENABLED // Remove implementations entirely for efficiency if not asserting. - -// intercepted due to not being supported by ASCollectionView (prevent bugs caused by usage) - -- (BOOL)collectionView:(UICollectionView *)collectionView canMoveItemAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(9_0) -{ - ASDisplayNodeAssert(![self.asyncDataSource respondsToSelector:_cmd], @"%@ is not supported by ASCollectionView - please remove or disable this data source method.", NSStringFromSelector(_cmd)); - return NO; -} - -- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath NS_AVAILABLE_IOS(9_0) -{ - ASDisplayNodeAssert(![self.asyncDataSource respondsToSelector:_cmd], @"%@ is not supported by ASCollectionView - please remove or disable this data source method.", NSStringFromSelector(_cmd)); -} - -#endif - @end diff --git a/Source/Details/ASDataController.mm b/Source/Details/ASDataController.mm index 51f35d49a..512b844fa 100644 --- a/Source/Details/ASDataController.mm +++ b/Source/Details/ASDataController.mm @@ -526,7 +526,7 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet BOOL canDelegate = (self.layoutDelegate != nil); ASElementMap *newMap; - id layoutContext; + ASCollectionLayoutContext *layoutContext; { as_activity_scope(as_activity_create("Latch new data for collection update", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT)); diff --git a/Source/Layout/ASLayoutElement.h b/Source/Layout/ASLayoutElement.h index ca56c6722..e84c54569 100644 --- a/Source/Layout/ASLayoutElement.h +++ b/Source/Layout/ASLayoutElement.h @@ -192,7 +192,7 @@ extern NSString * const ASLayoutElementStyleLayoutPositionProperty; #pragma mark - Sizing /** - * @abstract The width property specifies the height of the content area of an ASLayoutElement. + * @abstract The width property specifies the width of the content area of an ASLayoutElement. * The minWidth and maxWidth properties override width. * Defaults to ASDimensionAuto */ diff --git a/Source/Private/ASCollectionLayout.mm b/Source/Private/ASCollectionLayout.mm index 59273c975..eafc445a8 100644 --- a/Source/Private/ASCollectionLayout.mm +++ b/Source/Private/ASCollectionLayout.mm @@ -158,6 +158,21 @@ - (void)invalidateLayout } } +/** + * NOTE: It is suggested practice on the Web to override invalidationContextForInteractivelyMovingItems… and call out to the + * data source to move the item (so that if e.g. the item size depends on the data, you get the data you expect). However, as of iOS 11 this + * doesn't work, because UICV machinery will also call out to the data source to move the item after the interaction is done. The result is + * that your data source state will be incorrect due to this last move call. Plus it's just an API violation. + * + * Things tried: + * - Doing the speculative data source moves, and then UNDOING the last one in invalidationContextForEndingInteractiveMovementOfItems… + * but this does not work because the UICV machinery informs its data source before it calls that method on us, so we are too late. + * + * The correct practice is to use the UIDataSourceTranslating API introduced in iOS 11. Currently Texture does not support this API but we can + * build it if there is demand. We could add an id field onto the layout context object, and the layout client can + * use data source index paths when it reads nodes or other data source data. + */ + - (CGSize)collectionViewContentSize { ASDisplayNodeAssertMainThread(); diff --git a/examples/ASCollectionView/Sample/ViewController.m b/examples/ASCollectionView/Sample/ViewController.m index 3755b01d6..10c974776 100644 --- a/examples/ASCollectionView/Sample/ViewController.m +++ b/examples/ASCollectionView/Sample/ViewController.m @@ -23,10 +23,13 @@ #define ASYNC_COLLECTION_LAYOUT 0 +static CGSize const kItemSize = (CGSize){180, 90}; + @interface ViewController () @property (nonatomic, strong) ASCollectionNode *collectionNode; -@property (nonatomic, strong) NSArray *data; +@property (nonatomic, strong) NSMutableArray *> *data; +@property (nonatomic, strong) UILongPressGestureRecognizer *moveRecognizer; @end @@ -34,18 +37,13 @@ @implementation ViewController #pragma mark - Lifecycle -- (void)dealloc -{ - self.collectionNode.dataSource = nil; - self.collectionNode.delegate = nil; - - NSLog(@"ViewController is deallocing"); -} - - (void)viewDidLoad { [super viewDidLoad]; + self.moveRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleLongPress)]; + [self.view addGestureRecognizer:self.moveRecognizer]; + #if ASYNC_COLLECTION_LAYOUT ASCollectionGalleryLayoutDelegate *layoutDelegate = [[ASCollectionGalleryLayoutDelegate alloc] initWithScrollableDirections:ASScrollDirectionVerticalDirections]; layoutDelegate.propertiesProvider = self; @@ -54,6 +52,7 @@ - (void)viewDidLoad UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init]; layout.headerReferenceSize = CGSizeMake(50.0, 50.0); layout.footerReferenceSize = CGSizeMake(50.0, 50.0); + layout.itemSize = kItemSize; self.collectionNode = [[ASCollectionNode alloc] initWithFrame:self.view.bounds collectionViewLayout:layout]; [self.collectionNode registerSupplementaryNodeOfKind:UICollectionElementKindSectionHeader]; [self.collectionNode registerSupplementaryNodeOfKind:UICollectionElementKindSectionFooter]; @@ -73,34 +72,37 @@ - (void)viewDidLoad self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(reloadTapped)]; -#endif - -#if SIMULATE_WEB_RESPONSE + [self loadData]; +#else __weak typeof(self) weakSelf = self; - void(^mockWebService)() = ^{ - NSLog(@"ViewController \"got data from a web service\""); - ViewController *strongSelf = weakSelf; - if (strongSelf != nil) - { - NSLog(@"ViewController is not nil"); - strongSelf->_data = [[NSArray alloc] init]; - [strongSelf->_collectionNode performBatchUpdates:^{ - [strongSelf->_collectionNode insertSections:[[NSIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, 100)]]; - } completion:nil]; - NSLog(@"ViewController finished updating collectionNode"); - } - else { - NSLog(@"ViewController is nil - won't update collectionNode"); - } - }; - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), mockWebService); - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - [self.navigationController popViewControllerAnimated:YES]; + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + [weakSelf handleSimulatedWebResponse]; }); #endif } +- (void)handleSimulatedWebResponse +{ + [self.collectionNode performBatchUpdates:^{ + [self loadData]; + [self.collectionNode insertSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.data.count)]]; + } completion:nil]; +} + +- (void)loadData +{ + // Form our data array + typeof(self.data) data = [NSMutableArray array]; + for (NSInteger s = 0; s < 100; s++) { + NSMutableArray *items = [NSMutableArray array]; + for (NSInteger i = 0; i < 10; i++) { + items[i] = [NSString stringWithFormat:@"[%zd.%zd] says hi", s, i]; + } + data[s] = items; + } + self.data = data; +} + #pragma mark - Button Actions - (void)reloadTapped @@ -115,14 +117,42 @@ - (void)reloadTapped - (CGSize)galleryLayoutDelegate:(ASCollectionGalleryLayoutDelegate *)delegate sizeForElements:(ASElementMap *)elements { ASDisplayNodeAssertMainThread(); - return CGSizeMake(180, 90); + return kItemSize; +} + +- (void)handleLongPress +{ + UICollectionView *collectionView = self.collectionNode.view; + CGPoint location = [self.moveRecognizer locationInView:collectionView]; + switch (self.moveRecognizer.state) { + case UIGestureRecognizerStateBegan: { + NSIndexPath *indexPath = [collectionView indexPathForItemAtPoint:location]; + if (indexPath) { + [collectionView beginInteractiveMovementForItemAtIndexPath:indexPath]; + } + break; + } + case UIGestureRecognizerStateChanged: + [collectionView updateInteractiveMovementTargetPosition:location]; + break; + case UIGestureRecognizerStateEnded: + [collectionView endInteractiveMovement]; + break; + case UIGestureRecognizerStateFailed: + case UIGestureRecognizerStateCancelled: + [collectionView cancelInteractiveMovement]; + break; + case UIGestureRecognizerStatePossible: + // nop + break; + } } -#pragma mark - ASCollectionView Data Source +#pragma mark - ASCollectionDataSource - (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath; { - NSString *text = [NSString stringWithFormat:@"[%zd.%zd] says hi", indexPath.section, indexPath.item]; + NSString *text = self.data[indexPath.section][indexPath.item]; return ^{ return [[ItemNode alloc] initWithString:text]; }; @@ -139,18 +169,29 @@ - (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForSupplem - (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section { - return 10; + return self.data[section].count; } - (NSInteger)numberOfSectionsInCollectionNode:(ASCollectionNode *)collectionNode { -#if SIMULATE_WEB_RESPONSE - return _data == nil ? 0 : 100; -#else - return 100; -#endif + return self.data.count; +} + +- (BOOL)collectionNode:(ASCollectionNode *)collectionNode canMoveItemWithNode:(ASCellNode *)node +{ + return YES; } +- (void)collectionNode:(ASCollectionNode *)collectionNode moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath +{ + __auto_type sectionArray = self.data[sourceIndexPath.section]; + __auto_type object = sectionArray[sourceIndexPath.item]; + [sectionArray removeObjectAtIndex:sourceIndexPath.item]; + [self.data[destinationIndexPath.section] insertObject:object atIndex:destinationIndexPath.item]; +} + +#pragma mark - ASCollectionDelegate + - (void)collectionNode:(ASCollectionNode *)collectionNode willBeginBatchFetchWithContext:(ASBatchContext *)context { NSLog(@"fetch additional content"); From 1dfdb484971dd268f0c740cb3788ba61863d86b1 Mon Sep 17 00:00:00 2001 From: Sudhanshu Date: Wed, 10 Jan 2018 23:45:53 +0530 Subject: [PATCH 051/133] Add MensXP to Showcase (#739) This change is for adding "MensXP: Fashion, Grooming tips" iOS App to Texture showcase. --- docs/showcase.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/showcase.md b/docs/showcase.md index 4b3e68233..95b72f331 100755 --- a/docs/showcase.md +++ b/docs/showcase.md @@ -229,6 +229,16 @@ permalink: /showcase.html + + + + +
+ MensXP + + + +
From 3708f2e44848b71f6986b5bc034825d09832b062 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Fri, 12 Jan 2018 12:56:19 -0800 Subject: [PATCH 052/133] Update CHANGELOG for 2.6 --- CHANGELOG.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5281189d..4b3196be3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,6 @@ - [ASCollectionView] Add missing flags for ASCollectionDelegate [Ilya Zheleznikov](https://github.com/ilyailya) [#718](https://github.com/TextureGroup/Texture/pull/718) - [ASNetworkImageNode] Deprecates .URLs in favor of .URL [Garrett Moon](https://github.com/garrettmoon) [#699](https://github.com/TextureGroup/Texture/pull/699) - [iOS11] Update project settings and fix errors [Eke](https://github.com/Eke) [#676](https://github.com/TextureGroup/Texture/pull/676) -- [ASCollectionView] Improve performance and behavior of rotation / bounds changes. [Scott Goodson](https://github.com/appleguy) [#431](https://github.com/TextureGroup/Texture/pull/431) -- [ASCollectionView] Improve index space translation of Flow Layout Delegate methods. [Scott Goodson](https://github.com/appleguy) -- [Animated Image] Adds support for animated WebP as well as improves GIF handling. [#605](https://github.com/TextureGroup/Texture/pull/605) [Garrett Moon](https://github.com/garrettmoon) -- [ASCollectionView] Check if batch fetching is needed if batch fetching parameter has been changed. [#624](https://github.com/TextureGroup/Texture/pull/624) [Garrett Moon](https://github.com/garrettmoon) -- [ASNetworkImageNode] New delegate callback to tell the consumer whether the image was loaded from cache or download. [Adlai Holler](https://github.com/Adlai-Holler) -- [Layout] Fixes a deadlock in layout. [#638](https://github.com/TextureGroup/Texture/pull/638) [Garrett Moon](https://github.com/garrettmoon) -- Updated to be backwards compatible with Xcode 8. [Adlai Holler](https://github.com/Adlai-Holler) -- [API CHANGES] `ASPerformMainThreadDeallocation` and `ASPerformBackgroundDeallocation` functions take `id *` instead of `id` and they're now more reliable. Also, in Swift, `ASDeallocQueue.sharedDeallocationQueue() -> ASDeallocQueue.sharedDeallocationQueue`. [Adlai Holler](https://github.com/Adlai-Holler) [#651](https://github.com/TextureGroup/Texture/pull/651) -- [Collection/Table] Added direct support for mapping section indexes between data spaces. [Adlai Holler](https://github.com/Adlai-Holler) [#651](https://github.com/TextureGroup/Texture/pull/660) - [ASCornerLayoutSpec] New layout spec class for declarative corner element layout. [#657](https://github.com/TextureGroup/Texture/pull/657) [huangkun](https://github.com/huang-kun) - [Layout] Fix an issue that causes a pending layout to be applied multiple times. [Huy Nguyen](https://github.com/nguyenhuy) [#695](https://github.com/TextureGroup/Texture/pull/695) - [ASScrollNode] Ensure the node respects the given size range while calculating its layout. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) @@ -23,6 +14,15 @@ ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) +- [ASCollectionView] Improve performance and behavior of rotation / bounds changes. [Scott Goodson](https://github.com/appleguy) [#431](https://github.com/TextureGroup/Texture/pull/431) +- [ASCollectionView] Improve index space translation of Flow Layout Delegate methods. [Scott Goodson](https://github.com/appleguy) +- [Animated Image] Adds support for animated WebP as well as improves GIF handling. [#605](https://github.com/TextureGroup/Texture/pull/605) [Garrett Moon](https://github.com/garrettmoon) +- [ASCollectionView] Check if batch fetching is needed if batch fetching parameter has been changed. [#624](https://github.com/TextureGroup/Texture/pull/624) [Garrett Moon](https://github.com/garrettmoon) +- [ASNetworkImageNode] New delegate callback to tell the consumer whether the image was loaded from cache or download. [Adlai Holler](https://github.com/Adlai-Holler) +- [Layout] Fixes a deadlock in layout. [#638](https://github.com/TextureGroup/Texture/pull/638) [Garrett Moon](https://github.com/garrettmoon) +- Updated to be backwards compatible with Xcode 8. [Adlai Holler](https://github.com/Adlai-Holler) +- [API CHANGES] `ASPerformMainThreadDeallocation` and `ASPerformBackgroundDeallocation` functions take `id *` instead of `id` and they're now more reliable. Also, in Swift, `ASDeallocQueue.sharedDeallocationQueue() -> ASDeallocQueue.sharedDeallocationQueue`. [Adlai Holler](https://github.com/Adlai-Holler) [#651](https://github.com/TextureGroup/Texture/pull/651) +- [Collection/Table] Added direct support for mapping section indexes between data spaces. [Adlai Holler](https://github.com/Adlai-Holler) [#651](https://github.com/TextureGroup/Texture/pull/660) ## 2.5.1 - [ASVideoNode] Fix unreleased time observer. [Flo Vouin](https://github.com/flovouin) From 1d105c205623a53888743fdb7f8575cf03a225dc Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sat, 13 Jan 2018 19:19:08 -0800 Subject: [PATCH 053/133] Add an experimental "no-copy" renderer (#741) * Add "ASGraphicsContext" to skip copying our rendered images * Zero the buffer before making a context * Update license header * Update dangerfile * Make it a runtime flag * Restore GState for good measure * Free buffer if end without image * Enable the experiment, and cut out the middle-man * Fix typo --- AsyncDisplayKit.xcodeproj/project.pbxproj | 8 + CHANGELOG.md | 1 + Dangerfile | 2 +- Source/ASDisplayNode.mm | 7 +- Source/ASImageNode.mm | 25 ++- Source/ASMapNode.mm | 6 +- Source/ASTextNode.mm | 6 +- Source/AsyncDisplayKit.h | 1 + Source/Debug/AsyncDisplayKit+Debug.m | 6 +- Source/Details/ASGraphicsContext.h | 62 +++++++ Source/Details/ASGraphicsContext.m | 160 +++++++++++++++++++ Source/Private/ASDisplayNode+AsyncDisplay.mm | 22 ++- Source/UIImage+ASConvenience.m | 6 +- examples/ASDKgram/Sample/AppDelegate.m | 4 + 14 files changed, 272 insertions(+), 44 deletions(-) create mode 100644 Source/Details/ASGraphicsContext.h create mode 100644 Source/Details/ASGraphicsContext.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 3a4b1c534..f3ed289f1 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -402,6 +402,8 @@ CCCCCCE41EC3EF060087FE10 /* NSParagraphStyle+ASText.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.m */; }; CCCCCCE71EC3F0FC0087FE10 /* NSAttributedString+ASText.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */; }; CCCCCCE81EC3F0FC0087FE10 /* NSAttributedString+ASText.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.m */; }; + CCDC9B4D200991D10063C1F8 /* ASGraphicsContext.h in Headers */ = {isa = PBXBuildFile; fileRef = CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CCDC9B4E200991D10063C1F8 /* ASGraphicsContext.m in Sources */ = {isa = PBXBuildFile; fileRef = CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.m */; }; CCDD148B1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */; }; CCE4F9B31F0D60AC00062E4E /* ASIntegerMapTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B21F0D60AC00062E4E /* ASIntegerMapTests.m */; }; CCE4F9B51F0DA4F300062E4E /* ASLayoutEngineTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B41F0DA4F300062E4E /* ASLayoutEngineTests.mm */; }; @@ -895,6 +897,8 @@ CCCCCCD41EC3EF060087FE10 /* NSParagraphStyle+ASText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSParagraphStyle+ASText.m"; sourceTree = ""; }; CCCCCCE51EC3F0FC0087FE10 /* NSAttributedString+ASText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSAttributedString+ASText.h"; sourceTree = ""; }; CCCCCCE61EC3F0FC0087FE10 /* NSAttributedString+ASText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSAttributedString+ASText.m"; sourceTree = ""; }; + CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASGraphicsContext.h; sourceTree = ""; }; + CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASGraphicsContext.m; sourceTree = ""; }; CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASCollectionModernDataSourceTests.m; sourceTree = ""; }; CCE04B1E1E313EA7006AEBBB /* ASSectionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASSectionController.h; sourceTree = ""; }; CCE04B201E313EB9006AEBBB /* IGListAdapter+AsyncDisplayKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "IGListAdapter+AsyncDisplayKit.h"; sourceTree = ""; }; @@ -1270,6 +1274,8 @@ 058D09E1195D050800B7D73C /* Details */ = { isa = PBXGroup; children = ( + CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */, + CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.m */, CC5601391F06E9A700DC4FBE /* ASIntegerMap.h */, CC56013A1F06E9A700DC4FBE /* ASIntegerMap.mm */, CC0F885E1E4280B800576FED /* _ASCollectionViewCell.h */, @@ -1836,6 +1842,7 @@ 68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */, CCCCCCE11EC3EF060087FE10 /* ASTextUtilities.h in Headers */, B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */, + CCDC9B4D200991D10063C1F8 /* ASGraphicsContext.h in Headers */, E5C347B11ECB3D9200EC4BE4 /* ASBatchFetchingDelegate.h in Headers */, CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */, B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */, @@ -2268,6 +2275,7 @@ E5B078001E69F4EB00C24B5B /* ASElementMap.m in Sources */, 9C8898BC1C738BA800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */, 690ED59B1E36D118000627C0 /* ASImageNode+tvOS.m in Sources */, + CCDC9B4E200991D10063C1F8 /* ASGraphicsContext.m in Sources */, CCCCCCD81EC3EF060087FE10 /* ASTextInput.m in Sources */, 34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */, DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b3196be3..02331367f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424) - [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so the subnodes can preload too. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706) - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) +- Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Dangerfile b/Dangerfile index edd455d53..3b8169582 100644 --- a/Dangerfile +++ b/Dangerfile @@ -67,7 +67,7 @@ end # Ensure new files have proper header new_source_license_header = <<-HEREDOC -// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 3a537ca4b..0f2bf7719 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -36,6 +36,7 @@ #import #import #import +#import #import #import #import @@ -1507,7 +1508,7 @@ - (void)_updateClipCornerLayerContentsWithRadius:(CGFloat)radius backgroundColor BOOL isRight = (idx == 1 || idx == 2); CGSize size = CGSizeMake(radius + 1, radius + 1); - UIGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay); + ASGraphicsBeginImageContextWithOptions(size, NO, self.contentsScaleForDisplay); CGContextRef ctx = UIGraphicsGetCurrentContext(); if (isRight == YES) { @@ -1524,11 +1525,9 @@ - (void)_updateClipCornerLayerContentsWithRadius:(CGFloat)radius backgroundColor // No lock needed, as _clipCornerLayers is only modified on the main thread. CALayer *clipCornerLayer = _clipCornerLayers[idx]; - clipCornerLayer.contents = (id)(UIGraphicsGetImageFromCurrentImageContext().CGImage); + clipCornerLayer.contents = (id)(ASGraphicsGetImageAndEndCurrentContext().CGImage); clipCornerLayer.bounds = CGRectMake(0.0, 0.0, size.width, size.height); clipCornerLayer.anchorPoint = CGPointMake(isRight ? 1.0 : 0.0, isTop ? 1.0 : 0.0); - - UIGraphicsEndImageContext(); } [self _layoutClipCornersIfNeeded]; }); diff --git a/Source/ASImageNode.mm b/Source/ASImageNode.mm index 5e2383081..ed56c9ceb 100644 --- a/Source/ASImageNode.mm +++ b/Source/ASImageNode.mm @@ -25,6 +25,7 @@ #import #import #import +#import #import #import #import @@ -213,11 +214,10 @@ - (UIImage *)placeholderImage ASDN::MutexLocker l(__instanceLock__); - UIGraphicsBeginImageContext(size); + ASGraphicsBeginImageContextWithOptions(size, NO, 1); [self.placeholderColor setFill]; UIRectFill(CGRectMake(0, 0, size.width, size.height)); - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + UIImage *image = ASGraphicsGetImageAndEndCurrentContext(); return image; } @@ -472,7 +472,7 @@ + (ASWeakMapEntry *)contentsForkey:(ASImageNodeContentsKey *)key drawParameters: + (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:(id)drawParameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelled { - // The following `UIGraphicsBeginImageContextWithOptions` call will sometimes take take longer than 5ms on an + // The following `ASGraphicsBeginImageContextWithOptions` call will sometimes take take longer than 5ms on an // A5 processor for a 400x800 backingSize. // Check for cancellation before we call it. if (isCancelled()) { @@ -481,7 +481,7 @@ + (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:( // Use contentsScale of 1.0 and do the contentsScale handling in boundsSizeInPixels so ASCroppedImageBackingSizeAndDrawRectInBounds // will do its rounding on pixel instead of point boundaries - UIGraphicsBeginImageContextWithOptions(key.backingSize, key.isOpaque, 1.0); + ASGraphicsBeginImageContextWithOptions(key.backingSize, key.isOpaque, 1.0); BOOL contextIsClean = YES; @@ -529,9 +529,7 @@ + (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:( return nil; } - UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); - - UIGraphicsEndImageContext(); + UIImage *result = ASGraphicsGetImageAndEndCurrentContext(); if (key.imageModificationBlock) { result = key.imageModificationBlock(result); @@ -742,7 +740,7 @@ - (NSDictionary *)debugLabelAttributes extern asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock(CGFloat borderWidth, UIColor *borderColor) { return ^(UIImage *originalImage) { - UIGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); + ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); UIBezierPath *roundOutline = [UIBezierPath bezierPathWithOvalInRect:(CGRect){CGPointZero, originalImage.size}]; // Make the image round @@ -758,24 +756,21 @@ extern asimagenode_modification_block_t ASImageNodeRoundBorderModificationBlock( [roundOutline stroke]; } - UIImage *modifiedImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); - return modifiedImage; + return ASGraphicsGetImageAndEndCurrentContext(); }; } extern asimagenode_modification_block_t ASImageNodeTintColorModificationBlock(UIColor *color) { return ^(UIImage *originalImage) { - UIGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); + ASGraphicsBeginImageContextWithOptions(originalImage.size, NO, originalImage.scale); // Set color and render template [color setFill]; UIImage *templateImage = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; [templateImage drawAtPoint:CGPointZero blendMode:kCGBlendModeCopy alpha:1]; - UIImage *modifiedImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + UIImage *modifiedImage = ASGraphicsGetImageAndEndCurrentContext(); // if the original image was stretchy, keep it stretchy if (!UIEdgeInsetsEqualToEdgeInsets(originalImage.capInsets, UIEdgeInsetsZero)) { diff --git a/Source/ASMapNode.mm b/Source/ASMapNode.mm index a9394a4d7..4697a2c05 100644 --- a/Source/ASMapNode.mm +++ b/Source/ASMapNode.mm @@ -24,6 +24,7 @@ #import #import +#import #import #import #import @@ -222,7 +223,7 @@ - (void)takeSnapshot CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height); - UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); + ASGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); [image drawAtPoint:CGPointZero]; UIImage *pinImage; @@ -254,8 +255,7 @@ - (void)takeSnapshot } } - image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + image = ASGraphicsGetImageAndEndCurrentContext(); } strongSelf.image = image; diff --git a/Source/ASTextNode.mm b/Source/ASTextNode.mm index e5338f521..c3343528a 100644 --- a/Source/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -28,6 +28,7 @@ #import #import #import +#import #import #import @@ -907,7 +908,7 @@ - (UIImage *)placeholderImage ASDN::MutexLocker l(__instanceLock__); - UIGraphicsBeginImageContext(size); + ASGraphicsBeginImageContextWithOptions(size, NO, 1.0); [self.placeholderColor setFill]; ASTextKitRenderer *renderer = [self _locked_renderer]; @@ -926,8 +927,7 @@ - (UIImage *)placeholderImage } } - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + UIImage *image = ASGraphicsGetImageAndEndCurrentContext(); return image; } diff --git a/Source/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h index c20f5077a..a3935ec21 100644 --- a/Source/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -119,6 +119,7 @@ #import #import #import +#import #import #import #import diff --git a/Source/Debug/AsyncDisplayKit+Debug.m b/Source/Debug/AsyncDisplayKit+Debug.m index 20d67cd70..3225700e0 100644 --- a/Source/Debug/AsyncDisplayKit+Debug.m +++ b/Source/Debug/AsyncDisplayKit+Debug.m @@ -17,6 +17,7 @@ #import #import +#import #import #import #import @@ -148,7 +149,7 @@ - (void)layout UIColor *clipsBorderColor = [UIColor colorWithRed:30/255.0 green:90/255.0 blue:50/255.0 alpha:0.7]; CGRect imgRect = CGRectMake(0, 0, 2.0 * borderWidth + 1.0, 2.0 * borderWidth + 1.0); - UIGraphicsBeginImageContext(imgRect.size); + ASGraphicsBeginImageContextWithOptions(imgRect.size, NO, 1); [fillColor setFill]; UIRectFill(imgRect); @@ -156,8 +157,7 @@ - (void)layout [self drawEdgeIfClippedWithEdges:clippedEdges color:clipsBorderColor borderWidth:borderWidth imgRect:imgRect]; [self drawEdgeIfClippedWithEdges:clipsToBoundsClippedEdges color:borderColor borderWidth:borderWidth imgRect:imgRect]; - UIImage *debugHighlightImage = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + UIImage *debugHighlightImage = ASGraphicsGetImageAndEndCurrentContext(); UIEdgeInsets edgeInsets = UIEdgeInsetsMake(borderWidth, borderWidth, borderWidth, borderWidth); debugOverlay.image = [debugHighlightImage resizableImageWithCapInsets:edgeInsets resizingMode:UIImageResizingModeStretch]; diff --git a/Source/Details/ASGraphicsContext.h b/Source/Details/ASGraphicsContext.h new file mode 100644 index 000000000..0a1c80fc4 --- /dev/null +++ b/Source/Details/ASGraphicsContext.h @@ -0,0 +1,62 @@ +// +// ASGraphicsContext.h +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import +#import + +@class UIImage; + +/** + * Functions for creating one-shot graphics contexts that do not have to copy + * their contents when an image is generated from them. This is efficient + * for our use, since we do not reuse graphics contexts. + * + * The API mirrors the UIGraphics API, with the exception that forming an image + * ends the context as well. + */ + +NS_ASSUME_NONNULL_BEGIN +ASDISPLAYNODE_EXTERN_C_BEGIN + +/** + * Call this to enable the experimental no-copy rendering. + * + * Returns YES if it was enabled, or NO + assert if it's too late because + * rendering has already started. In practice it's fine to call this + * during -didFinishLaunchingWithOptions:. + */ +extern BOOL ASEnableNoCopyRendering(void); + +/** + * Creates a one-shot context. + * + * Behavior is the same as UIGraphicsBeginImageContextWithOptions. + */ +extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale); + +/** + * Generates and image and ends the current one-shot context. + * + * Behavior is the same as UIGraphicsGetImageFromCurrentImageContext followed by UIGraphicsEndImageContext. + */ +extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext(void); + +/** + * Call this if you want to end the current context without making an image. + * + * Behavior is the same as UIGraphicsEndImageContext. + */ +extern void ASGraphicsEndImageContext(void); + +ASDISPLAYNODE_EXTERN_C_END +NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASGraphicsContext.m b/Source/Details/ASGraphicsContext.m new file mode 100644 index 000000000..1c3092598 --- /dev/null +++ b/Source/Details/ASGraphicsContext.m @@ -0,0 +1,160 @@ +// +// ASGraphicsContext.m +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import "ASGraphicsContext.h" +#import +#import +#import +#import + +#pragma mark - Feature Gating + +// Two flags that we atomically manipulate to control the feature. +typedef NS_OPTIONS(uint, ASNoCopyFlags) { + ASNoCopyEnabled = 1 << 0, + ASNoCopyBlocked = 1 << 1 +}; +static atomic_uint __noCopyFlags; + +// Check if it's blocked, and set the enabled flag if not. +extern BOOL ASEnableNoCopyRendering() +{ + ASNoCopyFlags expectedFlags = 0; + BOOL enabled = atomic_compare_exchange_strong(&__noCopyFlags, &expectedFlags, ASNoCopyEnabled); + ASDisplayNodeCAssert(enabled, @"Can't enable no-copy rendering after first render started."); + return enabled; +} + +// Check if it's enabled and set the "blocked" flag either way. +static BOOL ASNoCopyRenderingBlockAndCheckEnabled() { + ASNoCopyFlags oldFlags = atomic_fetch_or(&__noCopyFlags, ASNoCopyBlocked); + return (oldFlags & ASNoCopyEnabled) != 0; +} + +#pragma mark - Callbacks + +void _ASReleaseCGDataProviderData(__unused void *info, const void *data, __unused size_t size) +{ + free((void *)data); +} + +#pragma mark - Graphics Contexts + +extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) +{ + if (!ASNoCopyRenderingBlockAndCheckEnabled()) { + UIGraphicsBeginImageContextWithOptions(size, opaque, scale); + return; + } + + // Only create device RGB color space once. UIGraphics actually doesn't do this but it's safe. + static dispatch_once_t onceToken; + static CGFloat defaultScale; + static CGColorSpaceRef deviceRGB; + dispatch_once(&onceToken, ^{ + deviceRGB = CGColorSpaceCreateDeviceRGB(); + UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 0); + CGContextRef uikitContext = UIGraphicsGetCurrentContext(); + defaultScale = CGContextGetCTM(uikitContext).a; + UIGraphicsEndImageContext(); + }); + + // These options are taken from UIGraphicsBeginImageContext. + CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | (opaque ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaPremultipliedFirst); + + if (scale == 0) { + scale = defaultScale; + } + size_t intWidth = (size_t)ceil(size.width * scale); + size_t intHeight = (size_t)ceil(size.height * scale); + size_t bytesPerPixel = 4; + size_t bytesPerRow = bytesPerPixel * intWidth; + size_t bufferSize = bytesPerRow * intHeight; + + // We create our own buffer, and wrap the context around that. This way we can prevent + // the copy that usually gets made when you form a CGImage from the context. + void *buf = calloc(bufferSize, 1); + CGContextRef context = CGBitmapContextCreate(buf, intWidth, intHeight, 8, bytesPerRow, deviceRGB, bitmapInfo); + + // Set the CTM to account for iOS orientation & specified scale. + // If only we could use CGContextSetBaseCTM. It doesn't + // seem like there are any consequences for our use case + // but we'll be on the look out. The internet hinted that it + // affects shadowing but I tested and shadowing works. + CGContextTranslateCTM(context, 0, intHeight); + CGContextScaleCTM(context, scale, -scale); + + // Save the state so we can restore it and recover our scale in GetImageAndEnd + CGContextSaveGState(context); + + UIGraphicsPushContext(context); +} + +extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext() +{ + if (!ASNoCopyRenderingBlockAndCheckEnabled()) { + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; + } + + // Pop the context and make sure we have one. + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context == NULL) { + ASDisplayNodeCFailAssert(@"Can't end image context without having begun one."); + return nil; + } + UIGraphicsPopContext(); + + // Do some math to get the image properties. + size_t width = CGBitmapContextGetWidth(context); + size_t height = CGBitmapContextGetHeight(context); + size_t bitsPerPixel = CGBitmapContextGetBitsPerPixel(context); + size_t bytesPerRow = CGBitmapContextGetBytesPerRow(context); + size_t bufferSize = bytesPerRow * height; + + // This is the buf that we malloc'd above. + void *buf = CGBitmapContextGetData(context); + + // Wrap it in a CGDataProvider, passing along our release callback for when the CGImage dies. + CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buf, bufferSize, _ASReleaseCGDataProviderData); + + // Create the CGImage. Options taken from CGBitmapContextCreateImage. + CGImageRef cgImg = CGImageCreate(width, height, CGBitmapContextGetBitsPerComponent(context), bitsPerPixel, bytesPerRow, CGBitmapContextGetColorSpace(context), CGBitmapContextGetBitmapInfo(context), provider, NULL, true, kCGRenderingIntentDefault); + CGDataProviderRelease(provider); + + // We saved our GState right after setting the CTM so that we could restore it + // here and get the original scale back. + CGContextRestoreGState(context); + CGFloat scale = CGContextGetCTM(context).a; + CGContextRelease(context); + + UIImage *result = [[UIImage alloc] initWithCGImage:cgImg scale:scale orientation:UIImageOrientationUp]; + CGImageRelease(cgImg); + return result; +} + +extern void ASGraphicsEndImageContext() +{ + if (!ASNoCopyRenderingBlockAndCheckEnabled()) { + UIGraphicsEndImageContext(); + return; + } + + CGContextRef context = UIGraphicsGetCurrentContext(); + if (context) { + // We manually allocated this buffer so we need to free it. + free(CGBitmapContextGetData(context)); + CGContextRelease(context); + UIGraphicsPopContext(); + } +} diff --git a/Source/Private/ASDisplayNode+AsyncDisplay.mm b/Source/Private/ASDisplayNode+AsyncDisplay.mm index 986f3acf9..96426d479 100644 --- a/Source/Private/ASDisplayNode+AsyncDisplay.mm +++ b/Source/Private/ASDisplayNode+AsyncDisplay.mm @@ -21,6 +21,7 @@ #import #import #import +#import #import #import #import @@ -218,15 +219,14 @@ - (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchro displayBlock = ^id{ CHECK_CANCELLED_AND_RETURN_NIL(); - UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay); + ASGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay); for (dispatch_block_t block in displayBlocks) { - CHECK_CANCELLED_AND_RETURN_NIL(UIGraphicsEndImageContext()); + CHECK_CANCELLED_AND_RETURN_NIL(ASGraphicsEndImageContext()); block(); } - UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + UIImage *image = ASGraphicsGetImageAndEndCurrentContext(); ASDN_DELAY_FOR_DISPLAY(); return image; @@ -236,8 +236,8 @@ - (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchro CHECK_CANCELLED_AND_RETURN_NIL(); if (shouldCreateGraphicsContext) { - UIGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay); - CHECK_CANCELLED_AND_RETURN_NIL( UIGraphicsEndImageContext(); ); + ASGraphicsBeginImageContextWithOptions(bounds.size, opaque, contentsScaleForDisplay); + CHECK_CANCELLED_AND_RETURN_NIL( ASGraphicsEndImageContext(); ); } CGContextRef currentContext = UIGraphicsGetCurrentContext(); @@ -256,9 +256,8 @@ - (asyncdisplaykit_async_transaction_operation_block_t)_displayBlockWithAsynchro [self __didDisplayNodeContentWithRenderingContext:currentContext image:&image drawParameters:drawParameters backgroundColor:backgroundColor borderWidth:borderWidth borderColor:borderColor]; if (shouldCreateGraphicsContext) { - CHECK_CANCELLED_AND_RETURN_NIL( UIGraphicsEndImageContext(); ); - image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + CHECK_CANCELLED_AND_RETURN_NIL( ASGraphicsEndImageContext(); ); + image = ASGraphicsGetImageAndEndCurrentContext(); } ASDN_DELAY_FOR_DISPLAY(); @@ -332,7 +331,7 @@ - (void)__didDisplayNodeContentWithRenderingContext:(CGContextRef)context image: bounds.size.height *= contentsScale; CGFloat white = 0.0f, alpha = 0.0f; [backgroundColor getWhite:&white alpha:&alpha]; - UIGraphicsBeginImageContextWithOptions(bounds.size, (alpha == 1.0f), contentsScale); + ASGraphicsBeginImageContextWithOptions(bounds.size, (alpha == 1.0f), contentsScale); [*image drawInRect:bounds]; } else { bounds = CGContextGetClipBoundingBox(context); @@ -362,8 +361,7 @@ - (void)__didDisplayNodeContentWithRenderingContext:(CGContextRef)context image: [roundedPath stroke]; // Won't do anything if borderWidth is 0 and roundedPath is nil. if (*image) { - *image = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + *image = ASGraphicsGetImageAndEndCurrentContext(); } } } diff --git a/Source/UIImage+ASConvenience.m b/Source/UIImage+ASConvenience.m index efc1e3536..35e6f0d56 100644 --- a/Source/UIImage+ASConvenience.m +++ b/Source/UIImage+ASConvenience.m @@ -16,6 +16,7 @@ // #import +#import #import #import @@ -138,7 +139,7 @@ + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius // We should probably check if the background color has any alpha component but that // might be expensive due to needing to check mulitple color spaces. - UIGraphicsBeginImageContextWithOptions(bounds.size, cornerColor != nil, scale); + ASGraphicsBeginImageContextWithOptions(bounds.size, cornerColor != nil, scale); BOOL contextIsClean = YES; if (cornerColor) { @@ -168,8 +169,7 @@ + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius [strokePath strokeWithBlendMode:(canUseCopy ? kCGBlendModeCopy : kCGBlendModeNormal) alpha:1]; } - UIImage *result = UIGraphicsGetImageFromCurrentImageContext(); - UIGraphicsEndImageContext(); + UIImage *result = ASGraphicsGetImageAndEndCurrentContext(); UIEdgeInsets capInsets = UIEdgeInsetsMake(cornerRadius, cornerRadius, cornerRadius, cornerRadius); result = [result resizableImageWithCapInsets:capInsets resizingMode:UIImageResizingModeStretch]; diff --git a/examples/ASDKgram/Sample/AppDelegate.m b/examples/ASDKgram/Sample/AppDelegate.m index 16484467f..21c47a273 100644 --- a/examples/ASDKgram/Sample/AppDelegate.m +++ b/examples/ASDKgram/Sample/AppDelegate.m @@ -22,6 +22,8 @@ #import "WindowWithStatusBarUnderlay.h" #import "Utilities.h" +#import + #define WEAVER 0 #if WEAVER @@ -38,6 +40,8 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + ASEnableNoCopyRendering(); + // this UIWindow subclass is neccessary to make the status bar opaque _window = [[WindowWithStatusBarUnderlay alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; _window.backgroundColor = [UIColor whiteColor]; From 61dade6bda20ff1ad0003fbd79159046705fcb55 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 15 Jan 2018 15:13:05 -0800 Subject: [PATCH 054/133] Raise deployment target to iOS 9 (#743) https://github.com/TextureGroup/Texture/pull/743 Manually merged since I forgot to retarget that diff onto master before merge --- AsyncDisplayKit.xcodeproj/project.pbxproj | 6 +-- CHANGELOG.md | 1 + Podfile | 2 +- Source/ASCellNode.mm | 2 +- Source/ASCollectionView.mm | 35 ------------- Source/ASDisplayNode+Yoga.mm | 10 ++-- Source/ASMultiplexImageNode.mm | 9 +++- Source/ASRunLoopQueue.mm | 5 -- Source/ASTableView.mm | 13 ----- Source/ASTextNode.mm | 2 + Source/Base/ASAvailability.h | 5 -- Source/Details/ASTraitCollection.m | 39 +++++--------- Source/Private/ASDisplayNode+UIViewBridge.mm | 11 ++-- .../TextExperiment/Component/ASTextLayout.m | 18 +------ .../TextExperiment/String/ASTextAttribute.m | 3 ++ .../Utility/NSAttributedString+ASText.h | 15 ------ .../Utility/NSAttributedString+ASText.m | 51 +++---------------- Source/Private/_ASPendingState.mm | 14 ++--- .../ASDKListKit.xcodeproj/project.pbxproj | 8 +-- SubspecWorkspaces/ASDKListKit/Podfile | 2 +- Texture.podspec | 2 +- examples/ASCollectionView/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 11 ++-- examples/ASDKLayoutTransition/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 9 ++-- examples/ASDKTube/Podfile | 2 +- .../ASDKTube/Sample.xcodeproj/project.pbxproj | 9 ++-- examples/ASDKgram/Podfile | 2 +- .../ASDKgram/Sample.xcodeproj/project.pbxproj | 9 ++-- examples/ASMapNode/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 9 ++-- examples/ASViewController/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 9 ++-- examples/AnimatedGIF/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 5 +- examples/AsyncDisplayKitOverview/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 9 ++-- examples/CatDealsCollectionView/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 9 ++-- examples/CustomCollectionView-Swift/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- examples/CustomCollectionView/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 11 ++-- .../CustomCollectionView/Sample/AppDelegate.m | 18 +++---- .../HorizontalWithinVerticalScrolling/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 27 +++++----- examples/Kittens/Podfile | 2 +- .../Kittens/Sample.xcodeproj/project.pbxproj | 27 +++++----- examples/LayoutSpecExamples-Swift/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- examples/LayoutSpecExamples/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- examples/PagerNode/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 27 +++++----- examples/SocialAppLayout-Inverted/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 9 ++-- examples/SocialAppLayout/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 27 +++++----- examples/Swift/Podfile | 2 +- .../Swift/Sample.xcodeproj/project.pbxproj | 4 +- .../VerticalWithinHorizontalScrolling/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 27 +++++----- examples/Videos/Podfile | 2 +- .../Videos/Sample.xcodeproj/project.pbxproj | 27 +++++----- .../ASLayoutSpecPlayground-Swift/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 11 ++-- .../contents.xcworkspacedata | 10 ++++ examples_extra/ASTableViewStressTest/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 29 ++++++----- .../contents.xcworkspacedata | 10 ++++ examples_extra/ASTraitCollection/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- .../BackgroundPropertySetting/Podfile | 2 +- .../Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- examples_extra/EditableText/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- examples_extra/Multiplex/Podfile | 2 +- examples_extra/Placeholders/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- examples_extra/RepoSearcher/Podfile | 3 +- .../RepoSearcher.xcodeproj/project.pbxproj | 4 +- .../Shop/Shop.xcodeproj/project.pbxproj | 4 +- examples_extra/SynchronousConcurrency/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 27 +++++----- .../contents.xcworkspacedata | 10 ++++ .../Sample/AsyncTableViewController.m | 4 +- .../Sample/AsyncViewController.h | 2 +- .../Sample/RandomCoreGraphicsNode.m | 2 +- examples_extra/SynchronousKittens/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- examples_extra/TextStressTest/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- .../contents.xcworkspacedata | 10 ++++ examples_extra/VideoTableView/Podfile | 2 +- .../Sample.xcodeproj/project.pbxproj | 4 +- .../Sample.xcodeproj/project.pbxproj | 4 +- .../project.pbxproj | 4 +- 98 files changed, 361 insertions(+), 415 deletions(-) create mode 100644 examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcworkspace/contents.xcworkspacedata create mode 100644 examples_extra/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata create mode 100644 examples_extra/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata create mode 100644 examples_extra/TextStressTest/Sample.xcworkspace/contents.xcworkspacedata diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index f3ed289f1..a04b06421 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -2509,7 +2509,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2553,7 +2553,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -2702,7 +2702,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; diff --git a/CHANGELOG.md b/CHANGELOG.md index 02331367f..c884b8023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so the subnodes can preload too. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706) - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) - Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) +- Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Podfile b/Podfile index 0ebd8c98c..21e72482c 100644 --- a/Podfile +++ b/Podfile @@ -1,6 +1,6 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target :'AsyncDisplayKitTests' do pod 'OCMock', '~> 3.4' diff --git a/Source/ASCellNode.mm b/Source/ASCellNode.mm index 400be0796..a3816df18 100644 --- a/Source/ASCellNode.mm +++ b/Source/ASCellNode.mm @@ -87,7 +87,7 @@ - (void)didLoad if ([_viewController isKindOfClass:[ASViewController class]]) { ASViewController *asViewController = (ASViewController *)_viewController; _viewControllerNode = asViewController.node; - [_viewController view]; + [_viewController loadViewIfNeeded]; } else { // Careful to avoid retain cycle UIViewController *viewController = _viewController; diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index fda3ea803..1b06d6bef 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -125,15 +125,6 @@ @interface ASCollectionView () = 9. - */ - CALayer *_retainedLayer; - /** * If YES, the `UICollectionView` will reload its data on next layout pass so we should not forward any updates to it. @@ -316,10 +307,6 @@ - (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionV [self registerClass:[_ASCollectionViewCell class] forCellWithReuseIdentifier:kReuseIdentifier]; - if (!AS_AT_LEAST_IOS9) { - _retainedLayer = self.layer; - } - [self _configureCollectionViewLayout:layout]; return self; @@ -2039,28 +2026,6 @@ - (ASRangeController *)rangeController return _rangeController; } -/// The UIKit version of this method is only available on iOS >= 9 -- (NSArray *)asdk_indexPathsForVisibleSupplementaryElementsOfKind:(NSString *)kind -{ - if (AS_AVAILABLE_IOS(9)) { - return [self indexPathsForVisibleSupplementaryElementsOfKind:kind]; - } - - // iOS 8 workaround - // We cannot use willDisplaySupplementaryView/didEndDisplayingSupplementaryView - // because those methods send index paths for _deleted items_ (invalid index paths) - [self layoutIfNeeded]; - NSArray *visibleAttributes = [self.collectionViewLayout layoutAttributesForElementsInRect:self.bounds]; - NSMutableArray *result = [NSMutableArray array]; - for (UICollectionViewLayoutAttributes *attributes in visibleAttributes) { - if (attributes.representedElementCategory == UICollectionElementCategorySupplementaryView - && [attributes.representedElementKind isEqualToString:kind]) { - [result addObject:attributes.indexPath]; - } - } - return result; -} - - (NSHashTable *)visibleElementsForRangeController:(ASRangeController *)rangeController { return ASPointerTableByFlatMapping(_visibleElements, id element, element); diff --git a/Source/ASDisplayNode+Yoga.mm b/Source/ASDisplayNode+Yoga.mm index 70cdd953c..b52f94821 100644 --- a/Source/ASDisplayNode+Yoga.mm +++ b/Source/ASDisplayNode+Yoga.mm @@ -94,12 +94,10 @@ - (void)insertYogaChild:(ASDisplayNode *)child atIndex:(NSUInteger)index - (void)semanticContentAttributeDidChange:(UISemanticContentAttribute)attribute { - if (AS_AT_LEAST_IOS9) { - UIUserInterfaceLayoutDirection layoutDirection = - [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:attribute]; - self.style.direction = (layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight - ? YGDirectionLTR : YGDirectionRTL); - } + UIUserInterfaceLayoutDirection layoutDirection = + [UIView userInterfaceLayoutDirectionForSemanticContentAttribute:attribute]; + self.style.direction = (layoutDirection == UIUserInterfaceLayoutDirectionLeftToRight + ? YGDirectionLTR : YGDirectionRTL); } - (void)setYogaParent:(ASDisplayNode *)yogaParent diff --git a/Source/ASMultiplexImageNode.mm b/Source/ASMultiplexImageNode.mm index dc689d829..a2bed044a 100644 --- a/Source/ASMultiplexImageNode.mm +++ b/Source/ASMultiplexImageNode.mm @@ -625,7 +625,7 @@ - (void)_loadNextImage finishedLoadingBlock(downloadedImage, nextImageIdentifier, error); }]; } - // Likewise, if it's a iOS 8 Photo asset, we need to fetch it accordingly. + // Likewise, if it's a Photos asset, we need to fetch it accordingly. else if (ASPhotosFrameworkImageRequest *request = [ASPhotosFrameworkImageRequest requestWithURL:nextImageURL]) { [self _loadPHAssetWithRequest:request identifier:nextImageIdentifier completion:^(UIImage *image, NSError *error) { as_log_verbose(ASImageLoadingLog(), "Acquired image from Photos for %@ %@", weakSelf, nextImageIdentifier); @@ -673,7 +673,11 @@ - (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL com ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); ASDisplayNodeAssertNotNil(assetURL, @"assetURL is required"); ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); - + + // ALAssetsLibrary was replaced in iOS 8 and deprecated in iOS 9. + // We'll drop support very soon. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc] init]; [assetLibrary assetForURL:assetURL resultBlock:^(ALAsset *asset) { @@ -685,6 +689,7 @@ - (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL com } failureBlock:^(NSError *error) { completionBlock(nil, error); }]; +#pragma clang diagnostic pop } - (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock diff --git a/Source/ASRunLoopQueue.mm b/Source/ASRunLoopQueue.mm index ced798252..974e34814 100644 --- a/Source/ASRunLoopQueue.mm +++ b/Source/ASRunLoopQueue.mm @@ -57,11 +57,6 @@ + (ASDeallocQueue *)sharedDeallocationQueue - (void)releaseObjectInBackground:(id _Nullable __strong *)objectPtr { - // Disable background deallocation on iOS 8 and below to avoid crashes related to UIAXDelegateClearer (#2767). - if (!AS_AT_LEAST_IOS9) { - return; - } - if (objectPtr != NULL && *objectPtr != nil) { ASDN::MutexLocker l(_queueLock); _queue.push_back(*objectPtr); diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 1cc369b07..f78a688ea 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -187,15 +187,6 @@ @interface ASTableView () = 9. - */ - CALayer *_retainedLayer; CGFloat _nodesConstrainedWidth; BOOL _queuedNodeHeightUpdate; @@ -350,10 +341,6 @@ - (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataC [self registerClass:_ASTableViewCell.class forCellReuseIdentifier:kCellReuseIdentifier]; - if (!AS_AT_LEAST_IOS9) { - _retainedLayer = self.layer; - } - // iOS 11 automatically uses estimated heights, so disable those (see PR #485) if (AS_AT_LEAST_IOS11) { super.estimatedRowHeight = 0.0; diff --git a/Source/ASTextNode.mm b/Source/ASTextNode.mm index c3343528a..e2a594b05 100644 --- a/Source/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -230,6 +230,8 @@ - (void)dealloc { CGColorRelease(_shadowColor); + // TODO: This may not be necessary post-iOS-9 when most UIKit assign APIs + // were changed to weak. if (_longPressGestureRecognizer) { _longPressGestureRecognizer.delegate = nil; [_longPressGestureRecognizer removeTarget:nil action:NULL]; diff --git a/Source/Base/ASAvailability.h b/Source/Base/ASAvailability.h index 6a80e274a..f78ed9aaf 100644 --- a/Source/Base/ASAvailability.h +++ b/Source/Base/ASAvailability.h @@ -19,10 +19,6 @@ #pragma once -#ifndef kCFCoreFoundationVersionNumber_iOS_9_0 - #define kCFCoreFoundationVersionNumber_iOS_9_0 1240.10 -#endif - #ifndef kCFCoreFoundationVersionNumber_iOS_10_0 #define kCFCoreFoundationVersionNumber_iOS_10_0 1348.00 #endif @@ -35,7 +31,6 @@ #define __IPHONE_11_0 110000 #endif -#define AS_AT_LEAST_IOS9 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_9_0) #define AS_AT_LEAST_IOS10 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_10_0) #define AS_AT_LEAST_IOS11 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_11_0) diff --git a/Source/Details/ASTraitCollection.m b/Source/Details/ASTraitCollection.m index 04eaea608..570095ec4 100644 --- a/Source/Details/ASTraitCollection.m +++ b/Source/Details/ASTraitCollection.m @@ -67,28 +67,17 @@ BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTr // Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceIdiom(UIUserInterfaceIdiom idiom) { - if (AS_AVAILABLE_IOS(9)) { - switch (idiom) { - case UIUserInterfaceIdiomTV: - return @"TV"; - case UIUserInterfaceIdiomPad: - return @"Pad"; - case UIUserInterfaceIdiomPhone: - return @"Phone"; - case UIUserInterfaceIdiomCarPlay: - return @"CarPlay"; - default: - return @"Unspecified"; - } - } else { - switch (idiom) { - case UIUserInterfaceIdiomPad: - return @"Pad"; - case UIUserInterfaceIdiomPhone: - return @"Phone"; - default: - return @"Unspecified"; - } + switch (idiom) { + case UIUserInterfaceIdiomTV: + return @"TV"; + case UIUserInterfaceIdiomPad: + return @"Pad"; + case UIUserInterfaceIdiomPhone: + return @"Phone"; + case UIUserInterfaceIdiomCarPlay: + return @"CarPlay"; + default: + return @"Unspecified"; } } @@ -178,15 +167,11 @@ + (instancetype)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitC + (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection containerSize:(CGSize)windowSize { - UIForceTouchCapability forceTouch = UIForceTouchCapabilityUnknown; - if(AS_AVAILABLE_IOS(9)) { - forceTouch = traitCollection.forceTouchCapability; - } return [self traitCollectionWithDisplayScale:traitCollection.displayScale userInterfaceIdiom:traitCollection.userInterfaceIdiom horizontalSizeClass:traitCollection.horizontalSizeClass verticalSizeClass:traitCollection.verticalSizeClass - forceTouchCapability:forceTouch + forceTouchCapability:traitCollection.forceTouchCapability containerSize:windowSize]; } diff --git a/Source/Private/ASDisplayNode+UIViewBridge.mm b/Source/Private/ASDisplayNode+UIViewBridge.mm index cf07bd700..a3eceb741 100644 --- a/Source/Private/ASDisplayNode+UIViewBridge.mm +++ b/Source/Private/ASDisplayNode+UIViewBridge.mm @@ -854,21 +854,16 @@ - (void)setEdgeAntialiasingMask:(unsigned int)edgeAntialiasingMask - (UISemanticContentAttribute)semanticContentAttribute { _bridge_prologue_read; - if (AS_AT_LEAST_IOS9) { - return _getFromViewOnly(semanticContentAttribute); - } - return UISemanticContentAttributeUnspecified; + return _getFromViewOnly(semanticContentAttribute); } - (void)setSemanticContentAttribute:(UISemanticContentAttribute)semanticContentAttribute { _bridge_prologue_write; - if (AS_AT_LEAST_IOS9) { - _setToViewOnly(semanticContentAttribute, semanticContentAttribute); + _setToViewOnly(semanticContentAttribute, semanticContentAttribute); #if YOGA - [self semanticContentAttributeDidChange:semanticContentAttribute]; + [self semanticContentAttributeDidChange:semanticContentAttribute]; #endif - } } @end diff --git a/Source/Private/TextExperiment/Component/ASTextLayout.m b/Source/Private/TextExperiment/Component/ASTextLayout.m index df8d356f6..fa61946c5 100755 --- a/Source/Private/TextExperiment/Component/ASTextLayout.m +++ b/Source/Private/TextExperiment/Component/ASTextLayout.m @@ -405,25 +405,9 @@ + (ASTextLayout *)layoutWithContainer:(ASTextContainer *)container text:(NSAttri container->_readonly = YES; maximumNumberOfRows = container.maximumNumberOfRows; - // CoreText bug when draw joined emoji since iOS 8.3. - // See -[NSMutableAttributedString setClearColorToJoinedEmoji] for more information. - static BOOL needFixJoinedEmojiBug = NO; // It may use larger constraint size when create CTFrame with // CTFramesetterCreateFrame in iOS 10. - static BOOL needFixLayoutSizeBug = NO; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - double systemVersionDouble = [UIDevice currentDevice].systemVersion.doubleValue; - if (8.3 <= systemVersionDouble && systemVersionDouble < 9) { - needFixJoinedEmojiBug = YES; - } - if (systemVersionDouble >= 10) { - needFixLayoutSizeBug = YES; - } - }); - if (needFixJoinedEmojiBug) { - [((NSMutableAttributedString *)text) as_setClearColorToJoinedEmoji]; - } + BOOL needFixLayoutSizeBug = AS_AT_LEAST_IOS10; layout = [[ASTextLayout alloc] _init]; layout.text = text; diff --git a/Source/Private/TextExperiment/String/ASTextAttribute.m b/Source/Private/TextExperiment/String/ASTextAttribute.m index 084bdcf82..0cc8e22a7 100755 --- a/Source/Private/TextExperiment/String/ASTextAttribute.m +++ b/Source/Private/TextExperiment/String/ASTextAttribute.m @@ -63,7 +63,10 @@ ASTextAttributeType ASTextAttributeGetType(NSString *name){ dic[(id)kCTSuperscriptAttributeName] = UIKit; //it's a CoreText attrubite, but only supported by UIKit... dic[NSVerticalGlyphFormAttributeName] = All; dic[(id)kCTGlyphInfoAttributeName] = CoreText_ASText; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" dic[(id)kCTCharacterShapeAttributeName] = CoreText_ASText; +#pragma clang diagnostic pop dic[(id)kCTRunDelegateAttributeName] = CoreText_ASText; dic[(id)kCTBaselineClassAttributeName] = CoreText_ASText; dic[(id)kCTBaselineInfoAttributeName] = CoreText_ASText; diff --git a/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h b/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h index 9fc8ec222..31ef75d12 100755 --- a/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h +++ b/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h @@ -1357,21 +1357,6 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)as_appendString:(NSString *)string; -/** - Set foreground color with [UIColor clearColor] in joined-emoji range. - Emoji drawing will not be affected by the foreground color. - - @discussion In iOS 8.3, Apple releases some new diversified emojis. - There's some single emoji which can be assembled to a new 'joined-emoji'. - The joiner is unicode character 'ZERO WIDTH JOINER' (U+200D). - For example: 👨👩👧👧 -> 👨‍👩‍👧‍👧. - - When there are more than 5 'joined-emoji' in a same CTLine, CoreText may render some - extra glyphs above the emoji. It's a bug in CoreText, try this method to avoid. - This bug is fixed in iOS 9. - */ -- (void)as_setClearColorToJoinedEmoji; - /** Removes all discontinuous attributes in a specified range. See `allDiscontinuousAttributeKeys`. diff --git a/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.m b/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.m index d67dd15d2..327bc41e2 100755 --- a/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.m +++ b/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.m @@ -600,7 +600,10 @@ - (BOOL)as_canDrawWithUIKit { dispatch_once(&onceToken, ^{ failSet = [NSMutableSet new]; [failSet addObject:(id)kCTGlyphInfoAttributeName]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" [failSet addObject:(id)kCTCharacterShapeAttributeName]; +#pragma clang diagnostic pop [failSet addObject:(id)kCTLanguageAttributeName]; [failSet addObject:(id)kCTRunDelegateAttributeName]; [failSet addObject:(id)kCTBaselineClassAttributeName]; @@ -1049,7 +1052,10 @@ - (void)as_setGlyphInfo:(CTGlyphInfoRef)glyphInfo range:(NSRange)range { } - (void)as_setCharacterShape:(NSNumber *)characterShape range:(NSRange)range { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" [self as_setAttribute:(id)kCTCharacterShapeAttributeName value:characterShape range:range]; +#pragma clang diagnostic pop } - (void)as_setRunDelegate:(CTRunDelegateRef)runDelegate range:(NSRange)range { @@ -1178,51 +1184,6 @@ - (void)as_appendString:(NSString *)string { [self as_removeDiscontinuousAttributesInRange:NSMakeRange(length, string.length)]; } -- (void)as_setClearColorToJoinedEmoji { - NSString *str = self.string; - if (str.length < 8) return; - - // Most string do not contains the joined-emoji, test the joiner first. - BOOL containsJoiner = NO; - { - CFStringRef cfStr = (__bridge CFStringRef)str; - BOOL needFree = NO; - UniChar *chars = NULL; - chars = (UniChar *)CFStringGetCharactersPtr(cfStr); - if (!chars) { - chars = (UniChar *)malloc(str.length * sizeof(UniChar)); - if (chars) { - needFree = YES; - CFStringGetCharacters(cfStr, CFRangeMake(0, str.length), chars); - } - } - if (!chars) { // fail to get unichar.. - containsJoiner = YES; - } else { - for (int i = 0, max = (int)str.length; i < max; i++) { - if (chars[i] == 0x200D) { // 'ZERO WIDTH JOINER' (U+200D) - containsJoiner = YES; - break; - } - } - if (needFree) free(chars); - } - } - if (!containsJoiner) return; - - // NSRegularExpression is designed to be immutable and thread safe. - static NSRegularExpression *regex; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - regex = [NSRegularExpression regularExpressionWithPattern:@"((👨‍👩‍👧‍👦|👨‍👩‍👦‍👦|👨‍👩‍👧‍👧|👩‍👩‍👧‍👦|👩‍👩‍👦‍👦|👩‍👩‍👧‍👧|👨‍👨‍👧‍👦|👨‍👨‍👦‍👦|👨‍👨‍👧‍👧)+|(👨‍👩‍👧|👩‍👩‍👦|👩‍👩‍👧|👨‍👨‍👦|👨‍👨‍👧))" options:kNilOptions error:nil]; - }); - - UIColor *clear = [UIColor clearColor]; - [regex enumerateMatchesInString:str options:kNilOptions range:NSMakeRange(0, str.length) usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) { - [self as_setColor:clear range:result.range]; - }]; -} - - (void)as_removeDiscontinuousAttributesInRange:(NSRange)range { NSArray *keys = [NSMutableAttributedString as_allDiscontinuousAttributeKeys]; for (NSString *key in keys) { diff --git a/Source/Private/_ASPendingState.mm b/Source/Private/_ASPendingState.mm index 4374401d8..1a87e496b 100644 --- a/Source/Private/_ASPendingState.mm +++ b/Source/Private/_ASPendingState.mm @@ -295,9 +295,7 @@ - (instancetype)init accessibilityActivationPoint = CGPointZero; accessibilityPath = nil; edgeAntialiasingMask = (kCALayerLeftEdge | kCALayerRightEdge | kCALayerTopEdge | kCALayerBottomEdge); - if (AS_AVAILABLE_IOS(9)) { - semanticContentAttribute = UISemanticContentAttributeUnspecified; - } + semanticContentAttribute = UISemanticContentAttributeUnspecified; return self; } @@ -1051,10 +1049,8 @@ - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPr if (flags.setOpaque) ASDisplayNodeAssert(layer.opaque == opaque, @"Didn't set opaque as desired"); - if (AS_AVAILABLE_IOS(9)) { - if (flags.setSemanticContentAttribute) { - view.semanticContentAttribute = semanticContentAttribute; - } + if (flags.setSemanticContentAttribute) { + view.semanticContentAttribute = semanticContentAttribute; } if (flags.setIsAccessibilityElement) @@ -1215,9 +1211,7 @@ + (_ASPendingState *)pendingViewStateFromView:(UIView *)view pendingState.allowsGroupOpacity = layer.allowsGroupOpacity; pendingState.allowsEdgeAntialiasing = layer.allowsEdgeAntialiasing; pendingState.edgeAntialiasingMask = layer.edgeAntialiasingMask; - if (AS_AVAILABLE_IOS(9)) { - pendingState.semanticContentAttribute = view.semanticContentAttribute; - } + pendingState.semanticContentAttribute = view.semanticContentAttribute; pendingState.isAccessibilityElement = view.isAccessibilityElement; pendingState.accessibilityLabel = view.accessibilityLabel; pendingState.accessibilityHint = view.accessibilityHint; diff --git a/SubspecWorkspaces/ASDKListKit/ASDKListKit.xcodeproj/project.pbxproj b/SubspecWorkspaces/ASDKListKit/ASDKListKit.xcodeproj/project.pbxproj index 814ad945a..e275becc1 100644 --- a/SubspecWorkspaces/ASDKListKit/ASDKListKit.xcodeproj/project.pbxproj +++ b/SubspecWorkspaces/ASDKListKit/ASDKListKit.xcodeproj/project.pbxproj @@ -283,14 +283,14 @@ CC55321D1E16EB7A0011C01F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; }; name = Debug; }; CC55321E1E16EB7A0011C01F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; }; name = Release; }; @@ -336,7 +336,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = ASDKListKitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; @@ -382,7 +382,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = ASDKListKitTests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = asyncdisplaykit.ASDKListKitTests; diff --git a/SubspecWorkspaces/ASDKListKit/Podfile b/SubspecWorkspaces/ASDKListKit/Podfile index d5b84556f..36498c247 100644 --- a/SubspecWorkspaces/ASDKListKit/Podfile +++ b/SubspecWorkspaces/ASDKListKit/Podfile @@ -1,6 +1,6 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'ASDKListKitTests' do pod 'Texture/IGListKit', :path => '../..' pod 'OCMock', '~> 3.4' diff --git a/Texture.podspec b/Texture.podspec index 73e904772..8b75b4334 100644 --- a/Texture.podspec +++ b/Texture.podspec @@ -14,7 +14,7 @@ Pod::Spec.new do |spec| spec.weak_frameworks = 'Photos','MapKit','AssetsLibrary' spec.requires_arc = true - spec.ios.deployment_target = '8.0' + spec.ios.deployment_target = '9.0' # Uncomment when fixed: issues with tvOS build for release 2.0 # spec.tvos.deployment_target = '9.0' diff --git a/examples/ASCollectionView/Podfile b/examples/ASCollectionView/Podfile index 922ff50ec..08d1b7add 100644 --- a/examples/ASCollectionView/Podfile +++ b/examples/ASCollectionView/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/ASCollectionView/Sample.xcodeproj/project.pbxproj b/examples/ASCollectionView/Sample.xcodeproj/project.pbxproj index 634184297..967d1f7cd 100644 --- a/examples/ASCollectionView/Sample.xcodeproj/project.pbxproj +++ b/examples/ASCollectionView/Sample.xcodeproj/project.pbxproj @@ -240,13 +240,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -302,7 +305,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -338,7 +341,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -353,7 +356,6 @@ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = 1; @@ -367,7 +369,6 @@ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = 1; diff --git a/examples/ASDKLayoutTransition/Podfile b/examples/ASDKLayoutTransition/Podfile index 90c6ad7ea..0fbf4c952 100644 --- a/examples/ASDKLayoutTransition/Podfile +++ b/examples/ASDKLayoutTransition/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' pod 'Texture/Yoga', :path => '../..' diff --git a/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj b/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj index 3b4f1e6f1..17537d093 100644 --- a/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj +++ b/examples/ASDKLayoutTransition/Sample.xcodeproj/project.pbxproj @@ -196,13 +196,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { @@ -271,7 +274,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -306,7 +309,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/ASDKTube/Podfile b/examples/ASDKTube/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples/ASDKTube/Podfile +++ b/examples/ASDKTube/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/ASDKTube/Sample.xcodeproj/project.pbxproj b/examples/ASDKTube/Sample.xcodeproj/project.pbxproj index 0e6c890fa..1b8c982be 100644 --- a/examples/ASDKTube/Sample.xcodeproj/project.pbxproj +++ b/examples/ASDKTube/Sample.xcodeproj/project.pbxproj @@ -277,13 +277,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { @@ -356,7 +359,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -391,7 +394,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/ASDKgram/Podfile b/examples/ASDKgram/Podfile index eb879f3f1..4aebd0493 100644 --- a/examples/ASDKgram/Podfile +++ b/examples/ASDKgram/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture/IGListKit', :path => '../..' pod 'Texture/PINRemoteImage', :path => '../..' diff --git a/examples/ASDKgram/Sample.xcodeproj/project.pbxproj b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj index 9cfb0e254..743b9456e 100644 --- a/examples/ASDKgram/Sample.xcodeproj/project.pbxproj +++ b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj @@ -381,13 +381,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { @@ -477,7 +480,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -512,7 +515,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/ASMapNode/Podfile b/examples/ASMapNode/Podfile index 922ff50ec..08d1b7add 100644 --- a/examples/ASMapNode/Podfile +++ b/examples/ASMapNode/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/ASMapNode/Sample.xcodeproj/project.pbxproj b/examples/ASMapNode/Sample.xcodeproj/project.pbxproj index 529b58783..48093ffda 100644 --- a/examples/ASMapNode/Sample.xcodeproj/project.pbxproj +++ b/examples/ASMapNode/Sample.xcodeproj/project.pbxproj @@ -214,13 +214,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -288,7 +291,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -325,7 +328,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/ASViewController/Podfile b/examples/ASViewController/Podfile index 922ff50ec..08d1b7add 100644 --- a/examples/ASViewController/Podfile +++ b/examples/ASViewController/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/ASViewController/Sample.xcodeproj/project.pbxproj b/examples/ASViewController/Sample.xcodeproj/project.pbxproj index 200b3c225..e304fdb8e 100644 --- a/examples/ASViewController/Sample.xcodeproj/project.pbxproj +++ b/examples/ASViewController/Sample.xcodeproj/project.pbxproj @@ -219,13 +219,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -294,7 +297,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -331,7 +334,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/AnimatedGIF/Podfile b/examples/AnimatedGIF/Podfile index e784c52d1..c998fa0a8 100644 --- a/examples/AnimatedGIF/Podfile +++ b/examples/AnimatedGIF/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' pod 'PINRemoteImage/WebP' diff --git a/examples/AnimatedGIF/Sample.xcodeproj/project.pbxproj b/examples/AnimatedGIF/Sample.xcodeproj/project.pbxproj index 7498a38dd..453c359c0 100644 --- a/examples/AnimatedGIF/Sample.xcodeproj/project.pbxproj +++ b/examples/AnimatedGIF/Sample.xcodeproj/project.pbxproj @@ -214,13 +214,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ diff --git a/examples/AsyncDisplayKitOverview/Podfile b/examples/AsyncDisplayKitOverview/Podfile index 84d7dee82..ff6cb63a3 100644 --- a/examples/AsyncDisplayKitOverview/Podfile +++ b/examples/AsyncDisplayKitOverview/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '8.0' +platform :ios, '9.0' # Uncomment this line if you're using Swift # use_frameworks! diff --git a/examples/AsyncDisplayKitOverview/Sample.xcodeproj/project.pbxproj b/examples/AsyncDisplayKitOverview/Sample.xcodeproj/project.pbxproj index 8d5abb4bb..3683dbd63 100644 --- a/examples/AsyncDisplayKitOverview/Sample.xcodeproj/project.pbxproj +++ b/examples/AsyncDisplayKitOverview/Sample.xcodeproj/project.pbxproj @@ -217,13 +217,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 84F93825AFB1CA7FBB116BA4 /* [CP] Copy Pods Resources */ = { @@ -309,7 +312,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -348,7 +351,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/examples/CatDealsCollectionView/Podfile b/examples/CatDealsCollectionView/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples/CatDealsCollectionView/Podfile +++ b/examples/CatDealsCollectionView/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj b/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj index 5a9485def..29fb32d34 100644 --- a/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj +++ b/examples/CatDealsCollectionView/Sample.xcodeproj/project.pbxproj @@ -242,13 +242,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -308,7 +311,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -344,7 +347,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/examples/CustomCollectionView-Swift/Podfile b/examples/CustomCollectionView-Swift/Podfile index a0eec32ed..92a9acc9c 100644 --- a/examples/CustomCollectionView-Swift/Podfile +++ b/examples/CustomCollectionView-Swift/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' use_frameworks! diff --git a/examples/CustomCollectionView-Swift/Sample.xcodeproj/project.pbxproj b/examples/CustomCollectionView-Swift/Sample.xcodeproj/project.pbxproj index 6969496c8..e1912f909 100755 --- a/examples/CustomCollectionView-Swift/Sample.xcodeproj/project.pbxproj +++ b/examples/CustomCollectionView-Swift/Sample.xcodeproj/project.pbxproj @@ -294,7 +294,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -344,7 +344,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/examples/CustomCollectionView/Podfile b/examples/CustomCollectionView/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples/CustomCollectionView/Podfile +++ b/examples/CustomCollectionView/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj b/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj index 91818f8f8..edaa0d127 100644 --- a/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj +++ b/examples/CustomCollectionView/Sample.xcodeproj/project.pbxproj @@ -228,13 +228,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -291,7 +294,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -327,7 +330,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; @@ -342,7 +345,6 @@ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; DEVELOPMENT_TEAM = XSR3D45JSF; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample.CustomCollectionView; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -357,7 +359,6 @@ ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; DEVELOPMENT_TEAM = XSR3D45JSF; INFOPLIST_FILE = Sample/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample.CustomCollectionView; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/examples/CustomCollectionView/Sample/AppDelegate.m b/examples/CustomCollectionView/Sample/AppDelegate.m index c0769e5d5..dec3c29fe 100644 --- a/examples/CustomCollectionView/Sample/AppDelegate.m +++ b/examples/CustomCollectionView/Sample/AppDelegate.m @@ -4,15 +4,15 @@ // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// 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. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import "AppDelegate.h" @@ -23,7 +23,7 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; self.window.backgroundColor = [UIColor whiteColor]; self.window.rootViewController = [[ViewController alloc] init]; diff --git a/examples/HorizontalWithinVerticalScrolling/Podfile b/examples/HorizontalWithinVerticalScrolling/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples/HorizontalWithinVerticalScrolling/Podfile +++ b/examples/HorizontalWithinVerticalScrolling/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/HorizontalWithinVerticalScrolling/Sample.xcodeproj/project.pbxproj b/examples/HorizontalWithinVerticalScrolling/Sample.xcodeproj/project.pbxproj index c99967990..6472106a7 100644 --- a/examples/HorizontalWithinVerticalScrolling/Sample.xcodeproj/project.pbxproj +++ b/examples/HorizontalWithinVerticalScrolling/Sample.xcodeproj/project.pbxproj @@ -123,12 +123,12 @@ isa = PBXNativeTarget; buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */, 05E2127D19D4DB510098F589 /* Sources */, 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, - F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - A79A9172A45D7C9595AA01CC /* Embed Pods Frameworks */, + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */, + A79A9172A45D7C9595AA01CC /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -185,14 +185,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - A79A9172A45D7C9595AA01CC /* Embed Pods Frameworks */ = { + A79A9172A45D7C9595AA01CC /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -200,29 +200,32 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); 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"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -282,7 +285,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -317,7 +320,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/Kittens/Podfile b/examples/Kittens/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples/Kittens/Podfile +++ b/examples/Kittens/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/Kittens/Sample.xcodeproj/project.pbxproj b/examples/Kittens/Sample.xcodeproj/project.pbxproj index 7e5f1a9ef..3c2cb1a59 100644 --- a/examples/Kittens/Sample.xcodeproj/project.pbxproj +++ b/examples/Kittens/Sample.xcodeproj/project.pbxproj @@ -125,12 +125,12 @@ isa = PBXNativeTarget; buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */, 05E2127D19D4DB510098F589 /* Sources */, 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, - F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - 9C2078E0C7EEEFF207C7F6A9 /* Embed Pods Frameworks */, + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */, + 9C2078E0C7EEEFF207C7F6A9 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -187,14 +187,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 9C2078E0C7EEEFF207C7F6A9 /* Embed Pods Frameworks */ = { + 9C2078E0C7EEEFF207C7F6A9 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -202,29 +202,32 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); 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"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -285,7 +288,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -320,7 +323,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/LayoutSpecExamples-Swift/Podfile b/examples/LayoutSpecExamples-Swift/Podfile index 8458e64c6..83d2cae8b 100644 --- a/examples/LayoutSpecExamples-Swift/Podfile +++ b/examples/LayoutSpecExamples-Swift/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' use_frameworks! diff --git a/examples/LayoutSpecExamples-Swift/Sample.xcodeproj/project.pbxproj b/examples/LayoutSpecExamples-Swift/Sample.xcodeproj/project.pbxproj index d69d6e6d8..5ac0a8718 100644 --- a/examples/LayoutSpecExamples-Swift/Sample.xcodeproj/project.pbxproj +++ b/examples/LayoutSpecExamples-Swift/Sample.xcodeproj/project.pbxproj @@ -310,7 +310,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -355,7 +355,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/examples/LayoutSpecExamples/Podfile b/examples/LayoutSpecExamples/Podfile index 922ff50ec..08d1b7add 100644 --- a/examples/LayoutSpecExamples/Podfile +++ b/examples/LayoutSpecExamples/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/LayoutSpecExamples/Sample.xcodeproj/project.pbxproj b/examples/LayoutSpecExamples/Sample.xcodeproj/project.pbxproj index 3c9041a2b..c065fcab3 100644 --- a/examples/LayoutSpecExamples/Sample.xcodeproj/project.pbxproj +++ b/examples/LayoutSpecExamples/Sample.xcodeproj/project.pbxproj @@ -299,7 +299,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -336,7 +336,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/PagerNode/Podfile b/examples/PagerNode/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples/PagerNode/Podfile +++ b/examples/PagerNode/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/PagerNode/Sample.xcodeproj/project.pbxproj b/examples/PagerNode/Sample.xcodeproj/project.pbxproj index 16d7ec840..febfa871c 100644 --- a/examples/PagerNode/Sample.xcodeproj/project.pbxproj +++ b/examples/PagerNode/Sample.xcodeproj/project.pbxproj @@ -118,12 +118,12 @@ isa = PBXNativeTarget; buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */, 05E2127D19D4DB510098F589 /* Sources */, 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, - F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - 6E05308BEF86AD80AEB4EEE7 /* Embed Pods Frameworks */, + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */, + 6E05308BEF86AD80AEB4EEE7 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -180,14 +180,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 6E05308BEF86AD80AEB4EEE7 /* Embed Pods Frameworks */ = { + 6E05308BEF86AD80AEB4EEE7 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -195,29 +195,32 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); 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"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -277,7 +280,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -312,7 +315,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/SocialAppLayout-Inverted/Podfile b/examples/SocialAppLayout-Inverted/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples/SocialAppLayout-Inverted/Podfile +++ b/examples/SocialAppLayout-Inverted/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/SocialAppLayout-Inverted/Sample.xcodeproj/project.pbxproj b/examples/SocialAppLayout-Inverted/Sample.xcodeproj/project.pbxproj index b43fcaac6..3ad0bf877 100644 --- a/examples/SocialAppLayout-Inverted/Sample.xcodeproj/project.pbxproj +++ b/examples/SocialAppLayout-Inverted/Sample.xcodeproj/project.pbxproj @@ -319,13 +319,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -385,7 +388,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -422,7 +425,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/SocialAppLayout/Podfile b/examples/SocialAppLayout/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples/SocialAppLayout/Podfile +++ b/examples/SocialAppLayout/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/SocialAppLayout/Sample.xcodeproj/project.pbxproj b/examples/SocialAppLayout/Sample.xcodeproj/project.pbxproj index abd0cb665..b187eec74 100644 --- a/examples/SocialAppLayout/Sample.xcodeproj/project.pbxproj +++ b/examples/SocialAppLayout/Sample.xcodeproj/project.pbxproj @@ -200,12 +200,12 @@ isa = PBXNativeTarget; buildConfigurationList = 3EEA4EFB1BECC4A1008A7F35 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - B5BD9E5609B2CB179EEE0CF4 /* Check Pods Manifest.lock */, + B5BD9E5609B2CB179EEE0CF4 /* [CP] Check Pods Manifest.lock */, 3EEA4EE01BECC4A1008A7F35 /* Sources */, 3EEA4EE11BECC4A1008A7F35 /* Frameworks */, 3EEA4EE21BECC4A1008A7F35 /* Resources */, - 21F2C1D9B53F9468EAF1653F /* Copy Pods Resources */, - 852437589F1D53B9483A75DF /* Embed Pods Frameworks */, + 21F2C1D9B53F9468EAF1653F /* [CP] Copy Pods Resources */, + 852437589F1D53B9483A75DF /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -281,14 +281,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 21F2C1D9B53F9468EAF1653F /* Copy Pods Resources */ = { + 21F2C1D9B53F9468EAF1653F /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -296,14 +296,14 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 852437589F1D53B9483A75DF /* Embed Pods Frameworks */ = { + 852437589F1D53B9483A75DF /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -311,19 +311,22 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - B5BD9E5609B2CB179EEE0CF4 /* Check Pods Manifest.lock */ = { + B5BD9E5609B2CB179EEE0CF4 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); 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"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -383,7 +386,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -420,7 +423,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/Swift/Podfile b/examples/Swift/Podfile index 8458e64c6..83d2cae8b 100644 --- a/examples/Swift/Podfile +++ b/examples/Swift/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' use_frameworks! diff --git a/examples/Swift/Sample.xcodeproj/project.pbxproj b/examples/Swift/Sample.xcodeproj/project.pbxproj index a377337a3..533a738ea 100644 --- a/examples/Swift/Sample.xcodeproj/project.pbxproj +++ b/examples/Swift/Sample.xcodeproj/project.pbxproj @@ -291,7 +291,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -336,7 +336,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/examples/VerticalWithinHorizontalScrolling/Podfile b/examples/VerticalWithinHorizontalScrolling/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples/VerticalWithinHorizontalScrolling/Podfile +++ b/examples/VerticalWithinHorizontalScrolling/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/VerticalWithinHorizontalScrolling/Sample.xcodeproj/project.pbxproj b/examples/VerticalWithinHorizontalScrolling/Sample.xcodeproj/project.pbxproj index 7f7950060..80493caef 100644 --- a/examples/VerticalWithinHorizontalScrolling/Sample.xcodeproj/project.pbxproj +++ b/examples/VerticalWithinHorizontalScrolling/Sample.xcodeproj/project.pbxproj @@ -123,12 +123,12 @@ isa = PBXNativeTarget; buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */, 05E2127D19D4DB510098F589 /* Sources */, 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, - F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - 6E94068CFAA7736333E7D960 /* Embed Pods Frameworks */, + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */, + 6E94068CFAA7736333E7D960 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -185,14 +185,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 6E94068CFAA7736333E7D960 /* Embed Pods Frameworks */ = { + 6E94068CFAA7736333E7D960 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -200,29 +200,32 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); 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"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -282,7 +285,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -317,7 +320,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples/Videos/Podfile b/examples/Videos/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples/Videos/Podfile +++ b/examples/Videos/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples/Videos/Sample.xcodeproj/project.pbxproj b/examples/Videos/Sample.xcodeproj/project.pbxproj index b43c7e7fc..8a0460100 100644 --- a/examples/Videos/Sample.xcodeproj/project.pbxproj +++ b/examples/Videos/Sample.xcodeproj/project.pbxproj @@ -125,12 +125,12 @@ isa = PBXNativeTarget; buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - E080B80F89C34A25B3488E26 /* 📦 Check Pods Manifest.lock */, + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */, 05E2127D19D4DB510098F589 /* Sources */, 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, - F012A6F39E0149F18F564F50 /* 📦 Copy Pods Resources */, - 93B7780A33739EF25F20366B /* 📦 Embed Pods Frameworks */, + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */, + 93B7780A33739EF25F20366B /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -191,14 +191,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 93B7780A33739EF25F20366B /* 📦 Embed Pods Frameworks */ = { + 93B7780A33739EF25F20366B /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "📦 Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -206,29 +206,32 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E080B80F89C34A25B3488E26 /* 📦 Check Pods Manifest.lock */ = { + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "📦 Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); 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"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F012A6F39E0149F18F564F50 /* 📦 Copy Pods Resources */ = { + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "📦 Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -286,7 +289,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -321,7 +324,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples_extra/ASLayoutSpecPlayground-Swift/Podfile b/examples_extra/ASLayoutSpecPlayground-Swift/Podfile index f092b7ba8..3b379097a 100644 --- a/examples_extra/ASLayoutSpecPlayground-Swift/Podfile +++ b/examples_extra/ASLayoutSpecPlayground-Swift/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' use_frameworks! target 'Sample' do pod 'Texture', :path => '../..' diff --git a/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcodeproj/project.pbxproj b/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcodeproj/project.pbxproj index ced4aae5c..b77a5e410 100644 --- a/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcodeproj/project.pbxproj @@ -188,13 +188,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -245,7 +248,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -280,7 +283,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 7.1; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; @@ -305,7 +308,6 @@ GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = Sample/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample; @@ -339,7 +341,6 @@ GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = Sample/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Sample; diff --git a/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcworkspace/contents.xcworkspacedata b/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..7b5a2f305 --- /dev/null +++ b/examples_extra/ASLayoutSpecPlayground-Swift/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples_extra/ASTableViewStressTest/Podfile b/examples_extra/ASTableViewStressTest/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples_extra/ASTableViewStressTest/Podfile +++ b/examples_extra/ASTableViewStressTest/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj b/examples_extra/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj index 3e68545a8..c0da5a201 100644 --- a/examples_extra/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj @@ -113,12 +113,12 @@ isa = PBXNativeTarget; buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */, 05E2127D19D4DB510098F589 /* Sources */, 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, - F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - 75CADB9ECE58AB74892E1D67 /* Embed Pods Frameworks */, + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */, + 75CADB9ECE58AB74892E1D67 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -175,14 +175,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 75CADB9ECE58AB74892E1D67 /* Embed Pods Frameworks */ = { + 75CADB9ECE58AB74892E1D67 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -190,29 +190,32 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); 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"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -270,7 +273,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -305,7 +308,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; @@ -318,7 +321,6 @@ 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)"; }; @@ -330,7 +332,6 @@ 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)"; }; diff --git a/examples_extra/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata b/examples_extra/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..7b5a2f305 --- /dev/null +++ b/examples_extra/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples_extra/ASTraitCollection/Podfile b/examples_extra/ASTraitCollection/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples_extra/ASTraitCollection/Podfile +++ b/examples_extra/ASTraitCollection/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/ASTraitCollection/Sample.xcodeproj/project.pbxproj b/examples_extra/ASTraitCollection/Sample.xcodeproj/project.pbxproj index 20186b37a..ca9ec160e 100644 --- a/examples_extra/ASTraitCollection/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/ASTraitCollection/Sample.xcodeproj/project.pbxproj @@ -292,7 +292,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -327,7 +327,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples_extra/BackgroundPropertySetting/Podfile b/examples_extra/BackgroundPropertySetting/Podfile index f092b7ba8..3b379097a 100644 --- a/examples_extra/BackgroundPropertySetting/Podfile +++ b/examples_extra/BackgroundPropertySetting/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' use_frameworks! target 'Sample' do pod 'Texture', :path => '../..' diff --git a/examples_extra/CollectionViewWithViewControllerCells/Podfile b/examples_extra/CollectionViewWithViewControllerCells/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples_extra/CollectionViewWithViewControllerCells/Podfile +++ b/examples_extra/CollectionViewWithViewControllerCells/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj b/examples_extra/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj index 0c20d4914..718ba91ba 100644 --- a/examples_extra/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/CollectionViewWithViewControllerCells/Sample.xcodeproj/project.pbxproj @@ -284,7 +284,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -320,7 +320,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.1; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/examples_extra/EditableText/Podfile b/examples_extra/EditableText/Podfile index 922ff50ec..08d1b7add 100644 --- a/examples_extra/EditableText/Podfile +++ b/examples_extra/EditableText/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/EditableText/Sample.xcodeproj/project.pbxproj b/examples_extra/EditableText/Sample.xcodeproj/project.pbxproj index 46aa0e942..2afe6c7b2 100644 --- a/examples_extra/EditableText/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/EditableText/Sample.xcodeproj/project.pbxproj @@ -273,7 +273,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -308,7 +308,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples_extra/Multiplex/Podfile b/examples_extra/Multiplex/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples_extra/Multiplex/Podfile +++ b/examples_extra/Multiplex/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/Placeholders/Podfile b/examples_extra/Placeholders/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples_extra/Placeholders/Podfile +++ b/examples_extra/Placeholders/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/Placeholders/Sample.xcodeproj/project.pbxproj b/examples_extra/Placeholders/Sample.xcodeproj/project.pbxproj index e360fd6cd..6054b17bd 100644 --- a/examples_extra/Placeholders/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/Placeholders/Sample.xcodeproj/project.pbxproj @@ -298,7 +298,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -333,7 +333,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples_extra/RepoSearcher/Podfile b/examples_extra/RepoSearcher/Podfile index ac26415f1..21dc49860 100644 --- a/examples_extra/RepoSearcher/Podfile +++ b/examples_extra/RepoSearcher/Podfile @@ -1,5 +1,4 @@ -# Uncomment the next line to define a global platform for your project -# platform :ios, '9.0' +platform :ios, '9.0' target 'RepoSearcher' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks diff --git a/examples_extra/RepoSearcher/RepoSearcher.xcodeproj/project.pbxproj b/examples_extra/RepoSearcher/RepoSearcher.xcodeproj/project.pbxproj index 9439f1622..aa470f0eb 100644 --- a/examples_extra/RepoSearcher/RepoSearcher.xcodeproj/project.pbxproj +++ b/examples_extra/RepoSearcher/RepoSearcher.xcodeproj/project.pbxproj @@ -330,7 +330,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -380,7 +380,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/examples_extra/Shop/Shop.xcodeproj/project.pbxproj b/examples_extra/Shop/Shop.xcodeproj/project.pbxproj index 836c0e771..5c9cc401b 100644 --- a/examples_extra/Shop/Shop.xcodeproj/project.pbxproj +++ b/examples_extra/Shop/Shop.xcodeproj/project.pbxproj @@ -523,7 +523,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -573,7 +573,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; diff --git a/examples_extra/SynchronousConcurrency/Podfile b/examples_extra/SynchronousConcurrency/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples_extra/SynchronousConcurrency/Podfile +++ b/examples_extra/SynchronousConcurrency/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj b/examples_extra/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj index 80d3e6e37..f3964a956 100644 --- a/examples_extra/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/SynchronousConcurrency/Sample.xcodeproj/project.pbxproj @@ -123,12 +123,12 @@ isa = PBXNativeTarget; buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; buildPhases = ( - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */, 05E2127D19D4DB510098F589 /* Sources */, 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, - F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - 0342F7A1563F38A62746D4B8 /* Embed Pods Frameworks */, + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */, + 0342F7A1563F38A62746D4B8 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -185,14 +185,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 0342F7A1563F38A62746D4B8 /* Embed Pods Frameworks */ = { + 0342F7A1563F38A62746D4B8 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Embed Pods Frameworks"; + name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -200,29 +200,32 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */ = { + E080B80F89C34A25B3488E26 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); - name = "Check Pods Manifest.lock"; + name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", ); 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"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { + F012A6F39E0149F18F564F50 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Copy Pods Resources"; + name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; @@ -282,7 +285,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -317,7 +320,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples_extra/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata b/examples_extra/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..7b5a2f305 --- /dev/null +++ b/examples_extra/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples_extra/SynchronousConcurrency/Sample/AsyncTableViewController.m b/examples_extra/SynchronousConcurrency/Sample/AsyncTableViewController.m index 3ab470844..d87c22ea5 100644 --- a/examples_extra/SynchronousConcurrency/Sample/AsyncTableViewController.m +++ b/examples_extra/SynchronousConcurrency/Sample/AsyncTableViewController.m @@ -64,7 +64,7 @@ - (void)viewDidLoad tuningParameters.leadingBufferScreenfuls = 0.5; tuningParameters.trailingBufferScreenfuls = 1.0; [_tableView setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypePreload]; - [_tableView setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeRender]; + [_tableView setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeDisplay]; [self.view addSubview:_tableView]; } @@ -75,7 +75,7 @@ - (ASCellNodeBlock)tableView:(ASTableView *)tableView nodeBlockForRowAtIndexPath { return ^{ RandomCoreGraphicsNode *elementNode = [[RandomCoreGraphicsNode alloc] init]; - elementNode.size = ASRelativeSizeRangeMakeWithExactCGSize(CGSizeMake(320, 100)); + elementNode.style.preferredSize = CGSizeMake(320, 100); return elementNode; }; } diff --git a/examples_extra/SynchronousConcurrency/Sample/AsyncViewController.h b/examples_extra/SynchronousConcurrency/Sample/AsyncViewController.h index fd357593c..f307e65d6 100644 --- a/examples_extra/SynchronousConcurrency/Sample/AsyncViewController.h +++ b/examples_extra/SynchronousConcurrency/Sample/AsyncViewController.h @@ -17,7 +17,7 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#import "ASViewController.h" +#import @interface AsyncViewController : ASViewController diff --git a/examples_extra/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.m b/examples_extra/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.m index d2afbd6e1..66b490df0 100644 --- a/examples_extra/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.m +++ b/examples_extra/SynchronousConcurrency/Sample/RandomCoreGraphicsNode.m @@ -86,7 +86,7 @@ - (instancetype)init - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { - [_textNode measure:constrainedSize]; + [_textNode layoutThatFits:ASSizeRangeMake(CGSizeZero, constrainedSize)]; return CGSizeMake(constrainedSize.width, 100); } diff --git a/examples_extra/SynchronousKittens/Podfile b/examples_extra/SynchronousKittens/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples_extra/SynchronousKittens/Podfile +++ b/examples_extra/SynchronousKittens/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/SynchronousKittens/Sample.xcodeproj/project.pbxproj b/examples_extra/SynchronousKittens/Sample.xcodeproj/project.pbxproj index e7ecd2fc2..05a646e3c 100644 --- a/examples_extra/SynchronousKittens/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/SynchronousKittens/Sample.xcodeproj/project.pbxproj @@ -282,7 +282,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -317,7 +317,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples_extra/TextStressTest/Podfile b/examples_extra/TextStressTest/Podfile index 667002269..73e26195c 100644 --- a/examples_extra/TextStressTest/Podfile +++ b/examples_extra/TextStressTest/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture/Yoga', :path => '../..' end diff --git a/examples_extra/TextStressTest/Sample.xcodeproj/project.pbxproj b/examples_extra/TextStressTest/Sample.xcodeproj/project.pbxproj index 86a3f35f7..806d9a2e8 100644 --- a/examples_extra/TextStressTest/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/TextStressTest/Sample.xcodeproj/project.pbxproj @@ -292,7 +292,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -327,7 +327,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples_extra/TextStressTest/Sample.xcworkspace/contents.xcworkspacedata b/examples_extra/TextStressTest/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..7b5a2f305 --- /dev/null +++ b/examples_extra/TextStressTest/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples_extra/VideoTableView/Podfile b/examples_extra/VideoTableView/Podfile index b75e492fa..71a7f2c4b 100644 --- a/examples_extra/VideoTableView/Podfile +++ b/examples_extra/VideoTableView/Podfile @@ -1,5 +1,5 @@ source 'https://github.com/CocoaPods/Specs.git' -platform :ios, '8.0' +platform :ios, '9.0' target 'Sample' do pod 'Texture', :path => '../..' end diff --git a/examples_extra/VideoTableView/Sample.xcodeproj/project.pbxproj b/examples_extra/VideoTableView/Sample.xcodeproj/project.pbxproj index 8bf0789b1..c7a68ff0b 100644 --- a/examples_extra/VideoTableView/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/VideoTableView/Sample.xcodeproj/project.pbxproj @@ -282,7 +282,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -317,7 +317,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/smoke-tests/Framework/Sample.xcodeproj/project.pbxproj b/smoke-tests/Framework/Sample.xcodeproj/project.pbxproj index b4eeb8cc2..62bb5e98b 100644 --- a/smoke-tests/Framework/Sample.xcodeproj/project.pbxproj +++ b/smoke-tests/Framework/Sample.xcodeproj/project.pbxproj @@ -298,7 +298,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -334,7 +334,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj index 75e17d52f..dd795b754 100644 --- a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj +++ b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj @@ -410,7 +410,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -447,7 +447,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; From 193be32bcfbc52c2c34d72ee448f38cc9c3eb071 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 15 Jan 2018 15:13:54 -0800 Subject: [PATCH 055/133] Update dangerfile for 2018 #trivial (#746) * Update the dangerfile * Make a trivial change to test new dangerfile * Try out the new value with another trivial change --- Dangerfile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Dangerfile b/Dangerfile index 3b8169582..db9f90b33 100644 --- a/Dangerfile +++ b/Dangerfile @@ -52,7 +52,11 @@ def check_file_header(files_to_check, licenses) correct_license = false licenses.each do |license| license_header = full_license(license, filename) - if data.start_with?(license_header) + # Hack for https://github.com/TextureGroup/Texture/issues/745 + # If it's already a "modified-post-Texture" file, leave it with it original copyright year. + if data.include "Modifications to this file made after 4/13/2017" + correct_license = true + elsif data.start_with?(license_header) correct_license = true end end @@ -87,7 +91,7 @@ modified_source_license_header = <<-HEREDOC // LICENSE file in the /ASDK-Licenses directory of this source tree. An additional // grant of patent rights can be found in the PATENTS file in the same directory. // -// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, // Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at From a3136b02250bfcedfe98c93caabeb554069ad4e7 Mon Sep 17 00:00:00 2001 From: Yevgen Pogribnyi Date: Tue, 16 Jan 2018 20:08:29 +0200 Subject: [PATCH 056/133] [ASTraitCollection] Add missing properties to ASTraitCollection (#625) * [ASTraitCollection] Add missing properties to ASTraitCollection * ASTraitCollection now completely reflects UITraitCollection * Add ASContentSizeCategory enum that corresponds to UIContentSizeCategory and can be used inside a struct. * * Remove enum ASContentSizeCategory. * Use __unsafe_unretained UIContentSizeCategory instead of the enum. * Added ASPrimitiveTraitCollection lifetime test * Changes requested at code review: * Restore one of the ASTraitCollection constructors with a deprecation notice. * Clean up API by the separation of tvOS-specific interfaces. * Use [NSString -isEqualToString:] for ASPrimitiveContentSizeCategory equality tests for better readability. * Encapsulate fallback logic for UIContentSizeCategoryUnspecified. * Fix failing test --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 + CHANGELOG.md | 1 + Source/Details/ASTraitCollection.h | 96 ++++- Source/Details/ASTraitCollection.m | 405 +++++++++++++++++++--- Tests/ASCollectionViewTests.mm | 2 +- Tests/ASTraitCollectionTests.m | 34 ++ 6 files changed, 477 insertions(+), 65 deletions(-) create mode 100644 Tests/ASTraitCollectionTests.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index a04b06421..1cff2cac0 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -99,6 +99,7 @@ 3917EBD41E9C2FC400D04A01 /* _ASCollectionReusableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */; settings = {ATTRIBUTES = (Private, ); }; }; 3917EBD51E9C2FC400D04A01 /* _ASCollectionReusableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */; }; 3C9C128519E616EF00E942A0 /* ASTableViewTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */; }; + 4496D0731FA9EA6B001CC8D5 /* ASTraitCollectionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4496D0721FA9EA6B001CC8D5 /* ASTraitCollectionTests.m */; }; 4E9127691F64157600499623 /* ASRunLoopQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 4E9127681F64157600499623 /* ASRunLoopQueueTests.m */; }; 509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E111B371BD7007741D0 /* ASScrollDirection.m */; }; 509E68611B3AEDA0009B9150 /* ASAbstractLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E171B37339C007741D0 /* ASAbstractLayoutController.h */; }; @@ -634,6 +635,7 @@ 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCollectionReusableView.h; sourceTree = ""; }; 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASCollectionReusableView.m; sourceTree = ""; }; 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASTableViewTests.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; + 4496D0721FA9EA6B001CC8D5 /* ASTraitCollectionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASTraitCollectionTests.m; sourceTree = ""; }; 464052191A3F83C40061C0BA /* ASDataController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDataController.h; sourceTree = ""; }; 4640521A1A3F83C40061C0BA /* ASDataController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDataController.mm; sourceTree = ""; }; 4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableLayoutController.h; sourceTree = ""; }; @@ -1256,6 +1258,7 @@ 695BE2541DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm */, 699B83501E3C1BA500433FA4 /* ASLayoutSpecTests.m */, 4E9127681F64157600499623 /* ASRunLoopQueueTests.m */, + 4496D0721FA9EA6B001CC8D5 /* ASTraitCollectionTests.m */, ); path = Tests; sourceTree = ""; @@ -2163,6 +2166,7 @@ buildActionMask = 2147483647; files = ( E51B78BF1F028ABF00E32604 /* ASLayoutFlatteningTests.m in Sources */, + 4496D0731FA9EA6B001CC8D5 /* ASTraitCollectionTests.m in Sources */, 29CDC2E21AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m in Sources */, CC583AD71EF9BDC100134156 /* NSInvocation+ASTestHelpers.m in Sources */, CC051F1F1D7A286A006434CB /* ASCALayerTests.m in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index c884b8023..87165dc7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASTraitCollection] Add new properties of UITraitCollection to ASTraitCollection. [Yevgen Pogribnyi](https://github.com/ypogribnyi) - [ASRectMap] Replace implementation of ASRectTable with a simpler one based on unordered_map.[Scott Goodson](https://github.com/appleguy) [#719](https://github.com/TextureGroup/Texture/pull/719) - [ASCollectionView] Add missing flags for ASCollectionDelegate [Ilya Zheleznikov](https://github.com/ilyailya) [#718](https://github.com/TextureGroup/Texture/pull/718) - [ASNetworkImageNode] Deprecates .URLs in favor of .URL [Garrett Moon](https://github.com/garrettmoon) [#699](https://github.com/TextureGroup/Texture/pull/699) diff --git a/Source/Details/ASTraitCollection.h b/Source/Details/ASTraitCollection.h index 26714aa64..7f8ae3a47 100644 --- a/Source/Details/ASTraitCollection.h +++ b/Source/Details/ASTraitCollection.h @@ -17,6 +17,7 @@ #import + #import @class ASTraitCollection; @@ -27,14 +28,51 @@ NS_ASSUME_NONNULL_BEGIN ASDISPLAYNODE_EXTERN_C_BEGIN +#pragma mark - ASPrimitiveContentSizeCategory + +/** + * ASPrimitiveContentSizeCategory is a UIContentSizeCategory that can be used inside a struct. + * + * We need an unretained pointer because ARC can't manage struct memory. + * + * WARNING: DO NOT cast UIContentSizeCategory values to ASPrimitiveContentSizeCategory directly. + * Use ASPrimitiveContentSizeCategoryMake(UIContentSizeCategory) instead. + * This is because we make some assumptions about the lifetime of the object it points to. + * Also note that cast from ASPrimitiveContentSizeCategory to UIContentSizeCategory is always safe. + */ +typedef __unsafe_unretained UIContentSizeCategory ASPrimitiveContentSizeCategory; + +/** + * Safely casts from UIContentSizeCategory to ASPrimitiveContentSizeCategory. + * + * The UIKit documentation doesn't specify if we can receive a copy of the UIContentSizeCategory constant. While getting + * copies is fine with ARC, usage of unretained pointers requires us to ensure the lifetime of the object it points to. + * Manual retain&release of the UIContentSizeCategory object is not an option because it would require us to do that + * everywhere ASPrimitiveTraitCollection is used. This is error-prone and can lead to crashes and memory leaks. So, we + * explicitly limit possible values of ASPrimitiveContentSizeCategory to the predetermined set of global constants with + * known lifetime. + * + * @return a pointer to one of the UIContentSizeCategory constants. + */ +extern ASPrimitiveContentSizeCategory ASPrimitiveContentSizeCategoryMake(UIContentSizeCategory sizeCategory); + #pragma mark - ASPrimitiveTraitCollection typedef struct ASPrimitiveTraitCollection { - CGFloat displayScale; UIUserInterfaceSizeClass horizontalSizeClass; - UIUserInterfaceIdiom userInterfaceIdiom; UIUserInterfaceSizeClass verticalSizeClass; + + CGFloat displayScale; + UIDisplayGamut displayGamut; + + UIUserInterfaceIdiom userInterfaceIdiom; UIForceTouchCapability forceTouchCapability; + UITraitEnvironmentLayoutDirection layoutDirection; +#if TARGET_OS_TV + UIUserInterfaceStyle userInterfaceStyle; +#endif + + ASPrimitiveContentSizeCategory preferredContentSizeCategory; CGSize containerSize; } ASPrimitiveTraitCollection; @@ -124,11 +162,21 @@ ASDISPLAYNODE_EXTERN_C_END AS_SUBCLASSING_RESTRICTED @interface ASTraitCollection : NSObject -@property (nonatomic, assign, readonly) CGFloat displayScale; @property (nonatomic, assign, readonly) UIUserInterfaceSizeClass horizontalSizeClass; -@property (nonatomic, assign, readonly) UIUserInterfaceIdiom userInterfaceIdiom; @property (nonatomic, assign, readonly) UIUserInterfaceSizeClass verticalSizeClass; + +@property (nonatomic, assign, readonly) CGFloat displayScale; +@property (nonatomic, assign, readonly) UIDisplayGamut displayGamut; + +@property (nonatomic, assign, readonly) UIUserInterfaceIdiom userInterfaceIdiom; @property (nonatomic, assign, readonly) UIForceTouchCapability forceTouchCapability; +@property (nonatomic, assign, readonly) UITraitEnvironmentLayoutDirection layoutDirection; +#if TARGET_OS_TV +@property (nonatomic, assign, readonly) UIUserInterfaceStyle userInterfaceStyle; +#endif + +@property (nonatomic, assign, readonly) UIContentSizeCategory preferredContentSizeCategory; + @property (nonatomic, assign, readonly) CGSize containerSize; + (ASTraitCollection *)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits; @@ -136,17 +184,47 @@ AS_SUBCLASSING_RESTRICTED + (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection containerSize:(CGSize)windowSize; ++ (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection + containerSize:(CGSize)windowSize + fallbackContentSizeCategory:(UIContentSizeCategory)fallbackContentSizeCategory; + +#if TARGET_OS_TV ++ (ASTraitCollection *)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + userInterfaceStyle:(UIUserInterfaceStyle)userInterfaceStyle + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize; +#else ++ (ASTraitCollection *)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize; +#endif + +- (ASPrimitiveTraitCollection)primitiveTraitCollection; +- (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection; + +@end + +@interface ASTraitCollection (Deprecated) + (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass forceTouchCapability:(UIForceTouchCapability)forceTouchCapability - containerSize:(CGSize)windowSize; - - -- (ASPrimitiveTraitCollection)primitiveTraitCollection; -- (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection; + containerSize:(CGSize)windowSize + ASDISPLAYNODE_DEPRECATED_MSG("Use full version of this method instead."); @end diff --git a/Source/Details/ASTraitCollection.m b/Source/Details/ASTraitCollection.m index 570095ec4..dba756baf 100644 --- a/Source/Details/ASTraitCollection.m +++ b/Source/Details/ASTraitCollection.m @@ -20,6 +20,60 @@ #import #import +#pragma mark - ASPrimitiveContentSizeCategory + +// UIContentSizeCategoryUnspecified is available only in iOS 10.0 and later. +// This is used for compatibility with older iOS versions. +ASDISPLAYNODE_INLINE UIContentSizeCategory AS_UIContentSizeCategoryUnspecified() { + if (AS_AVAILABLE_IOS(10)) { + return UIContentSizeCategoryUnspecified; + } else { + return @"_UICTContentSizeCategoryUnspecified"; + } +} + +ASPrimitiveContentSizeCategory ASPrimitiveContentSizeCategoryMake(UIContentSizeCategory sizeCategory) { + if ([sizeCategory isEqualToString:UIContentSizeCategoryExtraSmall]) { + return UIContentSizeCategoryExtraSmall; + } + if ([sizeCategory isEqualToString:UIContentSizeCategorySmall]) { + return UIContentSizeCategorySmall; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryMedium]) { + return UIContentSizeCategoryMedium; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryLarge]) { + return UIContentSizeCategoryLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryExtraLarge]) { + return UIContentSizeCategoryExtraLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryExtraExtraLarge]) { + return UIContentSizeCategoryExtraExtraLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) { + return UIContentSizeCategoryExtraExtraExtraLarge; + } + + if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityMedium]) { + return UIContentSizeCategoryAccessibilityMedium; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityLarge]) { + return UIContentSizeCategoryAccessibilityLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) { + return UIContentSizeCategoryAccessibilityExtraLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) { + return UIContentSizeCategoryAccessibilityExtraExtraLarge; + } + if ([sizeCategory isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) { + return UIContentSizeCategoryAccessibilityExtraExtraExtraLarge; + } + + return AS_UIContentSizeCategoryUnspecified(); +} + #pragma mark - ASPrimitiveTraitCollection extern void ASTraitCollectionPropagateDown(id element, ASPrimitiveTraitCollection traitCollection) { @@ -32,36 +86,57 @@ extern void ASTraitCollectionPropagateDown(id element, ASPrimit } } -ASPrimitiveTraitCollection ASPrimitiveTraitCollectionMakeDefault() -{ +ASPrimitiveTraitCollection ASPrimitiveTraitCollectionMakeDefault() { return (ASPrimitiveTraitCollection) { // Default values can be defined in here + .displayGamut = UIDisplayGamutUnspecified, .userInterfaceIdiom = UIUserInterfaceIdiomUnspecified, + .layoutDirection = UITraitEnvironmentLayoutDirectionUnspecified, + .preferredContentSizeCategory = ASPrimitiveContentSizeCategoryMake(AS_UIContentSizeCategoryUnspecified()), .containerSize = CGSizeZero, }; } -ASPrimitiveTraitCollection ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection) -{ +ASPrimitiveTraitCollection ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection) { ASPrimitiveTraitCollection environmentTraitCollection = ASPrimitiveTraitCollectionMakeDefault(); - environmentTraitCollection.displayScale = traitCollection.displayScale; environmentTraitCollection.horizontalSizeClass = traitCollection.horizontalSizeClass; environmentTraitCollection.verticalSizeClass = traitCollection.verticalSizeClass; + environmentTraitCollection.displayScale = traitCollection.displayScale; environmentTraitCollection.userInterfaceIdiom = traitCollection.userInterfaceIdiom; - if (AS_AVAILABLE_IOS(9)) { - environmentTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability; + environmentTraitCollection.forceTouchCapability = traitCollection.forceTouchCapability; + if (AS_AVAILABLE_IOS(10)) { + environmentTraitCollection.displayGamut = traitCollection.displayGamut; + environmentTraitCollection.layoutDirection = traitCollection.layoutDirection; + + // preferredContentSizeCategory is also available on older iOS versions, but only via UIApplication class. + // It should be noted that [UIApplication sharedApplication] is unavailable because Texture is built with only extension-safe API. + environmentTraitCollection.preferredContentSizeCategory = ASPrimitiveContentSizeCategoryMake(traitCollection.preferredContentSizeCategory); + + #if TARGET_OS_TV + environmentTraitCollection.userInterfaceStyle = traitCollection.userInterfaceStyle; + #endif } return environmentTraitCollection; } -BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTraitCollection lhs, ASPrimitiveTraitCollection rhs) -{ +BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTraitCollection lhs, ASPrimitiveTraitCollection rhs) { + UIContentSizeCategory leftSizeCategory = (UIContentSizeCategory)lhs.preferredContentSizeCategory; + UIContentSizeCategory rightSizeCategory = (UIContentSizeCategory)rhs.preferredContentSizeCategory; + return lhs.verticalSizeClass == rhs.verticalSizeClass && lhs.horizontalSizeClass == rhs.horizontalSizeClass && lhs.displayScale == rhs.displayScale && + lhs.displayGamut == rhs.displayGamut && lhs.userInterfaceIdiom == rhs.userInterfaceIdiom && lhs.forceTouchCapability == rhs.forceTouchCapability && + lhs.layoutDirection == rhs.layoutDirection && + #if TARGET_OS_TV + lhs.userInterfaceStyle == rhs.userInterfaceStyle && + #endif + + [leftSizeCategory isEqualToString:rightSizeCategory] && // Simple pointer comparison should be sufficient here + CGSizeEqualToSize(lhs.containerSize, rhs.containerSize); } @@ -105,14 +180,58 @@ BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTr } } -NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection traits) -{ +// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline +ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIDisplayGamut(UIDisplayGamut displayGamut) { + switch (displayGamut) { + case UIDisplayGamutSRGB: + return @"sRGB"; + case UIDisplayGamutP3: + return @"P3"; + default: + return @"Unspecified"; + } +} + +// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline +ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUITraitEnvironmentLayoutDirection(UITraitEnvironmentLayoutDirection layoutDirection) { + switch (layoutDirection) { + case UITraitEnvironmentLayoutDirectionLeftToRight: + return @"LeftToRight"; + case UITraitEnvironmentLayoutDirectionRightToLeft: + return @"RightToLeft"; + default: + return @"Unspecified"; + } +} + +#if TARGET_OS_TV +// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline +ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceStyle(UIUserInterfaceStyle userInterfaceStyle) { + switch (userInterfaceStyle) { + case UIUserInterfaceStyleLight: + return @"Light"; + case UIUserInterfaceStyleDark: + return @"Dark"; + default: + return @"Unspecified"; + } +} +#endif + +NSString *NSStringFromASPrimitiveTraitCollection(ASPrimitiveTraitCollection traits) { NSMutableArray *props = [NSMutableArray array]; - [props addObject:@{ @"userInterfaceIdiom": AS_NSStringFromUIUserInterfaceIdiom(traits.userInterfaceIdiom) }]; - [props addObject:@{ @"containerSize": NSStringFromCGSize(traits.containerSize) }]; - [props addObject:@{ @"horizontalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.horizontalSizeClass) }]; [props addObject:@{ @"verticalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.verticalSizeClass) }]; + [props addObject:@{ @"horizontalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.horizontalSizeClass) }]; + [props addObject:@{ @"displayScale": [NSString stringWithFormat: @"%.0lf", (double)traits.displayScale] }]; + [props addObject:@{ @"displayGamut": AS_NSStringFromUIDisplayGamut(traits.displayGamut) }]; + [props addObject:@{ @"userInterfaceIdiom": AS_NSStringFromUIUserInterfaceIdiom(traits.userInterfaceIdiom) }]; [props addObject:@{ @"forceTouchCapability": AS_NSStringFromUIForceTouchCapability(traits.forceTouchCapability) }]; + [props addObject:@{ @"layoutDirection": AS_NSStringFromUITraitEnvironmentLayoutDirection(traits.layoutDirection) }]; + #if TARGET_OS_TV + [props addObject:@{ @"userInterfaceStyle": AS_NSStringFromUIUserInterfaceStyle(traits.userInterfaceStyle) }]; + #endif + [props addObject:@{ @"preferredContentSizeCategory": (UIContentSizeCategory)traits.preferredContentSizeCategory }]; + [props addObject:@{ @"containerSize": NSStringFromCGSize(traits.containerSize) }]; return ASObjectDescriptionMakeWithoutObject(props); } @@ -120,69 +239,238 @@ BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTr @implementation ASTraitCollection -- (instancetype)initWithDisplayScale:(CGFloat)displayScale - userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom - horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass - verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass - forceTouchCapability:(UIForceTouchCapability)forceTouchCapability - containerSize:(CGSize)windowSize +#if TARGET_OS_TV + +- (instancetype)initWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + userInterfaceStyle:(UIUserInterfaceStyle)userInterfaceStyle + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize { self = [super init]; if (self) { - _displayScale = displayScale; - _userInterfaceIdiom = userInterfaceIdiom; _horizontalSizeClass = horizontalSizeClass; _verticalSizeClass = verticalSizeClass; + _displayScale = displayScale; + _displayGamut = displayGamut; + _userInterfaceIdiom = userInterfaceIdiom; _forceTouchCapability = forceTouchCapability; + _layoutDirection = layoutDirection; + _userInterfaceStyle = userInterfaceStyle; + _preferredContentSizeCategory = preferredContentSizeCategory; _containerSize = windowSize; } return self; } -+ (instancetype)traitCollectionWithDisplayScale:(CGFloat)displayScale - userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom - horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass - verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass - forceTouchCapability:(UIForceTouchCapability)forceTouchCapability - containerSize:(CGSize)windowSize ++ (instancetype)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + userInterfaceStyle:(UIUserInterfaceStyle)userInterfaceStyle + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize +{ + return [[self alloc] initWithHorizontalSizeClass:horizontalSizeClass + verticalSizeClass:verticalSizeClass + displayScale:displayScale + displayGamut:displayGamut + userInterfaceIdiom:userInterfaceIdiom + forceTouchCapability:forceTouchCapability + layoutDirection:layoutDirection + userInterfaceStyle:userIntefaceStyle + preferredContentSizeCategory:preferredContentSizeCategory + containerSize:windowSize]; +} + +#else + +- (instancetype)initWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize +{ + self = [super init]; + if (self) { + _horizontalSizeClass = horizontalSizeClass; + _verticalSizeClass = verticalSizeClass; + _displayScale = displayScale; + _displayGamut = displayGamut; + _userInterfaceIdiom = userInterfaceIdiom; + _forceTouchCapability = forceTouchCapability; + _layoutDirection = layoutDirection; + _preferredContentSizeCategory = preferredContentSizeCategory; + _containerSize = windowSize; + } + return self; +} + ++ (instancetype)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + displayScale:(CGFloat)displayScale + displayGamut:(UIDisplayGamut)displayGamut + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection + preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + containerSize:(CGSize)windowSize { - return [[self alloc] initWithDisplayScale:displayScale - userInterfaceIdiom:userInterfaceIdiom - horizontalSizeClass:horizontalSizeClass - verticalSizeClass:verticalSizeClass - forceTouchCapability:forceTouchCapability - containerSize:windowSize]; + return [[self alloc] initWithHorizontalSizeClass:horizontalSizeClass + verticalSizeClass:verticalSizeClass + displayScale:displayScale + displayGamut:displayGamut + userInterfaceIdiom:userInterfaceIdiom + forceTouchCapability:forceTouchCapability + layoutDirection:layoutDirection + preferredContentSizeCategory:preferredContentSizeCategory + containerSize:windowSize]; +} + +#endif + ++ (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + containerSize:(CGSize)windowSize +{ +#if TARGET_OS_TV + return [self traitCollectionWithHorizontalSizeClass:horizontalSizeClass + verticalSizeClass:verticalSizeClass + displayScale:displayScale + displayGamut:UIDisplayGamutUnspecified + userInterfaceIdiom:userInterfaceIdiom + forceTouchCapability:forceTouchCapability + layoutDirection:UITraitEnvironmentLayoutDirectionUnspecified + userInterfaceStyle:UIUserInterfaceStyleUnspecified + preferredContentSizeCategory:AS_UIContentSizeCategoryUnspecified() + containerSize:windowSize]; +#else + return [self traitCollectionWithHorizontalSizeClass:horizontalSizeClass + verticalSizeClass:verticalSizeClass + displayScale:displayScale + displayGamut:UIDisplayGamutUnspecified + userInterfaceIdiom:userInterfaceIdiom + forceTouchCapability:forceTouchCapability + layoutDirection:UITraitEnvironmentLayoutDirectionUnspecified + preferredContentSizeCategory:AS_UIContentSizeCategoryUnspecified() + containerSize:windowSize]; +#endif } + (instancetype)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits { - return [self traitCollectionWithDisplayScale:traits.displayScale - userInterfaceIdiom:traits.userInterfaceIdiom - horizontalSizeClass:traits.horizontalSizeClass - verticalSizeClass:traits.verticalSizeClass - forceTouchCapability:traits.forceTouchCapability - containerSize:traits.containerSize]; +#if TARGET_OS_TV + return [self traitCollectionWithHorizontalSizeClass:traits.horizontalSizeClass + verticalSizeClass:traits.verticalSizeClass + displayScale:traits.displayScale + displayGamut:traits.displayGamut + userInterfaceIdiom:traits.userInterfaceIdiom + forceTouchCapability:traits.forceTouchCapability + layoutDirection:traits.layoutDirection + userInterfaceStyle:traits.userInterfaceStyle + preferredContentSizeCategory:(UIContentSizeCategory)traits.preferredContentSizeCategory + containerSize:traits.containerSize]; +#else + return [self traitCollectionWithHorizontalSizeClass:traits.horizontalSizeClass + verticalSizeClass:traits.verticalSizeClass + displayScale:traits.displayScale + displayGamut:traits.displayGamut + userInterfaceIdiom:traits.userInterfaceIdiom + forceTouchCapability:traits.forceTouchCapability + layoutDirection:traits.layoutDirection + preferredContentSizeCategory:(UIContentSizeCategory)traits.preferredContentSizeCategory + containerSize:traits.containerSize]; +#endif } + (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection - containerSize:(CGSize)windowSize + containerSize:(CGSize)windowSize { - return [self traitCollectionWithDisplayScale:traitCollection.displayScale - userInterfaceIdiom:traitCollection.userInterfaceIdiom - horizontalSizeClass:traitCollection.horizontalSizeClass - verticalSizeClass:traitCollection.verticalSizeClass - forceTouchCapability:traitCollection.forceTouchCapability - containerSize:windowSize]; + return [self traitCollectionWithUITraitCollection:traitCollection + containerSize:windowSize + fallbackContentSizeCategory:AS_UIContentSizeCategoryUnspecified()]; +} + + ++ (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection + containerSize:(CGSize)windowSize + fallbackContentSizeCategory:(UIContentSizeCategory)fallbackContentSizeCategory +{ + UIDisplayGamut displayGamut; + UITraitEnvironmentLayoutDirection layoutDirection; + UIContentSizeCategory sizeCategory; + #if TARGET_OS_TV + UIUserInterfaceStyle userInterfaceStyle; + #endif + if (AS_AVAILABLE_IOS(10)) { + displayGamut = traitCollection.displayGamut; + layoutDirection = traitCollection.layoutDirection; + sizeCategory = traitCollection.preferredContentSizeCategory; + #if TARGET_OS_TV + userInterfaceStyle = traitCollection.userInterfaceStyle; + #endif + } else { + displayGamut = UIDisplayGamutUnspecified; + layoutDirection = UITraitEnvironmentLayoutDirectionUnspecified; + sizeCategory = fallbackContentSizeCategory; + #if TARGET_OS_TV + userInterfaceStyle = UIUserInterfaceStyleUnspecified; + #endif + } + +#if TARGET_OS_TV + return [self traitCollectionWithHorizontalSizeClass:traitCollection.horizontalSizeClass + verticalSizeClass:traitCollection.verticalSizeClass + displayScale:traitCollection.displayScale + displayGamut:displayGamut + userInterfaceIdiom:traitCollection.userInterfaceIdiom + forceTouchCapability:traitCollection.forceTouchCapability + layoutDirection:layoutDirection + userInterfaceStyle:userInterfaceStyle + preferredContentSizeCategory:sizeCategory + containerSize:windowSize]; +#else + return [self traitCollectionWithHorizontalSizeClass:traitCollection.horizontalSizeClass + verticalSizeClass:traitCollection.verticalSizeClass + displayScale:traitCollection.displayScale + displayGamut:displayGamut + userInterfaceIdiom:traitCollection.userInterfaceIdiom + forceTouchCapability:traitCollection.forceTouchCapability + layoutDirection:layoutDirection + preferredContentSizeCategory:sizeCategory + containerSize:windowSize]; +#endif } - (ASPrimitiveTraitCollection)primitiveTraitCollection { return (ASPrimitiveTraitCollection) { - .displayScale = self.displayScale, .horizontalSizeClass = self.horizontalSizeClass, - .userInterfaceIdiom = self.userInterfaceIdiom, .verticalSizeClass = self.verticalSizeClass, + .displayScale = self.displayScale, + .displayGamut = self.displayGamut, + .userInterfaceIdiom = self.userInterfaceIdiom, .forceTouchCapability = self.forceTouchCapability, + .layoutDirection = self.layoutDirection, + #if TARGET_OS_TV + .userInterfaceStyle = self.userInterfaceStyle, + #endif + .preferredContentSizeCategory = ASPrimitiveContentSizeCategoryMake(self.preferredContentSizeCategory), .containerSize = self.containerSize, }; } @@ -193,12 +481,19 @@ - (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection return YES; } - return self.displayScale == traitCollection.displayScale && - self.horizontalSizeClass == traitCollection.horizontalSizeClass && - self.verticalSizeClass == traitCollection.verticalSizeClass && - self.userInterfaceIdiom == traitCollection.userInterfaceIdiom && - CGSizeEqualToSize(self.containerSize, traitCollection.containerSize) && - self.forceTouchCapability == traitCollection.forceTouchCapability; + return + self.horizontalSizeClass == traitCollection.horizontalSizeClass && + self.verticalSizeClass == traitCollection.verticalSizeClass && + self.displayScale == traitCollection.displayScale && + self.displayGamut == traitCollection.displayGamut && + self.userInterfaceIdiom == traitCollection.userInterfaceIdiom && + self.forceTouchCapability == traitCollection.forceTouchCapability && + self.layoutDirection == traitCollection.layoutDirection && + #if TARGET_OS_TV + self.userInterfaceStyle == traitCollection.userInterfaceStyle && + #endif + [self.preferredContentSizeCategory isEqualToString:traitCollection.preferredContentSizeCategory] && + CGSizeEqualToSize(self.containerSize, traitCollection.containerSize); } @end diff --git a/Tests/ASCollectionViewTests.mm b/Tests/ASCollectionViewTests.mm index 06c6fcd89..0391e992d 100644 --- a/Tests/ASCollectionViewTests.mm +++ b/Tests/ASCollectionViewTests.mm @@ -1097,7 +1097,7 @@ - (void)testTraitCollectionChangesMidUpdate [window layoutIfNeeded]; // The initial reload is async, changing the trait collection here should be "mid-update" - ASPrimitiveTraitCollection traitCollection; + ASPrimitiveTraitCollection traitCollection = ASPrimitiveTraitCollectionMakeDefault(); traitCollection.displayScale = cn.primitiveTraitCollection.displayScale + 1; // Just a dummy change traitCollection.containerSize = screenBounds.size; cn.primitiveTraitCollection = traitCollection; diff --git a/Tests/ASTraitCollectionTests.m b/Tests/ASTraitCollectionTests.m new file mode 100644 index 000000000..aa106d75e --- /dev/null +++ b/Tests/ASTraitCollectionTests.m @@ -0,0 +1,34 @@ +// +// ASTraitCollectionTests.m +// Texture +// +// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import + +@interface ASTraitCollectionTests : XCTestCase + +@end + +@implementation ASTraitCollectionTests + +- (void)testPrimitiveContentSizeCategoryLifetime +{ + ASPrimitiveContentSizeCategory primitiveContentSize; + @autoreleasepool { + // Make sure the compiler won't optimize string alloc/dealloc + NSString *contentSizeCategory = [NSString stringWithCString:"UICTContentSizeCategoryL" encoding:NSUTF8StringEncoding]; + primitiveContentSize = ASPrimitiveContentSizeCategoryMake(contentSizeCategory); + } + + XCTAssertEqual(primitiveContentSize, UIContentSizeCategoryLarge); +} + +@end From 4776cb3dcdaf85e6aaf3fed18870a4dacbe821ff Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 16 Jan 2018 12:55:27 -0800 Subject: [PATCH 057/133] Fix the dangerfile for real (#750) --- Dangerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dangerfile b/Dangerfile index db9f90b33..e9779d610 100644 --- a/Dangerfile +++ b/Dangerfile @@ -54,7 +54,7 @@ def check_file_header(files_to_check, licenses) license_header = full_license(license, filename) # Hack for https://github.com/TextureGroup/Texture/issues/745 # If it's already a "modified-post-Texture" file, leave it with it original copyright year. - if data.include "Modifications to this file made after 4/13/2017" + if data.include? "Modifications to this file made after 4/13/2017" correct_license = true elsif data.start_with?(license_header) correct_license = true From 2e9858837251cf16c9ffd19ba2eaeaa1012c8977 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Wed, 17 Jan 2018 15:35:02 +0000 Subject: [PATCH 058/133] [ASDisplayNode] Don't force a layout pass on a visible node that enters preload state (#751) - After #706, a layout pass is forced on an ASM-enabled node that enters preload state to make sure that its subnodes can start preloading as well. However, when the node is visible, a (coalesced, thus more efficient) layout pass will be triggered by CA soon anyways, so rely on it instead. --- CHANGELOG.md | 2 +- Source/ASDisplayNode.mm | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87165dc7e..1f6ea8c3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ - [ASScrollNode] Ensure the node respects the given size range while calculating its layout. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - [ASScrollNode] Invalidate the node's calculated layout if its scrollable directions changed. Also add unit tests for the class. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424) -- [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so the subnodes can preload too. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706) +- [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so subnodes can start preloading right away. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706) [#751](https://github.com/TextureGroup/Texture/pull/751) - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) - Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) - Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 0f2bf7719..899331ea4 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -3079,16 +3079,23 @@ - (void)didEnterPreloadState ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); - if (self.automaticallyManagesSubnodes) { - // Tell the node to apply its applicable pending layout, if any, so that its subnodes are inserted/deleted - // and start preloading right away. - // - // If this node has an up-to-date layout (and subnodes), calling layoutIfNeeded will be fast. - // - // If this node doesn't have a calculated or pending layout that fits its current bounds, a measurement pass will occur - // (see __layout and _u_measureNodeWithBoundsIfNecessary:). - // This scenario should be uncommon, and running a measurement pass here is a fine trade-off because preloading - // any time after this point would be late. + // If this node has ASM enabled and is not yet visible, force a layout pass to apply its applicable pending layout, if any, + // so that its subnodes are inserted/deleted and start preloading right away. + // + // - If it has an up-to-date layout (and subnodes), calling -layoutIfNeeded will be fast. + // + // - If it doesn't have a calculated or pending layout that fits its current bounds, a measurement pass will occur + // (see -__layout and -_u_measureNodeWithBoundsIfNecessary:). This scenario is uncommon, + // and running a measurement pass here is a fine trade-off because preloading any time after this point would be late. + // + // Don't force a layout pass if the node is already visible. Soon CoreAnimation will trigger + // a (coalesced, thus more efficient) pass on the backing store. Rely on it instead. + BOOL shouldForceLayoutPass = NO; + { + ASDN::MutexLocker l(__instanceLock__); + shouldForceLayoutPass = _automaticallyManagesSubnodes && !ASInterfaceStateIncludesVisible(_interfaceState); + } + if (shouldForceLayoutPass) { [self layoutIfNeeded]; } From b5d3e52e8b71401a66bb20279ba2993ee787cabd Mon Sep 17 00:00:00 2001 From: janechoi6 Date: Fri, 19 Jan 2018 01:00:15 +0900 Subject: [PATCH 059/133] Update subclassing.md (#753) --- docs/_docs/subclassing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_docs/subclassing.md b/docs/_docs/subclassing.md index 44c3260ee..ea4cd24b9 100755 --- a/docs/_docs/subclassing.md +++ b/docs/_docs/subclassing.md @@ -51,7 +51,7 @@ An `ASViewController` is a regular `UIViewController` subclass that has special ### `-init` -This method is called once, at the very begining of an ASViewController's lifecycle. As with UIViewController initialization, it is best practice to **never access** `self.view` or `self.node.view` in this method as it will force the view to be created early. Instead, do any view access in -viewDidLoad. +This method is called once, at the very beginning of an ASViewController's lifecycle. As with UIViewController initialization, it is best practice to **never access** `self.view` or `self.node.view` in this method as it will force the view to be created early. Instead, do any view access in -viewDidLoad. ASViewController's designated initializer is `initWithNode:`. A typical initializer will look something like the code below. Note how the ASViewController's node is created _before_ calling super. An ASViewController manages a node similarly to how a UIViewController manages a view, but the initialization is slightly different. From 5c13403ef75c030adc7a4d51a7792a9c6c1c348b Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 22 Jan 2018 05:22:03 -0800 Subject: [PATCH 060/133] Faster collection operations (#748) * Faster collection operations * Fix a few things * Put the stupid semicolon * Address warning * Cut down retain/releases during collection operations * Update CHANGELOG.md --- .../xcschemes/AsyncDisplayKit.xcscheme | 1 + CHANGELOG.md | 1 + Source/ASCollectionView.mm | 39 ++------- Source/ASDisplayNode+Yoga.mm | 10 +-- Source/ASTableView.mm | 12 +-- Source/Base/ASBaseDefines.h | 83 ++++++++++++------- .../Details/ASCollectionFlowLayoutDelegate.m | 2 +- .../ASCollectionGalleryLayoutDelegate.mm | 6 +- Source/Details/ASElementMap.h | 5 ++ Source/Details/ASElementMap.m | 5 ++ Source/Layout/ASLayout.mm | 17 ++-- Source/Layout/ASLayoutSpec.mm | 10 +-- Source/Private/ASTwoDimensionalArrayUtils.m | 9 +- Source/_ASTransitionContext.m | 8 +- 14 files changed, 100 insertions(+), 108 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme index 8cf72597a..5387aa675 100644 --- a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme +++ b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme @@ -60,6 +60,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + disableMainThreadChecker = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f6ea8c3a..e9351912f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) - Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) - Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler) +- Optimize internal collection operations. [Adlai Holler](https://github.com/Adlai-Holler) [#748](https://github.com/TextureGroup/Texture/pull/748) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 1b06d6bef..dfc5dde0c 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -719,19 +719,9 @@ - (NSIndexPath *)convertIndexPathToCollectionNode:(NSIndexPath *)indexPath - (NSArray *)convertIndexPathsToCollectionNode:(NSArray *)indexPaths { - if (indexPaths == nil) { - return nil; - } - - NSMutableArray *indexPathsArray = [NSMutableArray arrayWithCapacity:indexPaths.count]; - - for (NSIndexPath *indexPathInView in indexPaths) { - NSIndexPath *indexPath = [self convertIndexPathToCollectionNode:indexPathInView]; - if (indexPath != nil) { - [indexPathsArray addObject:indexPath]; - } - } - return indexPathsArray; + return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPathInView, ({ + [self convertIndexPathToCollectionNode:indexPathInView]; + })); } - (ASCellNode *)supplementaryNodeForElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath @@ -747,17 +737,10 @@ - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode - (NSArray *)visibleNodes { NSArray *indexPaths = [self indexPathsForVisibleItems]; - NSMutableArray *visibleNodes = [[NSMutableArray alloc] init]; - for (NSIndexPath *indexPath in indexPaths) { - ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; - if (node) { - // It is possible for UICollectionView to return indexPaths before the node is completed. - [visibleNodes addObject:node]; - } - } - - return visibleNodes; + return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPath, ({ + [self nodeForItemAtIndexPath:indexPath]; + })); } - (BOOL)usesSynchronousDataLoading @@ -2176,13 +2159,9 @@ - (void)nodesDidRelayout:(NSArray *)nodes return; } - NSMutableArray *uikitIndexPaths = [NSMutableArray arrayWithCapacity:nodes.count]; - for (ASCellNode *node in nodes) { - NSIndexPath *uikitIndexPath = [self indexPathForNode:node]; - if (uikitIndexPath != nil) { - [uikitIndexPaths addObject:uikitIndexPath]; - } - } + NSArray *uikitIndexPaths = ASArrayByFlatMapping(nodes, ASCellNode *node, ({ + [self indexPathForNode:node]; + })); [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:uikitIndexPaths batched:NO]; diff --git a/Source/ASDisplayNode+Yoga.mm b/Source/ASDisplayNode+Yoga.mm index b52f94821..d4a0ba115 100644 --- a/Source/ASDisplayNode+Yoga.mm +++ b/Source/ASDisplayNode+Yoga.mm @@ -158,14 +158,12 @@ - (ASLayout *)layoutForYogaNode - (void)setupYogaCalculatedLayout { YGNodeRef yogaNode = self.style.yogaNode; - uint32_t childCount = YGNodeGetChildCount(yogaNode); - ASDisplayNodeAssert(childCount == self.yogaChildren.count, + ASDisplayNodeAssert(YGNodeGetChildCount(yogaNode) == self.yogaChildren.count, @"Yoga tree should always be in sync with .yogaNodes array! %@", self.yogaChildren); - NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:childCount]; - for (ASDisplayNode *subnode in self.yogaChildren) { - [sublayouts addObject:[subnode layoutForYogaNode]]; - } + NSArray *sublayouts = ASArrayByFlatMapping(self.yogaChildren, ASDisplayNode *subnode, ({ + [subnode layoutForYogaNode]; + })); // The layout for self should have position CGPointNull, but include the calculated size. CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode)); diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index f78a688ea..fc0a2dbbe 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -607,15 +607,9 @@ - (NSIndexPath *)convertIndexPathToTableNode:(NSIndexPath *)indexPath return nil; } - NSMutableArray *indexPathsArray = [NSMutableArray new]; - - for (NSIndexPath *indexPathInView in indexPaths) { - NSIndexPath *indexPath = [self convertIndexPathToTableNode:indexPathInView]; - if (indexPath != nil) { - [indexPathsArray addObject:indexPath]; - } - } - return indexPathsArray; + return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPathInView, ({ + [self convertIndexPathToTableNode:indexPathInView]; + })); } - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode diff --git a/Source/Base/ASBaseDefines.h b/Source/Base/ASBaseDefines.h index 9eb1ec0b0..37749e53d 100755 --- a/Source/Base/ASBaseDefines.h +++ b/Source/Base/ASBaseDefines.h @@ -132,22 +132,6 @@ #define __has_attribute(x) 0 // Compatibility with non-clang compilers. #endif -#ifndef NS_CONSUMED -#if __has_feature(attribute_ns_consumed) -#define NS_CONSUMED __attribute__((ns_consumed)) -#else -#define NS_CONSUMED -#endif -#endif - -#ifndef NS_RETURNS_RETAINED -#if __has_feature(attribute_ns_returns_retained) -#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained)) -#else -#define NS_RETURNS_RETAINED -#endif -#endif - #ifndef CF_RETURNS_RETAINED #if __has_feature(attribute_cf_returns_retained) #define CF_RETURNS_RETAINED __attribute__((cf_returns_retained)) @@ -245,26 +229,38 @@ * Create a new set by mapping `collection` over `work`, ignoring nil. */ #define ASSetByFlatMapping(collection, decl, work) ({ \ - NSMutableSet *s = [NSMutableSet set]; \ + CFTypeRef _cArray[collection.count]; \ + NSUInteger _i = 0; \ for (decl in collection) {\ - id result = work; \ - if (result != nil) { \ - [s addObject:result]; \ + if ((_cArray[_i] = (__bridge_retained CFTypeRef)work)) { \ + _i++; \ } \ } \ - s; \ + NSSet *result; \ + if (_i == 0) { \ + /** Zero fast path. */ \ + result = [NSSet set]; \ + } else if (_i == 1) { \ + /** NSSingleObjectSet is fast. Create one and release. */ \ + CFTypeRef val = _cArray[0]; \ + result = [NSSet setWithObject:(__bridge id)val]; \ + CFBridgingRelease(val); \ + } else { \ + CFSetCallBacks cb = kCFTypeSetCallBacks; \ + cb.retain = NULL; \ + result = (__bridge NSSet *)CFSetCreate(kCFAllocatorDefault, _cArray, _i, &cb); \ + } \ + result; \ }) /** * Create a new ObjectPointerPersonality NSHashTable by mapping `collection` over `work`, ignoring nil. */ #define ASPointerTableByFlatMapping(collection, decl, work) ({ \ - NSHashTable *t = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; \ + NSHashTable *t = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality capacity:collection.count]; \ for (decl in collection) {\ - id result = work; \ - if (result != nil) { \ - [t addObject:result]; \ - } \ + /* NSHashTable accepts nil and avoid extra retain/release. */ \ + [t addObject:work]; \ } \ t; \ }) @@ -273,12 +269,37 @@ * Create a new array by mapping `collection` over `work`, ignoring nil. */ #define ASArrayByFlatMapping(collection, decl, work) ({ \ - NSMutableArray *a = [NSMutableArray array]; \ + CFTypeRef _cArray[collection.count]; \ + NSUInteger _i = 0; \ + for (decl in collection) {\ + if ((_cArray[_i] = (__bridge_retained CFTypeRef)work)) { \ + _i++; \ + } \ + } \ + NSArray *result; \ + if (_i == 0) { \ + /** Zero array fast path. */ \ + result = @[]; \ + } else if (_i == 1) { \ + /** NSSingleObjectArray is fast. Create one and release. */ \ + CFTypeRef val = _cArray[0]; \ + result = [NSArray arrayWithObject:(__bridge id)val]; \ + CFBridgingRelease(val); \ + } else { \ + CFArrayCallBacks cb = kCFTypeArrayCallBacks; \ + cb.retain = NULL; \ + result = (__bridge NSArray *)CFArrayCreate(kCFAllocatorDefault, _cArray, _i, &cb); \ + } \ + result; \ +}) + +#define ASMutableArrayByFlatMapping(collection, decl, work) ({ \ + id _cArray[collection.count]; \ + NSUInteger _i = 0; \ for (decl in collection) {\ - id result = work; \ - if (result != nil) { \ - [a addObject:result]; \ + if ((_cArray[_i] = work)) { \ + _i++; \ } \ } \ - a; \ + [[NSMutableArray alloc] initWithObjects:_cArray count:_i]; \ }) diff --git a/Source/Details/ASCollectionFlowLayoutDelegate.m b/Source/Details/ASCollectionFlowLayoutDelegate.m index 548fa0181..77e092226 100644 --- a/Source/Details/ASCollectionFlowLayoutDelegate.m +++ b/Source/Details/ASCollectionFlowLayoutDelegate.m @@ -59,7 +59,7 @@ - (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements + (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context { ASElementMap *elements = context.elements; - NSMutableArray *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node); + NSArray *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node); if (children.count == 0) { return [[ASCollectionLayoutState alloc] initWithContext:context]; } diff --git a/Source/Details/ASCollectionGalleryLayoutDelegate.mm b/Source/Details/ASCollectionGalleryLayoutDelegate.mm index 2734e9649..482123c51 100644 --- a/Source/Details/ASCollectionGalleryLayoutDelegate.mm +++ b/Source/Details/ASCollectionGalleryLayoutDelegate.mm @@ -102,9 +102,9 @@ + (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutConte return [[ASCollectionLayoutState alloc] initWithContext:context]; } - NSMutableArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements, - ASCollectionElement *element, - [[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]); + NSArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements, + ASCollectionElement *element, + [[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]); if (children.count == 0) { return [[ASCollectionLayoutState alloc] initWithContext:context]; } diff --git a/Source/Details/ASElementMap.h b/Source/Details/ASElementMap.h index eed7eff26..a73f80a92 100644 --- a/Source/Details/ASElementMap.h +++ b/Source/Details/ASElementMap.h @@ -31,6 +31,11 @@ NS_ASSUME_NONNULL_BEGIN AS_SUBCLASSING_RESTRICTED @interface ASElementMap : NSObject +/** + * The total number of elements in this map. + */ +@property (readonly) NSUInteger count; + /** * The number of sections (of items) in this map. */ diff --git a/Source/Details/ASElementMap.m b/Source/Details/ASElementMap.m index 08e90d401..f5646de78 100644 --- a/Source/Details/ASElementMap.m +++ b/Source/Details/ASElementMap.m @@ -75,6 +75,11 @@ - (instancetype)initWithSections:(NSArray *)sections items:(ASColle return self; } +- (NSUInteger)count +{ + return _elementToIndexPathMap.count; +} + - (NSArray *)itemIndexPaths { return ASIndexPathsForTwoDimensionalArray(_sectionsOfItems); diff --git a/Source/Layout/ASLayout.mm b/Source/Layout/ASLayout.mm index f6fbe9127..226b4a89e 100644 --- a/Source/Layout/ASLayout.mm +++ b/Source/Layout/ASLayout.mm @@ -203,13 +203,9 @@ - (void)setRetainSublayoutLayoutElements:(BOOL)retainSublayoutLayoutElements _sublayoutLayoutElements = nil; } else { // Add sublayouts layout elements to an internal array to retain it while the layout lives - NSUInteger sublayoutCount = _sublayouts.count; - if (sublayoutCount > 0) { - _sublayoutLayoutElements = [NSMutableArray arrayWithCapacity:sublayoutCount]; - for (ASLayout *sublayout in _sublayouts) { - [_sublayoutLayoutElements addObject:sublayout.layoutElement]; - } - } + _sublayoutLayoutElements = ASMutableArrayByFlatMapping(_sublayouts, ASLayout *sublayout, ({ + sublayout.layoutElement; + })); } } } @@ -236,7 +232,7 @@ - (ASLayout *)filteredNodeLayoutTree queue.push_back({sublayout, sublayout.position}); } - NSMutableArray *flattenedSublayouts = [NSMutableArray array]; + std::vector flattenedSublayouts; while (!queue.empty()) { const Context context = queue.front(); @@ -255,7 +251,7 @@ - (ASLayout *)filteredNodeLayoutTree position:absolutePosition sublayouts:@[]]; } - [flattenedSublayouts addObject:layout]; + flattenedSublayouts.push_back(layout); } else if (sublayoutsCount > 0){ std::vector sublayoutContexts; for (ASLayout *sublayout in sublayouts) { @@ -265,7 +261,8 @@ - (ASLayout *)filteredNodeLayoutTree } } - ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:flattenedSublayouts]; + NSArray *sublayoutsArray = [NSArray arrayWithObjects:flattenedSublayouts.data() count:flattenedSublayouts.size()]; + ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:sublayoutsArray]; // All flattened layouts must have this flag enabled // to ensure sublayout elements are retained until the layouts are applied. layout.retainSublayoutLayoutElements = YES; diff --git a/Source/Layout/ASLayoutSpec.mm b/Source/Layout/ASLayoutSpec.mm index 76901d1ac..a6aabdb39 100644 --- a/Source/Layout/ASLayoutSpec.mm +++ b/Source/Layout/ASLayoutSpec.mm @@ -295,19 +295,15 @@ - (instancetype)initWithLayoutElements:(NSArray> *)layoutEle - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize { - NSArray *children = self.children; - NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:children.count]; - CGSize size = constrainedSize.min; - for (id child in children) { + NSArray *sublayouts = ASArrayByFlatMapping(self.children, id child, ({ ASLayout *sublayout = [child layoutThatFits:constrainedSize parentSize:constrainedSize.max]; sublayout.position = CGPointZero; size.width = MAX(size.width, sublayout.size.width); size.height = MAX(size.height, sublayout.size.height); - - [sublayouts addObject:sublayout]; - } + sublayout; + })); return [ASLayout layoutWithLayoutElement:self size:size sublayouts:sublayouts]; } diff --git a/Source/Private/ASTwoDimensionalArrayUtils.m b/Source/Private/ASTwoDimensionalArrayUtils.m index 71a48d450..b21aeb41b 100644 --- a/Source/Private/ASTwoDimensionalArrayUtils.m +++ b/Source/Private/ASTwoDimensionalArrayUtils.m @@ -27,13 +27,10 @@ NSMutableArray *ASTwoDimensionalArrayDeepMutableCopy(NSArray *array) { - NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; - NSInteger i = 0; - for (NSArray *subarray in array) { + return ASMutableArrayByFlatMapping(array, NSArray *subarray, ({ ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); - newArray[i++] = [subarray mutableCopy]; - } - return newArray; + [subarray mutableCopy]; + })); } void ASDeleteElementsInTwoDimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) diff --git a/Source/_ASTransitionContext.m b/Source/_ASTransitionContext.m index da6350a4a..b664feaf5 100644 --- a/Source/_ASTransitionContext.m +++ b/Source/_ASTransitionContext.m @@ -69,11 +69,9 @@ - (CGRect)finalFrameForNode:(ASDisplayNode *)node - (NSArray *)subnodesForKey:(NSString *)key { - NSMutableArray *subnodes = [NSMutableArray array]; - for (ASLayout *sublayout in [self layoutForKey:key].sublayouts) { - [subnodes addObject:(ASDisplayNode *)sublayout.layoutElement]; - } - return subnodes; + return ASArrayByFlatMapping([self layoutForKey:key].sublayouts, ASLayout *sublayout, ({ + (ASDisplayNode *)sublayout.layoutElement; + })); } - (NSArray *)insertedSubnodes From 9b8a919a938321a34982208346d7a8e1d7949ef6 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 22 Jan 2018 11:38:09 -0800 Subject: [PATCH 061/133] Revert "Faster collection operations (#748)" (#759) This reverts commit 5c13403ef75c030adc7a4d51a7792a9c6c1c348b. --- .../xcschemes/AsyncDisplayKit.xcscheme | 1 - CHANGELOG.md | 1 - Source/ASCollectionView.mm | 39 +++++++-- Source/ASDisplayNode+Yoga.mm | 10 ++- Source/ASTableView.mm | 12 ++- Source/Base/ASBaseDefines.h | 83 +++++++------------ .../Details/ASCollectionFlowLayoutDelegate.m | 2 +- .../ASCollectionGalleryLayoutDelegate.mm | 6 +- Source/Details/ASElementMap.h | 5 -- Source/Details/ASElementMap.m | 5 -- Source/Layout/ASLayout.mm | 17 ++-- Source/Layout/ASLayoutSpec.mm | 10 ++- Source/Private/ASTwoDimensionalArrayUtils.m | 9 +- Source/_ASTransitionContext.m | 8 +- 14 files changed, 108 insertions(+), 100 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme index 5387aa675..8cf72597a 100644 --- a/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme +++ b/AsyncDisplayKit.xcodeproj/xcshareddata/xcschemes/AsyncDisplayKit.xcscheme @@ -60,7 +60,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - disableMainThreadChecker = "YES" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/CHANGELOG.md b/CHANGELOG.md index e9351912f..1f6ea8c3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,6 @@ - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) - Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) - Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler) -- Optimize internal collection operations. [Adlai Holler](https://github.com/Adlai-Holler) [#748](https://github.com/TextureGroup/Texture/pull/748) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index dfc5dde0c..1b06d6bef 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -719,9 +719,19 @@ - (NSIndexPath *)convertIndexPathToCollectionNode:(NSIndexPath *)indexPath - (NSArray *)convertIndexPathsToCollectionNode:(NSArray *)indexPaths { - return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPathInView, ({ - [self convertIndexPathToCollectionNode:indexPathInView]; - })); + if (indexPaths == nil) { + return nil; + } + + NSMutableArray *indexPathsArray = [NSMutableArray arrayWithCapacity:indexPaths.count]; + + for (NSIndexPath *indexPathInView in indexPaths) { + NSIndexPath *indexPath = [self convertIndexPathToCollectionNode:indexPathInView]; + if (indexPath != nil) { + [indexPathsArray addObject:indexPath]; + } + } + return indexPathsArray; } - (ASCellNode *)supplementaryNodeForElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath @@ -737,10 +747,17 @@ - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode - (NSArray *)visibleNodes { NSArray *indexPaths = [self indexPathsForVisibleItems]; + NSMutableArray *visibleNodes = [[NSMutableArray alloc] init]; - return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPath, ({ - [self nodeForItemAtIndexPath:indexPath]; - })); + for (NSIndexPath *indexPath in indexPaths) { + ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; + if (node) { + // It is possible for UICollectionView to return indexPaths before the node is completed. + [visibleNodes addObject:node]; + } + } + + return visibleNodes; } - (BOOL)usesSynchronousDataLoading @@ -2159,9 +2176,13 @@ - (void)nodesDidRelayout:(NSArray *)nodes return; } - NSArray *uikitIndexPaths = ASArrayByFlatMapping(nodes, ASCellNode *node, ({ - [self indexPathForNode:node]; - })); + NSMutableArray *uikitIndexPaths = [NSMutableArray arrayWithCapacity:nodes.count]; + for (ASCellNode *node in nodes) { + NSIndexPath *uikitIndexPath = [self indexPathForNode:node]; + if (uikitIndexPath != nil) { + [uikitIndexPaths addObject:uikitIndexPath]; + } + } [_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:uikitIndexPaths batched:NO]; diff --git a/Source/ASDisplayNode+Yoga.mm b/Source/ASDisplayNode+Yoga.mm index d4a0ba115..b52f94821 100644 --- a/Source/ASDisplayNode+Yoga.mm +++ b/Source/ASDisplayNode+Yoga.mm @@ -158,12 +158,14 @@ - (ASLayout *)layoutForYogaNode - (void)setupYogaCalculatedLayout { YGNodeRef yogaNode = self.style.yogaNode; - ASDisplayNodeAssert(YGNodeGetChildCount(yogaNode) == self.yogaChildren.count, + uint32_t childCount = YGNodeGetChildCount(yogaNode); + ASDisplayNodeAssert(childCount == self.yogaChildren.count, @"Yoga tree should always be in sync with .yogaNodes array! %@", self.yogaChildren); - NSArray *sublayouts = ASArrayByFlatMapping(self.yogaChildren, ASDisplayNode *subnode, ({ - [subnode layoutForYogaNode]; - })); + NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:childCount]; + for (ASDisplayNode *subnode in self.yogaChildren) { + [sublayouts addObject:[subnode layoutForYogaNode]]; + } // The layout for self should have position CGPointNull, but include the calculated size. CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode)); diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index fc0a2dbbe..f78a688ea 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -607,9 +607,15 @@ - (NSIndexPath *)convertIndexPathToTableNode:(NSIndexPath *)indexPath return nil; } - return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPathInView, ({ - [self convertIndexPathToTableNode:indexPathInView]; - })); + NSMutableArray *indexPathsArray = [NSMutableArray new]; + + for (NSIndexPath *indexPathInView in indexPaths) { + NSIndexPath *indexPath = [self convertIndexPathToTableNode:indexPathInView]; + if (indexPath != nil) { + [indexPathsArray addObject:indexPath]; + } + } + return indexPathsArray; } - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode diff --git a/Source/Base/ASBaseDefines.h b/Source/Base/ASBaseDefines.h index 37749e53d..9eb1ec0b0 100755 --- a/Source/Base/ASBaseDefines.h +++ b/Source/Base/ASBaseDefines.h @@ -132,6 +132,22 @@ #define __has_attribute(x) 0 // Compatibility with non-clang compilers. #endif +#ifndef NS_CONSUMED +#if __has_feature(attribute_ns_consumed) +#define NS_CONSUMED __attribute__((ns_consumed)) +#else +#define NS_CONSUMED +#endif +#endif + +#ifndef NS_RETURNS_RETAINED +#if __has_feature(attribute_ns_returns_retained) +#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained)) +#else +#define NS_RETURNS_RETAINED +#endif +#endif + #ifndef CF_RETURNS_RETAINED #if __has_feature(attribute_cf_returns_retained) #define CF_RETURNS_RETAINED __attribute__((cf_returns_retained)) @@ -229,38 +245,26 @@ * Create a new set by mapping `collection` over `work`, ignoring nil. */ #define ASSetByFlatMapping(collection, decl, work) ({ \ - CFTypeRef _cArray[collection.count]; \ - NSUInteger _i = 0; \ + NSMutableSet *s = [NSMutableSet set]; \ for (decl in collection) {\ - if ((_cArray[_i] = (__bridge_retained CFTypeRef)work)) { \ - _i++; \ + id result = work; \ + if (result != nil) { \ + [s addObject:result]; \ } \ } \ - NSSet *result; \ - if (_i == 0) { \ - /** Zero fast path. */ \ - result = [NSSet set]; \ - } else if (_i == 1) { \ - /** NSSingleObjectSet is fast. Create one and release. */ \ - CFTypeRef val = _cArray[0]; \ - result = [NSSet setWithObject:(__bridge id)val]; \ - CFBridgingRelease(val); \ - } else { \ - CFSetCallBacks cb = kCFTypeSetCallBacks; \ - cb.retain = NULL; \ - result = (__bridge NSSet *)CFSetCreate(kCFAllocatorDefault, _cArray, _i, &cb); \ - } \ - result; \ + s; \ }) /** * Create a new ObjectPointerPersonality NSHashTable by mapping `collection` over `work`, ignoring nil. */ #define ASPointerTableByFlatMapping(collection, decl, work) ({ \ - NSHashTable *t = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality capacity:collection.count]; \ + NSHashTable *t = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; \ for (decl in collection) {\ - /* NSHashTable accepts nil and avoid extra retain/release. */ \ - [t addObject:work]; \ + id result = work; \ + if (result != nil) { \ + [t addObject:result]; \ + } \ } \ t; \ }) @@ -269,37 +273,12 @@ * Create a new array by mapping `collection` over `work`, ignoring nil. */ #define ASArrayByFlatMapping(collection, decl, work) ({ \ - CFTypeRef _cArray[collection.count]; \ - NSUInteger _i = 0; \ - for (decl in collection) {\ - if ((_cArray[_i] = (__bridge_retained CFTypeRef)work)) { \ - _i++; \ - } \ - } \ - NSArray *result; \ - if (_i == 0) { \ - /** Zero array fast path. */ \ - result = @[]; \ - } else if (_i == 1) { \ - /** NSSingleObjectArray is fast. Create one and release. */ \ - CFTypeRef val = _cArray[0]; \ - result = [NSArray arrayWithObject:(__bridge id)val]; \ - CFBridgingRelease(val); \ - } else { \ - CFArrayCallBacks cb = kCFTypeArrayCallBacks; \ - cb.retain = NULL; \ - result = (__bridge NSArray *)CFArrayCreate(kCFAllocatorDefault, _cArray, _i, &cb); \ - } \ - result; \ -}) - -#define ASMutableArrayByFlatMapping(collection, decl, work) ({ \ - id _cArray[collection.count]; \ - NSUInteger _i = 0; \ + NSMutableArray *a = [NSMutableArray array]; \ for (decl in collection) {\ - if ((_cArray[_i] = work)) { \ - _i++; \ + id result = work; \ + if (result != nil) { \ + [a addObject:result]; \ } \ } \ - [[NSMutableArray alloc] initWithObjects:_cArray count:_i]; \ + a; \ }) diff --git a/Source/Details/ASCollectionFlowLayoutDelegate.m b/Source/Details/ASCollectionFlowLayoutDelegate.m index 77e092226..548fa0181 100644 --- a/Source/Details/ASCollectionFlowLayoutDelegate.m +++ b/Source/Details/ASCollectionFlowLayoutDelegate.m @@ -59,7 +59,7 @@ - (id)additionalInfoForLayoutWithElements:(ASElementMap *)elements + (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context { ASElementMap *elements = context.elements; - NSArray *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node); + NSMutableArray *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node); if (children.count == 0) { return [[ASCollectionLayoutState alloc] initWithContext:context]; } diff --git a/Source/Details/ASCollectionGalleryLayoutDelegate.mm b/Source/Details/ASCollectionGalleryLayoutDelegate.mm index 482123c51..2734e9649 100644 --- a/Source/Details/ASCollectionGalleryLayoutDelegate.mm +++ b/Source/Details/ASCollectionGalleryLayoutDelegate.mm @@ -102,9 +102,9 @@ + (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutConte return [[ASCollectionLayoutState alloc] initWithContext:context]; } - NSArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements, - ASCollectionElement *element, - [[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]); + NSMutableArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements, + ASCollectionElement *element, + [[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]); if (children.count == 0) { return [[ASCollectionLayoutState alloc] initWithContext:context]; } diff --git a/Source/Details/ASElementMap.h b/Source/Details/ASElementMap.h index a73f80a92..eed7eff26 100644 --- a/Source/Details/ASElementMap.h +++ b/Source/Details/ASElementMap.h @@ -31,11 +31,6 @@ NS_ASSUME_NONNULL_BEGIN AS_SUBCLASSING_RESTRICTED @interface ASElementMap : NSObject -/** - * The total number of elements in this map. - */ -@property (readonly) NSUInteger count; - /** * The number of sections (of items) in this map. */ diff --git a/Source/Details/ASElementMap.m b/Source/Details/ASElementMap.m index f5646de78..08e90d401 100644 --- a/Source/Details/ASElementMap.m +++ b/Source/Details/ASElementMap.m @@ -75,11 +75,6 @@ - (instancetype)initWithSections:(NSArray *)sections items:(ASColle return self; } -- (NSUInteger)count -{ - return _elementToIndexPathMap.count; -} - - (NSArray *)itemIndexPaths { return ASIndexPathsForTwoDimensionalArray(_sectionsOfItems); diff --git a/Source/Layout/ASLayout.mm b/Source/Layout/ASLayout.mm index 226b4a89e..f6fbe9127 100644 --- a/Source/Layout/ASLayout.mm +++ b/Source/Layout/ASLayout.mm @@ -203,9 +203,13 @@ - (void)setRetainSublayoutLayoutElements:(BOOL)retainSublayoutLayoutElements _sublayoutLayoutElements = nil; } else { // Add sublayouts layout elements to an internal array to retain it while the layout lives - _sublayoutLayoutElements = ASMutableArrayByFlatMapping(_sublayouts, ASLayout *sublayout, ({ - sublayout.layoutElement; - })); + NSUInteger sublayoutCount = _sublayouts.count; + if (sublayoutCount > 0) { + _sublayoutLayoutElements = [NSMutableArray arrayWithCapacity:sublayoutCount]; + for (ASLayout *sublayout in _sublayouts) { + [_sublayoutLayoutElements addObject:sublayout.layoutElement]; + } + } } } } @@ -232,7 +236,7 @@ - (ASLayout *)filteredNodeLayoutTree queue.push_back({sublayout, sublayout.position}); } - std::vector flattenedSublayouts; + NSMutableArray *flattenedSublayouts = [NSMutableArray array]; while (!queue.empty()) { const Context context = queue.front(); @@ -251,7 +255,7 @@ - (ASLayout *)filteredNodeLayoutTree position:absolutePosition sublayouts:@[]]; } - flattenedSublayouts.push_back(layout); + [flattenedSublayouts addObject:layout]; } else if (sublayoutsCount > 0){ std::vector sublayoutContexts; for (ASLayout *sublayout in sublayouts) { @@ -261,8 +265,7 @@ - (ASLayout *)filteredNodeLayoutTree } } - NSArray *sublayoutsArray = [NSArray arrayWithObjects:flattenedSublayouts.data() count:flattenedSublayouts.size()]; - ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:sublayoutsArray]; + ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:flattenedSublayouts]; // All flattened layouts must have this flag enabled // to ensure sublayout elements are retained until the layouts are applied. layout.retainSublayoutLayoutElements = YES; diff --git a/Source/Layout/ASLayoutSpec.mm b/Source/Layout/ASLayoutSpec.mm index a6aabdb39..76901d1ac 100644 --- a/Source/Layout/ASLayoutSpec.mm +++ b/Source/Layout/ASLayoutSpec.mm @@ -295,15 +295,19 @@ - (instancetype)initWithLayoutElements:(NSArray> *)layoutEle - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize { + NSArray *children = self.children; + NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:children.count]; + CGSize size = constrainedSize.min; - NSArray *sublayouts = ASArrayByFlatMapping(self.children, id child, ({ + for (id child in children) { ASLayout *sublayout = [child layoutThatFits:constrainedSize parentSize:constrainedSize.max]; sublayout.position = CGPointZero; size.width = MAX(size.width, sublayout.size.width); size.height = MAX(size.height, sublayout.size.height); - sublayout; - })); + + [sublayouts addObject:sublayout]; + } return [ASLayout layoutWithLayoutElement:self size:size sublayouts:sublayouts]; } diff --git a/Source/Private/ASTwoDimensionalArrayUtils.m b/Source/Private/ASTwoDimensionalArrayUtils.m index b21aeb41b..71a48d450 100644 --- a/Source/Private/ASTwoDimensionalArrayUtils.m +++ b/Source/Private/ASTwoDimensionalArrayUtils.m @@ -27,10 +27,13 @@ NSMutableArray *ASTwoDimensionalArrayDeepMutableCopy(NSArray *array) { - return ASMutableArrayByFlatMapping(array, NSArray *subarray, ({ + NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; + NSInteger i = 0; + for (NSArray *subarray in array) { ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray *"); - [subarray mutableCopy]; - })); + newArray[i++] = [subarray mutableCopy]; + } + return newArray; } void ASDeleteElementsInTwoDimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray *indexPaths) diff --git a/Source/_ASTransitionContext.m b/Source/_ASTransitionContext.m index b664feaf5..da6350a4a 100644 --- a/Source/_ASTransitionContext.m +++ b/Source/_ASTransitionContext.m @@ -69,9 +69,11 @@ - (CGRect)finalFrameForNode:(ASDisplayNode *)node - (NSArray *)subnodesForKey:(NSString *)key { - return ASArrayByFlatMapping([self layoutForKey:key].sublayouts, ASLayout *sublayout, ({ - (ASDisplayNode *)sublayout.layoutElement; - })); + NSMutableArray *subnodes = [NSMutableArray array]; + for (ASLayout *sublayout in [self layoutForKey:key].sublayouts) { + [subnodes addObject:(ASDisplayNode *)sublayout.layoutElement]; + } + return subnodes; } - (NSArray *)insertedSubnodes From c3ae4474d0fa31036981f63542fe741b3d496a0c Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 23 Jan 2018 10:45:34 -0800 Subject: [PATCH 062/133] NoCopyRendering experiment: Fix possible memory leak if image node rendering is canceled #trivial (#765) * Fix memory leak if image node rendering is canceled * Update comment --- Source/ASImageNode.mm | 5 ++--- Source/Details/ASGraphicsContext.h | 3 +++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Source/ASImageNode.mm b/Source/ASImageNode.mm index ed56c9ceb..b1a561eea 100644 --- a/Source/ASImageNode.mm +++ b/Source/ASImageNode.mm @@ -522,10 +522,9 @@ + (UIImage *)createContentsForkey:(ASImageNodeContentsKey *)key drawParameters:( key.didDisplayNodeContentWithRenderingContext(context, drawParameters); } - // The following `UIGraphicsGetImageFromCurrentImageContext` call will commonly take more than 20ms on an - // A5 processor. Check for cancellation before we call it. + // Check cancellation one last time before forming image. if (isCancelled()) { - UIGraphicsEndImageContext(); + ASGraphicsEndImageContext(); return nil; } diff --git a/Source/Details/ASGraphicsContext.h b/Source/Details/ASGraphicsContext.h index 0a1c80fc4..26183d595 100644 --- a/Source/Details/ASGraphicsContext.h +++ b/Source/Details/ASGraphicsContext.h @@ -23,6 +23,9 @@ * * The API mirrors the UIGraphics API, with the exception that forming an image * ends the context as well. + * + * Note: You must not mix-and-match between ASGraphics* and UIGraphics* functions + * within the same drawing operation. */ NS_ASSUME_NONNULL_BEGIN From 3f27546ec8834620e8b35e0b0cccea178e37b92f Mon Sep 17 00:00:00 2001 From: Denis Morozov Date: Mon, 29 Jan 2018 15:05:33 +0300 Subject: [PATCH 063/133] Fix typos in layout2-layoutspec-types.md #trivial (#770) --- docs/_docs/layout2-layoutspec-types.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/_docs/layout2-layoutspec-types.md b/docs/_docs/layout2-layoutspec-types.md index ad14cf075..f1bb432e3 100755 --- a/docs/_docs/layout2-layoutspec-types.md +++ b/docs/_docs/layout2-layoutspec-types.md @@ -394,17 +394,17 @@ Within `ASAbsoluteLayoutSpec` you can specify exact locations (x/y coordinates) CGSize maxConstrainedSize = constrainedSize.max; // Layout all nodes absolute in a static layout spec - guitarVideoNode.layoutPosition = CGPointMake(0, 0); - guitarVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width, maxConstrainedSize.height / 3.0)); + guitarVideoNode.style.layoutPosition = CGPointMake(0, 0); + guitarVideoNode.style.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width, maxConstrainedSize.height / 3.0)); - nicCageVideoNode.layoutPosition = CGPointMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0); - nicCageVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0)); + nicCageVideoNode.style.layoutPosition = CGPointMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0); + nicCageVideoNode.style.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0)); - simonVideoNode.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height - (maxConstrainedSize.height / 3.0)); - simonVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width/2, maxConstrainedSize.height / 3.0)); + simonVideoNode.style.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height - (maxConstrainedSize.height / 3.0)); + simonVideoNode.style.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width/2, maxConstrainedSize.height / 3.0)); - hlsVideoNode.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height / 3.0); - hlsVideoNode.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0)); + hlsVideoNode.style.layoutPosition = CGPointMake(0.0, maxConstrainedSize.height / 3.0); + hlsVideoNode.style.size = ASSizeMakeFromCGSize(CGSizeMake(maxConstrainedSize.width / 2.0, maxConstrainedSize.height / 3.0)); return [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[guitarVideoNode, nicCageVideoNode, simonVideoNode, hlsVideoNode]]; } From b0c3e65da887b4eca4ba5b58d974eef1490da9dd Mon Sep 17 00:00:00 2001 From: Flatout73 Date: Mon, 29 Jan 2018 15:05:48 +0300 Subject: [PATCH 064/133] Fix misprint (#768) --- docs/_docs/editable-text-node.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_docs/editable-text-node.md b/docs/_docs/editable-text-node.md index a1fccd44a..9eccfd032 100755 --- a/docs/_docs/editable-text-node.md +++ b/docs/_docs/editable-text-node.md @@ -137,7 +137,7 @@ optional public func editableTextNodeDidUpdateText(_ editableTextNode: ASEditabl
---  Indicates to the delegate that teh text node has finished editing. +--  Indicates to the delegate that the text node has finished editing.
SwiftObjective-C From 2e94bb8120adc3ee5fd1f523e5e44a4b02a7c68e Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 30 Jan 2018 14:18:37 -0500 Subject: [PATCH 065/133] Improve no-copy rendering experiment, remove +load method (#771) * Improve graphics contexts experiment * Update changelog * Remove extra space * Add a unit test for screen scale * Fix typo and use unique value --- CHANGELOG.md | 1 + Source/ASDisplayNode.mm | 6 -- Source/Details/ASGraphicsContext.m | 111 +++++++++++++++++++---------- Source/Private/ASInternalHelpers.m | 5 +- Tests/ASDisplayNodeTests.mm | 5 ++ 5 files changed, 81 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f6ea8c3a..0c1f2475a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) - Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) - Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler) +- Removed +load static initializer from ASDisplayNode. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 899331ea4..a976e3f09 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -226,12 +226,6 @@ + (void)initialize class_replaceMethod(self, @selector(_staticInitialize), staticInitialize, "v:@"); } -+ (void)load -{ - // Ensure this value is cached on the main thread before needed in the background. - ASScreenScale(); -} - + (Class)viewClass { return [_ASDisplayView class]; diff --git a/Source/Details/ASGraphicsContext.m b/Source/Details/ASGraphicsContext.m index 1c3092598..f87102fab 100644 --- a/Source/Details/ASGraphicsContext.m +++ b/Source/Details/ASGraphicsContext.m @@ -12,9 +12,11 @@ #import "ASGraphicsContext.h" #import +#import #import #import #import +#import #pragma mark - Feature Gating @@ -40,13 +42,26 @@ static BOOL ASNoCopyRenderingBlockAndCheckEnabled() { return (oldFlags & ASNoCopyEnabled) != 0; } -#pragma mark - Callbacks - -void _ASReleaseCGDataProviderData(__unused void *info, const void *data, __unused size_t size) -{ - free((void *)data); +/** + * Our version of the private CGBitmapGetAlignedBytesPerRow function. + * + * In both 32-bit and 64-bit, this function rounds up to nearest multiple of 32 + * in iOS 9, 10, and 11. We'll try to catch if this ever changes by asserting that + * the bytes-per-row for a 1x1 context from the system is 32. + */ +static size_t ASGraphicsGetAlignedBytesPerRow(size_t baseValue) { + // Add 31 then zero out low 5 bits. + return (baseValue + 31) & ~0x1F; } +/** + * A key used to associate CGContextRef -> NSMutableData, nonatomic retain. + * + * That way the data will be released when the context dies. If they pull an image, + * we will retain the data object (in a CGDataProvider) before releasing the context. + */ +static UInt8 __contextDataAssociationKey; + #pragma mark - Graphics Contexts extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) @@ -56,34 +71,46 @@ extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGF return; } - // Only create device RGB color space once. UIGraphics actually doesn't do this but it's safe. + // We use "reference contexts" to get device-specific options that UIKit + // uses. static dispatch_once_t onceToken; - static CGFloat defaultScale; - static CGColorSpaceRef deviceRGB; + static CGContextRef refCtxOpaque; + static CGContextRef refCtxTransparent; dispatch_once(&onceToken, ^{ - deviceRGB = CGColorSpaceCreateDeviceRGB(); - UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 0); - CGContextRef uikitContext = UIGraphicsGetCurrentContext(); - defaultScale = CGContextGetCTM(uikitContext).a; + UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 1); + refCtxOpaque = CGContextRetain(UIGraphicsGetCurrentContext()); + ASDisplayNodeCAssert(CGBitmapContextGetBytesPerRow(refCtxOpaque) == 32, @"Expected bytes per row to be aligned to 32. Has CGBitmapGetAlignedBytesPerRow implementation changed?"); + UIGraphicsEndImageContext(); + + // Make transparent ref context. + UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), NO, 1); + refCtxTransparent = CGContextRetain(UIGraphicsGetCurrentContext()); UIGraphicsEndImageContext(); }); // These options are taken from UIGraphicsBeginImageContext. - CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | (opaque ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaPremultipliedFirst); + CGContextRef refCtx = opaque ? refCtxOpaque : refCtxTransparent; + CGBitmapInfo bitmapInfo = CGBitmapContextGetBitmapInfo(refCtx); if (scale == 0) { - scale = defaultScale; + scale = ASScreenScale(); } size_t intWidth = (size_t)ceil(size.width * scale); size_t intHeight = (size_t)ceil(size.height * scale); - size_t bytesPerPixel = 4; - size_t bytesPerRow = bytesPerPixel * intWidth; + size_t bitsPerComponent = CGBitmapContextGetBitsPerComponent(refCtx); + size_t bytesPerRow = CGBitmapContextGetBitsPerPixel(refCtx) * intWidth / 8; + bytesPerRow = ASGraphicsGetAlignedBytesPerRow(bytesPerRow); size_t bufferSize = bytesPerRow * intHeight; + CGColorSpaceRef colorspace = CGBitmapContextGetColorSpace(refCtx); // We create our own buffer, and wrap the context around that. This way we can prevent // the copy that usually gets made when you form a CGImage from the context. - void *buf = calloc(bufferSize, 1); - CGContextRef context = CGBitmapContextCreate(buf, intWidth, intHeight, 8, bytesPerRow, deviceRGB, bitmapInfo); + NSMutableData *data = [[NSMutableData alloc] initWithLength:bufferSize]; + CGContextRef context = CGBitmapContextCreate(data.mutableBytes, intWidth, intHeight, bitsPerComponent, bytesPerRow, colorspace, bitmapInfo); + + // Transfer ownership of the data to the context. So that if the context + // is destroyed before we create an image from it, the data will be released. + objc_setAssociatedObject((__bridge id)context, &__contextDataAssociationKey, data, OBJC_ASSOCIATION_RETAIN_NONATOMIC); // Set the CTM to account for iOS orientation & specified scale. // If only we could use CGContextSetBaseCTM. It doesn't @@ -96,7 +123,9 @@ extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGF // Save the state so we can restore it and recover our scale in GetImageAndEnd CGContextSaveGState(context); + // Transfer context ownership to the UIKit stack. UIGraphicsPushContext(context); + CGContextRelease(context); } extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext() @@ -113,30 +142,40 @@ extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGF ASDisplayNodeCFailAssert(@"Can't end image context without having begun one."); return nil; } - UIGraphicsPopContext(); - // Do some math to get the image properties. - size_t width = CGBitmapContextGetWidth(context); - size_t height = CGBitmapContextGetHeight(context); - size_t bitsPerPixel = CGBitmapContextGetBitsPerPixel(context); - size_t bytesPerRow = CGBitmapContextGetBytesPerRow(context); - size_t bufferSize = bytesPerRow * height; - - // This is the buf that we malloc'd above. - void *buf = CGBitmapContextGetData(context); + // Read the device-specific ICC-based color space to use for the image. + // For DeviceRGB contexts (e.g. UIGraphics), CGBitmapContextCreateImage + // generates an image in a device-specific color space (for wide color support). + // We replicate that behavior, even though at this time CA does not + // require the image to be in this space. Plain DeviceRGB images seem + // to be treated exactly the same, but better safe than sorry. + static CGColorSpaceRef imageColorSpace; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0); + UIImage *refImage = UIGraphicsGetImageFromCurrentImageContext(); + imageColorSpace = CGImageGetColorSpace(refImage.CGImage); + UIGraphicsEndImageContext(); + }); - // Wrap it in a CGDataProvider, passing along our release callback for when the CGImage dies. - CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buf, bufferSize, _ASReleaseCGDataProviderData); + // Retrieve our data and wrap it in a CGDataProvider. + // Don't worry, the provider doesn't copy the data – it just retains it. + NSMutableData *data = objc_getAssociatedObject((__bridge id)context, &__contextDataAssociationKey); + ASDisplayNodeCAssertNotNil(data, nil); + CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data); // Create the CGImage. Options taken from CGBitmapContextCreateImage. - CGImageRef cgImg = CGImageCreate(width, height, CGBitmapContextGetBitsPerComponent(context), bitsPerPixel, bytesPerRow, CGBitmapContextGetColorSpace(context), CGBitmapContextGetBitmapInfo(context), provider, NULL, true, kCGRenderingIntentDefault); + CGImageRef cgImg = CGImageCreate(CGBitmapContextGetWidth(context), CGBitmapContextGetHeight(context), CGBitmapContextGetBitsPerComponent(context), CGBitmapContextGetBitsPerPixel(context), CGBitmapContextGetBytesPerRow(context), imageColorSpace, CGBitmapContextGetBitmapInfo(context), provider, NULL, true, kCGRenderingIntentDefault); CGDataProviderRelease(provider); // We saved our GState right after setting the CTM so that we could restore it // here and get the original scale back. CGContextRestoreGState(context); CGFloat scale = CGContextGetCTM(context).a; - CGContextRelease(context); + + // Note: popping from the UIKit stack will probably destroy the context. + context = NULL; + UIGraphicsPopContext(); UIImage *result = [[UIImage alloc] initWithCGImage:cgImg scale:scale orientation:UIImageOrientationUp]; CGImageRelease(cgImg); @@ -150,11 +189,5 @@ extern void ASGraphicsEndImageContext() return; } - CGContextRef context = UIGraphicsGetCurrentContext(); - if (context) { - // We manually allocated this buffer so we need to free it. - free(CGBitmapContextGetData(context)); - CGContextRelease(context); - UIGraphicsPopContext(); - } + UIGraphicsPopContext(); } diff --git a/Source/Private/ASInternalHelpers.m b/Source/Private/ASInternalHelpers.m index ad55f295a..dab3d2ae5 100644 --- a/Source/Private/ASInternalHelpers.m +++ b/Source/Private/ASInternalHelpers.m @@ -143,8 +143,9 @@ CGFloat ASScreenScale() static CGFloat __scale = 0.0; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - ASDisplayNodeCAssertMainThread(); - __scale = [[UIScreen mainScreen] scale]; + UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0); + __scale = CGContextGetCTM(UIGraphicsGetCurrentContext()).a; + UIGraphicsEndImageContext(); }); return __scale; } diff --git a/Tests/ASDisplayNodeTests.mm b/Tests/ASDisplayNodeTests.mm index df4adbec6..9c5b668c4 100644 --- a/Tests/ASDisplayNodeTests.mm +++ b/Tests/ASDisplayNodeTests.mm @@ -2349,4 +2349,9 @@ - (void)testThatItIsAllowedToRetrieveDebugDescriptionIncludingVCOffMainThread XCTAssert(hasVC); } +- (void)testScreenScale +{ + XCTAssertEqual(ASScreenScale(), UIScreen.mainScreen.scale); +} + @end From 7ba4376b6ff94fd5fb7657d9a3eecca181f45759 Mon Sep 17 00:00:00 2001 From: Justin Swart Date: Tue, 30 Jan 2018 11:19:37 -0800 Subject: [PATCH 066/133] Update PINCache (#769) --- Cartfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cartfile b/Cartfile index aebf9308e..f1a449b01 100644 --- a/Cartfile +++ b/Cartfile @@ -1,2 +1,2 @@ github "pinterest/PINRemoteImage" "3.0.0-beta.13" -github "pinterest/PINCache" +github "pinterest/PINCache" "3.0.1-beta.6" From 196d76d82d2e6b329fcc90c9234aa6d074fcd3c9 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Tue, 30 Jan 2018 14:24:46 -0800 Subject: [PATCH 067/133] Expose asyncdisplaykit_node in _ASDisplayView same as in _ASDisplayLayer #trivial (#773) * Expose asyncdisplaykit_node in _ASDisplayView same as in _ASDisplayLayer * Change comment --- Source/Details/_ASDisplayView.h | 8 ++++++++ Source/Details/_ASDisplayView.mm | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/Details/_ASDisplayView.h b/Source/Details/_ASDisplayView.h index c4968d183..6a0b9a386 100644 --- a/Source/Details/_ASDisplayView.h +++ b/Source/Details/_ASDisplayView.h @@ -20,8 +20,16 @@ // This class is only for use by ASDisplayNode and should never be subclassed or used directly. // Note that the "node" property is added to UIView directly via a category in ASDisplayNode. +@class ASDisplayNode; + @interface _ASDisplayView : UIView +/** + @discussion This property overrides the UIView category method which implements this via associated objects. + This should result in much better performance for _ASDisplayView. + */ +@property (nonatomic, weak) ASDisplayNode *asyncdisplaykit_node; + // These methods expose a way for ASDisplayNode touch events to let the view call super touch events // Some UIKit mechanisms, like UITableView and UICollectionView selection handling, require this to work - (void)__forwardTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; diff --git a/Source/Details/_ASDisplayView.mm b/Source/Details/_ASDisplayView.mm index 2c2d2c383..aa2c43eca 100644 --- a/Source/Details/_ASDisplayView.mm +++ b/Source/Details/_ASDisplayView.mm @@ -27,7 +27,6 @@ #import @interface _ASDisplayView () -@property (nullable, atomic, weak, readwrite) ASDisplayNode *asyncdisplaykit_node; // Keep the node alive while its view is active. If you create a view, add its layer to a layer hierarchy, then release // the view, the layer retains the view to prevent a crash. This replicates this behaviour for the node abstraction. From fef965f78ef5424b8dae80cfeb29cd829ad62f4a Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 30 Jan 2018 17:50:38 -0500 Subject: [PATCH 068/133] Add support for providing additional info to network image node delegate (#775) * Add support for piping arbitrary user info from ASImageDownloader to the ASNetworkImageNodeDelegate * s/source/sourceType * Fix stuff and take Michael's advice --- AsyncDisplayKit.xcodeproj/project.pbxproj | 12 +++++ CHANGELOG.md | 3 ++ Source/ASMultiplexImageNode.mm | 2 +- Source/ASNetworkImageLoadInfo.h | 39 ++++++++++++++++ Source/ASNetworkImageLoadInfo.m | 32 ++++++++++++++ Source/ASNetworkImageNode.h | 19 ++------ Source/ASNetworkImageNode.mm | 44 ++++++++++--------- Source/AsyncDisplayKit.h | 1 + Source/Details/ASBasicImageDownloader.h | 5 ++- Source/Details/ASBasicImageDownloader.mm | 10 ++--- Source/Details/ASImageProtocols.h | 3 +- Source/Details/ASPINRemoteImageDownloader.h | 5 ++- Source/Details/ASPINRemoteImageDownloader.m | 8 ++-- .../Private/ASNetworkImageLoadInfo+Private.h | 22 ++++++++++ Tests/ASBasicImageDownloaderTests.m | 4 +- Tests/ASMultiplexImageNodeTests.m | 2 +- examples/ASDKgram/Sample/PhotoCellNode.m | 17 +++++++ 17 files changed, 173 insertions(+), 55 deletions(-) create mode 100644 Source/ASNetworkImageLoadInfo.h create mode 100644 Source/ASNetworkImageLoadInfo.m create mode 100644 Source/Private/ASNetworkImageLoadInfo+Private.h diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 1cff2cac0..19771c9ea 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -410,6 +410,9 @@ CCE4F9B51F0DA4F300062E4E /* ASLayoutEngineTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B41F0DA4F300062E4E /* ASLayoutEngineTests.mm */; }; CCE4F9BA1F0DBB5000062E4E /* ASLayoutTestNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9B71F0DBA5000062E4E /* ASLayoutTestNode.mm */; }; CCE4F9BE1F0ECE5200062E4E /* ASTLayoutFixture.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCE4F9BD1F0ECE5200062E4E /* ASTLayoutFixture.mm */; }; + CCED5E3E2020D36800395C40 /* ASNetworkImageLoadInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CCED5E3F2020D36800395C40 /* ASNetworkImageLoadInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.m */; }; + CCED5E412020D49D00395C40 /* ASNetworkImageLoadInfo+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CCED5E402020D41600395C40 /* ASNetworkImageLoadInfo+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; settings = {ATTRIBUTES = (Private, ); }; }; DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; @@ -913,6 +916,9 @@ CCE4F9BB1F0EA67F00062E4E /* debugbreak.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = debugbreak.h; sourceTree = ""; }; CCE4F9BC1F0ECE5200062E4E /* ASTLayoutFixture.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTLayoutFixture.h; sourceTree = ""; }; CCE4F9BD1F0ECE5200062E4E /* ASTLayoutFixture.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTLayoutFixture.mm; sourceTree = ""; }; + CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASNetworkImageLoadInfo.h; sourceTree = ""; }; + CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASNetworkImageLoadInfo.m; sourceTree = ""; }; + CCED5E402020D41600395C40 /* ASNetworkImageLoadInfo+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASNetworkImageLoadInfo+Private.h"; sourceTree = ""; }; D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = ""; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASScrollNode.mm; sourceTree = ""; }; @@ -1074,6 +1080,8 @@ 058D09B1195D04C000B7D73C /* Source */ = { isa = PBXGroup; children = ( + CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */, + CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.m */, CCE04B1D1E313E99006AEBBB /* Collection Data Adapter */, CC58AA4A1E398E1D002C8CB4 /* ASBlockTypes.h */, DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */, @@ -1376,6 +1384,7 @@ 058D0A01195D050800B7D73C /* Private */ = { isa = PBXGroup; children = ( + CCED5E402020D41600395C40 /* ASNetworkImageLoadInfo+Private.h */, CCA282CE1E9EBF6C0037E8B7 /* ASTipsWindow.h */, CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.m */, CCA282C61E9EB64B0037E8B7 /* ASDisplayNodeTipState.h */, @@ -1913,6 +1922,7 @@ DE4843DC1C93EAC100A1F33B /* ASLayoutTransition.h in Headers */, CC57EAF81E3939450034C595 /* ASTableView+Undeprecated.h in Headers */, 254C6B781BF94DF4003EC431 /* ASTextKitContext.h in Headers */, + CCED5E412020D49D00395C40 /* ASNetworkImageLoadInfo+Private.h in Headers */, 9CDC18CD1B910E12004965E2 /* ASLayoutElementPrivate.h in Headers */, B35062201B010EFD0018CF92 /* ASLayoutController.h in Headers */, B35062211B010EFD0018CF92 /* ASLayoutRangeType.h in Headers */, @@ -1964,6 +1974,7 @@ B35062391B010EFD0018CF92 /* ASThread.h in Headers */, 2C107F5B1BA9F54500F13DE5 /* AsyncDisplayKit.h in Headers */, 509E68651B3AEDC5009B9150 /* CoreGraphics+ASConvenience.h in Headers */, + CCED5E3E2020D36800395C40 /* ASNetworkImageLoadInfo.h in Headers */, B350623A1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.h in Headers */, 044284FF1BAA3BD600D16268 /* UICollectionViewLayout+ASConvenience.h in Headers */, B35062431B010EFD0018CF92 /* UIView+ASConvenience.h in Headers */, @@ -2292,6 +2303,7 @@ E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */, 68FC85EC1CE29C7D00EDD713 /* ASVisibilityProtocols.m in Sources */, CC55A7121E52A0F200594372 /* ASResponderChainEnumerator.m in Sources */, + CCED5E3F2020D36800395C40 /* ASNetworkImageLoadInfo.m in Sources */, 68B8A4E41CBDB958007E4543 /* ASWeakProxy.m in Sources */, E5775B041F16759F00CAC9BC /* ASCollectionLayoutCache.mm in Sources */, 9C70F20A1CDBE949007D6C76 /* ASTableNode.mm in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c1f2475a..55954a8fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) - Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) - Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler) +- **Breaking** Changes to ASNetworkImageNode: [Adlai Holler](https://github.com/Adlai-Holler) + - Modified `ASImageDownloaderCompletion` to add an optional `id userInfo` field. Your custom downloader can pass `nil`. + - Modified the last argument to `-[ASNetworkImageNodeDelegate imageNode:didLoadImage:info:]` method from a struct to an object of new class `ASNetworkImageLoadInfo` which includes other metadata about the load operation. - Removed +load static initializer from ASDisplayNode. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.6 diff --git a/Source/ASMultiplexImageNode.mm b/Source/ASMultiplexImageNode.mm index a2bed044a..ecc5fd7af 100644 --- a/Source/ASMultiplexImageNode.mm +++ b/Source/ASMultiplexImageNode.mm @@ -823,7 +823,7 @@ - (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL c [self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() downloadProgress:downloadProgressBlock - completion:^(id imageContainer, NSError *error, id downloadIdentifier) { + completion:^(id imageContainer, NSError *error, id downloadIdentifier, id userInfo) { // We dereference iVars directly, so we can't have weakSelf going nil on us. __typeof__(self) strongSelf = weakSelf; if (!strongSelf) diff --git a/Source/ASNetworkImageLoadInfo.h b/Source/ASNetworkImageLoadInfo.h new file mode 100644 index 000000000..51e23e375 --- /dev/null +++ b/Source/ASNetworkImageLoadInfo.h @@ -0,0 +1,39 @@ +// +// ASNetworkImageLoadInfo.h +// AsyncDisplayKit +// +// Created by Adlai on 1/30/18. +// Copyright © 2018 Facebook. All rights reserved. +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, ASNetworkImageSourceType) { + ASNetworkImageSourceUnspecified = 0, + ASNetworkImageSourceSynchronousCache, + ASNetworkImageSourceAsynchronousCache, + ASNetworkImageSourceFileURL, + ASNetworkImageSourceDownload, +}; + +AS_SUBCLASSING_RESTRICTED +@interface ASNetworkImageLoadInfo : NSObject + +/// The type of source from which the image was loaded. +@property (readonly) ASNetworkImageSourceType sourceType; + +/// The image URL that was downloaded. +@property (readonly) NSURL *url; + +/// The download identifier, if one was provided. +@property (nullable, readonly) id downloadIdentifier; + +/// The userInfo object provided by the downloader, if one was provided. +@property (nullable, readonly) id userInfo; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ASNetworkImageLoadInfo.m b/Source/ASNetworkImageLoadInfo.m new file mode 100644 index 000000000..69dfee760 --- /dev/null +++ b/Source/ASNetworkImageLoadInfo.m @@ -0,0 +1,32 @@ +// +// ASNetworkImageLoadInfo.m +// AsyncDisplayKit +// +// Created by Adlai on 1/30/18. +// Copyright © 2018 Facebook. All rights reserved. +// + +#import +#import + +@implementation ASNetworkImageLoadInfo + +- (instancetype)initWithURL:(NSURL *)url sourceType:(ASNetworkImageSourceType)sourceType downloadIdentifier:(id)downloadIdentifier userInfo:(id)userInfo +{ + if (self = [super init]) { + _url = [url copy]; + _sourceType = sourceType; + _downloadIdentifier = downloadIdentifier; + _userInfo = userInfo; + } + return self; +} + +#pragma mark - NSCopying + +- (id)copyWithZone:(NSZone *)zone +{ + return self; +} + +@end diff --git a/Source/ASNetworkImageNode.h b/Source/ASNetworkImageNode.h index c4c83214b..4100b5220 100644 --- a/Source/ASNetworkImageNode.h +++ b/Source/ASNetworkImageNode.h @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol ASNetworkImageNodeDelegate, ASImageCacheProtocol, ASImageDownloaderProtocol; +@class ASNetworkImageLoadInfo; /** @@ -134,20 +135,6 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - -typedef NS_ENUM(NSInteger, ASNetworkImageSource) { - ASNetworkImageSourceUnspecified = 0, - ASNetworkImageSourceSynchronousCache, - ASNetworkImageSourceAsynchronousCache, - ASNetworkImageSourceFileURL, - ASNetworkImageSourceDownload, -}; - -/// A struct that carries details about ASNetworkImageNode's image loads. -typedef struct { - /// The source from which the image was loaded. - ASNetworkImageSource imageSource; -} ASNetworkImageNodeDidLoadInfo; - /** * The methods declared by the ASNetworkImageNodeDelegate protocol allow the adopting delegate to respond to * notifications such as finished decoding and downloading an image. @@ -161,11 +148,11 @@ typedef struct { * * @param imageNode The sender. * @param image The newly-loaded image. - * @param info Misc information about the image load. + * @param info Additional information about the image load. * * @discussion Called on a background queue. */ -- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image info:(ASNetworkImageNodeDidLoadInfo)info; +- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image info:(ASNetworkImageLoadInfo *)info; /** * Notification that the image node finished downloading an image. diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index 2240e3ae9..c41a02ae7 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -27,6 +27,7 @@ #import #import #import +#import #if AS_PIN_REMOTE_IMAGE #import @@ -334,8 +335,9 @@ - (void)displayWillStartAsynchronously:(BOOL)asynchronously if (asynchronously == NO && _cacheFlags.cacheSupportsSynchronousFetch) { ASDN::MutexLocker l(__instanceLock__); - if (_imageLoaded == NO && _URL && _downloadIdentifier == nil) { - UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:_URL] asdk_image]; + NSURL *url = _URL; + if (_imageLoaded == NO && url && _downloadIdentifier == nil) { + UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:url] asdk_image]; if (result) { [self _locked_setCurrentImageQuality:1.0]; [self _locked__setImage:result]; @@ -344,8 +346,7 @@ - (void)displayWillStartAsynchronously:(BOOL)asynchronously // Call out to the delegate. if (_delegateFlags.delegateDidLoadImageWithInfo) { ASDN::MutexUnlocker l(__instanceLock__); - ASNetworkImageNodeDidLoadInfo info = {}; - info.imageSource = ASNetworkImageSourceSynchronousCache; + auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:url sourceType:ASNetworkImageSourceSynchronousCache downloadIdentifier:nil userInfo:nil]; [_delegate imageNode:self didLoadImage:result info:info]; } else if (_delegateFlags.delegateDidLoadImage) { ASDN::MutexUnlocker l(__instanceLock__); @@ -553,7 +554,7 @@ - (void)_locked_cancelImageDownloadWithResumePossibility:(BOOL)storeResume _cacheUUID = nil; } -- (void)_downloadImageWithCompletion:(void (^)(id imageContainer, NSError*, id downloadIdentifier))finished +- (void)_downloadImageWithCompletion:(void (^)(id imageContainer, NSError*, id downloadIdentifier, id userInfo))finished { ASPerformBlockOnBackgroundThread(^{ NSURL *url; @@ -572,9 +573,9 @@ - (void)_downloadImageWithCompletion:(void (^)(id ima downloadIdentifier = [_downloader downloadImageWithURL:url callbackQueue:dispatch_get_main_queue() downloadProgress:NULL - completion:^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) { + completion:^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) { if (finished != NULL) { - finished(imageContainer, error, downloadIdentifier); + finished(imageContainer, error, downloadIdentifier, userInfo); } }]; as_log_verbose(ASImageLoadingLog(), "Downloading image for %@ url: %@", self, url); @@ -629,15 +630,15 @@ - (void)_lazilyLoadImageIfNecessary } if (_shouldCacheImage) { - [self _locked__setImage:[UIImage imageNamed:_URL.path.lastPathComponent]]; + [self _locked__setImage:[UIImage imageNamed:URL.path.lastPathComponent]]; } else { // First try to load the path directly, for efficiency assuming a developer who // doesn't want caching is trying to be as minimal as possible. - UIImage *nonAnimatedImage = [UIImage imageWithContentsOfFile:_URL.path]; + UIImage *nonAnimatedImage = [UIImage imageWithContentsOfFile:URL.path]; if (nonAnimatedImage == nil) { // If we couldn't find it, execute an -imageNamed:-like search so we can find resources even if the // extension is not provided in the path. This allows the same path to work regardless of shouldCacheImage. - NSString *filename = [[NSBundle mainBundle] pathForResource:_URL.path.lastPathComponent ofType:nil]; + NSString *filename = [[NSBundle mainBundle] pathForResource:URL.path.lastPathComponent ofType:nil]; if (filename != nil) { nonAnimatedImage = [UIImage imageWithContentsOfFile:filename]; } @@ -646,7 +647,7 @@ - (void)_lazilyLoadImageIfNecessary // If the file may be an animated gif and then created an animated image. id animatedImage = nil; if (_downloaderFlags.downloaderImplementsAnimatedImage) { - NSData *data = [NSData dataWithContentsOfURL:_URL]; + NSData *data = [NSData dataWithContentsOfURL:URL]; if (data != nil) { animatedImage = [_downloader animatedImageWithData:data]; @@ -669,8 +670,7 @@ - (void)_lazilyLoadImageIfNecessary if (_delegateFlags.delegateDidLoadImageWithInfo) { ASDN::MutexUnlocker u(__instanceLock__); - ASNetworkImageNodeDidLoadInfo info = {}; - info.imageSource = ASNetworkImageSourceFileURL; + auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:URL sourceType:ASNetworkImageSourceFileURL downloadIdentifier:nil userInfo:nil]; [delegate imageNode:self didLoadImage:self.image info:info]; } else if (_delegateFlags.delegateDidLoadImage) { ASDN::MutexUnlocker u(__instanceLock__); @@ -679,7 +679,7 @@ - (void)_lazilyLoadImageIfNecessary }); } else { __weak __typeof__(self) weakSelf = self; - auto finished = ^(id imageContainer, NSError *error, id downloadIdentifier, ASNetworkImageSource imageSource) { + auto finished = ^(id imageContainer, NSError *error, id downloadIdentifier, ASNetworkImageSourceType imageSource, id userInfo) { ASPerformBlockOnBackgroundThread(^{ __typeof__(self) strongSelf = weakSelf; if (strongSelf == nil) { @@ -718,6 +718,9 @@ - (void)_lazilyLoadImageIfNecessary strongSelf->_downloadIdentifier = nil; strongSelf->_cacheUUID = nil; + // TODO: Why dispatch to main here? + // The docs say the image node delegate methods + // are called from background. ASPerformBlockOnMainThread(^{ __typeof__(self) strongSelf = weakSelf; if (strongSelf == nil) { @@ -730,8 +733,7 @@ - (void)_lazilyLoadImageIfNecessary if (imageContainer != nil) { if (strongSelf->_delegateFlags.delegateDidLoadImageWithInfo) { ASDN::MutexUnlocker u(strongSelf->__instanceLock__); - ASNetworkImageNodeDidLoadInfo info = {}; - info.imageSource = imageSource; + auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:URL sourceType:imageSource downloadIdentifier:downloadIdentifier userInfo:userInfo]; [delegate imageNode:strongSelf didLoadImage:strongSelf.image info:info]; } else if (strongSelf->_delegateFlags.delegateDidLoadImage) { ASDN::MutexUnlocker u(strongSelf->__instanceLock__); @@ -766,20 +768,20 @@ - (void)_lazilyLoadImageIfNecessary } if ([imageContainer asdk_image] == nil && _downloader != nil) { - [self _downloadImageWithCompletion:^(id imageContainer, NSError *error, id downloadIdentifier) { - finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload); + [self _downloadImageWithCompletion:^(id imageContainer, NSError *error, id downloadIdentifier, id userInfo) { + finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload, userInfo); }]; } else { as_log_verbose(ASImageLoadingLog(), "Decached image for %@ img: %@ url: %@", self, [imageContainer asdk_image], URL); - finished(imageContainer, nil, nil, ASNetworkImageSourceAsynchronousCache); + finished(imageContainer, nil, nil, ASNetworkImageSourceAsynchronousCache, nil); } }; [_cache cachedImageWithURL:URL callbackQueue:dispatch_get_main_queue() completion:completion]; } else { - [self _downloadImageWithCompletion:^(id imageContainer, NSError *error, id downloadIdentifier) { - finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload); + [self _downloadImageWithCompletion:^(id imageContainer, NSError *error, id downloadIdentifier, id userInfo) { + finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload, userInfo); }]; } } diff --git a/Source/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h index a3935ec21..241d8f2bb 100644 --- a/Source/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -35,6 +35,7 @@ #import #import #import +#import #import #import diff --git a/Source/Details/ASBasicImageDownloader.h b/Source/Details/ASBasicImageDownloader.h index 7238048c0..d1f8862d4 100644 --- a/Source/Details/ASBasicImageDownloader.h +++ b/Source/Details/ASBasicImageDownloader.h @@ -25,14 +25,15 @@ NS_ASSUME_NONNULL_BEGIN @interface ASBasicImageDownloader : NSObject /** - * A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes + * A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes. + * The userInfo provided by this downloader is `nil`. * * This is a very basic image downloader. It does not support caching, progressive downloading and likely * isn't something you should use in production. If you'd like something production ready, see @c ASPINRemoteImageDownloader * * @note It is strongly recommended you include PINRemoteImage and use @c ASPINRemoteImageDownloader instead. */ -+ (instancetype)sharedImageDownloader; +@property (class, readonly) ASBasicImageDownloader *sharedImageDownloader; + (instancetype)new __attribute__((unavailable("+[ASBasicImageDownloader sharedImageDownloader] must be used."))); - (instancetype)init __attribute__((unavailable("+[ASBasicImageDownloader sharedImageDownloader] must be used."))); diff --git a/Source/Details/ASBasicImageDownloader.mm b/Source/Details/ASBasicImageDownloader.mm index 0d8084265..ab88dfa4e 100644 --- a/Source/Details/ASBasicImageDownloader.mm +++ b/Source/Details/ASBasicImageDownloader.mm @@ -130,7 +130,7 @@ - (void)completeWithImage:(UIImage *)image error:(NSError *)error if (completionBlock) { dispatch_async(callbackQueue, ^{ - completionBlock(image, error, nil); + completionBlock(image, error, nil, nil); }); } } @@ -206,7 +206,7 @@ @interface ASBasicImageDownloader () @implementation ASBasicImageDownloader -+ (instancetype)sharedImageDownloader ++ (ASBasicImageDownloader *)sharedImageDownloader { static ASBasicImageDownloader *sharedImageDownloader = nil; static dispatch_once_t once = 0; @@ -235,9 +235,9 @@ - (instancetype)_init #pragma mark ASImageDownloaderProtocol. - (id)downloadImageWithURL:(NSURL *)URL - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion + callbackQueue:(dispatch_queue_t)callbackQueue + downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress + completion:(ASImageDownloaderCompletion)completion { ASBasicImageDownloaderContext *context = [ASBasicImageDownloaderContext contextForURL:URL]; diff --git a/Source/Details/ASImageProtocols.h b/Source/Details/ASImageProtocols.h index 3fdf321e4..c99867219 100644 --- a/Source/Details/ASImageProtocols.h +++ b/Source/Details/ASImageProtocols.h @@ -72,8 +72,9 @@ typedef void(^ASImageCacherCompletion)(id _Nullable i @param image The image that was downloaded, if the image could be successfully downloaded; nil otherwise. @param error An error describing why the download of `URL` failed, if the download failed; nil otherwise. @param downloadIdentifier The identifier for the download task that completed. + @param userInfo Any additional info that your downloader would like to communicate through Texture. */ -typedef void(^ASImageDownloaderCompletion)(id _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier); +typedef void(^ASImageDownloaderCompletion)(id _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo); /** @param progress The progress of the download, in the range of (0.0, 1.0), inclusive. diff --git a/Source/Details/ASPINRemoteImageDownloader.h b/Source/Details/ASPINRemoteImageDownloader.h index 179104901..abccd9f84 100644 --- a/Source/Details/ASPINRemoteImageDownloader.h +++ b/Source/Details/ASPINRemoteImageDownloader.h @@ -28,12 +28,13 @@ NS_ASSUME_NONNULL_BEGIN @interface ASPINRemoteImageDownloader : NSObject /** - * A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes + * A shared image downloader which can be used by @c ASNetworkImageNodes and @c ASMultiplexImageNodes. + * The userInfo provided by this downloader is an instance of `PINRemoteImageManagerResult`. * * This is the default downloader used by network backed image nodes if PINRemoteImage and PINCache are * available. It uses PINRemoteImage's features to provide caching and progressive image downloads. */ -+ (ASPINRemoteImageDownloader *)sharedDownloader; +@property (class, readonly) ASPINRemoteImageDownloader *sharedDownloader; /** diff --git a/Source/Details/ASPINRemoteImageDownloader.m b/Source/Details/ASPINRemoteImageDownloader.m index 3b46fc314..3d050fd9e 100644 --- a/Source/Details/ASPINRemoteImageDownloader.m +++ b/Source/Details/ASPINRemoteImageDownloader.m @@ -114,7 +114,7 @@ @interface ASPINRemoteImageDownloader () @implementation ASPINRemoteImageDownloader -+ (instancetype)sharedDownloader ++ (ASPINRemoteImageDownloader *)sharedDownloader { static dispatch_once_t onceToken = 0; @@ -235,12 +235,12 @@ - (nullable id)downloadImageWithURL:(NSURL *)URL [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{ #if PIN_ANIMATED_AVAILABLE if (result.alternativeRepresentation) { - completion(result.alternativeRepresentation, result.error, result.UUID); + completion(result.alternativeRepresentation, result.error, result.UUID, result); } else { - completion(result.image, result.error, result.UUID); + completion(result.image, result.error, result.UUID, result); } #else - completion(result.image, result.error, result.UUID); + completion(result.image, result.error, result.UUID, result); #endif }]; }; diff --git a/Source/Private/ASNetworkImageLoadInfo+Private.h b/Source/Private/ASNetworkImageLoadInfo+Private.h new file mode 100644 index 000000000..db0435231 --- /dev/null +++ b/Source/Private/ASNetworkImageLoadInfo+Private.h @@ -0,0 +1,22 @@ +// +// ASNetworkImageLoadInfo+Private.h +// AsyncDisplayKit +// +// Created by Adlai on 1/30/18. +// Copyright © 2018 Facebook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ASNetworkImageLoadInfo () + +- (instancetype)initWithURL:(NSURL *)url + sourceType:(ASNetworkImageSourceType)sourceType + downloadIdentifier:(nullable id)downloadIdentifier + userInfo:(nullable id)userInfo; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Tests/ASBasicImageDownloaderTests.m b/Tests/ASBasicImageDownloaderTests.m index 2dd7d3375..6144b5c25 100644 --- a/Tests/ASBasicImageDownloaderTests.m +++ b/Tests/ASBasicImageDownloaderTests.m @@ -38,14 +38,14 @@ - (void)testAsynchronouslyDownloadTheSameURLTwice [downloader downloadImageWithURL:URL callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) downloadProgress:nil - completion:^(id _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier) { + completion:^(id _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) { [firstExpectation fulfill]; }]; [downloader downloadImageWithURL:URL callbackQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) downloadProgress:nil - completion:^(id _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier) { + completion:^(id _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) { [secondExpectation fulfill]; }]; diff --git a/Tests/ASMultiplexImageNodeTests.m b/Tests/ASMultiplexImageNodeTests.m index 9eb41874b..71b1fe05e 100644 --- a/Tests/ASMultiplexImageNodeTests.m +++ b/Tests/ASMultiplexImageNodeTests.m @@ -238,7 +238,7 @@ - (void)testUncachedDownload // Simulate completion. ASImageDownloaderCompletion completionBlock = [inv as_argumentAtIndexAsObject:5]; - completionBlock([self _testImage], nil, nil); + completionBlock([self _testImage], nil, nil, nil); }); NSNumber *imageIdentifier = @1; diff --git a/examples/ASDKgram/Sample/PhotoCellNode.m b/examples/ASDKgram/Sample/PhotoCellNode.m index d424587c6..a094ad560 100644 --- a/examples/ASDKgram/Sample/PhotoCellNode.m +++ b/examples/ASDKgram/Sample/PhotoCellNode.m @@ -44,6 +44,9 @@ #define InsetForHeader UIEdgeInsetsMake(0, HORIZONTAL_BUFFER, 0, HORIZONTAL_BUFFER) #define InsetForFooter UIEdgeInsetsMake(VERTICAL_BUFFER, HORIZONTAL_BUFFER, VERTICAL_BUFFER, HORIZONTAL_BUFFER) +@interface PhotoCellNode () +@end + @implementation PhotoCellNode { PhotoModel *_photoModel; @@ -77,6 +80,7 @@ - (instancetype)initWithPhotoObject:(PhotoModel *)photo; }]; _photoImageNode = [[ASNetworkImageNode alloc] init]; + _photoImageNode.delegate = self; _photoImageNode.URL = photo.URL; _photoImageNode.layerBacked = YES; @@ -284,6 +288,19 @@ - (void)didEnterPreloadState }]; } +#pragma mark - Network Image Delegate + +- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image info:(ASNetworkImageLoadInfo *)info +{ + // Docs say method is called from bg but right now it's called from main. + // Save main thread time by shunting this. + if (info.sourceType == ASNetworkImageSourceDownload) { + ASPerformBlockOnBackgroundThread(^{ + NSLog(@"Received image %@ from %@ with userInfo %@", image, info.url.path, ASObjectDescriptionMakeTiny(info.userInfo)); + }); + } +} + #pragma mark - Helper Methods - (ASTextNode *)createLayerBackedTextNodeWithString:(NSAttributedString *)attributedString From 20e31f7d70ff44b603853d5bd3739aaacf73e002 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Wed, 31 Jan 2018 07:07:38 -0800 Subject: [PATCH 069/133] Fix synchronous state of node if +viewClass or +layerClass is overwritten #trivial (#776) * Fix synchronous state of node if +viewClass is overwritten * Also check for _layerClass overwrite for synchronous flag * Update some code style --- Source/ASDisplayNode.mm | 5 +++++ Tests/ASDisplayNodeTests.mm | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index a976e3f09..000b57acd 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -253,6 +253,11 @@ - (void)_initializeInstance _viewClass = [self.class viewClass]; _layerClass = [self.class layerClass]; + BOOL isSynchronous = ![_viewClass isSubclassOfClass:[_ASDisplayView class]] + || ![_layerClass isSubclassOfClass:[_ASDisplayLayer class]]; + setFlag(Synchronous, isSynchronous); + + _contentsScaleForDisplay = ASScreenScale(); _drawingPriority = ASDefaultDrawingPriority; diff --git a/Tests/ASDisplayNodeTests.mm b/Tests/ASDisplayNodeTests.mm index 9c5b668c4..ef5baee05 100644 --- a/Tests/ASDisplayNodeTests.mm +++ b/Tests/ASDisplayNodeTests.mm @@ -173,6 +173,28 @@ - (void)displayWillStartAsynchronously:(BOOL)asynchronously @end +@interface ASSynchronousTestDisplayNodeViaViewClass : ASDisplayNode +@end + +@implementation ASSynchronousTestDisplayNodeViaViewClass + ++ (Class)viewClass { + return [UIView class]; +} + +@end + +@interface ASSynchronousTestDisplayNodeViaLayerClass : ASDisplayNode +@end + +@implementation ASSynchronousTestDisplayNodeViaLayerClass + ++ (Class)layerClass { + return [CALayer class]; +} + +@end + @interface UIDisplayNodeTestView : UIView @end @@ -2354,4 +2376,16 @@ - (void)testScreenScale XCTAssertEqual(ASScreenScale(), UIScreen.mainScreen.scale); } +- (void)testThatIfViewClassIsOverwrittenItsSynchronous +{ + ASSynchronousTestDisplayNodeViaViewClass *node = [[ASSynchronousTestDisplayNodeViaViewClass alloc] init]; + XCTAssertTrue([node isSynchronous], @"Node should be synchronous if viewClass is ovewritten and not a subclass of _ASDisplayView"); +} + +- (void)testThatIfLayerClassIsOverwrittenItsSynchronous +{ + ASSynchronousTestDisplayNodeViaLayerClass *node = [[ASSynchronousTestDisplayNodeViaLayerClass alloc] init]; + XCTAssertTrue([node isSynchronous], @"Node should be synchronous if viewClass is ovewritten and not a subclass of _ASDisplayView"); +} + @end From 511bec63a2eb80e60a7c819c1b85b7de0e219d3b Mon Sep 17 00:00:00 2001 From: Denis Morozov Date: Wed, 31 Jan 2018 18:20:00 +0300 Subject: [PATCH 070/133] Fix capturing self in the block while loading image in ASNetworkImageNode (#777) * Fix capturing self in the block while loading image in ASNetworkImageNode * Restore re-strongify while switching on the main thread * Update CHANGELOG.md --- CHANGELOG.md | 1 + Source/ASNetworkImageNode.mm | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55954a8fe..9371048b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASNetworkImageNode] Fix capturing self in the block while loading image in ASNetworkImageNode. [Denis Mororozov](https://github.com/morozkin) [#777](https://github.com/TextureGroup/Texture/pull/777) - [ASTraitCollection] Add new properties of UITraitCollection to ASTraitCollection. [Yevgen Pogribnyi](https://github.com/ypogribnyi) - [ASRectMap] Replace implementation of ASRectTable with a simpler one based on unordered_map.[Scott Goodson](https://github.com/appleguy) [#719](https://github.com/TextureGroup/Texture/pull/719) - [ASCollectionView] Add missing flags for ASCollectionDelegate [Ilya Zheleznikov](https://github.com/ilyailya) [#718](https://github.com/TextureGroup/Texture/pull/718) diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index c41a02ae7..1189d58a0 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -697,9 +697,9 @@ - (void)_lazilyLoadImageIfNecessary } //No longer in preload range, no point in setting the results (they won't be cleared in exit preload range) - if (ASInterfaceStateIncludesPreload(self->_interfaceState) == NO) { - self->_downloadIdentifier = nil; - self->_cacheUUID = nil; + if (ASInterfaceStateIncludesPreload(strongSelf->_interfaceState) == NO) { + strongSelf->_downloadIdentifier = nil; + strongSelf->_cacheUUID = nil; return; } From d6971980a396c6e76efb7dc6a612a5cc70734bba Mon Sep 17 00:00:00 2001 From: Aaron Rosenberger Date: Wed, 31 Jan 2018 10:23:04 -0500 Subject: [PATCH 071/133] Fixed: completeBatchFetching is called on a background thread (#731) * Fixed breaking issue where completeBatchFetching is called on a background thread when no items are added to the collection * Changed spaces to tabs for consistency * Moved return statement for Code Review feedback * Fixed the same issue in the Objective-C version of ASDKgram * One more * Update PhotoFeedModel.m Fix header --- examples/ASDKgram/Sample/PhotoFeedModel.m | 28 +++++++++---------- .../ASDKgram-Swift/PhotoFeedModel.swift | 9 ++++-- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/examples/ASDKgram/Sample/PhotoFeedModel.m b/examples/ASDKgram/Sample/PhotoFeedModel.m index 27cf185e9..afac8ca9f 100644 --- a/examples/ASDKgram/Sample/PhotoFeedModel.m +++ b/examples/ASDKgram/Sample/PhotoFeedModel.m @@ -1,20 +1,18 @@ // // PhotoFeedModel.m -// Sample -// -// Created by Hannah Troisi on 2/28/16. +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// 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. +// http://www.apache.org/licenses/LICENSE-2.0 // #import "PhotoFeedModel.h" @@ -184,9 +182,11 @@ - (void)fetchPageWithCompletionBlock:(void (^)(NSArray *))block numResultsToRetu // early return if reached end of pages if (_totalPages) { if (_currentPage == _totalPages) { - if (block){ - block(@[]); - } + dispatch_async(dispatch_get_main_queue(), ^{ + if (block) { + block(@[]); + } + }); return; } } diff --git a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedModel.swift b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedModel.swift index 9a7cd5202..f344df34a 100644 --- a/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedModel.swift +++ b/examples_extra/ASDKgram-Swift/ASDKgram-Swift/PhotoFeedModel.swift @@ -67,7 +67,10 @@ final class PhotoFeedModel { private func fetchNextPageOfPopularPhotos(replaceData: Bool, numberOfAdditionsCompletion: @escaping (Int, NetworkingErrors?) -> ()) { if currentPage == totalPages, currentPage != 0 { - return numberOfAdditionsCompletion(0, .customError("No pages left to parse")) + DispatchQueue.main.async { + numberOfAdditionsCompletion(0, .customError("No pages left to parse")) + } + return } var newPhotos: [PhotoModel] = [] @@ -106,7 +109,9 @@ final class PhotoFeedModel { case .failure(let fail): print(fail) - numberOfAdditionsCompletion(0, fail) + DispatchQueue.main.async { + numberOfAdditionsCompletion(0, fail) + } } } } From ea547270f2d05e015167ebdaaac48c95318fe9d5 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Wed, 31 Jan 2018 15:35:14 +0000 Subject: [PATCH 072/133] [ASDisplayNode] Force a layout pass on a visible node as soon as it enters preload state (#779) This reverts commit 2e9858837251cf16c9ffd19ba2eaeaa1012c8977 (#751). The reason we can't wait for the coming CA's layout pass is that cell node visibility events occur before the pass, at which time the cell's subnodes don't have correct frames for impression tracking. The root cause of this problem is that, right now, cell node visible states are set by ASRangeController well before the layout pass of the hosting collection/table view. That means we're "jumping the gun". The more I think about this, the more I agree with @Adlai-Holler that we need to treat visible state differently. That is, a node should only be visible (and thus get visibility events) after it's fully loaded, it's view/layer attached to the hierarchy and laid out by a CA transaction. In other words, at the end of the CA layout pass. Such change needs time and effort to be thoroughly reviewed and tested. Until then, let's roll with this fix. --- CHANGELOG.md | 2 +- Source/ASDisplayNode.mm | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9371048b0..140fcb916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ - [ASScrollNode] Ensure the node respects the given size range while calculating its layout. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - [ASScrollNode] Invalidate the node's calculated layout if its scrollable directions changed. Also add unit tests for the class. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424) -- [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so subnodes can start preloading right away. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706) [#751](https://github.com/TextureGroup/Texture/pull/751) +- [Automatic Subnode Management] Nodes with ASM enabled now insert/delete their subnodes as soon as they enter preload state, so subnodes can start preloading right away. [Huy Nguyen](https://github.com/nguyenhuy) [#706](https://github.com/TextureGroup/Texture/pull/706) - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) - Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) - Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 000b57acd..1e772e514 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -3086,15 +3086,7 @@ - (void)didEnterPreloadState // - If it doesn't have a calculated or pending layout that fits its current bounds, a measurement pass will occur // (see -__layout and -_u_measureNodeWithBoundsIfNecessary:). This scenario is uncommon, // and running a measurement pass here is a fine trade-off because preloading any time after this point would be late. - // - // Don't force a layout pass if the node is already visible. Soon CoreAnimation will trigger - // a (coalesced, thus more efficient) pass on the backing store. Rely on it instead. - BOOL shouldForceLayoutPass = NO; - { - ASDN::MutexLocker l(__instanceLock__); - shouldForceLayoutPass = _automaticallyManagesSubnodes && !ASInterfaceStateIncludesVisible(_interfaceState); - } - if (shouldForceLayoutPass) { + if (self.automaticallyManagesSubnodes) { [self layoutIfNeeded]; } From 0bb53552b01cd11a2c78ed3f3f3bd37e3aa616a7 Mon Sep 17 00:00:00 2001 From: Alex Hill Date: Wed, 31 Jan 2018 10:07:08 -0800 Subject: [PATCH 073/133] [ASCellNode] focusStyle mapping (#727) * [ASCellNode] Adds mapping for UITableViewCell focusStyle * Update CHANGELOG.md --- CHANGELOG.md | 1 + Source/ASCellNode.h | 6 ++++++ Source/ASCellNode.mm | 1 + Source/ASTableView.mm | 4 ++-- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 140fcb916..d389e0231 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASCellNode] Adds mapping for UITableViewCell focusStyle [Alex Hill](https://github.com/alexhillc) [#727](https://github.com/TextureGroup/Texture/pull/727) - [ASNetworkImageNode] Fix capturing self in the block while loading image in ASNetworkImageNode. [Denis Mororozov](https://github.com/morozkin) [#777](https://github.com/TextureGroup/Texture/pull/777) - [ASTraitCollection] Add new properties of UITraitCollection to ASTraitCollection. [Yevgen Pogribnyi](https://github.com/ypogribnyi) - [ASRectMap] Replace implementation of ASRectTable with a simpler one based on unordered_map.[Scott Goodson](https://github.com/appleguy) [#719](https://github.com/TextureGroup/Texture/pull/719) diff --git a/Source/ASCellNode.h b/Source/ASCellNode.h index 2c911f17a..b846e549d 100644 --- a/Source/ASCellNode.h +++ b/Source/ASCellNode.h @@ -190,6 +190,12 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { */ @property (nonatomic) UITableViewCellSelectionStyle selectionStyle; +/* @abstract The focus style when a cell is focused + * @default UITableViewCellFocusStyleDefault + * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. + */ +@property (nonatomic) UITableViewCellFocusStyle focusStyle; + /* @abstract The view used as the background of the cell when it is selected. * ASTableView uses these properties when configuring UITableViewCells that host ASCellNodes. * ASCollectionView uses these properties when configuring UICollectionViewCells that host ASCellNodes. diff --git a/Source/ASCellNode.mm b/Source/ASCellNode.mm index a3816df18..cd3bed447 100644 --- a/Source/ASCellNode.mm +++ b/Source/ASCellNode.mm @@ -58,6 +58,7 @@ - (instancetype)init // Use UITableViewCell defaults _selectionStyle = UITableViewCellSelectionStyleDefault; + _focusStyle = UITableViewCellFocusStyleDefault; self.clipsToBounds = YES; return self; diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index f78a688ea..9e135d6ad 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -114,10 +114,10 @@ - (void)setElement:(ASCollectionElement *)element if (node) { self.backgroundColor = node.backgroundColor; - self.selectionStyle = node.selectionStyle; self.selectedBackgroundView = node.selectedBackgroundView; self.separatorInset = node.separatorInset; - self.selectionStyle = node.selectionStyle; + self.selectionStyle = node.selectionStyle; + self.focusStyle = node.focusStyle; self.accessoryType = node.accessoryType; // the following ensures that we clip the entire cell to it's bounds if node.clipsToBounds is set (the default) From 0c4ccc5253346fa056e73229b08a4fb045074066 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 31 Jan 2018 12:18:04 -0800 Subject: [PATCH 074/133] Improve ASNetworkImageNode delegate callout behavior (#778) * Improve ASNetworkImageNode delegate callout behavior * no message --- CHANGELOG.md | 1 + Source/ASNetworkImageNode.mm | 52 +++++++++++++++++++----------------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d389e0231..7d0015260 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Modified `ASImageDownloaderCompletion` to add an optional `id userInfo` field. Your custom downloader can pass `nil`. - Modified the last argument to `-[ASNetworkImageNodeDelegate imageNode:didLoadImage:info:]` method from a struct to an object of new class `ASNetworkImageLoadInfo` which includes other metadata about the load operation. - Removed +load static initializer from ASDisplayNode. [Adlai Holler](https://github.com/Adlai-Holler) +- Optimized ASNetworkImageNode loading and resolved edge cases where the image provided to the delegate was not the image that was loaded. [Adlai Holler](https://github.com/Adlai-Holler) [#778](https://github.com/TextureGroup/Texture/pull/778/) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index 1189d58a0..6cf70fa62 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -703,6 +703,7 @@ - (void)_lazilyLoadImageIfNecessary return; } + UIImage *newImage; if (imageContainer != nil) { [strongSelf _locked_setCurrentImageQuality:1.0]; NSData *animatedImageData = [imageContainer asdk_animatedImageData]; @@ -710,7 +711,8 @@ - (void)_lazilyLoadImageIfNecessary id animatedImage = [strongSelf->_downloader animatedImageWithData:animatedImageData]; [strongSelf _locked_setAnimatedImage:animatedImage]; } else { - [strongSelf _locked__setImage:[imageContainer asdk_image]]; + newImage = [imageContainer asdk_image]; + [strongSelf _locked__setImage:newImage]; } strongSelf->_imageLoaded = YES; } @@ -718,32 +720,32 @@ - (void)_lazilyLoadImageIfNecessary strongSelf->_downloadIdentifier = nil; strongSelf->_cacheUUID = nil; - // TODO: Why dispatch to main here? - // The docs say the image node delegate methods - // are called from background. - ASPerformBlockOnMainThread(^{ - __typeof__(self) strongSelf = weakSelf; - if (strongSelf == nil) { - return; - } - - // Grab the lock for the rest of the block - ASDN::MutexLocker l(strongSelf->__instanceLock__); - - if (imageContainer != nil) { - if (strongSelf->_delegateFlags.delegateDidLoadImageWithInfo) { - ASDN::MutexUnlocker u(strongSelf->__instanceLock__); + void (^calloutBlock)(ASNetworkImageNode *inst); + + if (newImage) { + if (_delegateFlags.delegateDidLoadImageWithInfo) { + calloutBlock = ^(ASNetworkImageNode *strongSelf) { auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:URL sourceType:imageSource downloadIdentifier:downloadIdentifier userInfo:userInfo]; - [delegate imageNode:strongSelf didLoadImage:strongSelf.image info:info]; - } else if (strongSelf->_delegateFlags.delegateDidLoadImage) { - ASDN::MutexUnlocker u(strongSelf->__instanceLock__); - [delegate imageNode:strongSelf didLoadImage:strongSelf.image]; - } - } else if (error && strongSelf->_delegateFlags.delegateDidFailWithError) { - ASDN::MutexUnlocker u(strongSelf->__instanceLock__); - [delegate imageNode:strongSelf didFailWithError:error]; + [delegate imageNode:strongSelf didLoadImage:newImage info:info]; + }; + } else if (_delegateFlags.delegateDidLoadImage) { + calloutBlock = ^(ASNetworkImageNode *strongSelf) { + [delegate imageNode:strongSelf didLoadImage:newImage]; + }; } - }); + } else if (error && _delegateFlags.delegateDidFailWithError) { + calloutBlock = ^(ASNetworkImageNode *strongSelf) { + [delegate imageNode:strongSelf didFailWithError:error]; + }; + } + + if (calloutBlock) { + ASPerformBlockOnMainThread(^{ + if (auto strongSelf = weakSelf) { + calloutBlock(strongSelf); + } + }); + } }); }; From 0f061b401eaa871abbc24774e237eb903d43b10b Mon Sep 17 00:00:00 2001 From: Vladyslav Chapaev Date: Thu, 1 Feb 2018 17:43:46 +0300 Subject: [PATCH 075/133] node tint color fix (#764) --- Source/ASTableView.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 9e135d6ad..acfe23906 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -119,6 +119,7 @@ - (void)setElement:(ASCollectionElement *)element self.selectionStyle = node.selectionStyle; self.focusStyle = node.focusStyle; self.accessoryType = node.accessoryType; + self.tintColor = node.tintColor; // the following ensures that we clip the entire cell to it's bounds if node.clipsToBounds is set (the default) // This is actually a workaround for a bug we are seeing in some rare cases (selected background view From 1be4d8d088943e602850018a7b2f4aa8bd6db79b Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 1 Feb 2018 06:45:07 -0800 Subject: [PATCH 076/133] Add #764 to the changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d0015260..3dc967bc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - Modified the last argument to `-[ASNetworkImageNodeDelegate imageNode:didLoadImage:info:]` method from a struct to an object of new class `ASNetworkImageLoadInfo` which includes other metadata about the load operation. - Removed +load static initializer from ASDisplayNode. [Adlai Holler](https://github.com/Adlai-Holler) - Optimized ASNetworkImageNode loading and resolved edge cases where the image provided to the delegate was not the image that was loaded. [Adlai Holler](https://github.com/Adlai-Holler) [#778](https://github.com/TextureGroup/Texture/pull/778/) +- Make `ASCellNode` tint color apply to table view cell accessories. [Vladyslav Chapaev](https://github.com/ShogunPhyched) [#764](https://github.com/TextureGroup/Texture/pull/764) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) From 38b76e0eb2d353fa4135a9091d2af49a3b831aec Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 1 Feb 2018 08:44:47 -0800 Subject: [PATCH 077/133] Improve nullable annotations for _ASDisplayLayer and _ASDisplayView (#780) --- Source/Details/_ASDisplayLayer.h | 16 ++++++++++++---- Source/Details/_ASDisplayView.h | 6 +++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Source/Details/_ASDisplayLayer.h b/Source/Details/_ASDisplayLayer.h index 11ed69578..97c1e9489 100644 --- a/Source/Details/_ASDisplayLayer.h +++ b/Source/Details/_ASDisplayLayer.h @@ -19,6 +19,8 @@ #import #import +NS_ASSUME_NONNULL_BEGIN + @class ASDisplayNode; @protocol _ASDisplayLayerDelegate; @@ -28,7 +30,7 @@ @discussion This property overrides the CALayer category method which implements this via associated objects. This should result in much better performance for _ASDisplayLayers. */ -@property (nonatomic, weak) ASDisplayNode *asyncdisplaykit_node; +@property (nullable, nonatomic, weak) ASDisplayNode *asyncdisplaykit_node; /** @summary Set to YES to enable asynchronous display for the receiver. @@ -57,7 +59,7 @@ @desc The asyncDelegate will have the opportunity to override the methods related to async display. */ -@property (atomic, weak) id<_ASDisplayLayerDelegate> asyncDelegate; +@property (nullable, atomic, weak) id<_ASDisplayLayerDelegate> asyncDelegate; /** @summary Suspends both asynchronous and synchronous display of the receiver if YES. @@ -109,7 +111,10 @@ @param isCancelledBlock Execute this block to check whether the current drawing operation has been cancelled to avoid unnecessary work. A return value of YES means cancel drawing and return. @param isRasterizing YES if the layer is being rasterized into another layer, in which case drawRect: probably wants to avoid doing things like filling its bounds with a zero-alpha color to clear the backing store. */ -+ (void)drawRect:(CGRect)bounds withParameters:(id)parameters isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing; ++ (void)drawRect:(CGRect)bounds + withParameters:(nullable id)parameters + isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock + isRasterizing:(BOOL)isRasterizing; /** @summary Delegate override to provide new layer contents as a UIImage. @@ -117,7 +122,8 @@ @param isCancelledBlock Execute this block to check whether the current drawing operation has been cancelled to avoid unnecessary work. A return value of YES means cancel drawing and return. @return A UIImage with contents that are ready to display on the main thread. Make sure that the image is already decoded before returning it here. */ -+ (UIImage *)displayWithParameters:(id)parameters isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock; ++ (UIImage *)displayWithParameters:(nullable id)parameters + isCancelled:(AS_NOESCAPE asdisplaynode_iscancelled_block_t)isCancelledBlock; // Called on the main thread only @@ -147,3 +153,5 @@ - (void)cancelDisplayAsyncLayer:(_ASDisplayLayer *)asyncLayer; @end + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/_ASDisplayView.h b/Source/Details/_ASDisplayView.h index 6a0b9a386..578dd8d58 100644 --- a/Source/Details/_ASDisplayView.h +++ b/Source/Details/_ASDisplayView.h @@ -17,6 +17,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + // This class is only for use by ASDisplayNode and should never be subclassed or used directly. // Note that the "node" property is added to UIView directly via a category in ASDisplayNode. @@ -28,7 +30,7 @@ @discussion This property overrides the UIView category method which implements this via associated objects. This should result in much better performance for _ASDisplayView. */ -@property (nonatomic, weak) ASDisplayNode *asyncdisplaykit_node; +@property (nullable, nonatomic, weak) ASDisplayNode *asyncdisplaykit_node; // These methods expose a way for ASDisplayNode touch events to let the view call super touch events // Some UIKit mechanisms, like UITableView and UICollectionView selection handling, require this to work @@ -38,3 +40,5 @@ - (void)__forwardTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; @end + +NS_ASSUME_NONNULL_END From c6454214acac298d049613681a1ad796fb3d0354 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 2 Feb 2018 10:36:17 -0800 Subject: [PATCH 078/133] Retain the reference color space (#784) --- Source/Details/ASGraphicsContext.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/Details/ASGraphicsContext.m b/Source/Details/ASGraphicsContext.m index f87102fab..3aaa14c09 100644 --- a/Source/Details/ASGraphicsContext.m +++ b/Source/Details/ASGraphicsContext.m @@ -154,7 +154,8 @@ extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGF dispatch_once(&onceToken, ^{ UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0); UIImage *refImage = UIGraphicsGetImageFromCurrentImageContext(); - imageColorSpace = CGImageGetColorSpace(refImage.CGImage); + imageColorSpace = CGColorSpaceRetain(CGImageGetColorSpace(refImage.CGImage)); + ASDisplayNodeCAssertNotNil(imageColorSpace, nil); UIGraphicsEndImageContext(); }); From 5ea4d51596a298e3ca7a8bb8f59d66118e801788 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 2 Feb 2018 11:41:19 -0800 Subject: [PATCH 079/133] Get CatDealsCollectionView example running again #trivial (#783) * Get CatDealsCollectionView example running again * Fix it for real and some other styling stuff * Fix some warning * Adjust headers --- .../CatDealsCollectionView/Sample/BlurbNode.h | 18 +-- .../CatDealsCollectionView/Sample/BlurbNode.m | 37 +++-- .../CatDealsCollectionView/Sample/ItemNode.h | 1 - .../CatDealsCollectionView/Sample/ItemNode.m | 131 +++++++++++------- .../Sample/ItemStyles.h | 20 ++- .../Sample/ItemStyles.m | 25 ++-- .../Sample/ItemViewModel.m | 19 +-- .../Sample/LoadingNode.h | 20 ++- .../Sample/LoadingNode.m | 35 ++--- .../Sample/PlaceholderNetworkImageNode.h | 20 ++- .../Sample/PlaceholderNetworkImageNode.m | 24 ++-- .../Sample/ViewController.m | 24 ++-- 12 files changed, 194 insertions(+), 180 deletions(-) diff --git a/examples/CatDealsCollectionView/Sample/BlurbNode.h b/examples/CatDealsCollectionView/Sample/BlurbNode.h index e6574bcd0..e40f99d8a 100644 --- a/examples/CatDealsCollectionView/Sample/BlurbNode.h +++ b/examples/CatDealsCollectionView/Sample/BlurbNode.h @@ -1,18 +1,18 @@ // // BlurbNode.h -// Sample +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// 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. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import diff --git a/examples/CatDealsCollectionView/Sample/BlurbNode.m b/examples/CatDealsCollectionView/Sample/BlurbNode.m index 7dbe86a8b..0b358273c 100644 --- a/examples/CatDealsCollectionView/Sample/BlurbNode.m +++ b/examples/CatDealsCollectionView/Sample/BlurbNode.m @@ -1,18 +1,18 @@ // // BlurbNode.m -// Sample +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// 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. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import "BlurbNode.h" @@ -57,16 +57,15 @@ - (instancetype)init NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:blurb]; [string addAttribute:NSFontAttributeName value:[UIFont fontWithName:@"HelveticaNeue-Light" size:16.0f] range:NSMakeRange(0, blurb.length)]; [string addAttributes:@{ - NSLinkAttributeName: [NSURL URLWithString:@"http://lorempixel.com/"], - NSForegroundColorAttributeName: [UIColor blueColor], - NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDot), - } - range:[blurb rangeOfString:@"lorempixel.com"]]; + NSLinkAttributeName: [NSURL URLWithString:@"http://lorempixel.com/"], + NSForegroundColorAttributeName: [UIColor blueColor], + NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDot), + } range:[blurb rangeOfString:@"lorempixel.com"]]; [string addAttributes:@{ - NSLinkAttributeName: [NSURL URLWithString:@"http://www.catipsum.com/"], - NSForegroundColorAttributeName: [UIColor blueColor], - NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDot), - } range:[blurb rangeOfString:@"catipsum.com"]]; + NSLinkAttributeName: [NSURL URLWithString:@"http://www.catipsum.com/"], + NSForegroundColorAttributeName: [UIColor blueColor], + NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDot), + } range:[blurb rangeOfString:@"catipsum.com"]]; _textNode.attributedText = string; // add it as a subnode, and we're done @@ -90,7 +89,7 @@ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize centerSpec.sizingOptions = ASCenterLayoutSpecSizingOptionMinimumY; centerSpec.child = _textNode; - UIEdgeInsets padding =UIEdgeInsetsMake(kTextPadding, kTextPadding, kTextPadding, kTextPadding); + UIEdgeInsets padding = UIEdgeInsetsMake(kTextPadding, kTextPadding, kTextPadding, kTextPadding); return [ASInsetLayoutSpec insetLayoutSpecWithInsets:padding child:centerSpec]; } diff --git a/examples/CatDealsCollectionView/Sample/ItemNode.h b/examples/CatDealsCollectionView/Sample/ItemNode.h index 678327e11..bb0f06857 100644 --- a/examples/CatDealsCollectionView/Sample/ItemNode.h +++ b/examples/CatDealsCollectionView/Sample/ItemNode.h @@ -20,7 +20,6 @@ @interface ItemNode : ASCellNode -- initWithViewModel:(ItemViewModel *)viewModel; + (CGSize)sizeForWidth:(CGFloat)width; + (CGSize)preferredViewSize; diff --git a/examples/CatDealsCollectionView/Sample/ItemNode.m b/examples/CatDealsCollectionView/Sample/ItemNode.m index 400f5b79e..4fd76e8a0 100644 --- a/examples/CatDealsCollectionView/Sample/ItemNode.m +++ b/examples/CatDealsCollectionView/Sample/ItemNode.m @@ -20,6 +20,16 @@ #import "PlaceholderNetworkImageNode.h" #import +static CGFloat ASIsRTL() +{ + static BOOL __isRTL = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + __isRTL = [UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft; + }); + return __isRTL; +} + const CGFloat kFixedLabelsAreaHeight = 96.0; const CGFloat kDesignWidth = 320.0; const CGFloat kDesignHeight = 299.0; @@ -28,7 +38,7 @@ @interface ItemNode() -@property (nonatomic, strong) ItemViewModel *viewModel; +@property (nonatomic, strong) ItemViewModel *nodeModel; @property (nonatomic, strong) PlaceholderNetworkImageNode *dealImageView; @@ -46,28 +56,35 @@ @interface ItemNode() @end @implementation ItemNode -@dynamic viewModel; +// Defined on ASCellNode +@dynamic nodeModel; -- (instancetype)initWithViewModel:(ItemViewModel *)viewModel ++ (void)load +{ + // Need to happen on main thread. + ASIsRTL(); +} + +- (instancetype)init { self = [super init]; if (self != nil) { - self.viewModel = viewModel; - [self setup]; - [self updateLabels]; + [self setupNodes]; [self updateBackgroundColor]; - - ASSetDebugName(self, @"Item #%zd", viewModel.identifier); - self.accessibilityIdentifier = viewModel.titleText; } return self; } -+ (BOOL)isRTL { - return [UIApplication sharedApplication].userInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft; +- (void)setNodeModel:(ItemViewModel *)nodeModel +{ + [super setNodeModel:nodeModel]; + + [self updateLabels]; + [self updateAccessibilityIdentifier]; } -- (void)setup { +- (void)setupNodes +{ self.dealImageView = [[PlaceholderNetworkImageNode alloc] init]; self.dealImageView.delegate = self; self.dealImageView.placeholderEnabled = YES; @@ -138,7 +155,7 @@ - (void)setup { self.soldOutLabelBackground.hidden = YES; self.soldOutLabelFlat.hidden = YES; - if ([ItemNode isRTL]) { + if (ASIsRTL()) { self.titleLabel.style.alignSelf = ASStackLayoutAlignSelfEnd; self.firstInfoLabel.style.alignSelf = ASStackLayoutAlignSelfEnd; self.distanceLabel.style.alignSelf = ASStackLayoutAlignSelfEnd; @@ -154,49 +171,56 @@ - (void)setup { } } -- (void)updateLabels { +- (void)updateLabels +{ // Set Title text - if (self.viewModel.titleText) { - self.titleLabel.attributedText = [[NSAttributedString alloc] initWithString:self.viewModel.titleText attributes:[ItemStyles titleStyle]]; + if (self.nodeModel.titleText) { + self.titleLabel.attributedText = [[NSAttributedString alloc] initWithString:self.nodeModel.titleText attributes:[ItemStyles titleStyle]]; } - if (self.viewModel.firstInfoText) { - self.firstInfoLabel.attributedText = [[NSAttributedString alloc] initWithString:self.viewModel.firstInfoText attributes:[ItemStyles subtitleStyle]]; + if (self.nodeModel.firstInfoText) { + self.firstInfoLabel.attributedText = [[NSAttributedString alloc] initWithString:self.nodeModel.firstInfoText attributes:[ItemStyles subtitleStyle]]; } - if (self.viewModel.secondInfoText) { - self.secondInfoLabel.attributedText = [[NSAttributedString alloc] initWithString:self.viewModel.secondInfoText attributes:[ItemStyles secondInfoStyle]]; + if (self.nodeModel.secondInfoText) { + self.secondInfoLabel.attributedText = [[NSAttributedString alloc] initWithString:self.nodeModel.secondInfoText attributes:[ItemStyles secondInfoStyle]]; } - if (self.viewModel.originalPriceText) { - self.originalPriceLabel.attributedText = [[NSAttributedString alloc] initWithString:self.viewModel.originalPriceText attributes:[ItemStyles originalPriceStyle]]; + if (self.nodeModel.originalPriceText) { + self.originalPriceLabel.attributedText = [[NSAttributedString alloc] initWithString:self.nodeModel.originalPriceText attributes:[ItemStyles originalPriceStyle]]; } - if (self.viewModel.finalPriceText) { - self.finalPriceLabel.attributedText = [[NSAttributedString alloc] initWithString:self.viewModel.finalPriceText attributes:[ItemStyles finalPriceStyle]]; + if (self.nodeModel.finalPriceText) { + self.finalPriceLabel.attributedText = [[NSAttributedString alloc] initWithString:self.nodeModel.finalPriceText attributes:[ItemStyles finalPriceStyle]]; } - if (self.viewModel.distanceLabelText) { - NSString *format = [ItemNode isRTL] ? @"%@ •" : @"• %@"; - NSString *distanceText = [NSString stringWithFormat:format, self.viewModel.distanceLabelText]; + if (self.nodeModel.distanceLabelText) { + NSString *format = ASIsRTL() ? @"%@ •" : @"• %@"; + NSString *distanceText = [NSString stringWithFormat:format, self.nodeModel.distanceLabelText]; self.distanceLabel.attributedText = [[NSAttributedString alloc] initWithString:distanceText attributes:[ItemStyles distanceStyle]]; } - BOOL isSoldOut = self.viewModel.soldOutText != nil; + BOOL isSoldOut = self.nodeModel.soldOutText != nil; if (isSoldOut) { - NSString *soldOutText = self.viewModel.soldOutText; + NSString *soldOutText = self.nodeModel.soldOutText; self.soldOutLabelFlat.attributedText = [[NSAttributedString alloc] initWithString:soldOutText attributes:[ItemStyles soldOutStyle]]; } self.soldOutOverlay.hidden = !isSoldOut; self.soldOutLabelFlat.hidden = !isSoldOut; self.soldOutLabelBackground.hidden = !isSoldOut; - BOOL hasBadge = self.viewModel.badgeText != nil; + BOOL hasBadge = self.nodeModel.badgeText != nil; if (hasBadge) { - self.badge.attributedText = [[NSAttributedString alloc] initWithString:self.viewModel.badgeText attributes:[ItemStyles badgeStyle]]; + self.badge.attributedText = [[NSAttributedString alloc] initWithString:self.nodeModel.badgeText attributes:[ItemStyles badgeStyle]]; self.badge.backgroundColor = [ItemStyles badgeColor]; } self.badge.hidden = !hasBadge; } +- (void)updateAccessibilityIdentifier +{ + ASSetDebugName(self, @"Item #%zd", self.nodeModel.identifier); + self.accessibilityIdentifier = self.nodeModel.titleText; +} + - (void)updateBackgroundColor { if (self.highlighted) { @@ -208,9 +232,6 @@ - (void)updateBackgroundColor } } -- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image { -} - - (void)setSelected:(BOOL)selected { [super setSelected:selected]; @@ -223,21 +244,22 @@ - (void)setHighlighted:(BOOL)highlighted [self updateBackgroundColor]; } -#pragma mark - superclass +#pragma mark - ASDisplayNode -- (void)displayWillStart { +- (void)displayWillStart +{ [super displayWillStart]; [self didEnterPreloadState]; } -- (void)didEnterPreloadState { +- (void)didEnterPreloadState +{ [super didEnterPreloadState]; - if (self.viewModel) { + if (self.nodeModel) { [self loadImage]; } } - - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { ASLayoutSpec *textSpec = [self textSpec]; @@ -253,7 +275,8 @@ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize { return soldOutOverlay; } -- (ASLayoutSpec *)textSpec { +- (ASLayoutSpec *)textSpec +{ CGFloat kInsetHorizontal = 16.0; CGFloat kInsetTop = 6.0; CGFloat kInsetBottom = 0.0; @@ -271,7 +294,7 @@ - (ASLayoutSpec *)textSpec { NSArray *info1Children = @[self.firstInfoLabel, self.distanceLabel, horizontalSpacer1, self.originalPriceLabel]; NSArray *info2Children = @[self.secondInfoLabel, horizontalSpacer2, self.finalPriceLabel]; - if ([ItemNode isRTL]) { + if (ASIsRTL()) { info1Children = [[info1Children reverseObjectEnumerator] allObjects]; info2Children = [[info2Children reverseObjectEnumerator] allObjects]; } @@ -288,7 +311,8 @@ - (ASLayoutSpec *)textSpec { return textWrapper; } -- (ASLayoutSpec *)imageSpecWithSize:(ASSizeRange)constrainedSize { +- (ASLayoutSpec *)imageSpecWithSize:(ASSizeRange)constrainedSize +{ CGFloat imageRatio = [self imageRatioFromSize:constrainedSize.max]; ASRatioLayoutSpec *imagePlace = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:imageRatio child:self.dealImageView]; @@ -310,34 +334,38 @@ - (ASLayoutSpec *)soldOutLabelSpec { return soldOutLabelOverBackground; } - -+ (CGSize)sizeForWidth:(CGFloat)width { ++ (CGSize)sizeForWidth:(CGFloat)width +{ CGFloat height = [self scaledHeightForPreferredSize:[self preferredViewSize] scaledWidth:width]; return CGSizeMake(width, height); } -+ (CGSize)preferredViewSize { ++ (CGSize)preferredViewSize +{ return CGSizeMake(kDesignWidth, kDesignHeight); } -+ (CGFloat)scaledHeightForPreferredSize:(CGSize)preferredSize scaledWidth:(CGFloat)scaledWidth { ++ (CGFloat)scaledHeightForPreferredSize:(CGSize)preferredSize scaledWidth:(CGFloat)scaledWidth +{ CGFloat scale = scaledWidth / kDesignWidth; CGFloat scaledHeight = ceilf(scale * (kDesignHeight - kFixedLabelsAreaHeight)) + kFixedLabelsAreaHeight; return scaledHeight; } -#pragma mark - view operations +#pragma mark - Image -- (CGFloat)imageRatioFromSize:(CGSize)size { +- (CGFloat)imageRatioFromSize:(CGSize)size +{ CGFloat imageHeight = size.height - kFixedLabelsAreaHeight; CGFloat imageRatio = imageHeight / size.width; return imageRatio; } -- (CGSize)imageSize { +- (CGSize)imageSize +{ if (!CGSizeEqualToSize(self.dealImageView.frame.size, CGSizeZero)) { return self.dealImageView.frame.size; } else if (!CGSizeEqualToSize(self.calculatedSize, CGSizeZero)) { @@ -349,13 +377,14 @@ - (CGSize)imageSize { } } -- (void)loadImage { +- (void)loadImage +{ CGSize imageSize = [self imageSize]; if (CGSizeEqualToSize(CGSizeZero, imageSize)) { return; } - NSURL *url = [self.viewModel imageURLWithSize:imageSize]; + NSURL *url = [self.nodeModel imageURLWithSize:imageSize]; // if we're trying to set the deal image to what it already was, skip the work if ([[url absoluteString] isEqualToString:[self.dealImageView.URL absoluteString]]) { diff --git a/examples/CatDealsCollectionView/Sample/ItemStyles.h b/examples/CatDealsCollectionView/Sample/ItemStyles.h index a5af90a01..09d3b81c8 100644 --- a/examples/CatDealsCollectionView/Sample/ItemStyles.h +++ b/examples/CatDealsCollectionView/Sample/ItemStyles.h @@ -1,20 +1,18 @@ // // ItemStyles.h -// Sample -// -// Created by Samuel Stow on 12/30/15. +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// 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. +// http://www.apache.org/licenses/LICENSE-2.0 // #import diff --git a/examples/CatDealsCollectionView/Sample/ItemStyles.m b/examples/CatDealsCollectionView/Sample/ItemStyles.m index 12871e3be..690980d49 100644 --- a/examples/CatDealsCollectionView/Sample/ItemStyles.m +++ b/examples/CatDealsCollectionView/Sample/ItemStyles.m @@ -1,20 +1,18 @@ // // ItemStyles.m -// Sample -// -// Created by Samuel Stow on 12/30/15. +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// 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. +// http://www.apache.org/licenses/LICENSE-2.0 // #import "ItemStyles.h" @@ -95,9 +93,10 @@ + (UIColor *)badgeColor { + (UIImage *)placeholderImage { static UIImage *__catFace = nil; - if (!__catFace) { + static dispatch_once_t onceToken; + dispatch_once (&onceToken, ^{ __catFace = [UIImage imageNamed:@"cat_face"]; - } + }); return __catFace; } diff --git a/examples/CatDealsCollectionView/Sample/ItemViewModel.m b/examples/CatDealsCollectionView/Sample/ItemViewModel.m index 4cf925e5d..6206e399c 100644 --- a/examples/CatDealsCollectionView/Sample/ItemViewModel.m +++ b/examples/CatDealsCollectionView/Sample/ItemViewModel.m @@ -35,7 +35,8 @@ + (ItemViewModel *)randomItem { return [[ItemViewModel alloc] init]; } -- (instancetype)init { +- (instancetype)init +{ self = [super init]; if (self) { static _Atomic(NSInteger) nextID = ATOMIC_VAR_INIT(1); @@ -45,11 +46,9 @@ - (instancetype)init { _secondInfoText = [NSString stringWithFormat:@"%zd+ bought", [self randomNumberInRange:5 to:6000]]; _originalPriceText = [NSString stringWithFormat:@"$%zd", [self randomNumberInRange:40 to:90]]; _finalPriceText = [NSString stringWithFormat:@"$%zd", [self randomNumberInRange:5 to:30]]; - BOOL isSoldOut = arc4random() % 5 == 0; - _soldOutText = isSoldOut ? @"SOLD OUT" : nil; + _soldOutText = (arc4random() % 5 == 0) ? @"SOLD OUT" : nil; _distanceLabelText = [NSString stringWithFormat:@"%zd mi", [self randomNumberInRange:1 to:20]]; - BOOL isBadged = arc4random() % 2 == 0; - if (isBadged) { + if (arc4random() % 2 == 0) { _badgeText = [self randomObjectFromArray:badges]; } _catNumber = [self randomNumberInRange:1 to:10]; @@ -58,18 +57,20 @@ - (instancetype)init { return self; } -- (NSURL *)imageURLWithSize:(CGSize)size { +- (NSURL *)imageURLWithSize:(CGSize)size +{ NSString *imageText = [NSString stringWithFormat:@"Fun cat pic %zd", self.labelNumber]; NSString *urlString = [NSString stringWithFormat:@"http://lorempixel.com/%zd/%zd/cats/%zd/%@", (NSInteger)roundl(size.width), (NSInteger)roundl(size.height), self.catNumber, imageText]; - urlString = [urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; - + + urlString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]; return [NSURL URLWithString:urlString]; } // titles courtesy of http://www.catipsum.com/ -+ (void)initialize { ++ (void)initialize +{ titles = @[@"Leave fur on owners clothes intrigued by the shower", @"Meowwww", @"Immediately regret falling into bathtub stare out the window", diff --git a/examples/CatDealsCollectionView/Sample/LoadingNode.h b/examples/CatDealsCollectionView/Sample/LoadingNode.h index d144de01a..6c0715726 100644 --- a/examples/CatDealsCollectionView/Sample/LoadingNode.h +++ b/examples/CatDealsCollectionView/Sample/LoadingNode.h @@ -1,20 +1,18 @@ // // LoadingNode.h -// Sample -// -// Created by Samuel Stow on 1/9/16. +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// 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. +// http://www.apache.org/licenses/LICENSE-2.0 // #import diff --git a/examples/CatDealsCollectionView/Sample/LoadingNode.m b/examples/CatDealsCollectionView/Sample/LoadingNode.m index 4fa29d6e3..03a6825c6 100644 --- a/examples/CatDealsCollectionView/Sample/LoadingNode.m +++ b/examples/CatDealsCollectionView/Sample/LoadingNode.m @@ -1,41 +1,29 @@ // // LoadingNode.m -// Sample -// -// Created by Samuel Stow on 1/9/16. +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// 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. +// http://www.apache.org/licenses/LICENSE-2.0 // #import "LoadingNode.h" -#import -#import -#import #import -@interface LoadingNode () -{ +@implementation LoadingNode { ASDisplayNode *_loadingSpinner; } -@end - -@implementation LoadingNode - - -#pragma mark - -#pragma mark ASCellNode. +#pragma mark - ASCellNode - (instancetype)init { @@ -61,7 +49,6 @@ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize centerSpec.centeringOptions = ASCenterLayoutSpecCenteringXY; centerSpec.sizingOptions = ASCenterLayoutSpecSizingOptionDefault; centerSpec.child = _loadingSpinner; - return centerSpec; } diff --git a/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.h b/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.h index 53ba71544..31470610b 100644 --- a/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.h +++ b/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.h @@ -1,20 +1,18 @@ // // PlaceholderNetworkImageNode.h -// Sample -// -// Created by Samuel Stow on 1/14/16. +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// 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. +// http://www.apache.org/licenses/LICENSE-2.0 // #import diff --git a/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.m b/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.m index cb1b7e81b..e7f0de47f 100644 --- a/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.m +++ b/examples/CatDealsCollectionView/Sample/PlaceholderNetworkImageNode.m @@ -1,29 +1,27 @@ // // PlaceholderNetworkImageNode.m -// Sample -// -// Created by Samuel Stow on 1/14/16. +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// 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. +// http://www.apache.org/licenses/LICENSE-2.0 // #import "PlaceholderNetworkImageNode.h" @implementation PlaceholderNetworkImageNode -- (UIImage *)placeholderImage { +- (UIImage *)placeholderImage +{ return self.placeholderImageOverride; } - @end diff --git a/examples/CatDealsCollectionView/Sample/ViewController.m b/examples/CatDealsCollectionView/Sample/ViewController.m index 6e63dabe0..78b58d67a 100644 --- a/examples/CatDealsCollectionView/Sample/ViewController.m +++ b/examples/CatDealsCollectionView/Sample/ViewController.m @@ -50,7 +50,6 @@ - (instancetype)init self = [super initWithNode:_collectionNode]; if (self) { - self.title = @"Cat Deals"; _collectionNode.dataSource = self; @@ -89,7 +88,8 @@ - (void)viewDidLoad [self fetchMoreCatsWithCompletion:nil]; } -- (void)fetchMoreCatsWithCompletion:(void (^)(BOOL))completion { +- (void)fetchMoreCatsWithCompletion:(void (^)(BOOL))completion +{ if (kSimulateWebResponse) { __weak typeof(self) weakSelf = self; void(^mockWebService)() = ^{ @@ -110,7 +110,8 @@ - (void)fetchMoreCatsWithCompletion:(void (^)(BOOL))completion { } } -- (void)appendMoreItems:(NSInteger)numberOfNewItems completion:(void (^)(BOOL))completion { +- (void)appendMoreItems:(NSInteger)numberOfNewItems completion:(void (^)(BOOL))completion +{ NSArray *newData = [self getMoreData:numberOfNewItems]; [_collectionNode performBatchAnimated:YES updates:^{ [_data addObjectsFromArray:newData]; @@ -119,7 +120,8 @@ - (void)appendMoreItems:(NSInteger)numberOfNewItems completion:(void (^)(BOOL))c } completion:completion]; } -- (NSArray *)getMoreData:(NSInteger)count { +- (NSArray *)getMoreData:(NSInteger)count +{ NSMutableArray *data = [NSMutableArray array]; for (int i = 0; i < count; i++) { [data addObject:[ItemViewModel randomItem]]; @@ -127,7 +129,8 @@ - (NSArray *)getMoreData:(NSInteger)count { return data; } -- (NSArray *)indexPathsForObjects:(NSArray *)data { +- (NSArray *)indexPathsForObjects:(NSArray *)data +{ NSMutableArray *indexPaths = [NSMutableArray array]; NSInteger section = 0; for (ItemViewModel *viewModel in data) { @@ -138,7 +141,8 @@ - (NSArray *)indexPathsForObjects:(NSArray *)data { return indexPaths; } -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { +- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator +{ [_collectionNode.view.collectionViewLayout invalidateLayout]; } @@ -151,12 +155,16 @@ - (void)reloadTapped - (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath { - ItemViewModel *viewModel = _data[indexPath.item]; return ^{ - return [[ItemNode alloc] initWithViewModel:viewModel]; + return [[ItemNode alloc] init]; }; } +- (id)collectionNode:(ASCollectionNode *)collectionNode nodeModelForItemAtIndexPath:(NSIndexPath *)indexPath +{ + return _data[indexPath.item]; +} + - (ASCellNode *)collectionNode:(ASCollectionNode *)collectionNode nodeForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { if ([kind isEqualToString:UICollectionElementKindSectionHeader] && indexPath.section == 0) { From 600b6cb76d01b3485e14539145303b80f7f18fe7 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 8 Feb 2018 08:28:14 -0800 Subject: [PATCH 080/133] Fix ASTextNode2 is accessing backgroundColor off main while sizing / layout is happening (#794) --- Source/ASTextNode2.mm | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Source/ASTextNode2.mm b/Source/ASTextNode2.mm index ecd9496c5..54475368d 100644 --- a/Source/ASTextNode2.mm +++ b/Source/ASTextNode2.mm @@ -232,7 +232,7 @@ - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize [self _ensureTruncationText]; NSMutableAttributedString *mutableText = [attributedText mutableCopy]; - [self prepareAttributedStringForDrawing:mutableText]; + [self prepareAttributedString:mutableText]; ASTextLayout *layout = [ASTextNode2 compatibleLayoutWithContainer:container text:mutableText]; [self setNeedsDisplay]; @@ -319,7 +319,7 @@ - (NSArray *)exclusionPaths return _textContainer.exclusionPaths; } -- (void)prepareAttributedStringForDrawing:(NSMutableAttributedString *)attributedString +- (void)prepareAttributedString:(NSMutableAttributedString *)attributedString { ASDN::MutexLocker lock(__instanceLock__); @@ -334,12 +334,6 @@ - (void)prepareAttributedStringForDrawing:(NSMutableAttributedString *)attribute [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range]; }]; - // Apply background color if needed - UIColor *backgroundColor = self.backgroundColor; - if (CGColorGetAlpha(backgroundColor.CGColor) > 0) { - [attributedString addAttribute:NSBackgroundColorAttributeName value:backgroundColor range:NSMakeRange(0, attributedString.length)]; - } - // Apply shadow if needed if (_shadowOpacity > 0 && (_shadowRadius != 0 || !CGSizeEqualToSize(_shadowOffset, CGSizeZero)) && CGColorGetAlpha(_shadowColor) > 0) { NSShadow *shadow = [[NSShadow alloc] init]; @@ -362,11 +356,19 @@ - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer ASTextContainer *copiedContainer = [_textContainer copy]; copiedContainer.size = self.bounds.size; NSMutableAttributedString *mutableText = [self.attributedText mutableCopy] ?: [[NSMutableAttributedString alloc] init]; - [self prepareAttributedStringForDrawing:mutableText]; + + [self prepareAttributedString:mutableText]; + + // Apply background color if needed before drawing. To access the backgroundColor we need to be on the main thread + UIColor *backgroundColor = self.backgroundColor; + if (CGColorGetAlpha(backgroundColor.CGColor) > 0) { + [mutableText addAttribute:NSBackgroundColorAttributeName value:backgroundColor range:NSMakeRange(0, mutableText.length)]; + } + return @{ - @"container": copiedContainer, - @"text": mutableText - }; + @"container": copiedContainer, + @"text": mutableText + }; } /** From 3ee52e5f3b58efe8a5ee7c532914bb3f6dc9c292 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Thu, 8 Feb 2018 08:34:12 -0800 Subject: [PATCH 081/133] Add #794 to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dc967bc1..1ebb9d573 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - Removed +load static initializer from ASDisplayNode. [Adlai Holler](https://github.com/Adlai-Holler) - Optimized ASNetworkImageNode loading and resolved edge cases where the image provided to the delegate was not the image that was loaded. [Adlai Holler](https://github.com/Adlai-Holler) [#778](https://github.com/TextureGroup/Texture/pull/778/) - Make `ASCellNode` tint color apply to table view cell accessories. [Vladyslav Chapaev](https://github.com/ShogunPhyched) [#764](https://github.com/TextureGroup/Texture/pull/764) +- Fix ASTextNode2 is accessing backgroundColor off main while sizing / layout is happening. [Michael Schneider](https://github.com/maicki) [#794](https://github.com/TextureGroup/Texture/pull/778/) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) From b4a269aabf9eab1d08c6abb3f98266959da29196 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 8 Feb 2018 17:08:04 +0000 Subject: [PATCH 082/133] [ASDisplayNode] Always return the thread-safe cornerRadius property, even in slow CALayer rounding mode (#749) - Failing to do so will introduce race conditions in which the property was updated on a background thread but main thread has not executed the block that updates the property of the node's layer. During that window, the layer's property is out-of-date and can't be used. - After this change, ASDisplayNode's cornerRadius is the only source of truth and users must always use it instead of CALayer's. --- CHANGELOG.md | 1 + Source/ASDisplayNode.h | 6 ++++++ Source/Private/ASDisplayNode+UIViewBridge.mm | 12 +++--------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ebb9d573..1392553a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- **Important** ASDisplayNode's cornerRadius is a new thread-safe bridged property that should be preferred over CALayer's. Use the latter at your own risk! [Huy Nguyen](https://github.com/nguyenhuy) [#749](https://github.com/TextureGroup/Texture/pull/749). - [ASCellNode] Adds mapping for UITableViewCell focusStyle [Alex Hill](https://github.com/alexhillc) [#727](https://github.com/TextureGroup/Texture/pull/727) - [ASNetworkImageNode] Fix capturing self in the block while loading image in ASNetworkImageNode. [Denis Mororozov](https://github.com/morozkin) [#777](https://github.com/TextureGroup/Texture/pull/777) - [ASTraitCollection] Add new properties of UITraitCollection to ASTraitCollection. [Yevgen Pogribnyi](https://github.com/ypogribnyi) diff --git a/Source/ASDisplayNode.h b/Source/ASDisplayNode.h index feecc3ad9..49f2d917c 100644 --- a/Source/ASDisplayNode.h +++ b/Source/ASDisplayNode.h @@ -659,6 +659,12 @@ extern NSInteger const ASDefaultDrawingPriority; * @default ASCornerRoundingTypeDefaultSlowCALayer */ @property (nonatomic, assign) ASCornerRoundingType cornerRoundingType; // default=Slow CALayer .cornerRadius (offscreen rendering) + +/** @abstract The radius to use when rounding corners of the ASDisplayNode. + * + * @discussion This property is thread-safe and should always be preferred over CALayer's cornerRadius property, + * even if corner rounding type is ASCornerRoundingTypeDefaultSlowCALayer. + */ @property (nonatomic, assign) CGFloat cornerRadius; // default=0.0 @property (nonatomic, assign) BOOL clipsToBounds; // default==NO diff --git a/Source/Private/ASDisplayNode+UIViewBridge.mm b/Source/Private/ASDisplayNode+UIViewBridge.mm index a3eceb741..578caa7f6 100644 --- a/Source/Private/ASDisplayNode+UIViewBridge.mm +++ b/Source/Private/ASDisplayNode+UIViewBridge.mm @@ -186,17 +186,12 @@ - (void)setAlpha:(CGFloat)newAlpha - (CGFloat)cornerRadius { ASDN::MutexLocker l(__instanceLock__); - if (_cornerRoundingType == ASCornerRoundingTypeDefaultSlowCALayer) { - return self.layerCornerRadius; - } else { - return _cornerRadius; - } + return _cornerRadius; } - (void)setCornerRadius:(CGFloat)newCornerRadius { - ASDN::MutexLocker l(__instanceLock__); - [self updateCornerRoundingWithType:_cornerRoundingType cornerRadius:newCornerRadius]; + [self updateCornerRoundingWithType:self.cornerRoundingType cornerRadius:newCornerRadius]; } - (ASCornerRoundingType)cornerRoundingType @@ -207,8 +202,7 @@ - (ASCornerRoundingType)cornerRoundingType - (void)setCornerRoundingType:(ASCornerRoundingType)newRoundingType { - ASDN::MutexLocker l(__instanceLock__); - [self updateCornerRoundingWithType:newRoundingType cornerRadius:_cornerRadius]; + [self updateCornerRoundingWithType:newRoundingType cornerRadius:self.cornerRadius]; } - (NSString *)contentsGravity From 31227da5779365672305ae774f4da171b682c878 Mon Sep 17 00:00:00 2001 From: appleguy Date: Fri, 9 Feb 2018 11:04:00 -0800 Subject: [PATCH 083/133] [ASRangeController] Fix stability of "minimum" rangeMode if the app has more than one layout before scrolling. (#790) This should result in memory savings in many apps, since errant relayouts are pretty common. --- CHANGELOG.md | 1 + Source/ASCollectionView.mm | 7 ++++--- Source/ASTableView.mm | 9 ++++---- Source/Details/ASRangeController.h | 5 +++++ Source/Details/ASRangeController.mm | 32 +++++++++++++++++++++-------- 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1392553a9..0b50cd3a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASRangeController] Fix stability of "minimum" rangeMode if the app has more than one layout before scrolling. - **Important** ASDisplayNode's cornerRadius is a new thread-safe bridged property that should be preferred over CALayer's. Use the latter at your own risk! [Huy Nguyen](https://github.com/nguyenhuy) [#749](https://github.com/TextureGroup/Texture/pull/749). - [ASCellNode] Adds mapping for UITableViewCell focusStyle [Alex Hill](https://github.com/alexhillc) [#727](https://github.com/TextureGroup/Texture/pull/727) - [ASNetworkImageNode] Fix capturing self in the block while loading image in ASNetworkImageNode. [Denis Mororozov](https://github.com/morozkin) [#777](https://github.com/TextureGroup/Texture/pull/777) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 1b06d6bef..05ed5ebf9 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -1549,13 +1549,10 @@ - (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(N - (void)scrollViewDidScroll:(UIScrollView *)scrollView { - // If a scroll happenes the current range mode needs to go to full ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; if (ASInterfaceStateIncludesVisible(interfaceState)) { - [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; [self _checkForBatchFetching]; } - for (_ASCollectionViewCell *cell in _cellsForVisibilityUpdates) { // _cellsForVisibilityUpdates only includes cells for ASCellNode subclasses with overrides of the visibility method. [cell cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged inScrollView:scrollView]; @@ -1594,6 +1591,10 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { + // If a scroll happens the current range mode needs to go to full + _rangeController.contentHasBeenScrolled = YES; + [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; + for (_ASCollectionViewCell *cell in _cellsForVisibilityUpdates) { [cell cellNodeVisibilityEvent:ASCellNodeVisibilityEventWillBeginDragging inScrollView:scrollView]; } diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index acfe23906..1b03797ca 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -1214,13 +1214,10 @@ - (void)scrollViewDidScroll:(UIScrollView *)scrollView [super scrollViewDidScroll:scrollView]; return; } - // If a scroll happenes the current range mode needs to go to full ASInterfaceState interfaceState = [self interfaceStateForRangeController:_rangeController]; if (ASInterfaceStateIncludesVisible(interfaceState)) { - [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; [self _checkForBatchFetching]; - } - + } for (_ASTableViewCell *tableCell in _cellsForVisibilityUpdates) { [[tableCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventVisibleRectChanged inScrollView:scrollView @@ -1272,6 +1269,10 @@ - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView [super scrollViewWillBeginDragging:scrollView]; return; } + // If a scroll happens the current range mode needs to go to full + _rangeController.contentHasBeenScrolled = YES; + [_rangeController updateCurrentRangeWithMode:ASLayoutRangeModeFull]; + for (_ASTableViewCell *tableViewCell in _cellsForVisibilityUpdates) { [[tableViewCell node] cellNodeVisibilityEvent:ASCellNodeVisibilityEventWillBeginDragging inScrollView:scrollView diff --git a/Source/Details/ASRangeController.h b/Source/Details/ASRangeController.h index 46a5cbf69..ca47ef862 100644 --- a/Source/Details/ASRangeController.h +++ b/Source/Details/ASRangeController.h @@ -100,6 +100,11 @@ AS_SUBCLASSING_RESTRICTED */ @property (nonatomic, weak) id delegate; +/** + * Property that indicates whether the scroll view for this range controller has ever changed its contentOffset. + */ +@property (nonatomic, assign) BOOL contentHasBeenScrolled; + @end diff --git a/Source/Details/ASRangeController.mm b/Source/Details/ASRangeController.mm index 818fb3a1d..3bb404428 100644 --- a/Source/Details/ASRangeController.mm +++ b/Source/Details/ASRangeController.mm @@ -45,6 +45,7 @@ @interface ASRangeController () NSSet *_allPreviousIndexPaths; NSHashTable *_visibleNodes; ASLayoutRangeMode _currentRangeMode; + BOOL _contentHasBeenScrolled; BOOL _preserveCurrentRangeMode; BOOL _didRegisterForNodeDisplayNotifications; CFTimeInterval _pendingDisplayNodesTimestamp; @@ -77,6 +78,7 @@ - (instancetype)init _rangeIsValid = YES; _currentRangeMode = ASLayoutRangeModeUnspecified; + _contentHasBeenScrolled = NO; _preserveCurrentRangeMode = NO; _previousScrollDirection = ASScrollDirectionDown | ASScrollDirectionRight; @@ -222,10 +224,6 @@ - (void)_updateVisibleNodeIndexPaths auto visibleElements = [_dataSource visibleElementsForRangeController:self]; NSHashTable *newVisibleNodes = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; - if (visibleElements.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)... - [self _setVisibleNodes:newVisibleNodes]; - return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later - } ASSignpostStart(ASSignpostRangeControllerUpdate); // Get the scroll direction. Default to using the previous one, if they're not scrolling. @@ -234,12 +232,28 @@ - (void)_updateVisibleNodeIndexPaths scrollDirection = _previousScrollDirection; } _previousScrollDirection = scrollDirection; - + + if (visibleElements.count == 0) { // if we don't have any visibleNodes currently (scrolled before or after content)... + // Verify the actual state by checking the layout with a "VisibleOnly" range. + // This allows us to avoid thrashing through -didExitVisibleState in the case of -reloadData, since that generates didEndDisplayingCell calls. + // Those didEndDisplayingCell calls result in items being removed from the visibleElements returned by the _dataSource, even though the layout remains correct. + visibleElements = [_layoutController elementsForScrolling:scrollDirection rangeMode:ASLayoutRangeModeVisibleOnly rangeType:ASLayoutRangeTypeDisplay map:map]; + for (ASCollectionElement *element in visibleElements) { + [newVisibleNodes addObject:element.node]; + } + [self _setVisibleNodes:newVisibleNodes]; + return; // don't do anything for this update, but leave _rangeIsValid == NO to make sure we update it later + } + ASInterfaceState selfInterfaceState = [self interfaceState]; ASLayoutRangeMode rangeMode = _currentRangeMode; - // If the range mode is explicitly set via updateCurrentRangeWithMode: it will last in that mode until the - // range controller becomes visible again or explicitly changes the range mode again - if ((!_preserveCurrentRangeMode && ASInterfaceStateIncludesVisible(selfInterfaceState)) || [[self class] isFirstRangeUpdateForRangeMode:rangeMode]) { + BOOL updateRangeMode = (!_preserveCurrentRangeMode && _contentHasBeenScrolled); + + // If we've never scrolled before, we never update the range mode, so it doesn't jump into Full too early. + // This can happen if we have multiple, noisy updates occurring from application code before the user has engaged. + // If the range mode is explicitly set via updateCurrentRangeWithMode:, we'll preserve that for at least one update cycle. + // Once the user has scrolled and the range is visible, we'll always resume managing the range mode automatically. + if ((updateRangeMode && ASInterfaceStateIncludesVisible(selfInterfaceState)) || [[self class] isFirstRangeUpdateForRangeMode:rangeMode]) { rangeMode = [ASRangeController rangeModeForInterfaceState:selfInterfaceState currentRangeMode:_currentRangeMode]; } @@ -412,7 +426,7 @@ - (void)_updateVisibleNodeIndexPaths // NSLog(@"custom: %@", visibleNodePathsSet); // } [modifiedIndexPaths sortUsingSelector:@selector(compare:)]; - NSLog(@"Range update complete; modifiedIndexPaths: %@", [self descriptionWithIndexPaths:modifiedIndexPaths]); + NSLog(@"Range update complete; modifiedIndexPaths: %@, rangeMode: %d", [self descriptionWithIndexPaths:modifiedIndexPaths], rangeMode); #endif ASSignpostEnd(ASSignpostRangeControllerUpdate); From 479d40464e78c6ac1b39406b2bf6059238b8ba85 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Fri, 9 Feb 2018 12:16:18 -0800 Subject: [PATCH 084/133] [ASTableNode & ASCollectionNode] Keepalive reference for node if their view is necessarily alive (has a superview). (#793) * fix SIMULATE_WEB_RESPONSE not imported #449 * Fix to make rangeMode update in right time * Keep collection/table node alive if view still in use. --- Source/ASCollectionNode.mm | 7 +++++++ Source/ASCollectionView.mm | 21 ++++++++++++++++++++- Source/ASTableNode.mm | 7 +++++++ Source/ASTableView.mm | 21 ++++++++++++++++++++- Tests/ASCollectionViewTests.mm | 1 + 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/Source/ASCollectionNode.mm b/Source/ASCollectionNode.mm index 2449377fd..01669ca79 100644 --- a/Source/ASCollectionNode.mm +++ b/Source/ASCollectionNode.mm @@ -172,6 +172,13 @@ - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionVi return self; } +- (void)dealloc +{ + if ([self isNodeLoaded]) { + ASDisplayNodeAssert(self.view.superview == nil, @"Node's view should be removed from hierarchy."); + } +} + #pragma mark ASDisplayNode - (void)didLoad diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 05ed5ebf9..10dd6dc9a 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -151,7 +151,12 @@ @interface ASCollectionView () Date: Mon, 12 Feb 2018 11:38:20 -0800 Subject: [PATCH 085/133] Add missing scrollViewWillEndDragging passthrough delegate (#796) * Add scrollViewWillEndDragging delegate * Make sure delegate can respond to scrollViewWillEndDragging * Add changes to CHANGELOG.md --- CHANGELOG.md | 1 + Source/Private/ASIGListAdapterBasedDataSource.m | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b50cd3a5..7704a477b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - Optimized ASNetworkImageNode loading and resolved edge cases where the image provided to the delegate was not the image that was loaded. [Adlai Holler](https://github.com/Adlai-Holler) [#778](https://github.com/TextureGroup/Texture/pull/778/) - Make `ASCellNode` tint color apply to table view cell accessories. [Vladyslav Chapaev](https://github.com/ShogunPhyched) [#764](https://github.com/TextureGroup/Texture/pull/764) - Fix ASTextNode2 is accessing backgroundColor off main while sizing / layout is happening. [Michael Schneider](https://github.com/maicki) [#794](https://github.com/TextureGroup/Texture/pull/778/) +- Pass scrollViewWillEndDragging delegation through in ASIGListAdapterDataSource for IGListKit integration. [#796](https://github.com/TextureGroup/Texture/pull/796) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/Private/ASIGListAdapterBasedDataSource.m b/Source/Private/ASIGListAdapterBasedDataSource.m index 31a6c4582..ab1863622 100644 --- a/Source/Private/ASIGListAdapterBasedDataSource.m +++ b/Source/Private/ASIGListAdapterBasedDataSource.m @@ -102,6 +102,14 @@ - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView [self.delegate scrollViewWillBeginDragging:scrollView]; } +- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset +{ + // IGListAdapter doesn't implement scrollViewWillEndDragging yet (pending pull request), so we need this check for now. Doesn't hurt to have it anyways :) + if ([self.delegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) { + [self.delegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset]; + } +} + - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { [self.delegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; From e2478fc7995b6ad48801c2a5138c002e4c750c85 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Mon, 12 Feb 2018 21:04:58 +0000 Subject: [PATCH 086/133] [ASDisplayNode layout] Fix an issue that sometimes causes a node's pending layout to not be applied (#792) * [ASDisplayNode layout] Fix an issue that causes a node's pending layout to not be applied - Since the implementation of layout version (#428), if a node's pending and calculated layouts have the same current version as well as the same constrained size, the 2 layouts are considered equal and can be used interchangeably. A layout version check between the 2 layouts was added in #695. This PR adds a missing constrained size check. - If the pending layout has the same version but a different constrained size compare to the calculated layout's, we can assume that the pending layout is newer and should be preferred over the calculated one. That is because layout operations always register their new layout as pending, which then (immediately or eventually) get applied to the node as calculated layout. --- CHANGELOG.md | 3 ++- Source/ASDisplayNode+Layout.mm | 28 ++++++++++++++++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7704a477b..471615641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ - [ASNetworkImageNode] Deprecates .URLs in favor of .URL [Garrett Moon](https://github.com/garrettmoon) [#699](https://github.com/TextureGroup/Texture/pull/699) - [iOS11] Update project settings and fix errors [Eke](https://github.com/Eke) [#676](https://github.com/TextureGroup/Texture/pull/676) - [ASCornerLayoutSpec] New layout spec class for declarative corner element layout. [#657](https://github.com/TextureGroup/Texture/pull/657) [huangkun](https://github.com/huang-kun) -- [Layout] Fix an issue that causes a pending layout to be applied multiple times. [Huy Nguyen](https://github.com/nguyenhuy) [#695](https://github.com/TextureGroup/Texture/pull/695) +- [ASDisplayNode layout] Fix an issue that causes a pending layout to be applied multiple times. [Huy Nguyen](https://github.com/nguyenhuy) [#695](https://github.com/TextureGroup/Texture/pull/695) +- [ASDisplayNode layout] Fix an issue that sometimes causes a node's pending layout to not be applied. [Huy Nguyen](https://github.com/nguyenhuy) [#792](https://github.com/TextureGroup/Texture/pull/792) - [ASScrollNode] Ensure the node respects the given size range while calculating its layout. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - [ASScrollNode] Invalidate the node's calculated layout if its scrollable directions changed. Also add unit tests for the class. [#637](https://github.com/TextureGroup/Texture/pull/637) [Huy Nguyen](https://github.com/nguyenhuy) - Add new unit testing to the layout engine. [Adlai Holler](https://github.com/Adlai-Holler) [#424](https://github.com/TextureGroup/Texture/pull/424) diff --git a/Source/ASDisplayNode+Layout.mm b/Source/ASDisplayNode+Layout.mm index dd0a2e2ad..084f4f912 100644 --- a/Source/ASDisplayNode+Layout.mm +++ b/Source/ASDisplayNode+Layout.mm @@ -2,8 +2,13 @@ // ASDisplayNode+Layout.mm // Texture // -// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0 (the "License"); +// 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 /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // @@ -300,13 +305,24 @@ - (void)_u_measureNodeWithBoundsIfNecessary:(CGRect)bounds } CGSize boundsSizeForLayout = ASCeilSizeValues(bounds.size); + NSUInteger calculatedVersion = _calculatedDisplayNodeLayout->version; // Prefer a newer and not yet applied _pendingDisplayNodeLayout over _calculatedDisplayNodeLayout // If there is no such _pending, check if _calculated is valid to reuse (avoiding recalculation below). - BOOL pendingLayoutIsPreferred = (_pendingDisplayNodeLayout != nullptr - && _pendingDisplayNodeLayout->version >= _layoutVersion - && _pendingDisplayNodeLayout->version > _calculatedDisplayNodeLayout->version); // _pending is not yet applied - BOOL calculatedLayoutIsReusable = (_calculatedDisplayNodeLayout->version >= _layoutVersion + BOOL pendingLayoutIsPreferred = NO; + if (_pendingDisplayNodeLayout != nullptr) { + NSUInteger pendingVersion = _pendingDisplayNodeLayout->version; + if (pendingVersion >= _layoutVersion) { + if (pendingVersion > calculatedVersion) { + pendingLayoutIsPreferred = YES; // Newer _pending + } else if (pendingVersion == calculatedVersion + && !ASSizeRangeEqualToSizeRange(_pendingDisplayNodeLayout->constrainedSize, + _calculatedDisplayNodeLayout->constrainedSize)) { + pendingLayoutIsPreferred = YES; // _pending with a different constrained size + } + } + } + BOOL calculatedLayoutIsReusable = (calculatedVersion >= _layoutVersion && (_calculatedDisplayNodeLayout->requestedLayoutFromAbove || CGSizeEqualToSize(_calculatedDisplayNodeLayout->layout.size, boundsSizeForLayout))); if (!pendingLayoutIsPreferred && calculatedLayoutIsReusable) { From 8b431733d38d08c838caaffe44ae26c1949d4b46 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Tue, 13 Feb 2018 12:02:30 -0800 Subject: [PATCH 087/133] Avoid triggering main thread assertions in collection/table dealloc #trivial (#803) * Avoid triggering main thread assertions in ASCollectionNode/ASTableNode dealloc * Put it back --- Source/ASCollectionNode.mm | 9 +++++++-- Source/ASTableNode.mm | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Source/ASCollectionNode.mm b/Source/ASCollectionNode.mm index 01669ca79..d0cc5f208 100644 --- a/Source/ASCollectionNode.mm +++ b/Source/ASCollectionNode.mm @@ -172,12 +172,17 @@ - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionVi return self; } +#if ASDISPLAYNODE_ASSERTIONS_ENABLED - (void)dealloc { - if ([self isNodeLoaded]) { - ASDisplayNodeAssert(self.view.superview == nil, @"Node's view should be removed from hierarchy."); + if (self.nodeLoaded) { + __weak UIView *view = self.view; + ASPerformBlockOnMainThread(^{ + ASDisplayNodeCAssertNil(view.superview, @"Node's view should be removed from hierarchy."); + }); } } +#endif #pragma mark ASDisplayNode diff --git a/Source/ASTableNode.mm b/Source/ASTableNode.mm index 09e5eb9bd..c35f90a87 100644 --- a/Source/ASTableNode.mm +++ b/Source/ASTableNode.mm @@ -104,12 +104,17 @@ - (instancetype)init return [self initWithStyle:UITableViewStylePlain]; } +#if ASDISPLAYNODE_ASSERTIONS_ENABLED - (void)dealloc { - if ([self isNodeLoaded]) { - ASDisplayNodeAssert(self.view.superview == nil, @"Node's view should be removed from hierarchy."); + if (self.nodeLoaded) { + __weak UIView *view = self.view; + ASPerformBlockOnMainThread(^{ + ASDisplayNodeCAssertNil(view.superview, @"Node's view should be removed from hierarchy."); + }); } } +#endif #pragma mark ASDisplayNode From 6f34691481fad4c69a3ad1b40ea008b6065cb319 Mon Sep 17 00:00:00 2001 From: John T McIntosh Date: Tue, 13 Feb 2018 14:08:31 -0600 Subject: [PATCH 088/133] Update IGListKit dependency to allow for updated versions (#802) --- Texture.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Texture.podspec b/Texture.podspec index 8b75b4334..b780df283 100644 --- a/Texture.podspec +++ b/Texture.podspec @@ -51,7 +51,7 @@ Pod::Spec.new do |spec| end spec.subspec 'IGListKit' do |igl| - igl.dependency 'IGListKit', '3.0.0' + igl.dependency 'IGListKit', '~> 3.0' igl.dependency 'Texture/Core' end From 2618c50073092e99fe1df5213ba6a0619e908988 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Tue, 13 Feb 2018 12:10:20 -0800 Subject: [PATCH 089/133] New runloop queue to coalesce Interface state update calls. (#788) * fix SIMULATE_WEB_RESPONSE not imported #449 * Coalesce interface state updates to ASCATransactionQueue before CATransaction commit. This will avoid duplicate interface state delegate calls caused by view repeatly added/removed to/from hierarchy during controller animation transition. * fix tests for new run loop queue * Support for disabling ASCATransactionQueue * Fix didExitHierarchy to use ASCATransactionQueue. * merge range managed and none range managed for didExitHierarchy * Revert "merge range managed and none range managed for didExitHierarchy" This reverts commit f807efaa65ed5dbdb6622d06da542e01a53715fa. * merge range managed and none range managed for didExitHierarchy * remove metadata * abstract queue to impl class methods * Add tests * Fix test fail because of shared object. * guard _pendingInterfaceState access with lock * name refactor * Refactor from comments https://github.com/TextureGroup/Texture/pull/788/\#pullrequestreview-94849919 * Apply InterfaceState immediately after ASCATranactionQueue is processed and before next runloop started. * refactor * no op to start CI build * remove unused var and kick off tests * change lisence * remove code for weak ref * add change log and adjust license --- CHANGELOG.md | 1 + Source/ASDisplayNode.mm | 96 ++++-- Source/ASRunLoopQueue.h | 35 ++- Source/ASRunLoopQueue.mm | 293 ++++++++++++++++-- .../Private/ASDisplayNode+FrameworkPrivate.h | 4 + Source/Private/ASDisplayNodeInternal.h | 2 +- Tests/ASCollectionViewTests.mm | 7 +- Tests/ASDisplayNodeImplicitHierarchyTests.m | 2 + Tests/ASDisplayNodeTests.mm | 19 +- Tests/ASDisplayNodeTestsHelper.h | 1 + Tests/ASDisplayNodeTestsHelper.m | 12 + Tests/ASRunLoopQueueTests.m | 36 ++- Tests/ASVideoNodeTests.m | 3 +- 13 files changed, 445 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 471615641..5716f6857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASRunloopQueue] Introduce new runloop queue(ASCATransactionQueue) to coalesce Interface state update calls for view controller transitions. - [ASRangeController] Fix stability of "minimum" rangeMode if the app has more than one layout before scrolling. - **Important** ASDisplayNode's cornerRadius is a new thread-safe bridged property that should be preferred over CALayer's. Use the latter at your own risk! [Huy Nguyen](https://github.com/nguyenhuy) [#749](https://github.com/TextureGroup/Texture/pull/749). - [ASCellNode] Adds mapping for UITableViewCell focusStyle [Alex Hill](https://github.com/alexhillc) [#727](https://github.com/TextureGroup/Texture/pull/727) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 1e772e514..5d6f305d3 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -63,7 +63,7 @@ // We have to forward declare the protocol as this place otherwise it will not compile compiling with an Base SDK < iOS 10 @protocol CALayerDelegate; -@interface ASDisplayNode () +@interface ASDisplayNode () /** * See ASDisplayNodeInternal.h for ivars @@ -2739,7 +2739,7 @@ - (void)setHierarchyState:(ASHierarchyState)newState // Entered or exited range managed state. if ((newState & ASHierarchyStateRangeManaged) != (oldState & ASHierarchyStateRangeManaged)) { if (newState & ASHierarchyStateRangeManaged) { - [self enterInterfaceState:self.supernode.interfaceState]; + [self enterInterfaceState:self.supernode.pendingInterfaceState]; } else { // The case of exiting a range-managed state should be fairly rare. Adding or removing the node // to a view hierarchy will cause its interfaceState to be either fully set or unset (all fields), @@ -2782,30 +2782,34 @@ - (void)didExitHierarchy ASDisplayNodeAssert(_flags.isExitingHierarchy, @"You should never call -didExitHierarchy directly. Appearance is automatically managed by ASDisplayNode"); ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); - - if (![self supportsRangeManagedInterfaceState]) { - self.interfaceState = ASInterfaceStateNone; - } else { - // This case is important when tearing down hierarchies. We must deliver a visibileStateDidChange:NO callback, as part our API guarantee that this method can be used for - // things like data analytics about user content viewing. We cannot call the method in the dealloc as any incidental retain operations in client code would fail. - // Additionally, it may be that a Standard UIView which is containing us is moving between hierarchies, and we should not send the call if we will be re-added in the - // same runloop. Strategy: strong reference (might be the last!), wait one runloop, and confirm we are still outside the hierarchy (both layer-backed and view-backed). - // TODO: This approach could be optimized by only performing the dispatch for root elements + recursively apply the interface state change. This would require a closer - // integration with _ASDisplayLayer to ensure that the superlayer pointer has been cleared by this stage (to check if we are root or not), or a different delegate call. - - if (ASInterfaceStateIncludesVisible(self.interfaceState)) { - dispatch_async(dispatch_get_main_queue(), ^{ - // This block intentionally retains self. - __instanceLock__.lock(); - unsigned isInHierarchy = _flags.isInHierarchy; - BOOL isVisible = ASInterfaceStateIncludesVisible(_interfaceState); - ASInterfaceState newState = (_interfaceState & ~ASInterfaceStateVisible); - __instanceLock__.unlock(); - - if (!isInHierarchy && isVisible) { - self.interfaceState = newState; + + // This case is important when tearing down hierarchies. We must deliver a visibileStateDidChange:NO callback, as part our API guarantee that this method can be used for + // things like data analytics about user content viewing. We cannot call the method in the dealloc as any incidental retain operations in client code would fail. + // Additionally, it may be that a Standard UIView which is containing us is moving between hierarchies, and we should not send the call if we will be re-added in the + // same runloop. Strategy: strong reference (might be the last!), wait one runloop, and confirm we are still outside the hierarchy (both layer-backed and view-backed). + // TODO: This approach could be optimized by only performing the dispatch for root elements + recursively apply the interface state change. This would require a closer + // integration with _ASDisplayLayer to ensure that the superlayer pointer has been cleared by this stage (to check if we are root or not), or a different delegate call. + if (ASInterfaceStateIncludesVisible(_pendingInterfaceState)) { + void(^exitVisibleInterfaceState)(void) = ^{ + // This block intentionally retains self. + __instanceLock__.lock(); + unsigned isStillInHierarchy = _flags.isInHierarchy; + BOOL isVisible = ASInterfaceStateIncludesVisible(_pendingInterfaceState); + ASInterfaceState newState = (_pendingInterfaceState & ~ASInterfaceStateVisible); + __instanceLock__.unlock(); + + if (!isStillInHierarchy && isVisible) { + if (![self supportsRangeManagedInterfaceState]) { + newState = ASInterfaceStateNone; } - }); + self.interfaceState = newState; + } + }; + + if ([[ASCATransactionQueue sharedQueue] disabled]) { + dispatch_async(dispatch_get_main_queue(), exitVisibleInterfaceState); + } else { + exitVisibleInterfaceState(); } } } @@ -2866,25 +2870,53 @@ - (ASInterfaceState)interfaceState } - (void)setInterfaceState:(ASInterfaceState)newState +{ + if ([[ASCATransactionQueue sharedQueue] disabled]) { + [self applyPendingInterfaceState:newState]; + } else { + ASDN::MutexLocker l(__instanceLock__); + if (_pendingInterfaceState != newState) { + _pendingInterfaceState = newState; + [[ASCATransactionQueue sharedQueue] enqueue:self]; + } + } +} + +- (ASInterfaceState)pendingInterfaceState +{ + ASDN::MutexLocker l(__instanceLock__); + return _pendingInterfaceState; +} + +- (void)applyPendingInterfaceState:(ASInterfaceState)newPendingState { //This method is currently called on the main thread. The assert has been added here because all of the //did(Enter|Exit)(Display|Visible|Preload)State methods currently guarantee calling on main. ASDisplayNodeAssertMainThread(); - // It should never be possible for a node to be visible but not be allowed / expected to display. - ASDisplayNodeAssertFalse(ASInterfaceStateIncludesVisible(newState) && !ASInterfaceStateIncludesDisplay(newState)); + // This method manages __instanceLock__ itself, to ensure the lock is not held while didEnter/Exit(.*)State methods are called, thus avoid potential deadlocks ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); ASInterfaceState oldState = ASInterfaceStateNone; + ASInterfaceState newState = ASInterfaceStateNone; { ASDN::MutexLocker l(__instanceLock__); - if (_interfaceState == newState) { - return; + // newPendingState will not be used when ASCATransactionQueue is enabled + // and use _pendingInterfaceState instead for interfaceState update. + if ([[ASCATransactionQueue sharedQueue] disabled]) { + _pendingInterfaceState = newPendingState; } oldState = _interfaceState; + newState = _pendingInterfaceState; + if (newState == oldState) { + return; + } _interfaceState = newState; } + // It should never be possible for a node to be visible but not be allowed / expected to display. + ASDisplayNodeAssertFalse(ASInterfaceStateIncludesVisible(newState) && !ASInterfaceStateIncludesDisplay(newState)); + // TODO: Trigger asynchronous measurement if it is not already cached or being calculated. // if ((newState & ASInterfaceStateMeasureLayout) != (oldState & ASInterfaceStateMeasureLayout)) { // } @@ -2981,6 +3013,12 @@ - (void)setInterfaceState:(ASInterfaceState)newState [self interfaceStateDidChange:newState fromState:oldState]; } +- (void)prepareForCATransactionCommit +{ + // Apply _pendingInterfaceState actual _interfaceState, note that ASInterfaceStateNone is not used. + [self applyPendingInterfaceState:ASInterfaceStateNone]; +} + - (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfaceState)oldState { // Subclass hook diff --git a/Source/ASRunLoopQueue.h b/Source/ASRunLoopQueue.h index 50a0eeb24..02711910b 100644 --- a/Source/ASRunLoopQueue.h +++ b/Source/ASRunLoopQueue.h @@ -20,8 +20,15 @@ NS_ASSUME_NONNULL_BEGIN +@protocol ASCATransactionQueueObserving +- (void)prepareForCATransactionCommit; +@end + +@interface ASAbstractRunLoopQueue : NSObject +@end + AS_SUBCLASSING_RESTRICTED -@interface ASRunLoopQueue : NSObject +@interface ASRunLoopQueue : ASAbstractRunLoopQueue /** * Create a new queue with the given run loop and handler. @@ -41,13 +48,37 @@ AS_SUBCLASSING_RESTRICTED - (void)enqueue:(ObjectType)object; -@property (nonatomic, readonly) BOOL isEmpty; +@property (atomic, readonly) BOOL isEmpty; @property (nonatomic, assign) NSUInteger batchSize; // Default == 1. @property (nonatomic, assign) BOOL ensureExclusiveMembership; // Default == YES. Set-like behavior. @end +AS_SUBCLASSING_RESTRICTED +@interface ASCATransactionQueue : ASAbstractRunLoopQueue + +@property (atomic, readonly) BOOL isEmpty; +@property (atomic, readonly) BOOL disabled; +/** + * The queue to run on main run loop before CATransaction commit. + * + * @discussion this queue will run after ASRunLoopQueue and before CATransaction commit + * to get last chance of updating/coalesce info like interface state. + * Each node will only be called once per transaction commit to reflect interface change. + */ +@property (class, atomic, readonly) ASCATransactionQueue *sharedQueue; + +- (void)enqueue:(id)object; + +/** + * @abstract Apply a node's interfaceState immediately rather than adding to the queue. + */ +- (void)disable; + +@end + + AS_SUBCLASSING_RESTRICTED @interface ASDeallocQueue : NSObject diff --git a/Source/ASRunLoopQueue.mm b/Source/ASRunLoopQueue.mm index 974e34814..12265d1df 100644 --- a/Source/ASRunLoopQueue.mm +++ b/Source/ASRunLoopQueue.mm @@ -181,27 +181,6 @@ - (void)dealloc @end -#pragma mark - ASRunLoopQueue - -@interface ASRunLoopQueue () { - CFRunLoopRef _runLoop; - CFRunLoopSourceRef _runLoopSource; - CFRunLoopObserverRef _runLoopObserver; - NSPointerArray *_internalQueue; // Use NSPointerArray so we can decide __strong or __weak per-instance. - ASDN::RecursiveMutex _internalQueueLock; - - // In order to not pollute the top-level activities, each queue has 1 root activity. - os_activity_t _rootActivity; - -#if ASRunLoopQueueLoggingEnabled - NSTimer *_runloopQueueLoggingTimer; -#endif -} - -@property (nonatomic, copy) void (^queueConsumer)(id dequeuedItem, BOOL isQueueDrained); - -@end - #if AS_KDEBUG_ENABLE /** * This is real, private CA API. Valid as of iOS 10. @@ -218,7 +197,23 @@ + (int)currentState; @end #endif -@implementation ASRunLoopQueue +#pragma mark - ASAbstractRunLoopQueue + +@interface ASAbstractRunLoopQueue (Private) ++ (void)load; ++ (void)registerCATransactionObservers; +@end + +@implementation ASAbstractRunLoopQueue + +- (instancetype)init +{ + if (self != [super init]) { + return nil; + } + ASDisplayNodeAssert(self.class != [ASAbstractRunLoopQueue class], @"Should never create instances of abstract class ASAbstractRunLoopQueue."); + return self; +} #if AS_KDEBUG_ENABLE + (void)load @@ -265,6 +260,31 @@ + (void)registerCATransactionObservers #endif // AS_KDEBUG_ENABLE +@end + +#pragma mark - ASRunLoopQueue + +@interface ASRunLoopQueue () { + CFRunLoopRef _runLoop; + CFRunLoopSourceRef _runLoopSource; + CFRunLoopObserverRef _runLoopObserver; + NSPointerArray *_internalQueue; // Use NSPointerArray so we can decide __strong or __weak per-instance. + ASDN::RecursiveMutex _internalQueueLock; + + // In order to not pollute the top-level activities, each queue has 1 root activity. + os_activity_t _rootActivity; + +#if ASRunLoopQueueLoggingEnabled + NSTimer *_runloopQueueLoggingTimer; +#endif +} + +@property (nonatomic, copy) void (^queueConsumer)(id dequeuedItem, BOOL isQueueDrained); + +@end + +@implementation ASRunLoopQueue + - (instancetype)initWithRunLoop:(CFRunLoopRef)runloop retainObjects:(BOOL)retainsObjects handler:(void (^)(id _Nullable, BOOL))handlerBlock { if (self = [super init]) { @@ -464,3 +484,232 @@ - (void)unlock } @end + +#pragma mark - ASCATransactionQueue + +@interface ASCATransactionQueue () { + CFRunLoopRef _runLoop; + CFRunLoopSourceRef _runLoopSource; + CFRunLoopObserverRef _preTransactionObserver; + CFRunLoopObserverRef _postTransactionObserver; + NSPointerArray *_internalQueue; + ASDN::RecursiveMutex _internalQueueLock; + BOOL _disableInterfaceStateCoalesce; + BOOL _CATransactionCommitInProgress; + + // In order to not pollute the top-level activities, each queue has 1 root activity. + os_activity_t _rootActivity; + +#if ASRunLoopQueueLoggingEnabled + NSTimer *_runloopQueueLoggingTimer; +#endif +} + +@end + +@implementation ASCATransactionQueue + +// CoreAnimation commit order is 2000000, the goal of this is to process shortly beforehand +// but after most other scheduled work on the runloop has processed. +static int const kASASCATransactionQueueOrder = 1000000; +// This will mark the end of current loop and any node enqueued between kASASCATransactionQueueOrder +// and kASASCATransactionQueuePostOrder will apply interface change immediately. +static int const kASASCATransactionQueuePostOrder = 3000000; + ++ (ASCATransactionQueue *)sharedQueue +{ + static dispatch_once_t onceToken; + static ASCATransactionQueue *sharedQueue; + dispatch_once(&onceToken, ^{ + sharedQueue = [[ASCATransactionQueue alloc] init]; + }); + return sharedQueue; +} + +- (instancetype)init +{ + if (self = [super init]) { + _runLoop = CFRunLoopGetMain(); + NSPointerFunctionsOptions options = NSPointerFunctionsStrongMemory; + _internalQueue = [[NSPointerArray alloc] initWithOptions:options]; + + // We don't want to pollute the top-level app activities with run loop batches, so we create one top-level + // activity per queue, and each batch activity joins that one instead. + _rootActivity = as_activity_create("Process run loop queue items", OS_ACTIVITY_NONE, OS_ACTIVITY_FLAG_DEFAULT); + { + // Log a message identifying this queue into the queue's root activity. + as_activity_scope_verbose(_rootActivity); + as_log_verbose(ASDisplayLog(), "Created run loop queue: %@", self); + } + + // Self is guaranteed to outlive the observer. Without the high cost of a weak pointer, + // __unsafe_unretained allows us to avoid flagging the memory cycle detector. + __unsafe_unretained __typeof__(self) weakSelf = self; + void (^handlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { + [weakSelf processQueue]; + }; + void (^postHandlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { + ASDN::MutexLocker l(_internalQueueLock); + _CATransactionCommitInProgress = NO; + }; + _preTransactionObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, kASASCATransactionQueueOrder, handlerBlock); + _postTransactionObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, kASASCATransactionQueuePostOrder, postHandlerBlock); + + CFRunLoopAddObserver(_runLoop, _preTransactionObserver, kCFRunLoopCommonModes); + CFRunLoopAddObserver(_runLoop, _postTransactionObserver, kCFRunLoopCommonModes); + + // It is not guaranteed that the runloop will turn if it has no scheduled work, and this causes processing of + // the queue to stop. Attaching a custom loop source to the run loop and signal it if new work needs to be done + CFRunLoopSourceContext sourceContext = {}; + sourceContext.perform = runLoopSourceCallback; +#if ASRunLoopQueueLoggingEnabled + sourceContext.info = (__bridge void *)self; +#endif + _runLoopSource = CFRunLoopSourceCreate(NULL, 0, &sourceContext); + CFRunLoopAddSource(_runLoop, _runLoopSource, kCFRunLoopCommonModes); + +#if ASRunLoopQueueLoggingEnabled + _runloopQueueLoggingTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(checkRunLoop) userInfo:nil repeats:YES]; + [[NSRunLoop mainRunLoop] addTimer:_runloopQueueLoggingTimer forMode:NSRunLoopCommonModes]; +#endif + } + return self; +} + +- (void)dealloc +{ + CFRunLoopRemoveSource(_runLoop, _runLoopSource, kCFRunLoopCommonModes); + CFRelease(_runLoopSource); + _runLoopSource = nil; + + if (CFRunLoopObserverIsValid(_preTransactionObserver)) { + CFRunLoopObserverInvalidate(_preTransactionObserver); + } + if (CFRunLoopObserverIsValid(_postTransactionObserver)) { + CFRunLoopObserverInvalidate(_postTransactionObserver); + } + CFRelease(_preTransactionObserver); + CFRelease(_postTransactionObserver); + _preTransactionObserver = nil; + _postTransactionObserver = nil; +} + +#if ASRunLoopQueueLoggingEnabled +- (void)checkRunLoop +{ + NSLog(@"<%@> - Jobs: %ld", self, _internalQueue.size()); +} +#endif + +- (void)processQueue +{ + // 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 itemsToProcess; + + { + ASDN::MutexLocker l(_internalQueueLock); + + // Mark the queue will end coalescing shortly until after CATransactionCommit. + // This will give the queue a chance to apply any further interfaceState changes/enqueue + // immediately within current runloop instead of pushing the work to next runloop cycle. + _CATransactionCommitInProgress = YES; + + NSInteger internalQueueCount = _internalQueue.count; + // Early-exit if the queue is empty. + if (internalQueueCount == 0) { + return; + } + + ASSignpostStart(ASSignpostRunLoopQueueBatch); + + /** + * For each item in the next batch, if it's non-nil then NULL it out + * and if we have an execution block then add it in. + * This could be written a bunch of different ways but + * this particular one nicely balances readability, safety, and efficiency. + */ + NSInteger foundItemCount = 0; + for (NSInteger i = 0; i < internalQueueCount && foundItemCount < internalQueueCount; i++) { + /** + * It is safe to use unsafe_unretained here. If the queue is weak, the + * object will be added to the autorelease pool. If the queue is strong, + * it will retain the object until we transfer it (retain it) in itemsToProcess. + */ + __unsafe_unretained id ptr = (__bridge id)[_internalQueue pointerAtIndex:i]; + if (ptr != nil) { + foundItemCount++; + itemsToProcess.push_back(ptr); + [_internalQueue replacePointerAtIndex:i withPointer:NULL]; + } + } + + [_internalQueue compact]; + } + + // itemsToProcess will be empty if _queueConsumer == nil so no need to check again. + auto count = itemsToProcess.size(); + if (count > 0) { + as_activity_scope_verbose(as_activity_create("Process run loop queue batch", _rootActivity, OS_ACTIVITY_FLAG_DEFAULT)); + auto itemsEnd = itemsToProcess.cend(); + for (auto iterator = itemsToProcess.begin(); iterator < itemsEnd; iterator++) { + __unsafe_unretained id value = *iterator; + [value prepareForCATransactionCommit]; + as_log_verbose(ASDisplayLog(), "processed %@", value); + } + if (count > 1) { + as_log_verbose(ASDisplayLog(), "processed %lu items", (unsigned long)count); + } + } + + ASSignpostEnd(ASSignpostRunLoopQueueBatch); +} + +- (void)enqueue:(id)object +{ + if (!object) { + return; + } + + if (_disableInterfaceStateCoalesce || _CATransactionCommitInProgress) { + [object prepareForCATransactionCommit]; + return; + } + + ASDN::MutexLocker l(_internalQueueLock); + + // Check if the object exists. + BOOL foundObject = NO; + + for (id currentObject in _internalQueue) { + if (currentObject == object) { + foundObject = YES; + break; + } + } + + if (!foundObject) { + [_internalQueue addPointer:(__bridge void *)object]; + + CFRunLoopSourceSignal(_runLoopSource); + CFRunLoopWakeUp(_runLoop); + } +} + +- (BOOL)isEmpty +{ + ASDN::MutexLocker l(_internalQueueLock); + return _internalQueue.count == 0; +} + +- (void)disable +{ + _disableInterfaceStateCoalesce = YES; +} + +- (BOOL)disabled +{ + return _disableInterfaceStateCoalesce; +} + +@end diff --git a/Source/Private/ASDisplayNode+FrameworkPrivate.h b/Source/Private/ASDisplayNode+FrameworkPrivate.h index dbb520787..b309bdcc2 100644 --- a/Source/Private/ASDisplayNode+FrameworkPrivate.h +++ b/Source/Private/ASDisplayNode+FrameworkPrivate.h @@ -141,6 +141,10 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyStateChange(ASHierarc // delegate to inform of ASInterfaceState changes (used by ASNodeController) @property (nonatomic, weak) id interfaceStateDelegate; +// The -pendingInterfaceState holds the value that will be applied to -interfaceState by the +// ASCATransactionQueue. If already applied, it matches -interfaceState. Thread-safe access. +@property (nonatomic, readonly) ASInterfaceState pendingInterfaceState; + // These methods are recursive, and either union or remove the provided interfaceState to all sub-elements. - (void)enterInterfaceState:(ASInterfaceState)interfaceState; - (void)exitInterfaceState:(ASInterfaceState)interfaceState; diff --git a/Source/Private/ASDisplayNodeInternal.h b/Source/Private/ASDisplayNodeInternal.h index 1be9e8c94..545f1c5a2 100644 --- a/Source/Private/ASDisplayNodeInternal.h +++ b/Source/Private/ASDisplayNodeInternal.h @@ -77,7 +77,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo { @package _ASPendingState *_pendingViewState; - + ASInterfaceState _pendingInterfaceState; UIView *_view; CALayer *_layer; diff --git a/Tests/ASCollectionViewTests.mm b/Tests/ASCollectionViewTests.mm index 311d7943f..c3dc8a2c4 100644 --- a/Tests/ASCollectionViewTests.mm +++ b/Tests/ASCollectionViewTests.mm @@ -23,6 +23,8 @@ #import #import #import +#import +#import "ASDisplayNodeTestsHelper.h" @interface ASTextCellNodeWithSetSelectedCounter : ASTextCellNode @@ -860,6 +862,7 @@ - (void)testThatDeletedItemsAreMarkedInvisible [cn waitUntilAllUpdatesAreProcessed]; [cn.view layoutIfNeeded]; ASCellNode *node = [cn nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; + ASCATransactionQueueWait(); XCTAssertTrue(node.visible); testController.asyncDelegate->_itemCounts = {0}; [cn deleteItemsAtIndexPaths: @[[NSIndexPath indexPathForItem:0 inSection:0]]]; @@ -1047,7 +1050,7 @@ - (void)testInitialRangeBounds window.rootViewController = testController; [window makeKeyAndVisible]; - // Trigger the initial reload to start + // Trigger the initial reload to start [window layoutIfNeeded]; // Test the APIs that monitor ASCollectionNode update handling @@ -1071,7 +1074,7 @@ - (void)testInitialRangeBounds for (NSInteger i = 0; i < c; i++) { NSIndexPath *ip = [NSIndexPath indexPathForItem:i inSection:s]; ASCellNode *node = [cn nodeForItemAtIndexPath:ip]; - [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.001]]; + ASCATransactionQueueWait(); if (node.inPreloadState) { CGRect frame = [cn.view layoutAttributesForItemAtIndexPath:ip].frame; r = CGRectUnion(r, frame); diff --git a/Tests/ASDisplayNodeImplicitHierarchyTests.m b/Tests/ASDisplayNodeImplicitHierarchyTests.m index 11e2ce4af..d033ac4f6 100644 --- a/Tests/ASDisplayNodeImplicitHierarchyTests.m +++ b/Tests/ASDisplayNodeImplicitHierarchyTests.m @@ -114,6 +114,7 @@ - (void)testInitialNodeInsertionWhenEnterPreloadState } ASSpecTestDisplayNode *node = [[ASSpecTestDisplayNode alloc] init]; + [node setHierarchyState:ASHierarchyStateRangeManaged]; node.automaticallyManagesSubnodes = YES; node.layoutSpecBlock = ^(ASDisplayNode *weakNode, ASSizeRange constrainedSize) { ASAbsoluteLayoutSpec *absoluteLayout = [ASAbsoluteLayoutSpec absoluteLayoutSpecWithChildren:@[subnodes[3]]]; @@ -130,6 +131,7 @@ - (void)testInitialNodeInsertionWhenEnterPreloadState ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))); [node recursivelySetInterfaceState:ASInterfaceStatePreload]; + ASCATransactionQueueWait(); // No premature view allocation XCTAssertFalse(node.isNodeLoaded); // Subnodes should be inserted, laid out and entered preload state diff --git a/Tests/ASDisplayNodeTests.mm b/Tests/ASDisplayNodeTests.mm index ef5baee05..17bf960e0 100644 --- a/Tests/ASDisplayNodeTests.mm +++ b/Tests/ASDisplayNodeTests.mm @@ -91,7 +91,7 @@ @interface ASDisplayNode (HackForTests) - (id)initWithViewClass:(Class)viewClass; - (id)initWithLayerClass:(Class)layerClass; - +- (void)setInterfaceState:(ASInterfaceState)state; // FIXME: Importing ASDisplayNodeInternal.h causes a heap of problems. - (void)enterInterfaceState:(ASInterfaceState)interfaceState; @end @@ -122,6 +122,12 @@ @interface ASTestResponderNode : ASTestDisplayNode @implementation ASTestDisplayNode +- (void)setInterfaceState:(ASInterfaceState)state +{ + [super setInterfaceState:state]; + ASCATransactionQueueWait(); +} + - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { return _calculateSizeBlock ? _calculateSizeBlock(self, constrainedSize) : CGSizeZero; @@ -2058,9 +2064,9 @@ - (void)testThatNodeGetsRenderedIfItGoesFromZeroSizeToRealSizeButOnlyOnce // Underlying issue for: https://github.com/facebook/AsyncDisplayKit/issues/2205 - (void)testThatRasterizedNodesGetInterfaceStateUpdatesWhenContainerEntersHierarchy { - ASDisplayNode *supernode = [[ASDisplayNode alloc] init]; + ASDisplayNode *supernode = [[ASTestDisplayNode alloc] init]; [supernode enableSubtreeRasterization]; - ASDisplayNode *subnode = [[ASDisplayNode alloc] init]; + ASDisplayNode *subnode = [[ASTestDisplayNode alloc] init]; ASSetDebugNames(supernode, subnode); UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; [supernode addSubnode:subnode]; @@ -2076,9 +2082,9 @@ - (void)testThatRasterizedNodesGetInterfaceStateUpdatesWhenContainerEntersHierar // Underlying issue for: https://github.com/facebook/AsyncDisplayKit/issues/2205 - (void)testThatRasterizedNodesGetInterfaceStateUpdatesWhenAddedToContainerThatIsInHierarchy { - ASDisplayNode *supernode = [[ASDisplayNode alloc] init]; + ASDisplayNode *supernode = [[ASTestDisplayNode alloc] init]; [supernode enableSubtreeRasterization]; - ASDisplayNode *subnode = [[ASDisplayNode alloc] init]; + ASDisplayNode *subnode = [[ASTestDisplayNode alloc] init]; ASSetDebugNames(supernode, subnode); UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; @@ -2192,8 +2198,7 @@ - (void)testThatSubnodeGetsInterfaceStateSetIfRasterized [node view]; // Node needs to be loaded [node enterInterfaceState:ASInterfaceStatePreload]; - - + XCTAssertTrue((node.interfaceState & ASInterfaceStatePreload) == ASInterfaceStatePreload); XCTAssertTrue((subnode.interfaceState & ASInterfaceStatePreload) == ASInterfaceStatePreload); XCTAssertTrue(node.hasPreloaded); diff --git a/Tests/ASDisplayNodeTestsHelper.h b/Tests/ASDisplayNodeTestsHelper.h index 447350004..98b4719c6 100644 --- a/Tests/ASDisplayNodeTestsHelper.h +++ b/Tests/ASDisplayNodeTestsHelper.h @@ -28,5 +28,6 @@ BOOL ASDisplayNodeRunRunLoopUntilBlockIsTrue(as_condition_block_t block); void ASDisplayNodeSizeToFitSize(ASDisplayNode *node, CGSize size); void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange); +void ASCATransactionQueueWait(void); ASDISPLAYNODE_EXTERN_C_END diff --git a/Tests/ASDisplayNodeTestsHelper.m b/Tests/ASDisplayNodeTestsHelper.m index 05397b83b..0a37da1e4 100644 --- a/Tests/ASDisplayNodeTestsHelper.m +++ b/Tests/ASDisplayNodeTestsHelper.m @@ -18,6 +18,7 @@ #import "ASDisplayNodeTestsHelper.h" #import #import +#import #import @@ -62,3 +63,14 @@ void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange) CGSize sizeThatFits = [node layoutThatFits:sizeRange].size; node.bounds = (CGRect){.origin = CGPointZero, .size = sizeThatFits}; } + +void ASCATransactionQueueWait(void) +{ + NSDate *date = [NSDate dateWithTimeIntervalSinceNow:1]; + BOOL whileResult = YES; + while ([date timeIntervalSinceNow] > 0 && + (whileResult = ![[ASCATransactionQueue sharedQueue] isEmpty])) { + [[NSRunLoop currentRunLoop] runUntilDate: + [NSDate dateWithTimeIntervalSinceNow:0.01]]; + } +} diff --git a/Tests/ASRunLoopQueueTests.m b/Tests/ASRunLoopQueueTests.m index ccf15ae7b..2fc9c04b5 100644 --- a/Tests/ASRunLoopQueueTests.m +++ b/Tests/ASRunLoopQueueTests.m @@ -2,8 +2,8 @@ // ASRunLoopQueueTests.m // Texture // -// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0 (the "License"); +// Copyright (c) 2017-present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // @@ -12,9 +12,21 @@ #import #import +#import "ASDisplayNodeTestsHelper.h" static NSTimeInterval const kRunLoopRunTime = 0.001; // Allow the RunLoop to run for one millisecond each time. +@interface QueueObject : NSObject +@property (nonatomic, assign) BOOL queueObjectProcessed; +@end + +@implementation QueueObject +- (void)prepareForCATransactionCommit +{ + self.queueObjectProcessed = YES; +} +@end + @interface ASRunLoopQueueTests : XCTestCase @end @@ -157,4 +169,24 @@ - (void)testWeakQueueWithAllDeallocatedObjectsIsDrained XCTAssertTrue(queue.isEmpty); } +- (void)testASCATransactionQueueDisable +{ + ASCATransactionQueue *queue = [[ASCATransactionQueue alloc] init]; + [queue disable]; + QueueObject *object = [[QueueObject alloc] init]; + [[ASCATransactionQueue sharedQueue] enqueue:object]; + XCTAssertTrue([queue isEmpty]); + XCTAssertTrue([queue disabled]); +} + +- (void)testASCATransactionQueueProcess +{ + ASCATransactionQueue *queue = [[ASCATransactionQueue alloc] init]; + QueueObject *object = [[QueueObject alloc] init]; + [queue enqueue:object]; + XCTAssertFalse(object.queueObjectProcessed); + ASCATransactionQueueWait(); + XCTAssertTrue(object.queueObjectProcessed); +} + @end diff --git a/Tests/ASVideoNodeTests.m b/Tests/ASVideoNodeTests.m index 278509efe..96892b176 100644 --- a/Tests/ASVideoNodeTests.m +++ b/Tests/ASVideoNodeTests.m @@ -21,6 +21,7 @@ #import #import #import +#import "ASDisplayNodeTestsHelper.h" @interface ASVideoNodeTests : XCTestCase { @@ -351,9 +352,9 @@ - (void)testVideoResumedWhenBufferIsLikelyToKeepUp [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStatePreload]; [_videoNode prepareToPlayAsset:assetMock withKeys:_requestedKeys]; + ASCATransactionQueueWait(); [_videoNode pause]; _videoNode.shouldBePlaying = YES; - XCTAssertFalse(_videoNode.isPlaying); [_videoNode observeValueForKeyPath:@"playbackLikelyToKeepUp" ofObject:[_videoNode currentItem] change:@{NSKeyValueChangeNewKey : @YES} context:NULL]; From 236cdd799c8a8d945a52be5947b3d4d1c30a28e6 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 23 Feb 2018 09:10:09 -0800 Subject: [PATCH 090/133] Fix UIResponder handling with view backing ASDisplayNode (#789) * Add failing tests * Fix responder chain handling in Texture * Add mores tests that horrible fail * Add Changelog.md entry * Some fixes * Update logic * Add tests that prevents infinite loops if a custom view is overwriting UIResponder methods * Add macro to forward methods in ASDisplayNode * Add macro for forwarding responder methods in _ASDisplayView * Remove junk * Address first comments * Update _ASDisplayView to cache responder forwarding methods * Use XCTAssertEqual --- CHANGELOG.md | 1 + Source/ASDisplayNode.mm | 93 ++++++++++++++ Source/Details/_ASDisplayView.h | 8 ++ Source/Details/_ASDisplayView.mm | 124 +++++++++++++++++-- Source/Private/ASDisplayNode+UIViewBridge.mm | 29 ++--- Source/Private/ASDisplayNodeInternal.h | 7 ++ Tests/ASDisplayNodeTests.mm | 82 +++++++++++- Tests/ASTableViewTests.mm | 50 +++++++- 8 files changed, 368 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5716f6857..655f186ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ - Make `ASCellNode` tint color apply to table view cell accessories. [Vladyslav Chapaev](https://github.com/ShogunPhyched) [#764](https://github.com/TextureGroup/Texture/pull/764) - Fix ASTextNode2 is accessing backgroundColor off main while sizing / layout is happening. [Michael Schneider](https://github.com/maicki) [#794](https://github.com/TextureGroup/Texture/pull/778/) - Pass scrollViewWillEndDragging delegation through in ASIGListAdapterDataSource for IGListKit integration. [#796](https://github.com/TextureGroup/Texture/pull/796) +- Fix UIResponder handling with view backing ASDisplayNode. [Michael Schneider](https://github.com/maicki) [#789] (https://github.com/TextureGroup/Texture/pull/789/) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 5d6f305d3..1cdedaa44 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -545,6 +545,8 @@ - (UIView *)_locked_viewToLoad // Special handling of wrapping UIKit components if (checkFlag(Synchronous)) { + [self checkResponderCompatibility]; + // UIImageView layers. More details on the flags if ([_viewClass isSubclassOfClass:[UIImageView class]]) { _flags.canClearContentsOfLayer = NO; @@ -828,6 +830,97 @@ - (void)nodeViewDidAddGestureRecognizer _flags.viewEverHadAGestureRecognizerAttached = YES; } +#pragma mark UIResponder + +#define HANDLE_NODE_RESPONDER_METHOD(__sel) \ + /* All responder methods should be called on the main thread */ \ + ASDisplayNodeAssertMainThread(); \ + if (checkFlag(Synchronous)) { \ + /* If the view is not a _ASDisplayView subclass (Synchronous) just call through to the view as we + expect it's a non _ASDisplayView subclass that will respond */ \ + return [_view __sel]; \ + } else { \ + if (ASSubclassOverridesSelector([_ASDisplayView class], _viewClass, @selector(__sel))) { \ + /* If the subclass overwrites canBecomeFirstResponder just call through + to it as we expect it will handle it */ \ + return [_view __sel]; \ + } else { \ + /* Call through to _ASDisplayView's superclass to get it handled */ \ + return [(_ASDisplayView *)_view __##__sel]; \ + } \ + } \ + +- (void)checkResponderCompatibility +{ +#if ASDISPLAYNODE_ASSERTIONS_ENABLED + // There are certain cases we cannot handle and are not supported: + // 1. If the _view class is not a subclass of _ASDisplayView + if (checkFlag(Synchronous)) { + // 2. At least one UIResponder methods are overwritten in the node subclass + NSString *message = @"Overwritting %@ and having a backing view that is not an _ASDisplayView is not supported."; + ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(canBecomeFirstResponder)), ([NSString stringWithFormat:message, @"canBecomeFirstResponder"])); + ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(becomeFirstResponder)), ([NSString stringWithFormat:message, @"becomeFirstResponder"])); + ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(canResignFirstResponder)), ([NSString stringWithFormat:message, @"canResignFirstResponder"])); + ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(resignFirstResponder)), ([NSString stringWithFormat:message, @"resignFirstResponder"])); + ASDisplayNodeAssert(!ASDisplayNodeSubclassOverridesSelector(self.class, @selector(isFirstResponder)), ([NSString stringWithFormat:message, @"isFirstResponder"])); + } +#endif +} + +- (BOOL)__canBecomeFirstResponder +{ + if (_view == nil) { + // By default we return NO if not view is created yet + return NO; + } + + HANDLE_NODE_RESPONDER_METHOD(canBecomeFirstResponder); +} + +- (BOOL)__becomeFirstResponder +{ + if (![self canBecomeFirstResponder]) { + return NO; + } + + // Note: This implicitly loads the view if it hasn't been loaded yet. + [self view]; + + HANDLE_NODE_RESPONDER_METHOD(becomeFirstResponder); +} + +- (BOOL)__canResignFirstResponder +{ + if (_view == nil) { + // By default we return YES if no view is created yet + return YES; + } + + HANDLE_NODE_RESPONDER_METHOD(canResignFirstResponder); +} + +- (BOOL)__resignFirstResponder +{ + if (![self canResignFirstResponder]) { + return NO; + } + + // Note: This implicitly loads the view if it hasn't been loaded yet. + [self view]; + + HANDLE_NODE_RESPONDER_METHOD(resignFirstResponder); +} + +- (BOOL)__isFirstResponder +{ + if (_view == nil) { + // If no view is created yet we can just return NO as it's unlikely it's the first responder + return NO; + } + + HANDLE_NODE_RESPONDER_METHOD(isFirstResponder); +} + #pragma mark - (NSString *)debugName diff --git a/Source/Details/_ASDisplayView.h b/Source/Details/_ASDisplayView.h index 578dd8d58..36f3a00d6 100644 --- a/Source/Details/_ASDisplayView.h +++ b/Source/Details/_ASDisplayView.h @@ -39,6 +39,14 @@ NS_ASSUME_NONNULL_BEGIN - (void)__forwardTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; - (void)__forwardTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event; +// These methods expose a way for ASDisplayNode responder methods to let the view call super responder methods +// They are called from ASDisplayNode to pass through UIResponder methods to the view +- (BOOL)__canBecomeFirstResponder; +- (BOOL)__becomeFirstResponder; +- (BOOL)__canResignFirstResponder; +- (BOOL)__resignFirstResponder; +- (BOOL)__isFirstResponder; + @end NS_ASSUME_NONNULL_END diff --git a/Source/Details/_ASDisplayView.mm b/Source/Details/_ASDisplayView.mm index aa2c43eca..2b72d1fd6 100644 --- a/Source/Details/_ASDisplayView.mm +++ b/Source/Details/_ASDisplayView.mm @@ -23,9 +23,54 @@ #import #import #import +#import #import #import +#pragma mark - _ASDisplayViewMethodOverrides + +typedef NS_OPTIONS(NSUInteger, _ASDisplayViewMethodOverrides) +{ + _ASDisplayViewMethodOverrideNone = 0, + _ASDisplayViewMethodOverrideCanBecomeFirstResponder = 1 << 0, + _ASDisplayViewMethodOverrideBecomeFirstResponder = 1 << 1, + _ASDisplayViewMethodOverrideCanResignFirstResponder = 1 << 2, + _ASDisplayViewMethodOverrideResignFirstResponder = 1 << 3, + _ASDisplayViewMethodOverrideIsFirstResponder = 1 << 4, +}; + +/** + * Returns _ASDisplayViewMethodOverrides for the given class + * + * @param c the class, required. + * + * @return _ASDisplayViewMethodOverrides. + */ +static _ASDisplayViewMethodOverrides GetASDisplayViewMethodOverrides(Class c) +{ + ASDisplayNodeCAssertNotNil(c, @"class is required"); + + _ASDisplayViewMethodOverrides overrides = _ASDisplayViewMethodOverrideNone; + if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(canBecomeFirstResponder))) { + overrides |= _ASDisplayViewMethodOverrideCanBecomeFirstResponder; + } + if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(becomeFirstResponder))) { + overrides |= _ASDisplayViewMethodOverrideBecomeFirstResponder; + } + if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(canResignFirstResponder))) { + overrides |= _ASDisplayViewMethodOverrideCanResignFirstResponder; + } + if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(resignFirstResponder))) { + overrides |= _ASDisplayViewMethodOverrideResignFirstResponder; + } + if (ASSubclassOverridesSelector([_ASDisplayView class], c, @selector(isFirstResponder))) { + overrides |= _ASDisplayViewMethodOverrideIsFirstResponder; + } + return overrides; +} + +#pragma mark - _ASDisplayView + @interface _ASDisplayView () // Keep the node alive while its view is active. If you create a view, add its layer to a layer hierarchy, then release @@ -40,6 +85,21 @@ @implementation _ASDisplayView NSArray *_accessibleElements; CGRect _lastAccessibleElementsFrame; + + _ASDisplayViewMethodOverrides _methodOverrides; +} + +#pragma mark - Class + ++ (void)initialize +{ + __unused Class initializeSelf = self; + IMP staticInitialize = imp_implementationWithBlock(^(_ASDisplayView *view) { + ASDisplayNodeAssert(view.class == initializeSelf, @"View class %@ does not have a matching _staticInitialize method; check to ensure [super initialize] is called within any custom +initialize implementations! Overridden methods will not be called unless they are also implemented by superclass %@", view.class, initializeSelf); + view->_methodOverrides = GetASDisplayViewMethodOverrides(view.class); + }); + + class_replaceMethod(self, @selector(_staticInitialize), staticInitialize, "v:@"); } + (Class)layerClass @@ -49,6 +109,26 @@ + (Class)layerClass #pragma mark - NSObject Overrides +- (instancetype)init +{ + if (!(self = [super init])) + return nil; + + [self _initializeInstance]; + + return self; +} + +- (void)_initializeInstance +{ + [self _staticInitialize]; +} + +- (void)_staticInitialize +{ + ASDisplayNodeAssert(NO, @"_staticInitialize must be overridden"); +} + // e.g. ; frame = ...> - (NSString *)description { @@ -358,15 +438,41 @@ - (void)tintColorDidChange [node tintColorDidChange]; } -- (BOOL)canBecomeFirstResponder { - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node canBecomeFirstResponder]; -} - -- (BOOL)canResignFirstResponder { - ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. - return [node canResignFirstResponder]; -} +#pragma mark UIResponder Handling + +#define IMPLEMENT_RESPONDER_METHOD(__sel, __methodOverride) \ +- (BOOL)__sel\ +{\ + ASDisplayNode *node = _asyncdisplaykit_node; /* Create strong reference to weak ivar. */ \ + SEL sel = @selector(__sel); \ + /* Prevent an infinite loop in here if [super canBecomeFirstResponder] was called on a + / _ASDisplayView subclass */ \ + if (self->_methodOverrides & __methodOverride) { \ + /* Check if we can call through to ASDisplayNode subclass directly */ \ + if (ASDisplayNodeSubclassOverridesSelector([node class], sel)) { \ + return [node __sel]; \ + } else { \ + /* Call through to views superclass as we expect super was called from the + _ASDisplayView subclass and a node subclass does not overwrite canBecomeFirstResponder */ \ + return [self __##__sel]; \ + } \ + } else { \ + /* Call through to internal node __canBecomeFirstResponder that will consider the view in responding */ \ + return [node __##__sel]; \ + } \ +}\ +/* All __ prefixed methods are called from ASDisplayNode to let the view decide in what UIResponder state they \ +are not overridden by a ASDisplayNode subclass */ \ +- (BOOL)__##__sel \ +{ \ + return [super __sel]; \ +} \ + +IMPLEMENT_RESPONDER_METHOD(canBecomeFirstResponder, _ASDisplayViewMethodOverrideCanBecomeFirstResponder); +IMPLEMENT_RESPONDER_METHOD(becomeFirstResponder, _ASDisplayViewMethodOverrideBecomeFirstResponder); +IMPLEMENT_RESPONDER_METHOD(canResignFirstResponder, _ASDisplayViewMethodOverrideCanResignFirstResponder); +IMPLEMENT_RESPONDER_METHOD(resignFirstResponder, _ASDisplayViewMethodOverrideResignFirstResponder); +IMPLEMENT_RESPONDER_METHOD(isFirstResponder, _ASDisplayViewMethodOverrideIsFirstResponder); - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { diff --git a/Source/Private/ASDisplayNode+UIViewBridge.mm b/Source/Private/ASDisplayNode+UIViewBridge.mm index 578caa7f6..13e932ef5 100644 --- a/Source/Private/ASDisplayNode+UIViewBridge.mm +++ b/Source/Private/ASDisplayNode+UIViewBridge.mm @@ -96,16 +96,6 @@ ASDISPLAYNODE_INLINE BOOL ASDisplayNodeShouldApplyBridgedWriteToView(ASDisplayNo */ @implementation ASDisplayNode (UIViewBridge) -- (BOOL)canBecomeFirstResponder -{ - return NO; -} - -- (BOOL)canResignFirstResponder -{ - return YES; -} - #if TARGET_OS_TV // Focus Engine - (BOOL)canBecomeFocused @@ -146,23 +136,34 @@ - (UIView *)preferredFocusedView } #endif +- (BOOL)canBecomeFirstResponder +{ + ASDisplayNodeAssertMainThread(); + return [self __canBecomeFirstResponder]; +} + +- (BOOL)canResignFirstResponder +{ + ASDisplayNodeAssertMainThread(); + return [self __canResignFirstResponder]; +} + - (BOOL)isFirstResponder { ASDisplayNodeAssertMainThread(); - return _view != nil && [_view isFirstResponder]; + return [self __isFirstResponder]; } -// Note: this implicitly loads the view if it hasn't been loaded yet. - (BOOL)becomeFirstResponder { ASDisplayNodeAssertMainThread(); - return !self.layerBacked && [self canBecomeFirstResponder] && [self.view becomeFirstResponder]; + return [self __becomeFirstResponder]; } - (BOOL)resignFirstResponder { ASDisplayNodeAssertMainThread(); - return !self.layerBacked && [self canResignFirstResponder] && [_view resignFirstResponder]; + return [self __resignFirstResponder]; } - (BOOL)canPerformAction:(SEL)action withSender:(id)sender diff --git a/Source/Private/ASDisplayNodeInternal.h b/Source/Private/ASDisplayNodeInternal.h index 545f1c5a2..ef2d460a3 100644 --- a/Source/Private/ASDisplayNodeInternal.h +++ b/Source/Private/ASDisplayNodeInternal.h @@ -275,6 +275,13 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo - (void)__incrementVisibilityNotificationsDisabled; - (void)__decrementVisibilityNotificationsDisabled; +// Helper methods for UIResponder forwarding +- (BOOL)__canBecomeFirstResponder; +- (BOOL)__becomeFirstResponder; +- (BOOL)__canResignFirstResponder; +- (BOOL)__resignFirstResponder; +- (BOOL)__isFirstResponder; + /// Helper method to summarize whether or not the node run through the display process - (BOOL)_implementsDisplay; diff --git a/Tests/ASDisplayNodeTests.mm b/Tests/ASDisplayNodeTests.mm index 17bf960e0..fcd277152 100644 --- a/Tests/ASDisplayNodeTests.mm +++ b/Tests/ASDisplayNodeTests.mm @@ -29,6 +29,7 @@ #import "ASDisplayNodeTestsHelper.h" #import #import +#import #import #import #import @@ -87,6 +88,10 @@ XCTAssertFalse(n.nodeLoaded, @"%@ should not be loaded", n.debugName);\ } +@interface UIWindow (Testing) +// UIWindow has this handy method that is not public but great for testing +- (UIResponder *)firstResponder; +@end @interface ASDisplayNode (HackForTests) - (id)initWithViewClass:(Class)viewClass; @@ -256,6 +261,20 @@ - (BOOL)resignFirstResponder { @end +@interface UIResponderNodeTestDisplayViewCallingSuper : _ASDisplayView +@end +@implementation UIResponderNodeTestDisplayViewCallingSuper +- (BOOL)canBecomeFirstResponder { return YES; } +- (BOOL)becomeFirstResponder { return [super becomeFirstResponder]; } +@end + +@interface UIResponderNodeTestViewCallingSuper : UIView +@end +@implementation UIResponderNodeTestViewCallingSuper +- (BOOL)canBecomeFirstResponder { return YES; } +- (BOOL)becomeFirstResponder { return [super becomeFirstResponder]; } +@end + @interface ASDisplayNodeTests : XCTestCase @end @@ -264,18 +283,77 @@ @implementation ASDisplayNodeTests dispatch_queue_t queue; } -- (void)testOverriddenFirstResponderBehavior { +- (void)testOverriddenNodeFirstResponderBehavior +{ ASTestDisplayNode *node = [[ASTestResponderNode alloc] init]; XCTAssertTrue([node canBecomeFirstResponder]); XCTAssertTrue([node becomeFirstResponder]); } -- (void)testDefaultFirstResponderBehavior { +- (void)testOverriddenDisplayViewFirstResponderBehavior +{ + UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + ASDisplayNode *node = [[ASDisplayNode alloc] initWithViewClass:[UIResponderNodeTestDisplayViewCallingSuper class]]; + + // We have to add the node to a window otherwise the super responder methods call responses are undefined + // This will also create the backing view of the node + [window addSubnode:node]; + [window makeKeyAndVisible]; + + XCTAssertTrue([node canBecomeFirstResponder]); + XCTAssertTrue([node becomeFirstResponder]); +} + +- (void)testOverriddenViewFirstResponderBehavior +{ + UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + ASDisplayNode *node = [[ASDisplayNode alloc] initWithViewClass:[UIResponderNodeTestViewCallingSuper class]]; + + // We have to add the node to a window otherwise the super responder methods call responses are undefined + // This will also create the backing view of the node + [window addSubnode:node]; + [window makeKeyAndVisible]; + + XCTAssertTrue([node canBecomeFirstResponder]); + XCTAssertTrue([node becomeFirstResponder]); +} + +- (void)testDefaultFirstResponderBehavior +{ ASTestDisplayNode *node = [[ASTestDisplayNode alloc] init]; XCTAssertFalse([node canBecomeFirstResponder]); XCTAssertFalse([node becomeFirstResponder]); } +- (void)testResponderMethodsBehavior +{ + UIWindow *window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + ASEditableTextNode *textNode = [[ASEditableTextNode alloc] init]; + + // We have to add the text node to a window otherwise the responder methods responses are undefined + // This will also create the backing view of the node + [window addSubnode:textNode]; + [window makeKeyAndVisible]; + + XCTAssertTrue([textNode canBecomeFirstResponder]); + XCTAssertTrue([textNode becomeFirstResponder]); + XCTAssertTrue([window firstResponder] == textNode.textView); + XCTAssertTrue([textNode resignFirstResponder]); + + // If the textNode resigns it's first responder the view should not be the first responder + XCTAssertTrue([window firstResponder] == nil); + XCTAssertFalse([textNode.view isFirstResponder]); +} + +- (void)testUnsupportedResponderSetupWillThrow +{ + ASTestResponderNode *node = [[ASTestResponderNode alloc] init]; + [node setViewBlock:^UIView * _Nonnull{ + return [[UIView alloc] init]; + }]; + XCTAssertThrows([node view], @"Externally provided views should be synchronous"); +} + - (void)setUp { [super setUp]; diff --git a/Tests/ASTableViewTests.mm b/Tests/ASTableViewTests.mm index 939c910fb..fe54a4895 100644 --- a/Tests/ASTableViewTests.mm +++ b/Tests/ASTableViewTests.mm @@ -139,6 +139,7 @@ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize @interface ASTableViewFilledDataSource : NSObject @property (nonatomic) BOOL usesSectionIndex; +@property (nonatomic) NSInteger numberOfSections; @property (nonatomic) NSInteger rowsPerSection; @property (nonatomic, nullable, copy) ASCellNodeBlock(^nodeBlockForItem)(NSIndexPath *); @end @@ -149,6 +150,7 @@ - (instancetype)init { self = [super init]; if (self != nil) { + _numberOfSections = NumberOfSections; _rowsPerSection = 20; } return self; @@ -165,7 +167,7 @@ - (BOOL)respondsToSelector:(SEL)aSelector - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { - return NumberOfSections; + return _numberOfSections; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section @@ -833,6 +835,52 @@ - (void)testAutomaticallyAdjustingContentOffset XCTAssertEqual(node.contentOffset.y, 10); } +- (void)testTableViewReloadDoesReloadIfEditableTextNodeIsFirstResponder +{ + ASEditableTextNode *editableTextNode = [[ASEditableTextNode alloc] init]; + + UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 375, 667)]; + ASTableNode *node = [[ASTableNode alloc] initWithStyle:UITableViewStyleGrouped]; + node.frame = window.bounds; + [window addSubnode:node]; + + ASTableViewFilledDataSource *dataSource = [ASTableViewFilledDataSource new]; + dataSource.rowsPerSection = 1; + dataSource.numberOfSections = 1; + // Currently this test requires that the text in the cell node fills the + // visible width, so we use the long description for the index path. + dataSource.nodeBlockForItem = ^(NSIndexPath *indexPath) { + return (ASCellNodeBlock)^{ + ASCellNode *cellNode = [[ASCellNode alloc] init]; + cellNode.automaticallyManagesSubnodes = YES; + cellNode.layoutSpecBlock = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { + return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(10, 10, 10, 10) child:editableTextNode]; + }; + return cellNode; + }; + }; + node.delegate = dataSource; + node.dataSource = dataSource; + + // Reload the data for the initial load + [node reloadData]; + [node waitUntilAllUpdatesAreProcessed]; + [node setNeedsLayout]; + [node layoutIfNeeded]; + + // Set the textView as first responder + [editableTextNode.textView becomeFirstResponder]; + + // Change data source count and try to reload a second time + dataSource.rowsPerSection = 2; + [node reloadData]; + [node waitUntilAllUpdatesAreProcessed]; + + // Check that numberOfRows in section 0 is 2 + XCTAssertEqual([node numberOfRowsInSection:0], 2); + XCTAssertEqual([node.view numberOfRowsInSection:0], 2); +} + @end @implementation UITableView (Testing) From 223f1c9a36770a8750b540f0db1b4ec3e2e013f3 Mon Sep 17 00:00:00 2001 From: appleguy Date: Sun, 25 Feb 2018 22:15:40 -0800 Subject: [PATCH 091/133] [ASDisplayNode] Consolidate main thread initialization and allow apps to invoke it manually instead of +load. (#798) * - [ASDisplayNode] Consolidate main thread initialization and allow apps to invoke it manually instead of +load. Additionally this has a few minor fixes for Yoga support, and adds some basic but universally valuable callbacks like -nodeDidLoad to ASNodeController. * Small fix for handling _layoutVersion. * Remove poking the scale accessor --- CHANGELOG.md | 1 + Source/ASDisplayNode+Beta.h | 2 ++ Source/ASDisplayNode+Subclasses.h | 7 +++++++ Source/ASDisplayNode+Yoga.mm | 20 +++++++++++++++++--- Source/ASDisplayNode.mm | 9 +++++++++ Source/ASNodeController+Beta.h | 3 +++ Source/ASNodeController+Beta.m | 1 + Source/AsyncDisplayKit.h | 1 + Source/Private/ASInternalHelpers.h | 9 +++++++++ Source/Private/ASInternalHelpers.m | 22 ++++++++++++++++++++++ Source/Private/_ASPendingState.mm | 17 ++--------------- 11 files changed, 74 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 655f186ce..33abc1772 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASDisplayNode] Consolidate main thread initialization and allow apps to invoke it manually instead of +load. - [ASRunloopQueue] Introduce new runloop queue(ASCATransactionQueue) to coalesce Interface state update calls for view controller transitions. - [ASRangeController] Fix stability of "minimum" rangeMode if the app has more than one layout before scrolling. - **Important** ASDisplayNode's cornerRadius is a new thread-safe bridged property that should be preferred over CALayer's. Use the latter at your own risk! [Huy Nguyen](https://github.com/nguyenhuy) [#749](https://github.com/TextureGroup/Texture/pull/749). diff --git a/Source/ASDisplayNode+Beta.h b/Source/ASDisplayNode+Beta.h index fff4ee975..7a7c7eb6a 100644 --- a/Source/ASDisplayNode+Beta.h +++ b/Source/ASDisplayNode+Beta.h @@ -192,6 +192,8 @@ extern void ASDisplayNodePerformBlockOnEveryYogaChild(ASDisplayNode * _Nullable @interface ASLayoutElementStyle (Yoga) - (YGNodeRef)yogaNodeCreateIfNeeded; +- (void)destroyYogaNode; + @property (nonatomic, assign, readonly) YGNodeRef yogaNode; @property (nonatomic, assign, readwrite) ASStackLayoutDirection flexDirection; diff --git a/Source/ASDisplayNode+Subclasses.h b/Source/ASDisplayNode+Subclasses.h index 622acc299..bdc30338f 100644 --- a/Source/ASDisplayNode+Subclasses.h +++ b/Source/ASDisplayNode+Subclasses.h @@ -103,6 +103,13 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)nodeDidLayout; +/** + * @abstract Called when the node loads. + * @discussion Can be used for operations that are performed after the node's view is available. + * @note This method is guaranteed to be called on main. + */ +- (void)nodeDidLoad; + @end @interface ASDisplayNode (Subclassing) diff --git a/Source/ASDisplayNode+Yoga.mm b/Source/ASDisplayNode+Yoga.mm index b52f94821..eca586b57 100644 --- a/Source/ASDisplayNode+Yoga.mm +++ b/Source/ASDisplayNode+Yoga.mm @@ -157,6 +157,8 @@ - (ASLayout *)layoutForYogaNode - (void)setupYogaCalculatedLayout { + ASDN::MutexLocker l(__instanceLock__); + YGNodeRef yogaNode = self.style.yogaNode; uint32_t childCount = YGNodeGetChildCount(yogaNode); ASDisplayNodeAssert(childCount == self.yogaChildren.count, @@ -210,7 +212,10 @@ - (void)setupYogaCalculatedLayout parentSize.width = YGNodeLayoutGetWidth(parentNode); parentSize.height = YGNodeLayoutGetHeight(parentNode); } - _pendingDisplayNodeLayout = std::make_shared(layout, ASSizeRangeUnconstrained, parentSize, 0); + // For the root node in a Yoga tree, make sure to preserve the constrainedSize originally provided. + // This will be used for all relayouts triggered by children, since they escalate to root. + ASSizeRange range = parentNode ? ASSizeRangeUnconstrained : self.constrainedSizeForCalculatedLayout; + _pendingDisplayNodeLayout = std::make_shared(layout, range, parentSize, _layoutVersion); } } @@ -235,9 +240,18 @@ - (void)updateYogaMeasureFuncIfNeeded - (void)invalidateCalculatedYogaLayout { YGNodeRef yogaNode = self.style.yogaNode; - if (yogaNode && YGNodeGetMeasureFunc(yogaNode)) { + if (yogaNode && [self shouldHaveYogaMeasureFunc]) { // Yoga internally asserts that MarkDirty() may only be called on nodes with a measurement function. + BOOL needsTemporaryMeasureFunc = (YGNodeGetMeasureFunc(yogaNode) == NULL); + if (needsTemporaryMeasureFunc) { + ASDisplayNodeAssert(self.yogaLayoutInProgress == NO, + @"shouldHaveYogaMeasureFunc == YES, and inside a layout pass, but no measure func pointer! %@", self); + YGNodeSetMeasureFunc(yogaNode, &ASLayoutElementYogaMeasureFunc); + } YGNodeMarkDirty(yogaNode); + if (needsTemporaryMeasureFunc) { + YGNodeSetMeasureFunc(yogaNode, NULL); + } } self.yogaCalculatedLayout = nil; } @@ -305,7 +319,7 @@ - (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize NSLog(@"node = %@", node); NSLog(@"style = %@", node.style); NSLog(@"layout = %@", node.yogaCalculatedLayout); - YGNodePrint(node.style.yogaNode, (YGPrintOptions)(YGPrintOptionsStyle | YGPrintOptionsLayout)); + YGNodePrint(node.yogaNode, (YGPrintOptions)(YGPrintOptionsStyle | YGPrintOptionsLayout)); }); } #endif /* YOGA_LAYOUT_LOGGING */ diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 1cdedaa44..716d4a5df 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -226,6 +226,13 @@ + (void)initialize class_replaceMethod(self, @selector(_staticInitialize), staticInitialize, "v:@"); } +#if !AS_INITIALIZE_FRAMEWORK_MANUALLY ++ (void)load +{ + ASInitializeFrameworkMainThread(); +} +#endif + + (Class)viewClass { return [_ASDisplayView class]; @@ -631,6 +638,8 @@ - (void)_didLoad for (ASDisplayNodeDidLoadBlock block in onDidLoadBlocks) { block(self); } + + [_interfaceStateDelegate nodeDidLoad]; } - (void)didLoad diff --git a/Source/ASNodeController+Beta.h b/Source/ASNodeController+Beta.h index b21a86f41..df85f303f 100644 --- a/Source/ASNodeController+Beta.h +++ b/Source/ASNodeController+Beta.h @@ -31,6 +31,9 @@ - (void)loadNode; // for descriptions see definition +- (void)nodeDidLoad ASDISPLAYNODE_REQUIRES_SUPER; +- (void)nodeDidLayout ASDISPLAYNODE_REQUIRES_SUPER; + - (void)didEnterVisibleState ASDISPLAYNODE_REQUIRES_SUPER; - (void)didExitVisibleState ASDISPLAYNODE_REQUIRES_SUPER; diff --git a/Source/ASNodeController+Beta.m b/Source/ASNodeController+Beta.m index bf14df070..1bd873e76 100644 --- a/Source/ASNodeController+Beta.m +++ b/Source/ASNodeController+Beta.m @@ -93,6 +93,7 @@ - (void)setShouldInvertStrongReference:(BOOL)shouldInvertStrongReference } // subclass overrides +- (void)nodeDidLoad {} - (void)nodeDidLayout {} - (void)didEnterVisibleState {} diff --git a/Source/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h index 241d8f2bb..67e1720b6 100644 --- a/Source/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -16,6 +16,7 @@ // #import +#import #import #import #import diff --git a/Source/Private/ASInternalHelpers.h b/Source/Private/ASInternalHelpers.h index dc67700e1..fcd82da0a 100644 --- a/Source/Private/ASInternalHelpers.h +++ b/Source/Private/ASInternalHelpers.h @@ -25,6 +25,11 @@ NS_ASSUME_NONNULL_BEGIN ASDISPLAYNODE_EXTERN_C_BEGIN +void ASInitializeFrameworkMainThread(void); + +BOOL ASDefaultAllowsGroupOpacity(void); +BOOL ASDefaultAllowsEdgeAntialiasing(void); + BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector); BOOL ASSubclassOverridesClassSelector(Class superclass, Class subclass, SEL selector); @@ -100,3 +105,7 @@ ASDISPLAYNODE_INLINE void ASBoundsAndPositionForFrame(CGRect rect, CGPoint origi @end NS_ASSUME_NONNULL_END + +#ifndef AS_INITIALIZE_FRAMEWORK_MANUALLY +#define AS_INITIALIZE_FRAMEWORK_MANUALLY 0 +#endif diff --git a/Source/Private/ASInternalHelpers.m b/Source/Private/ASInternalHelpers.m index dab3d2ae5..478f8694b 100644 --- a/Source/Private/ASInternalHelpers.m +++ b/Source/Private/ASInternalHelpers.m @@ -25,6 +25,28 @@ #import #import +static BOOL defaultAllowsGroupOpacity = YES; +static BOOL defaultAllowsEdgeAntialiasing = NO; + +void ASInitializeFrameworkMainThread(void) +{ + ASDisplayNodeThreadIsMain(); + // Ensure these values are cached on the main thread before needed in the background. + CALayer *layer = [[[UIView alloc] init] layer]; + defaultAllowsGroupOpacity = layer.allowsGroupOpacity; + defaultAllowsEdgeAntialiasing = layer.allowsEdgeAntialiasing; +} + +BOOL ASDefaultAllowsGroupOpacity(void) +{ + return defaultAllowsGroupOpacity; +} + +BOOL ASDefaultAllowsEdgeAntialiasing(void) +{ + return defaultAllowsEdgeAntialiasing; +} + BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector) { if (superclass == subclass) return NO; // Even if the class implements the selector, it doesn't override itself. diff --git a/Source/Private/_ASPendingState.mm b/Source/Private/_ASPendingState.mm index 1a87e496b..a51d56421 100644 --- a/Source/Private/_ASPendingState.mm +++ b/Source/Private/_ASPendingState.mm @@ -212,19 +212,6 @@ ASDISPLAYNODE_INLINE void ASPendingStateApplyMetricsToLayer(_ASPendingState *sta static CGColorRef blackColorRef = NULL; static UIColor *defaultTintColor = nil; -static BOOL defaultAllowsGroupOpacity = YES; -static BOOL defaultAllowsEdgeAntialiasing = NO; - -+ (void)load -{ - // Create temporary view to read default values that are based on linked SDK and Info.plist values - // Ensure this values cached on the main thread before needed - ASDisplayNodeCAssertMainThread(); - UIView *view = [[UIView alloc] init]; - defaultAllowsGroupOpacity = view.layer.allowsGroupOpacity; - defaultAllowsEdgeAntialiasing = view.layer.allowsEdgeAntialiasing; -} - - (instancetype)init { @@ -251,8 +238,8 @@ - (instancetype)init tintColor = defaultTintColor; isHidden = NO; needsDisplayOnBoundsChange = NO; - allowsGroupOpacity = defaultAllowsGroupOpacity; - allowsEdgeAntialiasing = defaultAllowsEdgeAntialiasing; + allowsGroupOpacity = ASDefaultAllowsGroupOpacity(); + allowsEdgeAntialiasing = ASDefaultAllowsEdgeAntialiasing(); autoresizesSubviews = YES; alpha = 1.0f; cornerRadius = 0.0f; From 8817a009c9067bde788287ddbda1408fde5f02dc Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Mon, 26 Feb 2018 13:16:38 -0800 Subject: [PATCH 092/133] Upgrade dangerfile (#810) * Upgrade dangerfile * Allow cocoapods warnings for now :( --- Dangerfile | 2 ++ build.sh | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Dangerfile b/Dangerfile index e9779d610..02759ad2c 100644 --- a/Dangerfile +++ b/Dangerfile @@ -1,3 +1,5 @@ +require 'open-uri' + source_pattern = /(\.m|\.mm|\.h)$/ # Sometimes it's a README fix, or something like that - which isn't relevant for diff --git a/build.sh b/build.sh index bffaad4cc..ec4a62104 100755 --- a/build.sh +++ b/build.sh @@ -195,7 +195,7 @@ fi if [ "$MODE" = "cocoapods-lint" -o "$MODE" = "all" ]; then echo "Verifying that podspec lints." - set -o pipefail && pod env && pod lib lint + set -o pipefail && pod env && pod lib lint --allow-warnings success="1" fi From 6b57b1cf1a31e96e15726e9e85e63fa2e11494b0 Mon Sep 17 00:00:00 2001 From: Karthik M Date: Wed, 28 Feb 2018 10:10:45 +0530 Subject: [PATCH 093/133] [ASDKGram Example] image_url has been changed from URL to Array by 500px. (#813) --- examples/ASDKgram/Sample/PhotoModel.m | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/examples/ASDKgram/Sample/PhotoModel.m b/examples/ASDKgram/Sample/PhotoModel.m index 575a7049a..4c852d596 100644 --- a/examples/ASDKgram/Sample/PhotoModel.m +++ b/examples/ASDKgram/Sample/PhotoModel.m @@ -1,20 +1,18 @@ // // PhotoModel.m -// Sample -// -// Created by Hannah Troisi on 2/26/16. +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// 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. +// http://www.apache.org/licenses/LICENSE-2.0 // #import "PhotoModel.h" @@ -53,7 +51,7 @@ - (instancetype)initWith500pxPhoto:(NSDictionary *)photoDictionary _commentsCount = [[photoDictionary objectForKey:@"comments_count"] integerValue]; _likesCount = [[photoDictionary objectForKey:@"positive_votes_count"] integerValue]; - NSString *urlString = [photoDictionary objectForKey:@"image_url"]; + NSString *urlString = [[photoDictionary objectForKey:@"image_url"] firstObject]; _URL = urlString ? [NSURL URLWithString:urlString] : nil; _location = [[LocationModel alloc] initWith500pxPhoto:photoDictionary]; From a1055254f70c12a55b02a6b445b22180a40332d4 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 28 Feb 2018 12:42:30 -0800 Subject: [PATCH 094/133] Replace pthread specifics with C11 thread-local variables (#811) * Replace pthread specifics with C11 thread-local variables for speed and safety * Increment changelog --- CHANGELOG.md | 1 + Source/ASDisplayNode+Subclasses.h | 2 -- Source/ASDisplayNode.mm | 11 +++-------- Source/Base/ASAssert.m | 21 +++++---------------- Source/Base/ASBaseDefines.h | 9 --------- Source/Layout/ASLayoutElement.mm | 27 ++++++++------------------- 6 files changed, 17 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33abc1772..bcbcd3917 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ - Fix ASTextNode2 is accessing backgroundColor off main while sizing / layout is happening. [Michael Schneider](https://github.com/maicki) [#794](https://github.com/TextureGroup/Texture/pull/778/) - Pass scrollViewWillEndDragging delegation through in ASIGListAdapterDataSource for IGListKit integration. [#796](https://github.com/TextureGroup/Texture/pull/796) - Fix UIResponder handling with view backing ASDisplayNode. [Michael Schneider](https://github.com/maicki) [#789] (https://github.com/TextureGroup/Texture/pull/789/) +- Optimized thread-local storage by replacing pthread_specific with C11 thread-local variables. [Adlai Holler](https://github.com/Adlai-Holler) [#811] (https://github.com/TextureGroup/Texture/pull/811/) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASDisplayNode+Subclasses.h b/Source/ASDisplayNode+Subclasses.h index bdc30338f..3b7b7d0d5 100644 --- a/Source/ASDisplayNode+Subclasses.h +++ b/Source/ASDisplayNode+Subclasses.h @@ -15,8 +15,6 @@ // http://www.apache.org/licenses/LICENSE-2.0 // -#import - #import #import diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 716d4a5df..1128383f5 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -1046,15 +1046,11 @@ - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize restrictedToSize:(ASLayoutElementSize)size relativeToParentSize:(CGSize)parentSize { - // Use a pthread specific to mark when this method is called re-entrant on same thread. // We only want one calculateLayout signpost interval per thread. - // This is fast enough to do it unconditionally. - auto key = ASPthreadStaticKey(NULL); - BOOL isRootCall = (pthread_getspecific(key) == NULL); + static _Thread_local NSInteger tls_callDepth; as_activity_scope_verbose(as_activity_create("Calculate node layout", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT)); as_log_verbose(ASLayoutLog(), "Calculating layout for %@ sizeRange %@", self, NSStringFromASSizeRange(constrainedSize)); - if (isRootCall) { - pthread_setspecific(key, kCFBooleanTrue); + if (tls_callDepth++ == 0) { ASSignpostStart(ASSignpostCalculateLayout); } @@ -1063,8 +1059,7 @@ - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize ASLayout *result = [self calculateLayoutThatFits:resolvedRange]; as_log_verbose(ASLayoutLog(), "Calculated layout %@", result); - if (isRootCall) { - pthread_setspecific(key, NULL); + if (--tls_callDepth == 0) { ASSignpostEnd(ASSignpostCalculateLayout); } return result; diff --git a/Source/Base/ASAssert.m b/Source/Base/ASAssert.m index 7759ff53f..d346dfedb 100644 --- a/Source/Base/ASAssert.m +++ b/Source/Base/ASAssert.m @@ -11,29 +11,18 @@ // #import -#import -static pthread_key_t ASMainThreadAssertionsDisabledKey() -{ - return ASPthreadStaticKey(NULL); -} +static _Thread_local int tls_mainThreadAssertionsDisabledCount; BOOL ASMainThreadAssertionsAreDisabled() { - return (size_t)pthread_getspecific(ASMainThreadAssertionsDisabledKey()) > 0; + return tls_mainThreadAssertionsDisabledCount > 0; } void ASPushMainThreadAssertionsDisabled() { - pthread_key_t key = ASMainThreadAssertionsDisabledKey(); - size_t oldValue = (size_t)pthread_getspecific(key); - pthread_setspecific(key, (void *)(oldValue + 1)); + tls_mainThreadAssertionsDisabledCount += 1; } void ASPopMainThreadAssertionsDisabled() { - pthread_key_t key = ASMainThreadAssertionsDisabledKey(); - size_t oldValue = (size_t)pthread_getspecific(key); - if (oldValue > 0) { - pthread_setspecific(key, (void *)(oldValue - 1)); - } else { - ASDisplayNodeCFailAssert(@"Attempt to pop thread assertion-disabling without corresponding push."); - } + tls_mainThreadAssertionsDisabledCount -= 1; + ASDisplayNodeCAssert(tls_mainThreadAssertionsDisabledCount >= 0, @"Attempt to pop thread assertion-disabling without corresponding push."); } diff --git a/Source/Base/ASBaseDefines.h b/Source/Base/ASBaseDefines.h index 9eb1ec0b0..21b85026d 100755 --- a/Source/Base/ASBaseDefines.h +++ b/Source/Base/ASBaseDefines.h @@ -211,15 +211,6 @@ #define AS_SUBCLASSING_RESTRICTED #endif -#define ASPthreadStaticKey(dtor) ({ \ - static dispatch_once_t onceToken; \ - static pthread_key_t key; \ - dispatch_once(&onceToken, ^{ \ - pthread_key_create(&key, dtor); \ - }); \ - key; \ -}) - #define ASCreateOnce(expr) ({ \ static dispatch_once_t onceToken; \ static __typeof__(expr) staticVar; \ diff --git a/Source/Layout/ASLayoutElement.mm b/Source/Layout/ASLayoutElement.mm index 9fc52b3bf..bf5c61373 100644 --- a/Source/Layout/ASLayoutElement.mm +++ b/Source/Layout/ASLayoutElement.mm @@ -50,38 +50,27 @@ - (instancetype)init int32_t const ASLayoutElementContextInvalidTransitionID = 0; int32_t const ASLayoutElementContextDefaultTransitionID = ASLayoutElementContextInvalidTransitionID + 1; -static void ASLayoutElementDestructor(void *p) { - if (p != NULL) { - ASDisplayNodeCFailAssert(@"Thread exited without clearing layout element context!"); - CFBridgingRelease(p); - } -}; - -pthread_key_t ASLayoutElementContextKey() -{ - return ASPthreadStaticKey(ASLayoutElementDestructor); -} +static _Thread_local __unsafe_unretained ASLayoutElementContext *tls_context; void ASLayoutElementPushContext(ASLayoutElementContext *context) { // NOTE: It would be easy to support nested contexts – just use an NSMutableArray here. - ASDisplayNodeCAssertNil(ASLayoutElementGetCurrentContext(), @"Nested ASLayoutElementContexts aren't supported."); - pthread_setspecific(ASLayoutElementContextKey(), CFBridgingRetain(context)); + ASDisplayNodeCAssertNil(tls_context, @"Nested ASLayoutElementContexts aren't supported."); + + tls_context = (__bridge ASLayoutElementContext *)(__bridge_retained CFTypeRef)context; } ASLayoutElementContext *ASLayoutElementGetCurrentContext() { // Don't retain here. Caller will retain if it wants to! - return (__bridge __unsafe_unretained ASLayoutElementContext *)pthread_getspecific(ASLayoutElementContextKey()); + return tls_context; } void ASLayoutElementPopContext() { - ASLayoutElementContextKey(); - ASDisplayNodeCAssertNotNil(ASLayoutElementGetCurrentContext(), @"Attempt to pop context when there wasn't a context!"); - auto key = ASLayoutElementContextKey(); - CFBridgingRelease(pthread_getspecific(key)); - pthread_setspecific(key, NULL); + ASDisplayNodeCAssertNotNil(tls_context, @"Attempt to pop context when there wasn't a context!"); + CFRelease((__bridge CFTypeRef)tls_context); + tls_context = nil; } #pragma mark - ASLayoutElementStyle From 8b4a7cdab831169ac30bcfd454595ab14e9ff494 Mon Sep 17 00:00:00 2001 From: appleguy Date: Fri, 2 Mar 2018 14:39:42 -0800 Subject: [PATCH 095/133] [ASDisplayNode] Add unit tests for layout z-order changes (with an open issue to fix). (#816) --- CHANGELOG.md | 1 + Tests/ASDisplayNodeTests.mm | 41 +++++++++++++++++++++++++++++++++++++ Tests/ArrayDiffingTests.m | 22 ++++++++++++++++++-- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcbcd3917..60ab5ecd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASDisplayNode] Add unit tests for layout z-order changes (with an open issue to fix). - [ASDisplayNode] Consolidate main thread initialization and allow apps to invoke it manually instead of +load. - [ASRunloopQueue] Introduce new runloop queue(ASCATransactionQueue) to coalesce Interface state update calls for view controller transitions. - [ASRangeController] Fix stability of "minimum" rangeMode if the app has more than one layout before scrolling. diff --git a/Tests/ASDisplayNodeTests.mm b/Tests/ASDisplayNodeTests.mm index fcd277152..1176a709c 100644 --- a/Tests/ASDisplayNodeTests.mm +++ b/Tests/ASDisplayNodeTests.mm @@ -33,6 +33,7 @@ #import #import #import +#import #import #import #import @@ -2374,6 +2375,46 @@ - (void)testThatHavingTheSameNodeTwiceInALayoutSpecCausesExceptionOnLayoutCalcul XCTAssertThrowsSpecificNamed([node calculateLayoutThatFits:ASSizeRangeMake(CGSizeMake(100, 100))], NSException, NSInternalInconsistencyException); } +- (void)testThatStackSpecOrdersSubnodesCorrectly +{ + // This test ensures that the z-order of nodes matches the stack spec, including after relayout / transition. + ASDisplayNode *node = [[ASDisplayNode alloc] init]; + node.automaticallyManagesSubnodes = YES; + + DeclareNodeNamed(a); + DeclareNodeNamed(b); + DeclareNodeNamed(c); + DeclareNodeNamed(d); + + NSArray *nodesForwardOrder = @[a, b, c, d]; + NSArray *nodesReverseOrder = @[d, c, b, a]; + __block BOOL flipItemOrder = NO; + + node.layoutSpecBlock = ^(ASDisplayNode *node, ASSizeRange size) { + ASStackLayoutSpec *stack = [ASStackLayoutSpec verticalStackLayoutSpec]; + stack.children = flipItemOrder ? nodesReverseOrder : nodesForwardOrder; + return stack; + }; + + ASDisplayNodeSizeToFitSize(node, CGSizeMake(100, 100)); + [node.view layoutIfNeeded]; + + // Because automaticallyManagesSubnodes is used, the subnodes array is constructed from the layout spec's children. + XCTAssert([node.subnodes isEqualToArray:nodesForwardOrder], @"subnodes: %@, array: %@", node.subnodes, nodesForwardOrder); + XCTAssertNodeSubnodeSubviewSublayerOrder(node, YES /* isLoaded */, NO /* isLayerBacked */, + @"a,b,c,d", @"Forward order"); + + flipItemOrder = YES; + [node invalidateCalculatedLayout]; + [node.view layoutIfNeeded]; + + // In this case, it's critical that the items are in the new order so that event handling and apparent z-position are correct. + // FIXME: The reversal case is not currently passing. + // XCTAssert([node.subnodes isEqualToArray:nodesReverseOrder], @"subnodes: %@, array: %@", node.subnodes, nodesReverseOrder); + // XCTAssertNodeSubnodeSubviewSublayerOrder(node, YES /* isLoaded */, NO /* isLayerBacked */, + // @"d,c,b,a", @"Reverse order"); +} + - (void)testThatOverlaySpecOrdersSubnodesCorrectly { ASDisplayNode *node = [[ASDisplayNode alloc] init]; diff --git a/Tests/ArrayDiffingTests.m b/Tests/ArrayDiffingTests.m index 854b32a2e..5d25bfde7 100644 --- a/Tests/ArrayDiffingTests.m +++ b/Tests/ArrayDiffingTests.m @@ -65,10 +65,14 @@ - (void)testDiffingCommonIndexes NSIndexSet *indexSet = [test[0] _asdk_commonIndexesWithArray:test[1] compareBlock:^BOOL(id lhs, id rhs) { return [lhs isEqual:rhs]; }]; + NSMutableIndexSet *mutableIndexSet = [indexSet mutableCopy]; for (NSNumber *index in (NSArray *)test[2]) { XCTAssert([indexSet containsIndex:[index integerValue]]); + [mutableIndexSet removeIndex:[index integerValue]]; } + + XCTAssert([mutableIndexSet count] == 0, @"Unaccounted deletions: %@", mutableIndexSet); } } @@ -80,6 +84,12 @@ - (void)testDiffingInsertionsAndDeletions { @[@3], @[], ], + @[ + @[@"a", @"b", @"c", @"d"], + @[@"d", @"c", @"b", @"a"], + @[@1, @2, @3], + @[@0, @1, @2], + ], @[ @[@"bob", @"alice", @"dave"], @[@"bob", @"gary", @"alice", @"dave"], @@ -121,12 +131,20 @@ - (void)testDiffingInsertionsAndDeletions { for (NSArray *test in tests) { NSIndexSet *insertions, *deletions; [test[0] asdk_diffWithArray:test[1] insertions:&insertions deletions:&deletions]; + NSMutableIndexSet *mutableInsertions = [insertions mutableCopy]; + NSMutableIndexSet *mutableDeletions = [deletions mutableCopy]; + for (NSNumber *index in (NSArray *)test[2]) { - XCTAssert([insertions containsIndex:[index integerValue]]); + XCTAssert([mutableInsertions containsIndex:[index integerValue]]); + [mutableInsertions removeIndex:[index integerValue]]; } for (NSNumber *index in (NSArray *)test[3]) { - XCTAssert([deletions containsIndex:[index integerValue]]); + XCTAssert([mutableDeletions containsIndex:[index integerValue]]); + [mutableDeletions removeIndex:[index integerValue]]; } + + XCTAssert([mutableInsertions count] == 0, @"Unaccounted insertions: %@", mutableInsertions); + XCTAssert([mutableDeletions count] == 0, @"Unaccounted deletions: %@", mutableDeletions); } } From 63e1f4e9d9672e10b361c188422b3a8435dd5920 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Thu, 8 Mar 2018 14:00:17 -0800 Subject: [PATCH 096/133] Hopefully made this a bit more readabl. (#823) --- Source/ASNetworkImageNode.mm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index 6cf70fa62..313b134cc 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -192,9 +192,10 @@ - (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset _URL = URL; - BOOL hasURL = (_URL == nil); - if (reset || hasURL) { - [self _locked_setCurrentImageQuality:(hasURL ? 0.0 : 1.0)]; + // If URL is nil and URL was not equal to _URL (checked at the top), then we previously had a URL but it's been nil'd out. + BOOL hadURL = (URL == nil); + if (reset || hadURL) { + [self _locked_setCurrentImageQuality:(hadURL ? 0.0 : 1.0)]; [self _locked__setImage:_defaultImage]; } } From d70ab3e43c1c6975b3c458289c3124b8d3e05f3b Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Thu, 8 Mar 2018 16:19:49 -0800 Subject: [PATCH 097/133] [#trivial] I don't think we need this extra locked method. (#824) --- Source/ASNetworkImageNode.mm | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index 313b134cc..4c600f061 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -195,7 +195,7 @@ - (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset // If URL is nil and URL was not equal to _URL (checked at the top), then we previously had a URL but it's been nil'd out. BOOL hadURL = (URL == nil); if (reset || hadURL) { - [self _locked_setCurrentImageQuality:(hadURL ? 0.0 : 1.0)]; + [self _setCurrentImageQuality:(hadURL ? 0.0 : 1.0)]; [self _locked__setImage:_defaultImage]; } } @@ -225,7 +225,7 @@ - (void)_locked_setDefaultImage:(UIImage *)defaultImage _defaultImage = defaultImage; if (!_imageLoaded) { - [self _locked_setCurrentImageQuality:((_URL == nil) ? 0.0 : 1.0)]; + [self _setCurrentImageQuality:((_URL == nil) ? 0.0 : 1.0)]; [self _locked__setImage:defaultImage]; } @@ -250,24 +250,18 @@ - (CGFloat)currentImageQuality } /** - * Always use this methods internally to update the current image quality + * Always use these methods internally to update the current image quality * We want to maintain the order that currentImageQuality is set regardless of the calling thread, - * so we always have to dispatch to the main threadto ensure that we queue the operations in the correct order. + * so we always have to dispatch to the main thread to ensure that we queue the operations in the correct order. * (see comment in displayDidFinish) */ - (void)_setCurrentImageQuality:(CGFloat)imageQuality -{ - ASDN::MutexLocker l(__instanceLock__); - [self _locked_setCurrentImageQuality:imageQuality]; -} - -- (void)_locked_setCurrentImageQuality:(CGFloat)imageQuality { dispatch_async(dispatch_get_main_queue(), ^{ // As the setting of the image quality is dispatched the lock is gone by the time the block is executing. // Therefore we have to grab the lock again __instanceLock__.lock(); - _currentImageQuality = imageQuality; + _currentImageQuality = imageQuality; __instanceLock__.unlock(); }); } @@ -340,7 +334,7 @@ - (void)displayWillStartAsynchronously:(BOOL)asynchronously if (_imageLoaded == NO && url && _downloadIdentifier == nil) { UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:url] asdk_image]; if (result) { - [self _locked_setCurrentImageQuality:1.0]; + [self _setCurrentImageQuality:1.0]; [self _locked__setImage:result]; _imageLoaded = YES; @@ -443,7 +437,7 @@ - (void)handleProgressImage:(UIImage *)progressImage progress:(CGFloat)progress } as_log_verbose(ASImageLoadingLog(), "Received progress image for %@ q: %.2g id: %@", self, progress, progressImage); - [self _locked_setCurrentImageQuality:progress]; + [self _setCurrentImageQuality:progress]; [self _locked__setImage:progressImage]; } @@ -516,7 +510,7 @@ - (void)_locked_cancelDownloadAndClearImageWithResumePossibility:(BOOL)storeResu [self _locked_cancelImageDownloadWithResumePossibility:storeResume]; [self _locked_setAnimatedImage:nil]; - [self _locked_setCurrentImageQuality:0.0]; + [self _setCurrentImageQuality:0.0]; [self _locked__setImage:_defaultImage]; _imageLoaded = NO; @@ -667,7 +661,7 @@ - (void)_lazilyLoadImageIfNecessary _imageLoaded = YES; - [self _locked_setCurrentImageQuality:1.0]; + [self _setCurrentImageQuality:1.0]; if (_delegateFlags.delegateDidLoadImageWithInfo) { ASDN::MutexUnlocker u(__instanceLock__); @@ -706,7 +700,7 @@ - (void)_lazilyLoadImageIfNecessary UIImage *newImage; if (imageContainer != nil) { - [strongSelf _locked_setCurrentImageQuality:1.0]; + [strongSelf _setCurrentImageQuality:1.0]; NSData *animatedImageData = [imageContainer asdk_animatedImageData]; if (animatedImageData && strongSelf->_downloaderFlags.downloaderImplementsAnimatedImage) { id animatedImage = [strongSelf->_downloader animatedImageWithData:animatedImageData]; From f99dd68a9f2d27f620fe69e79c9c324865ce1805 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Thu, 8 Mar 2018 16:21:54 -0800 Subject: [PATCH 098/133] [#trivial] fixes rendered image quality on networked image nodes which have their image directly set. (#826) --- Source/ASNetworkImageNode.h | 10 ++++++++-- Source/ASNetworkImageNode.mm | 6 +++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Source/ASNetworkImageNode.h b/Source/ASNetworkImageNode.h index 4100b5220..ec97e8469 100644 --- a/Source/ASNetworkImageNode.h +++ b/Source/ASNetworkImageNode.h @@ -120,13 +120,19 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign, readwrite) BOOL shouldRenderProgressImages; /** - * The image quality of the current image. This is a number between 0 and 1 and can be used to track + * The image quality of the current image. + * + * If the URL is set, this is a number between 0 and 1 and can be used to track * progressive progress. Calculated by dividing number of bytes / expected number of total bytes. + * This is zero until the first progressive render or the final display. + * + * If the URL is unset, this is 1 if defaultImage or image is set to non-nil. + * */ @property (nonatomic, assign, readonly) CGFloat currentImageQuality; /** - * The image quality (value between 0 and 1) of the last image that completed displaying. + * The currentImageQuality (value between 0 and 1) of the last image that completed displaying. */ @property (nonatomic, assign, readonly) CGFloat renderedImageQuality; diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index 4c600f061..f7c430a46 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -141,6 +141,11 @@ - (void)_locked_setImage:(UIImage *)image [self _locked_cancelDownloadAndClearImageWithResumePossibility:NO]; } + // If our image is being set externally, the image quality is 100% + if (imageWasSetExternally) { + [self _setCurrentImageQuality:1.0]; + } + [self _locked__setImage:image]; } @@ -227,7 +232,6 @@ - (void)_locked_setDefaultImage:(UIImage *)defaultImage if (!_imageLoaded) { [self _setCurrentImageQuality:((_URL == nil) ? 0.0 : 1.0)]; [self _locked__setImage:defaultImage]; - } } From 6f1d2d1f1d511217ff5f69f779166a9fc6c0c1d9 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 9 Mar 2018 14:23:27 -0800 Subject: [PATCH 099/133] [ASTextNode] Fix locking, add test for issue #trivial (#825) * A few small fixes plus a failing test for ASTextNode * Change the approach * Update changelog * Eh screw null_resettable * No need for changelog now --- Source/ASTextNode.mm | 7 +++++++ Tests/ASTextNodeSnapshotTests.m | 17 +++++++++++++++++ ...nheritedFromTextWhenTruncateTailMode@2x.png | Bin 0 -> 2396 bytes 3 files changed, 24 insertions(+) create mode 100644 Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testThatTruncationTokenAttributesPrecedeThoseInheritedFromTextWhenTruncateTailMode@2x.png diff --git a/Source/ASTextNode.mm b/Source/ASTextNode.mm index e2a594b05..522ebabed 100644 --- a/Source/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -177,6 +177,7 @@ @implementation ASTextNode { NSArray *_exclusionPaths; NSAttributedString *_attributedText; + NSAttributedString *_truncationAttributedText; NSAttributedString *_composedTruncationText; NSString *_highlightedLinkAttributeName; @@ -1189,6 +1190,12 @@ - (UIEdgeInsets)shadowPadding return defaultTruncationAttributedString; } +- (NSAttributedString *)truncationAttributedText +{ + ASDN::MutexLocker l(__instanceLock__); + return _truncationAttributedText; +} + - (void)setTruncationAttributedText:(NSAttributedString *)truncationAttributedText { { diff --git a/Tests/ASTextNodeSnapshotTests.m b/Tests/ASTextNodeSnapshotTests.m index 0d86a7274..c81f5241a 100644 --- a/Tests/ASTextNodeSnapshotTests.m +++ b/Tests/ASTextNodeSnapshotTests.m @@ -120,4 +120,21 @@ - (void)testShadowing ASSnapshotVerifyNode(textNode, nil); } +/** + * https://github.com/TextureGroup/Texture/issues/822 + */ +- (void)DISABLED_testThatTruncationTokenAttributesPrecedeThoseInheritedFromTextWhenTruncateTailMode +{ + ASTextNode *textNode = [[ASTextNode alloc] init]; + textNode.style.maxSize = CGSizeMake(20, 80); + NSMutableAttributedString *mas = [[NSMutableAttributedString alloc] initWithString:@"Quality is an important "]; + [mas appendAttributedString:[[NSAttributedString alloc] initWithString:@"thing" attributes:@{ NSBackgroundColorAttributeName : UIColor.yellowColor}]]; + textNode.attributedText = mas; + textNode.truncationMode = NSLineBreakByTruncatingTail; + + textNode.truncationAttributedText = [[NSAttributedString alloc] initWithString:@"\u2026" attributes:@{ NSBackgroundColorAttributeName: UIColor.greenColor }]; + ASDisplayNodeSizeToFitSizeRange(textNode, ASSizeRangeMake(CGSizeZero, CGSizeMake(INFINITY, INFINITY))); + ASSnapshotVerifyNode(textNode, nil); +} + @end diff --git a/Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testThatTruncationTokenAttributesPrecedeThoseInheritedFromTextWhenTruncateTailMode@2x.png b/Tests/ReferenceImages_iOS_10/ASTextNodeSnapshotTests/testThatTruncationTokenAttributesPrecedeThoseInheritedFromTextWhenTruncateTailMode@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..40351ad62b1b5757f6367d86c7d3a20fa42f676d GIT binary patch literal 2396 zcmV-i38VIjP)z1^@s6*eVE!00001b5ch_0Itp) z=>Px#32;bRa{vGqB>(^xB>_oNB=7(L032yVPgDQ^00IC20000007U=*04M+e07U=* z07U=*1n#vhrDp&D1iDE?K~#7F?3rtbRaF>+l_?7F-s(vw*G&{4o`} zGZgBi`9_L+DmZst;M7#a#Mg#GoitxfvCjtQt_w8x52?_tp-?By&r{rr;5;D$-%5oZ z4Tah@_rl@#6nA5A9!sEKUwORJB6%_eei#b1YhDzJof@38fU-q$bcmS|V%jzrhQP~O zc%MlBYv3lr+4~3-vjw7Mfqt16?E*UTcqM@*dOHPg<#?DK1zW?p$-jpOQrq((i1rO9 z_Fot~Q~T~T=6Gd+3kdovB~V1MfcMXXY1{%5S1+fd_9A~B^8-bj&&den*vbM;<|W80 z+C)2G6FdfMVH@NE9@oJ%h;{=f*~hE$5V#2aN%VLATX1}j1QOSGIJ^w;)8>L6KZD!h z3-H{%V{t*;Mqe@hCHl<$6^u(D@uRj6e@}pG;1;+X;{IobBHqYPIXB?oZY$1r`3X*zfi%nva>%A3G)791+A zov*8}%zWHb;F2)dQ-PuG=*{b87y>VEenC{bK%YMzhI}t7NVdba)UIs?7bXD;`T&-L z4@rvlW_}JFy9u)O^b-3{Mgcy#cGAhUf znLla22}i*k*aD7u)T#=+l9)V;=9y`dsy3H^tKfW?UI^~z{4|(V=&y1*4d;1qWR;-C zx0!)v2AUaYX5fD|1C!?6Osjb>K6bY9tF3?13oLs#Q`>Cyr0~^LaVNcajB3{F_<&CTpjppQ>_?X2t`+?$}h zqc%_3dms0krFHoiIw;upL6^dIF#jTGX=@qL z>FE-W)oJYUlCiR`GJ$#ahDrkNO8JLN#`^vN00960r91p8000CNNkl4B z_n8>C8pTO?IWC!)rg%e0JjluA!AvxXQYP0zlDr&J6s6=EAs(h7qvmCJAfq@g=Rrw$ z5Kg(3`!&~N`+WCVYy0oD*S7zEd;gDr{C~U8UTgi+-+sOs3T)l4Rj2bZB+u zzT5Nt7*sbl=PCd7+_WDOXxeCehe#DOXzeI;h0Z~zn^&j%AR!WKt)7g0x8MisER*^K z&ZCdw2#vE1eur)l+P2VA0rm!Z>@mLe0bjYSPoVlNfO}wtDt!;9U^V_+?ug%+@a8_1WntF%xDE$w)dzepOzJ}1zPQ;XvOXv}9&b{nlS zjdzpoGWlhsQOp%sN9QNd44OgLdf@l~64~9Ybjt%w!-u2~KzpPYL@`&O`q~5dnWR#&)eejPM^SqCf+;J?JyhWz+a%H)mSQrG8;^triAKI*vKbPdut`U z0>up?fswY6JM;cr+19ieJAfk=9w)Gbfwfx+gLN=(p}QjmY6A=uXD4^yYR zRK=#3<`0*Cq>cPlC_|3m=2 zT+ae6?@7==r3C6g`D`Xm^*QaU{1Tu3J=g(uFIrK0{k#pO1nR}xXEShCO`t9nR?vC! zYhfx#v|hFkg6bE6HM_*Rr1-3=0xvLv1Z?$rr05lZ6u%-%1%87w z*pn?W6m(0|4K4mpZlLgA_P+XWu1oy#A|H~(3#?`4^hQ^g)EUg_%6}zriS-!O0Ml|J z6DTn8doUGPbY;zN_;8tY2GbcVnt@6$Md7wyzE!#hi&h{%H{mk=8N5N*1%~FLA+nik zfqJ!dKi32GZvSw)@~!*=&oDsi-yPY^xuW5-{4w&W{;a3|bfCV;sm~7`E|YkHg+Iex zsyNDpBStL6A;^sxQBq-ZH&`6y!Vx2u;t=G9UgMW^7r`w O0000 Date: Fri, 9 Mar 2018 22:23:41 +0000 Subject: [PATCH 100/133] ASTextNode should avoid acquiring its lock multiple times #trivial (#820) Since the lock was acquire before we try to access the renderer, we should call `-_locked_renderer` to avoid re-acquiring the same (recursive) lock. --- Source/ASTextNode.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ASTextNode.mm b/Source/ASTextNode.mm index 522ebabed..ccbed9c07 100644 --- a/Source/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -769,7 +769,7 @@ - (void)_setHighlightRange:(NSRange)highlightRange forAttributeName:(NSString *) if (highlightTargetLayer != nil) { ASDN::MutexLocker l(__instanceLock__); - ASTextKitRenderer *renderer = [self _renderer]; + ASTextKitRenderer *renderer = [self _locked_renderer]; NSArray *highlightRects = [renderer rectsForTextRange:highlightRange measureOption:ASTextKitRendererMeasureOptionBlock]; NSMutableArray *converted = [NSMutableArray arrayWithCapacity:highlightRects.count]; From d9d9a2936536be97840d7a484fa8f2f060017afd Mon Sep 17 00:00:00 2001 From: Alex Hill Date: Sun, 11 Mar 2018 16:37:27 -0700 Subject: [PATCH 101/133] [tvOS] Fixes errors when building against tvOS SDK (#728) * [tvOS] Fixes errors when building against tvOS SDK * Update CHANGELOG.md * [tvOS] Fixes implicit conversion between UIViewAnimationCurve + UIViewAnimationOptions * Enable tvOS deployment target in Texture.podspec (for CI) * [ASMultiplexImageNode] Fixes typo * [tvOS] Fixes warnings related to @available guards in Xcode 9 [ASMultiplexImageNode] Enables support for Photos framework on tvOS 10+ [ASMultiplexImageNode] Fixes comment depth [ASAvailability] Adjust logic in AS_AVAILABLE_IOS_TVOS to account for both versions Adjusts API_AVAILABLE to minimum deployment target * [ASAvailability] Update AS_AVAILABLE_XXX fallbacks to function more like the built-in solution (more accurately target OS by checking target) Change AS_AVAILABLE_IOS -> AS_AVAILABLE_IOS_TVOS in places that shoud allow for both [ASAvailability] Simplify AS_AVAILABLE_IOS_TVOS * [ASControlNode] Adds missing 'super' call in -[ASControlNode didLoad] when targeting tvOS * Fix API_AVAILABLE iOS requirement * [ASDisplayNode] Fixes last of the linker warnings related to category overrides. Removes methods already implemented in ASDisplayNode+UIViewBridge category. [ASControlNode] Moves tvOS category declaration to ASControlNode header [ASImageNode] Moves tvOS category declaration to ASImageNode header [ASControlNode+Private] Adds private category for ASControlNode to access private selectors * [NSParagraphStyle+ASText] Fixes typo related to testing * [ASControlNode] Re-add helpful comment * [ASTextKitCoreTextAdditions] Adds mappings for kCTParagraphStyleSpecifierMinimumLineSpacing, kCTParagraphStyleSpecifierMaximumLineSpacing, kCTParagraphStyleSpecifierLineSpacingAdjustment when mapping CTParagraphStyle onto NSParagraphStyle [ASTextNode] Uses CoreText-cleansed attributed string when assigning ascender/descender to avoid crash when a CTParagraphStyle is passed as an attribute * [AsyncDisplayKit] Update project file to include new/deleted files * [ASControlNode+tvOS] Add missing Foundation import (whoops!) [ASImageNode+tvOS] Add missing Foundation import (whoops!) * Update podspec to only link AssetsLibrary framework on iOS * [ASTextKitCoreTextAdditions] If kCTParagraphStyleAttributeName key-value evaluates to an NSParagraphStyle, pass through to cleansed attributes. This fixes a bug that would occur if a CTParagraphStyle was passed as an attribute _alone_ (would not be caught by unsupported attributes check) * [ASMultiplexImageNode] Bump availability check to support < Xcode 9 * [ASTraitCollection] Fixes typo that was causing build to fail * Clean up formatting to adhere to character/line limit + braces --- AsyncDisplayKit.xcodeproj/project.pbxproj | 12 +- CHANGELOG.md | 1 + Source/ASCollectionView.mm | 2 +- Source/ASControlNode.h | 7 +- Source/ASControlNode.mm | 5 +- Source/ASDisplayNode.mm | 34 ----- Source/ASImageNode+AnimatedImage.mm | 2 +- Source/ASImageNode.h | 5 + Source/ASMultiplexImageNode.h | 14 +- Source/ASMultiplexImageNode.mm | 100 +++++++------- Source/ASTableView.mm | 7 + Source/ASTextNode.mm | 8 +- Source/Base/ASAvailability.h | 8 +- Source/Base/ASLog.h | 10 +- .../Details/ASPhotosFrameworkImageRequest.h | 1 + Source/Details/ASTraitCollection.m | 2 +- Source/Details/_ASDisplayViewAccessiblity.mm | 4 +- .../ASControlNode+Private.h} | 10 +- Source/Private/ASDisplayNode+UIViewBridge.mm | 6 +- .../TextExperiment/String/ASTextAttribute.m | 2 + .../Utility/NSAttributedString+ASText.h | 2 +- .../Utility/NSAttributedString+ASText.m | 2 + .../Utility/NSParagraphStyle+ASText.m | 4 + Source/Private/_ASPendingState.mm | 2 +- Source/TextKit/ASTextKitCoreTextAdditions.m | 125 +++++++++++++++--- Source/tvOS/ASControlNode+tvOS.m | 10 +- Source/tvOS/ASImageNode+tvOS.h | 24 ---- Source/tvOS/ASImageNode+tvOS.m | 3 +- Texture.podspec | 8 +- 29 files changed, 242 insertions(+), 178 deletions(-) rename Source/{tvOS/ASControlNode+tvOS.h => Private/ASControlNode+Private.h} (89%) delete mode 100644 Source/tvOS/ASImageNode+tvOS.h diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 19771c9ea..06db270dc 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -134,9 +134,7 @@ 690C35621E055C5D00069B91 /* ASDimensionInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = 690C35601E055C5D00069B91 /* ASDimensionInternal.mm */; }; 690C35641E055C7B00069B91 /* ASDimensionInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 690C35631E055C7B00069B91 /* ASDimensionInternal.h */; settings = {ATTRIBUTES = (Public, ); }; }; 690ED58E1E36BCA6000627C0 /* ASLayoutElementStylePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 690ED58D1E36BCA6000627C0 /* ASLayoutElementStylePrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; - 690ED5961E36D118000627C0 /* ASControlNode+tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 690ED5921E36D118000627C0 /* ASControlNode+tvOS.h */; settings = {ATTRIBUTES = (Private, ); }; }; 690ED5981E36D118000627C0 /* ASControlNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 690ED5931E36D118000627C0 /* ASControlNode+tvOS.m */; }; - 690ED5991E36D118000627C0 /* ASImageNode+tvOS.h in Headers */ = {isa = PBXBuildFile; fileRef = 690ED5941E36D118000627C0 /* ASImageNode+tvOS.h */; settings = {ATTRIBUTES = (Private, ); }; }; 690ED59B1E36D118000627C0 /* ASImageNode+tvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 690ED5951E36D118000627C0 /* ASImageNode+tvOS.m */; }; 692510141E74FB44003F2DD0 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 692510131E74FB44003F2DD0 /* Default-568h@2x.png */; }; 692BE8D71E36B65B00C86D87 /* ASLayoutSpecPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 692BE8D61E36B65B00C86D87 /* ASLayoutSpecPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -475,6 +473,7 @@ E5E2D72E1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = E5E2D72D1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; E5E2D7301EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5E2D72F1EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm */; }; F711994E1D20C21100568860 /* ASDisplayNodeExtrasTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.m */; }; + FA4FAF15200A850200E735BD /* ASControlNode+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = FA4FAF14200A850200E735BD /* ASControlNode+Private.h */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -670,9 +669,7 @@ 690C35601E055C5D00069B91 /* ASDimensionInternal.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDimensionInternal.mm; sourceTree = ""; }; 690C35631E055C7B00069B91 /* ASDimensionInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDimensionInternal.h; sourceTree = ""; }; 690ED58D1E36BCA6000627C0 /* ASLayoutElementStylePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutElementStylePrivate.h; sourceTree = ""; }; - 690ED5921E36D118000627C0 /* ASControlNode+tvOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASControlNode+tvOS.h"; sourceTree = ""; }; 690ED5931E36D118000627C0 /* ASControlNode+tvOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASControlNode+tvOS.m"; sourceTree = ""; }; - 690ED5941E36D118000627C0 /* ASImageNode+tvOS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASImageNode+tvOS.h"; sourceTree = ""; }; 690ED5951E36D118000627C0 /* ASImageNode+tvOS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASImageNode+tvOS.m"; sourceTree = ""; }; 692510131E74FB44003F2DD0 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 692BE8D61E36B65B00C86D87 /* ASLayoutSpecPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutSpecPrivate.h; sourceTree = ""; }; @@ -982,6 +979,7 @@ E5E2D72F1EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASCollectionGalleryLayoutDelegate.mm; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeExtrasTests.m; sourceTree = ""; }; + FA4FAF14200A850200E735BD /* ASControlNode+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASControlNode+Private.h"; sourceTree = ""; }; FB07EABBCF28656C6297BC2D /* Pods-AsyncDisplayKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -1424,6 +1422,7 @@ CC2E317F1DAC353700EEE891 /* ASCollectionView+Undeprecated.h */, CC0F885A1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.h */, CC0F88591E42807F00576FED /* ASCollectionViewFlowLayoutInspector.m */, + FA4FAF14200A850200E735BD /* ASControlNode+Private.h */, 9F98C0231DBDF2A300476D92 /* ASControlTargetAction.h */, 9F98C0241DBDF2A300476D92 /* ASControlTargetAction.m */, 8B0768B11CE752EC002E1453 /* ASDefaultPlaybackButton.h */, @@ -1531,9 +1530,7 @@ 690ED5911E36D118000627C0 /* tvOS */ = { isa = PBXGroup; children = ( - 690ED5921E36D118000627C0 /* ASControlNode+tvOS.h */, 690ED5931E36D118000627C0 /* ASControlNode+tvOS.m */, - 690ED5941E36D118000627C0 /* ASImageNode+tvOS.h */, 690ED5951E36D118000627C0 /* ASImageNode+tvOS.m */, ); path = tvOS; @@ -1887,6 +1884,7 @@ 9C70F20F1CDBE9FF007D6C76 /* ASLayoutManager.h in Headers */, 6947B0C31E36B5040007C478 /* ASStackPositionedLayout.h in Headers */, DBABFAFC1C6A8D2F0039EA4A /* _ASTransitionContext.h in Headers */, + FA4FAF15200A850200E735BD /* ASControlNode+Private.h in Headers */, B350624F1B010EFD0018CF92 /* ASDisplayNode+DebugTiming.h in Headers */, CC57EAF71E3939350034C595 /* ASCollectionView+Undeprecated.h in Headers */, B35062521B010EFD0018CF92 /* ASDisplayNodeInternal.h in Headers */, @@ -1902,14 +1900,12 @@ DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */, 68B8A4E21CBDB958007E4543 /* ASWeakProxy.h in Headers */, 9F98C0271DBE29FC00476D92 /* ASControlTargetAction.h in Headers */, - 690ED5961E36D118000627C0 /* ASControlNode+tvOS.h in Headers */, 695943401D70815300B0EE1F /* ASDisplayNodeLayout.h in Headers */, 0442850E1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.h in Headers */, DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */, B350623E1B010EFD0018CF92 /* _ASAsyncTransactionContainer+Private.h in Headers */, AC6145411D8AFAE8003D62A2 /* ASSection.h in Headers */, 8BBBAB8C1CEBAF1700107FC6 /* ASDefaultPlaybackButton.h in Headers */, - 690ED5991E36D118000627C0 /* ASImageNode+tvOS.h in Headers */, 254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */, 698DFF441E36B6C9002891F1 /* ASStackLayoutSpecUtilities.h in Headers */, CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 60ab5ecd9..466623147 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [tvOS] Fixes errors when building against tvOS SDK [Alex Hill](https://github.com/alexhillc) [#728](https://github.com/TextureGroup/Texture/pull/728) - [ASDisplayNode] Add unit tests for layout z-order changes (with an open issue to fix). - [ASDisplayNode] Consolidate main thread initialization and allow apps to invoke it manually instead of +load. - [ASRunloopQueue] Introduce new runloop queue(ASCATransactionQueue) to coalesce Interface state update calls for view controller transitions. diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 10dd6dc9a..451322564 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -275,7 +275,7 @@ - (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionV // Experiments done by Instagram show that this option being YES (default) // when unused causes a significant hit to scroll performance. // https://github.com/Instagram/IGListKit/issues/318 - if (AS_AVAILABLE_IOS(10)) { + if (AS_AVAILABLE_IOS_TVOS(10, 10)) { super.prefetchingEnabled = NO; } diff --git a/Source/ASControlNode.h b/Source/ASControlNode.h index e08f0650f..7896b93eb 100644 --- a/Source/ASControlNode.h +++ b/Source/ASControlNode.h @@ -141,12 +141,17 @@ static UIControlState const ASControlStateSelected ASDISPLAYNODE_DEPRECATED_MSG( @param event The event which triggered these control actions. May be nil. */ - (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent:(nullable UIEvent *)event; +@end + #if TARGET_OS_TV +@interface ASControlNode (tvOS) + /** @abstract How the node looks when it isn't focused. Exposed here so that subclasses can override. */ - (void)setDefaultFocusAppearance; -#endif + @end +#endif NS_ASSUME_NONNULL_END diff --git a/Source/ASControlNode.mm b/Source/ASControlNode.mm index fdd1155e7..702161721 100644 --- a/Source/ASControlNode.mm +++ b/Source/ASControlNode.mm @@ -16,6 +16,7 @@ // #import +#import #import #import #import @@ -103,10 +104,12 @@ - (instancetype)init #if TARGET_OS_TV - (void)didLoad { + [super didLoad]; + // On tvOS all controls, such as buttons, interact with the focus system even if they don't have a target set on them. // Here we add our own internal tap gesture to handle this behaviour. self.userInteractionEnabled = YES; - UITapGestureRecognizer *tapGestureRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(pressDown)]; + UITapGestureRecognizer *tapGestureRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_pressDown)]; tapGestureRec.allowedPressTypes = @[@(UIPressTypeSelect)]; [self.view addGestureRecognizer:tapGestureRec]; } diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 1128383f5..26b8f91d8 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -3601,40 +3601,6 @@ - (void)asyncTraitCollectionDidChange { // Subclass override } - -#if TARGET_OS_TV -#pragma mark - UIFocusEnvironment Protocol (tvOS) - -- (void)setNeedsFocusUpdate -{ - -} - -- (void)updateFocusIfNeeded -{ - -} - -- (BOOL)shouldUpdateFocusInContext:(UIFocusUpdateContext *)context -{ - return NO; -} - -- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator -{ - -} - -- (UIView *)preferredFocusedView -{ - if (self.nodeLoaded) { - return self.view; - } else { - return nil; - } -} -#endif - @end #pragma mark - ASDisplayNode (Debugging) diff --git a/Source/ASImageNode+AnimatedImage.mm b/Source/ASImageNode+AnimatedImage.mm index 246ee49cc2..a9ef4d33f 100644 --- a/Source/ASImageNode+AnimatedImage.mm +++ b/Source/ASImageNode+AnimatedImage.mm @@ -322,7 +322,7 @@ - (void)displayLinkFired:(CADisplayLink *)displayLink CFTimeInterval timeBetweenLastFire; if (self.lastDisplayLinkFire == 0) { timeBetweenLastFire = 0; - } else if (AS_AVAILABLE_IOS(10)){ + } else if (AS_AVAILABLE_IOS_TVOS(10, 10)) { timeBetweenLastFire = displayLink.targetTimestamp - displayLink.timestamp; } else { timeBetweenLastFire = CACurrentMediaTime() - self.lastDisplayLinkFire; diff --git a/Source/ASImageNode.h b/Source/ASImageNode.h index 547044c7d..99ef57549 100644 --- a/Source/ASImageNode.h +++ b/Source/ASImageNode.h @@ -141,6 +141,11 @@ typedef UIImage * _Nullable (^asimagenode_modification_block_t)(UIImage *image); @end +#if TARGET_OS_TV +@interface ASImageNode (tvOS) +@end +#endif + @interface ASImageNode (AnimatedImage) /** diff --git a/Source/ASMultiplexImageNode.h b/Source/ASMultiplexImageNode.h index 15baced60..b3aa331e7 100644 --- a/Source/ASMultiplexImageNode.h +++ b/Source/ASMultiplexImageNode.h @@ -131,14 +131,12 @@ typedef NS_ENUM(NSUInteger, ASMultiplexImageNodeErrorCode) { */ @property (nonatomic, assign, readwrite) BOOL shouldRenderProgressImages; -#if TARGET_OS_IOS /** * @abstract The image manager that this image node should use when requesting images from the Photos framework. If this is `nil` (the default), then `PHImageManager.defaultManager` is used. * @see `+[NSURL URLWithAssetLocalIdentifier:targetSize:contentMode:options:]` below. */ -@property (nullable, nonatomic, strong) PHImageManager *imageManager; -#endif +@property (nullable, nonatomic, strong) PHImageManager *imageManager API_AVAILABLE(ios(8.0), tvos(10.0)); @end @@ -245,7 +243,6 @@ didFinishDownloadingImageWithIdentifier:(ASImageIdentifier)imageIdentifier */ - (nullable NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(ASImageIdentifier)imageIdentifier; -#if TARGET_OS_IOS /** * @abstract A PHAsset for the specific asset local identifier * @param imageNode The sender. @@ -256,12 +253,10 @@ didFinishDownloadingImageWithIdentifier:(ASImageIdentifier)imageIdentifier * @note This method may be called from any thread. * @return A PHAsset corresponding to `assetLocalIdentifier`, or nil if none is available. */ -- (nullable PHAsset *)multiplexImageNode:(ASMultiplexImageNode *)imageNode assetForLocalIdentifier:(NSString *)assetLocalIdentifier; -#endif +- (nullable PHAsset *)multiplexImageNode:(ASMultiplexImageNode *)imageNode assetForLocalIdentifier:(NSString *)assetLocalIdentifier API_AVAILABLE(ios(8.0), tvos(10.0)); @end -#pragma mark - -#if TARGET_OS_IOS +#pragma mark - @interface NSURL (ASPhotosFrameworkURLs) /** @@ -275,9 +270,8 @@ didFinishDownloadingImageWithIdentifier:(ASImageIdentifier)imageIdentifier + (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode - options:(PHImageRequestOptions *)options AS_WARN_UNUSED_RESULT; + options:(PHImageRequestOptions *)options AS_WARN_UNUSED_RESULT API_AVAILABLE(ios(8.0), tvos(10.0)); @end -#endif NS_ASSUME_NONNULL_END diff --git a/Source/ASMultiplexImageNode.mm b/Source/ASMultiplexImageNode.mm index ecc5fd7af..12a2647e1 100644 --- a/Source/ASMultiplexImageNode.mm +++ b/Source/ASMultiplexImageNode.mm @@ -16,7 +16,10 @@ // #import + +#if TARGET_OS_IOS #import +#endif #import #import @@ -136,6 +139,7 @@ - (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imag @param completionBlock The block to be performed when the image has been loaded, if possible. May not be nil. */ - (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock; +#endif /** @abstract Loads the image corresponding to the given image request from the Photos framework. @@ -143,8 +147,8 @@ - (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL com @param request The photos image request to load. May not be nil. @param completionBlock The block to be performed when the image has been loaded, if possible. May not be nil. */ -- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock; -#endif +- (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock API_AVAILABLE(ios(8.0), tvos(10.0)); + /** @abstract Downloads the image corresponding to the given imageIdentifier from the given URL. @param imageIdentifier The identifier for the image to be downloaded. May not be nil. @@ -345,9 +349,9 @@ - (void)setDataSource:(id )dataSource _dataSource = dataSource; _dataSourceFlags.image = [_dataSource respondsToSelector:@selector(multiplexImageNode:imageForImageIdentifier:)]; _dataSourceFlags.URL = [_dataSource respondsToSelector:@selector(multiplexImageNode:URLForImageIdentifier:)]; - #if TARGET_OS_IOS - _dataSourceFlags.asset = [_dataSource respondsToSelector:@selector(multiplexImageNode:assetForLocalIdentifier:)]; - #endif + if (AS_AVAILABLE_IOS_TVOS(9, 10)) { + _dataSourceFlags.asset = [_dataSource respondsToSelector:@selector(multiplexImageNode:assetForLocalIdentifier:)]; + } } @@ -616,7 +620,7 @@ - (void)_loadNextImage return; } - #if TARGET_OS_IOS +#if TARGET_OS_IOS // If it's an assets-library URL, we need to fetch it from the assets library. if ([[nextImageURL scheme] isEqualToString:kAssetsLibraryURLScheme]) { // Load the asset. @@ -624,48 +628,54 @@ - (void)_loadNextImage as_log_verbose(ASImageLoadingLog(), "Acquired image from assets library for %@ %@", weakSelf, nextImageIdentifier); finishedLoadingBlock(downloadedImage, nextImageIdentifier, error); }]; + + return; } - // Likewise, if it's a Photos asset, we need to fetch it accordingly. - else if (ASPhotosFrameworkImageRequest *request = [ASPhotosFrameworkImageRequest requestWithURL:nextImageURL]) { - [self _loadPHAssetWithRequest:request identifier:nextImageIdentifier completion:^(UIImage *image, NSError *error) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from Photos for %@ %@", weakSelf, nextImageIdentifier); - finishedLoadingBlock(image, nextImageIdentifier, error); - }]; +#endif + + if (AS_AVAILABLE_IOS_TVOS(9, 10)) { + // Likewise, if it's a Photos asset, we need to fetch it accordingly. + if (ASPhotosFrameworkImageRequest *request = [ASPhotosFrameworkImageRequest requestWithURL:nextImageURL]) { + [self _loadPHAssetWithRequest:request identifier:nextImageIdentifier completion:^(UIImage *image, NSError *error) { + as_log_verbose(ASImageLoadingLog(), "Acquired image from Photos for %@ %@", weakSelf, nextImageIdentifier); + finishedLoadingBlock(image, nextImageIdentifier, error); + }]; + + return; + } } - #endif - else // Otherwise, it's a web URL that we can download. - { - // First, check the cache. - [self _fetchImageWithIdentifierFromCache:nextImageIdentifier URL:nextImageURL completion:^(UIImage *imageFromCache) { + + // Otherwise, it's a web URL that we can download. + // First, check the cache. + [self _fetchImageWithIdentifierFromCache:nextImageIdentifier URL:nextImageURL completion:^(UIImage *imageFromCache) { + __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) + return; + + // If we had a cache-hit, we're done. + if (imageFromCache) { + as_log_verbose(ASImageLoadingLog(), "Acquired image from cache for %@ id: %@ img: %@", strongSelf, nextImageIdentifier, imageFromCache); + finishedLoadingBlock(imageFromCache, nextImageIdentifier, nil); + return; + } + + // If the next image to load has changed, bail. + if (!ASObjectIsEqual([strongSelf _nextImageIdentifierToDownload], nextImageIdentifier)) { + finishedLoadingBlock(nil, nil, [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged userInfo:nil]); + return; + } + + // Otherwise, we've got to download it. + [strongSelf _downloadImageWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *downloadedImage, NSError *error) { __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - - // If we had a cache-hit, we're done. - if (imageFromCache) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from cache for %@ id: %@ img: %@", strongSelf, nextImageIdentifier, imageFromCache); - finishedLoadingBlock(imageFromCache, nextImageIdentifier, nil); - return; - } - - // If the next image to load has changed, bail. - if (!ASObjectIsEqual([strongSelf _nextImageIdentifierToDownload], nextImageIdentifier)) { - finishedLoadingBlock(nil, nil, [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged userInfo:nil]); - return; + if (downloadedImage) { + as_log_verbose(ASImageLoadingLog(), "Acquired image from download for %@ id: %@ img: %@", strongSelf, nextImageIdentifier, downloadedImage); + } else { + as_log_error(ASImageLoadingLog(), "Error downloading image for %@ id: %@ err: %@", strongSelf, nextImageIdentifier, error); } - - // Otherwise, we've got to download it. - [strongSelf _downloadImageWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *downloadedImage, NSError *error) { - __typeof__(self) strongSelf = weakSelf; - if (downloadedImage) { - as_log_verbose(ASImageLoadingLog(), "Acquired image from download for %@ id: %@ img: %@", strongSelf, nextImageIdentifier, downloadedImage); - } else { - as_log_error(ASImageLoadingLog(), "Error downloading image for %@ id: %@ err: %@", strongSelf, nextImageIdentifier, error); - } - finishedLoadingBlock(downloadedImage, nextImageIdentifier, error); - }]; + finishedLoadingBlock(downloadedImage, nextImageIdentifier, error); }]; - } + }]; } #if TARGET_OS_IOS - (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock @@ -691,7 +701,7 @@ - (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL com }]; #pragma clang diagnostic pop } - +#endif - (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identifier:(id)imageIdentifier completion:(void (^)(UIImage *image, NSError *error))completionBlock { ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); @@ -779,7 +789,7 @@ - (void)_loadPHAssetWithRequest:(ASPhotosFrameworkImageRequest *)request identif _phImageRequestOperation = newImageRequestOp; [phImageRequestQueue addOperation:newImageRequestOp]; } -#endif + - (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock { ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required"); diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 1896b37cf..5c8308444 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -115,7 +115,9 @@ - (void)setElement:(ASCollectionElement *)element if (node) { self.backgroundColor = node.backgroundColor; self.selectedBackgroundView = node.selectedBackgroundView; +#if TARGET_OS_IOS self.separatorInset = node.separatorInset; +#endif self.selectionStyle = node.selectionStyle; self.focusStyle = node.focusStyle; self.accessoryType = node.accessoryType; @@ -920,6 +922,7 @@ - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPa ASCellNode *node = [_dataController.visibleMap elementForItemAtIndexPath:indexPath].node; CGFloat height = node.calculatedSize.height; +#if TARGET_OS_IOS /** * Weirdly enough, Apple expects the return value here to _include_ the height * of the separator, if there is one! So if our node wants to be 43.5, we need @@ -929,6 +932,8 @@ - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPa if (tableView.separatorStyle != UITableViewCellSeparatorStyleNone) { height += 1.0 / ASScreenScale(); } +#endif + return height; } @@ -1771,6 +1776,7 @@ - (BOOL)dataController:(ASDataController *)dataController presentedSizeForElemen } CGRect rect = [self rectForRowAtIndexPath:indexPath]; +#if TARGET_OS_IOS /** * Weirdly enough, Apple expects the return value in tableView:heightForRowAtIndexPath: to _include_ the height * of the separator, if there is one! So if rectForRow would return 44.0 we need to use 43.5. @@ -1778,6 +1784,7 @@ - (BOOL)dataController:(ASDataController *)dataController presentedSizeForElemen if (self.separatorStyle != UITableViewCellSeparatorStyleNone) { rect.size.height -= 1.0 / ASScreenScale(); } +#endif return (fabs(rect.size.height - size.height) < FLT_EPSILON); } diff --git a/Source/ASTextNode.mm b/Source/ASTextNode.mm index ccbed9c07..768223070 100644 --- a/Source/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -451,10 +451,10 @@ - (void)setAttributedText:(NSAttributedString *)attributedText // Since truncation text matches style of attributedText, invalidate it now. [self _invalidateTruncationText]; - NSUInteger length = attributedText.length; + NSUInteger length = _attributedText.length; if (length > 0) { - self.style.ascender = [[self class] ascenderWithAttributedString:attributedText]; - self.style.descender = [[attributedText attribute:NSFontAttributeName atIndex:attributedText.length - 1 effectiveRange:NULL] descender]; + self.style.ascender = [[self class] ascenderWithAttributedString:_attributedText]; + self.style.descender = [[_attributedText attribute:NSFontAttributeName atIndex:length - 1 effectiveRange:NULL] descender]; } // Tell the display node superclasses that the cached layout is incorrect now @@ -465,7 +465,7 @@ - (void)setAttributedText:(NSAttributedString *)attributedText // Accessiblity - self.accessibilityLabel = attributedText.string; + self.accessibilityLabel = _attributedText.string; self.isAccessibilityElement = (length != 0); // We're an accessibility element by default if there is a string. } diff --git a/Source/Base/ASAvailability.h b/Source/Base/ASAvailability.h index f78ed9aaf..f400dea3c 100644 --- a/Source/Base/ASAvailability.h +++ b/Source/Base/ASAvailability.h @@ -36,9 +36,13 @@ // Use __builtin_available if we're on Xcode >= 9, AS_AT_LEAST otherwise. #if __has_builtin(__builtin_available) - #define AS_AVAILABLE_IOS(ver) __builtin_available(iOS ver, *) + #define AS_AVAILABLE_IOS(ver) __builtin_available(iOS ver, *) + #define AS_AVAILABLE_TVOS(ver) __builtin_available(tvOS ver, *) + #define AS_AVAILABLE_IOS_TVOS(ver1, ver2) __builtin_available(iOS ver1, tvOS ver2, *) #else - #define AS_AVAILABLE_IOS(ver) AS_AT_LEAST_IOS##ver + #define AS_AVAILABLE_IOS(ver) (TARGET_OS_IOS && AS_AT_LEAST_IOS##ver) + #define AS_AVAILABLE_TVOS(ver) (TARGET_OS_TV && AS_AT_LEAST_IOS##ver) + #define AS_AVAILABLE_IOS_TVOS(ver1, ver2) (AS_AVAILABLE_IOS(ver1) || AS_AVAILABLE_TVOS(ver2)) #endif // If Yoga is available, make it available anywhere we use ASAvailability. diff --git a/Source/Base/ASLog.h b/Source/Base/ASLog.h index c6aba76ba..e87404631 100644 --- a/Source/Base/ASLog.h +++ b/Source/Base/ASLog.h @@ -132,7 +132,7 @@ ASDISPLAYNODE_EXTERN_C_END #define as_log_create(subsystem, category) ({ \ os_log_t __val; \ -if (AS_AVAILABLE_IOS(9)) { \ +if (AS_AVAILABLE_IOS_TVOS(9, 9)) { \ __val = os_log_create(subsystem, category); \ } else { \ __val = (os_log_t)0; \ @@ -141,28 +141,28 @@ __val; \ }) #define as_log_debug(log, format, ...) \ -if (AS_AVAILABLE_IOS(9)) { \ +if (AS_AVAILABLE_IOS_TVOS(9, 9)) { \ os_log_debug(log, format, ##__VA_ARGS__); \ } else { \ (void)0; \ } \ #define as_log_info(log, format, ...) \ -if (AS_AVAILABLE_IOS(9)) { \ +if (AS_AVAILABLE_IOS_TVOS(9, 9)) { \ os_log_info(log, format, ##__VA_ARGS__); \ } else { \ (void)0; \ } \ #define as_log_error(log, format, ...) \ -if (AS_AVAILABLE_IOS(9)) { \ +if (AS_AVAILABLE_IOS_TVOS(9, 9)) { \ os_log_error(log, format, ##__VA_ARGS__); \ } else { \ (void)0; \ } \ #define as_log_fault(log, format, ...) \ -if (AS_AVAILABLE_IOS(9)) { \ +if (AS_AVAILABLE_IOS_TVOS(9, 9)) { \ os_log_fault(log, format, ##__VA_ARGS__); \ } else { \ (void)0; \ diff --git a/Source/Details/ASPhotosFrameworkImageRequest.h b/Source/Details/ASPhotosFrameworkImageRequest.h index 1ecd07dec..54f55047a 100644 --- a/Source/Details/ASPhotosFrameworkImageRequest.h +++ b/Source/Details/ASPhotosFrameworkImageRequest.h @@ -26,6 +26,7 @@ extern NSString *const ASPhotosURLScheme; @abstract Use ASPhotosFrameworkImageRequest to encapsulate all the information needed to request an image from the Photos framework and store it in a URL. */ +API_AVAILABLE(ios(8.0), tvos(10.0)) @interface ASPhotosFrameworkImageRequest : NSObject - (instancetype)initWithAssetIdentifier:(NSString *)assetIdentifier NS_DESIGNATED_INITIALIZER; diff --git a/Source/Details/ASTraitCollection.m b/Source/Details/ASTraitCollection.m index dba756baf..8755fc57c 100644 --- a/Source/Details/ASTraitCollection.m +++ b/Source/Details/ASTraitCollection.m @@ -286,7 +286,7 @@ + (instancetype)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass userInterfaceIdiom:userInterfaceIdiom forceTouchCapability:forceTouchCapability layoutDirection:layoutDirection - userInterfaceStyle:userIntefaceStyle + userInterfaceStyle:userInterfaceStyle preferredContentSizeCategory:preferredContentSizeCategory containerSize:windowSize]; } diff --git a/Source/Details/_ASDisplayViewAccessiblity.mm b/Source/Details/_ASDisplayViewAccessiblity.mm index 734cf2b7f..6b34c89a2 100644 --- a/Source/Details/_ASDisplayViewAccessiblity.mm +++ b/Source/Details/_ASDisplayViewAccessiblity.mm @@ -85,7 +85,7 @@ + (ASAccessibilityElement *)accessibilityElementWithContainer:(UIView *)containe accessibilityElement.accessibilityValue = node.accessibilityValue; accessibilityElement.accessibilityTraits = node.accessibilityTraits; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 - if (AS_AVAILABLE_IOS(11)) { + if (AS_AVAILABLE_IOS_TVOS(11, 11)) { accessibilityElement.accessibilityAttributedLabel = node.accessibilityAttributedLabel; accessibilityElement.accessibilityAttributedHint = node.accessibilityAttributedHint; accessibilityElement.accessibilityAttributedValue = node.accessibilityAttributedValue; @@ -179,7 +179,7 @@ static void CollectAccessibilityElementsForContainer(ASDisplayNode *container, _ SortAccessibilityElements(labeledNodes); #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 - if (AS_AVAILABLE_IOS(11)) { + if (AS_AVAILABLE_IOS_TVOS(11, 11)) { NSArray *attributedLabels = [labeledNodes valueForKey:@"accessibilityAttributedLabel"]; NSMutableAttributedString *attributedLabel = [NSMutableAttributedString new]; [attributedLabels enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { diff --git a/Source/tvOS/ASControlNode+tvOS.h b/Source/Private/ASControlNode+Private.h similarity index 89% rename from Source/tvOS/ASControlNode+tvOS.h rename to Source/Private/ASControlNode+Private.h index cd05078a7..f3249c11c 100644 --- a/Source/tvOS/ASControlNode+tvOS.h +++ b/Source/Private/ASControlNode+Private.h @@ -1,5 +1,5 @@ // -// ASControlNode+tvOS.h +// ASControlNode+Private.h // Texture // // Copyright (c) 2014-present, Facebook, Inc. All rights reserved. @@ -15,10 +15,12 @@ // http://www.apache.org/licenses/LICENSE-2.0 // -#if TARGET_OS_TV #import -@interface ASControlNode (tvOS) +@interface ASControlNode (Private) -@end +#if TARGET_OS_TV +- (void)_pressDown; #endif + +@end diff --git a/Source/Private/ASDisplayNode+UIViewBridge.mm b/Source/Private/ASDisplayNode+UIViewBridge.mm index 13e932ef5..21f091c3f 100644 --- a/Source/Private/ASDisplayNode+UIViewBridge.mm +++ b/Source/Private/ASDisplayNode+UIViewBridge.mm @@ -930,7 +930,7 @@ - (void)setAccessibilityLabel:(NSString *)accessibilityLabel _bridge_prologue_write; _setAccessibilityToViewAndProperty(_accessibilityLabel, accessibilityLabel, accessibilityLabel, accessibilityLabel); #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 - if (AS_AVAILABLE_IOS(11)) { + if (AS_AVAILABLE_IOS_TVOS(11, 11)) { NSAttributedString *accessibilityAttributedLabel = accessibilityLabel ? [[NSAttributedString alloc] initWithString:accessibilityLabel] : nil; _setAccessibilityToViewAndProperty(_accessibilityAttributedLabel, accessibilityAttributedLabel, accessibilityAttributedLabel, accessibilityAttributedLabel); } @@ -963,7 +963,7 @@ - (void)setAccessibilityHint:(NSString *)accessibilityHint _bridge_prologue_write; _setAccessibilityToViewAndProperty(_accessibilityHint, accessibilityHint, accessibilityHint, accessibilityHint); #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 - if (AS_AVAILABLE_IOS(11)) { + if (AS_AVAILABLE_IOS_TVOS(11, 11)) { NSAttributedString *accessibilityAttributedHint = accessibilityHint ? [[NSAttributedString alloc] initWithString:accessibilityHint] : nil; _setAccessibilityToViewAndProperty(_accessibilityAttributedHint, accessibilityAttributedHint, accessibilityAttributedHint, accessibilityAttributedHint); } @@ -997,7 +997,7 @@ - (void)setAccessibilityValue:(NSString *)accessibilityValue _bridge_prologue_write; _setAccessibilityToViewAndProperty(_accessibilityValue, accessibilityValue, accessibilityValue, accessibilityValue); #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 - if (AS_AVAILABLE_IOS(11)) { + if (AS_AVAILABLE_IOS_TVOS(11, 11)) { NSAttributedString *accessibilityAttributedValue = accessibilityValue ? [[NSAttributedString alloc] initWithString:accessibilityValue] : nil; _setAccessibilityToViewAndProperty(_accessibilityAttributedValue, accessibilityAttributedValue, accessibilityAttributedValue, accessibilityAttributedValue); } diff --git a/Source/Private/TextExperiment/String/ASTextAttribute.m b/Source/Private/TextExperiment/String/ASTextAttribute.m index 0cc8e22a7..76ff0df38 100755 --- a/Source/Private/TextExperiment/String/ASTextAttribute.m +++ b/Source/Private/TextExperiment/String/ASTextAttribute.m @@ -63,10 +63,12 @@ ASTextAttributeType ASTextAttributeGetType(NSString *name){ dic[(id)kCTSuperscriptAttributeName] = UIKit; //it's a CoreText attrubite, but only supported by UIKit... dic[NSVerticalGlyphFormAttributeName] = All; dic[(id)kCTGlyphInfoAttributeName] = CoreText_ASText; +#if TARGET_OS_IOS #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" dic[(id)kCTCharacterShapeAttributeName] = CoreText_ASText; #pragma clang diagnostic pop +#endif dic[(id)kCTRunDelegateAttributeName] = CoreText_ASText; dic[(id)kCTBaselineClassAttributeName] = CoreText_ASText; dic[(id)kCTBaselineInfoAttributeName] = CoreText_ASText; diff --git a/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h b/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h index 31ef75d12..f7734347d 100755 --- a/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h +++ b/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.h @@ -1270,7 +1270,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)as_setSuperscript:(nullable NSNumber *)superscript range:(NSRange)range; - (void)as_setGlyphInfo:(nullable CTGlyphInfoRef)glyphInfo range:(NSRange)range; -- (void)as_setCharacterShape:(nullable NSNumber *)characterShape range:(NSRange)range; +- (void)as_setCharacterShape:(nullable NSNumber *)characterShape range:(NSRange)range __TVOS_PROHIBITED; - (void)as_setRunDelegate:(nullable CTRunDelegateRef)runDelegate range:(NSRange)range; - (void)as_setBaselineClass:(nullable CFStringRef)baselineClass range:(NSRange)range; - (void)as_setBaselineInfo:(nullable CFDictionaryRef)baselineInfo range:(NSRange)range; diff --git a/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.m b/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.m index 327bc41e2..1b05bbf73 100755 --- a/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.m +++ b/Source/Private/TextExperiment/Utility/NSAttributedString+ASText.m @@ -600,10 +600,12 @@ - (BOOL)as_canDrawWithUIKit { dispatch_once(&onceToken, ^{ failSet = [NSMutableSet new]; [failSet addObject:(id)kCTGlyphInfoAttributeName]; +#if TARGET_OS_IOS #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" [failSet addObject:(id)kCTCharacterShapeAttributeName]; #pragma clang diagnostic pop +#endif [failSet addObject:(id)kCTLanguageAttributeName]; [failSet addObject:(id)kCTRunDelegateAttributeName]; [failSet addObject:(id)kCTBaselineClassAttributeName]; diff --git a/Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.m b/Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.m index 3401fcc82..2374e4021 100755 --- a/Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.m +++ b/Source/Private/TextExperiment/Utility/NSParagraphStyle+ASText.m @@ -25,6 +25,7 @@ + (NSParagraphStyle *)as_styleWithCTStyle:(CTParagraphStyleRef)CTStyle { NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy]; +#if TARGET_OS_IOS #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" CGFloat lineSpacing; @@ -32,6 +33,7 @@ + (NSParagraphStyle *)as_styleWithCTStyle:(CTParagraphStyleRef)CTStyle { style.lineSpacing = lineSpacing; } #pragma clang diagnostic pop +#endif CGFloat paragraphSpacing; if (CTParagraphStyleGetValueForSpecifier(CTStyle, kCTParagraphStyleSpecifierParagraphSpacing, sizeof(CGFloat), ¶graphSpacing)) { @@ -114,6 +116,7 @@ - (CTParagraphStyleRef)as_CTStyle CF_RETURNS_RETAINED { CTParagraphStyleSetting set[kCTParagraphStyleSpecifierCount] = { }; int count = 0; +#if TARGET_OS_IOS #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" CGFloat lineSpacing = self.lineSpacing; @@ -122,6 +125,7 @@ - (CTParagraphStyleRef)as_CTStyle CF_RETURNS_RETAINED { set[count].value = &lineSpacing; count++; #pragma clang diagnostic pop +#endif CGFloat paragraphSpacing = self.paragraphSpacing; set[count].spec = kCTParagraphStyleSpecifierParagraphSpacing; diff --git a/Source/Private/_ASPendingState.mm b/Source/Private/_ASPendingState.mm index a51d56421..d009771bc 100644 --- a/Source/Private/_ASPendingState.mm +++ b/Source/Private/_ASPendingState.mm @@ -1204,7 +1204,7 @@ + (_ASPendingState *)pendingViewStateFromView:(UIView *)view pendingState.accessibilityHint = view.accessibilityHint; pendingState.accessibilityValue = view.accessibilityValue; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 - if (AS_AVAILABLE_IOS(11)) { + if (AS_AVAILABLE_IOS_TVOS(11, 11)) { pendingState.accessibilityAttributedLabel = view.accessibilityAttributedLabel; pendingState.accessibilityAttributedHint = view.accessibilityAttributedHint; pendingState.accessibilityAttributedValue = view.accessibilityAttributedValue; diff --git a/Source/TextKit/ASTextKitCoreTextAdditions.m b/Source/TextKit/ASTextKitCoreTextAdditions.m index 05935db5c..7f48aff71 100644 --- a/Source/TextKit/ASTextKitCoreTextAdditions.m +++ b/Source/TextKit/ASTextKitCoreTextAdditions.m @@ -39,6 +39,7 @@ BOOL ASAttributeWithNameIsUnsupportedCoreTextAttribute(NSString *attributeName) kCTBaselineInfoAttributeName, kCTBaselineReferenceInfoAttributeName, kCTUnderlineColorAttributeName, + kCTParagraphStyleAttributeName, nil]; }); return [coreTextAttributes containsObject:attributeName]; @@ -97,8 +98,13 @@ BOOL ASAttributeWithNameIsUnsupportedCoreTextAttribute(NSString *attributeName) cleanAttributes[NSForegroundColorAttributeName] = [UIColor colorWithCGColor:(CGColorRef)coreTextValue]; } // kCTParagraphStyleAttributeName -> NSParagraphStyleAttributeName - else if ([coreTextKey isEqualToString:(NSString *)kCTParagraphStyleAttributeName] && ![coreTextValue isKindOfClass:[NSParagraphStyle class]]) { - cleanAttributes[NSParagraphStyleAttributeName] = [NSParagraphStyle paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreTextValue]; + else if ([coreTextKey isEqualToString:(NSString *)kCTParagraphStyleAttributeName]) { + if ([coreTextValue isKindOfClass:[NSParagraphStyle class]]) { + cleanAttributes[NSParagraphStyleAttributeName] = (NSParagraphStyle *)coreTextValue; + } + else { + cleanAttributes[NSParagraphStyleAttributeName] = [NSParagraphStyle paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreTextValue]; + } } // kCTStrokeWidthAttributeName -> NSStrokeWidthAttributeName else if ([coreTextKey isEqualToString:(NSString *)kCTStrokeWidthAttributeName]) { @@ -170,8 +176,9 @@ + (instancetype)paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreText { NSMutableParagraphStyle *newParagraphStyle = [[NSMutableParagraphStyle alloc] init]; - if (!coreTextParagraphStyle) + if (!coreTextParagraphStyle) { return newParagraphStyle; + } // The following paragraph style specifiers are not supported on NSParagraphStyle. Should they become available, we should add them. /* @@ -190,67 +197,145 @@ + (instancetype)paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreText // kCTParagraphStyleSpecifierAlignment -> alignment CTTextAlignment coreTextAlignment; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierAlignment, sizeof(coreTextAlignment), &coreTextAlignment)) + if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, + kCTParagraphStyleSpecifierAlignment, + sizeof(coreTextAlignment), + &coreTextAlignment)) { newParagraphStyle.alignment = NSTextAlignmentFromCTTextAlignment(coreTextAlignment); + } // kCTParagraphStyleSpecifierFirstLineHeadIndent -> firstLineHeadIndent CGFloat firstLineHeadIndent; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierFirstLineHeadIndent, sizeof(firstLineHeadIndent), &firstLineHeadIndent)) + if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, + kCTParagraphStyleSpecifierFirstLineHeadIndent, + sizeof(firstLineHeadIndent), + &firstLineHeadIndent)) { newParagraphStyle.firstLineHeadIndent = firstLineHeadIndent; + } // kCTParagraphStyleSpecifierHeadIndent -> headIndent CGFloat headIndent; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierHeadIndent, sizeof(headIndent), &headIndent)) + if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, + kCTParagraphStyleSpecifierHeadIndent, + sizeof(headIndent), + &headIndent)) { newParagraphStyle.headIndent = headIndent; + } // kCTParagraphStyleSpecifierTailIndent -> tailIndent CGFloat tailIndent; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierTailIndent, sizeof(tailIndent), &tailIndent)) + if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, + kCTParagraphStyleSpecifierTailIndent, + sizeof(tailIndent), + &tailIndent)) { newParagraphStyle.tailIndent = tailIndent; + } // kCTParagraphStyleSpecifierLineBreakMode -> lineBreakMode CTLineBreakMode coreTextLineBreakMode; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierLineBreakMode, sizeof(coreTextLineBreakMode), &coreTextLineBreakMode)) + if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, + kCTParagraphStyleSpecifierLineBreakMode, + sizeof(coreTextLineBreakMode), + &coreTextLineBreakMode)) { newParagraphStyle.lineBreakMode = (NSLineBreakMode)coreTextLineBreakMode; // They're the same enum. + } // kCTParagraphStyleSpecifierLineHeightMultiple -> lineHeightMultiple CGFloat lineHeightMultiple; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierLineHeightMultiple, sizeof(lineHeightMultiple), &lineHeightMultiple)) + if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, + kCTParagraphStyleSpecifierLineHeightMultiple, + sizeof(lineHeightMultiple), + &lineHeightMultiple)) { newParagraphStyle.lineHeightMultiple = lineHeightMultiple; + } // kCTParagraphStyleSpecifierMaximumLineHeight -> maximumLineHeight CGFloat maximumLineHeight; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(maximumLineHeight), &maximumLineHeight)) + if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, + kCTParagraphStyleSpecifierMaximumLineHeight, + sizeof(maximumLineHeight), + &maximumLineHeight)) { newParagraphStyle.maximumLineHeight = maximumLineHeight; + } // kCTParagraphStyleSpecifierMinimumLineHeight -> minimumLineHeight CGFloat minimumLineHeight; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(minimumLineHeight), &minimumLineHeight)) + if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, + kCTParagraphStyleSpecifierMinimumLineHeight, + sizeof(minimumLineHeight), + &minimumLineHeight)) { newParagraphStyle.minimumLineHeight = minimumLineHeight; - - // kCTParagraphStyleSpecifierLineSpacing -> lineSpacing - // Note that kCTParagraphStyleSpecifierLineSpacing is deprecated and will die soon. We should not be using it. + } + + CGFloat lineSpacing = 0; +#if TARGET_OS_IOS #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - CGFloat lineSpacing; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierLineSpacing, sizeof(lineSpacing), &lineSpacing)) - newParagraphStyle.lineSpacing = lineSpacing; + // kCTParagraphStyleSpecifierLineSpacing -> lineSpacing + // Note that kCTParagraphStyleSpecifierLineSpacing is deprecated and will die soon. We should not be using it. + if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, + kCTParagraphStyleSpecifierLineSpacing, + sizeof(lineSpacing), + &lineSpacing)) { + newParagraphStyle.lineSpacing = lineSpacing; + } #pragma clang diagnostic pop +#endif + + // Attempt to weakly map the following onto -[NSParagraphStyle lineSpacing]: + // - kCTParagraphStyleSpecifierMinimumLineSpacing + // - kCTParagraphStyleSpecifierMaximumLineSpacing + // - kCTParagraphStyleSpecifierLineSpacingAdjustment + if (fabs(lineSpacing) <= FLT_EPSILON && + CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, + kCTParagraphStyleSpecifierMinimumLineSpacing, + sizeof(lineSpacing), + &lineSpacing)) { + newParagraphStyle.lineSpacing = lineSpacing; + } + + if (fabs(lineSpacing) <= FLT_EPSILON && + CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, + kCTParagraphStyleSpecifierMaximumLineSpacing, + sizeof(lineSpacing), + &lineSpacing)) { + newParagraphStyle.lineSpacing = lineSpacing; + } + + if (fabs(lineSpacing) <= FLT_EPSILON && + CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, + kCTParagraphStyleSpecifierLineSpacingAdjustment, + sizeof(lineSpacing), + &lineSpacing)) { + newParagraphStyle.lineSpacing = lineSpacing; + } // kCTParagraphStyleSpecifierParagraphSpacing -> paragraphSpacing CGFloat paragraphSpacing; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierParagraphSpacing, sizeof(paragraphSpacing), ¶graphSpacing)) + if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, + kCTParagraphStyleSpecifierParagraphSpacing, + sizeof(paragraphSpacing), + ¶graphSpacing)) { newParagraphStyle.paragraphSpacing = paragraphSpacing; + } // kCTParagraphStyleSpecifierParagraphSpacingBefore -> paragraphSpacingBefore CGFloat paragraphSpacingBefore; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierParagraphSpacingBefore, sizeof(paragraphSpacingBefore), ¶graphSpacingBefore)) + if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, + kCTParagraphStyleSpecifierParagraphSpacingBefore, + sizeof(paragraphSpacingBefore), + ¶graphSpacingBefore)) { newParagraphStyle.paragraphSpacingBefore = paragraphSpacingBefore; + } // kCTParagraphStyleSpecifierBaseWritingDirection -> baseWritingDirection CTWritingDirection coreTextBaseWritingDirection; - if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, kCTParagraphStyleSpecifierBaseWritingDirection, sizeof(coreTextBaseWritingDirection), &coreTextBaseWritingDirection)) + if (CTParagraphStyleGetValueForSpecifier(coreTextParagraphStyle, + kCTParagraphStyleSpecifierBaseWritingDirection, + sizeof(coreTextBaseWritingDirection), + &coreTextBaseWritingDirection)) { newParagraphStyle.baseWritingDirection = (NSWritingDirection)coreTextBaseWritingDirection; // They're the same enum. + } return newParagraphStyle; } diff --git a/Source/tvOS/ASControlNode+tvOS.m b/Source/tvOS/ASControlNode+tvOS.m index 35acbc045..77e541862 100644 --- a/Source/tvOS/ASControlNode+tvOS.m +++ b/Source/tvOS/ASControlNode+tvOS.m @@ -16,20 +16,20 @@ // #import - #if TARGET_OS_TV -#import +#import +#import @implementation ASControlNode (tvOS) #pragma mark - tvOS -- (void)pressDown +- (void)_pressDown { - [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationCurveLinear animations:^{ + [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ [self setPressedState]; } completion:^(BOOL finished) { if (finished) { - [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationCurveLinear animations:^{ + [UIView animateWithDuration:0.1 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ [self setFocusedState]; } completion:nil]; } diff --git a/Source/tvOS/ASImageNode+tvOS.h b/Source/tvOS/ASImageNode+tvOS.h deleted file mode 100644 index d808db10a..000000000 --- a/Source/tvOS/ASImageNode+tvOS.h +++ /dev/null @@ -1,24 +0,0 @@ -// -// ASImageNode+tvOS.h -// Texture -// -// 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 /ASDK-Licenses directory of this source tree. An additional -// grant of patent rights can be found in the PATENTS file in the same directory. -// -// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, -// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// - -#if TARGET_OS_TV -#import - -@interface ASImageNode (tvOS) -@end -#endif - diff --git a/Source/tvOS/ASImageNode+tvOS.m b/Source/tvOS/ASImageNode+tvOS.m index d3da889c5..9482fdc8c 100644 --- a/Source/tvOS/ASImageNode+tvOS.m +++ b/Source/tvOS/ASImageNode+tvOS.m @@ -17,7 +17,8 @@ #import #if TARGET_OS_TV -#import +#import +#import #import #import diff --git a/Texture.podspec b/Texture.podspec index b780df283..a0d8b8422 100644 --- a/Texture.podspec +++ b/Texture.podspec @@ -11,13 +11,13 @@ Pod::Spec.new do |spec| spec.documentation_url = 'http://texturegroup.org/appledoc/' - spec.weak_frameworks = 'Photos','MapKit','AssetsLibrary' + spec.ios.weak_frameworks = 'AssetsLibrary' + spec.weak_frameworks = 'Photos','MapKit' + spec.requires_arc = true spec.ios.deployment_target = '9.0' - - # Uncomment when fixed: issues with tvOS build for release 2.0 - # spec.tvos.deployment_target = '9.0' + spec.tvos.deployment_target = '9.0' # Subspecs spec.subspec 'Core' do |core| From 9bffd881349c8645225c14336234dcb9521ceed8 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 12 Mar 2018 10:42:24 -0700 Subject: [PATCH 102/133] Fix Text Node Thread Sanitizer Warning (#830) * Fix thread sanitizer warning in ASTextNodeRendererKey * Update changelog * Comment on missing class check --- CHANGELOG.md | 1 + Source/ASTextNode.mm | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 466623147..8889c2696 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ - Pass scrollViewWillEndDragging delegation through in ASIGListAdapterDataSource for IGListKit integration. [#796](https://github.com/TextureGroup/Texture/pull/796) - Fix UIResponder handling with view backing ASDisplayNode. [Michael Schneider](https://github.com/maicki) [#789] (https://github.com/TextureGroup/Texture/pull/789/) - Optimized thread-local storage by replacing pthread_specific with C11 thread-local variables. [Adlai Holler](https://github.com/Adlai-Holler) [#811] (https://github.com/TextureGroup/Texture/pull/811/) +- Fixed a thread-sanitizer warning in ASTextNode. [Adlai Holler](https://github.com/Adlai-Holler) [#830] (https://github.com/TextureGroup/Texture/pull/830/) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASTextNode.mm b/Source/ASTextNode.mm index 768223070..0b6421e7a 100644 --- a/Source/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -63,10 +63,13 @@ @interface ASTextNodeRendererKey : NSObject @property (assign, nonatomic) CGSize constrainedSize; @end -@implementation ASTextNodeRendererKey +@implementation ASTextNodeRendererKey { + std::mutex _m; +} - (NSUInteger)hash { + std::lock_guard _l(_m); #pragma clang diagnostic push #pragma clang diagnostic warning "-Wpadded" struct { @@ -86,7 +89,14 @@ - (BOOL)isEqual:(ASTextNodeRendererKey *)object return YES; } - return _attributes == object.attributes && CGSizeEqualToSize(_constrainedSize, object.constrainedSize); + // NOTE: Skip the class check for this specialized, internal Key object. + + // Lock both objects, avoiding deadlock. + std::lock(_m, object->_m); + std::lock_guard lk1(_m, std::adopt_lock); + std::lock_guard lk2(object->_m, std::adopt_lock); + + return _attributes == object->_attributes && CGSizeEqualToSize(_constrainedSize, object->_constrainedSize); } @end From ed6da290575469329a2d49de9719b894cff96a17 Mon Sep 17 00:00:00 2001 From: Ha Hyun soo Date: Tue, 13 Mar 2018 04:43:40 +0900 Subject: [PATCH 103/133] Geektree0101 - Fix mensXP showcase indent on showcase table, attach vingle medium blog poster (#818) --- docs/showcase.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/showcase.md b/docs/showcase.md index 95b72f331..9e98ce2f4 100755 --- a/docs/showcase.md +++ b/docs/showcase.md @@ -213,10 +213,12 @@ permalink: /showcase.html -
+
Vingle +
+ Improvement feed performance with Texture - + @@ -227,10 +229,6 @@ permalink: /showcase.html Blendle - - - -
From 5cafdb90626b2510425f47e9a9235d707e3ea9d6 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 12 Mar 2018 13:42:33 -0700 Subject: [PATCH 104/133] [ASTextNode2] Fix background color drawing (#831) * Fix TextNode2 not respecting background color * ASTextNode2: Use locks and copies right * Increment changelog * Make the Dangerfile accept any license header with Pinterest in it --- CHANGELOG.md | 1 + Dangerfile | 6 +----- Source/ASTextNode2.mm | 41 +++++++++++++++++++++++++++++------------ 3 files changed, 31 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8889c2696..e2311fb0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ - Fix UIResponder handling with view backing ASDisplayNode. [Michael Schneider](https://github.com/maicki) [#789] (https://github.com/TextureGroup/Texture/pull/789/) - Optimized thread-local storage by replacing pthread_specific with C11 thread-local variables. [Adlai Holler](https://github.com/Adlai-Holler) [#811] (https://github.com/TextureGroup/Texture/pull/811/) - Fixed a thread-sanitizer warning in ASTextNode. [Adlai Holler](https://github.com/Adlai-Holler) [#830] (https://github.com/TextureGroup/Texture/pull/830/) +- Fix ASTextNode2 handling background color incorrectly. [Adlai Holler](https://github.com/Adlai-Holler) [#831] (https://github.com/TextureGroup/Texture/pull/831/) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Dangerfile b/Dangerfile index 02759ad2c..edbbec21e 100644 --- a/Dangerfile +++ b/Dangerfile @@ -54,11 +54,7 @@ def check_file_header(files_to_check, licenses) correct_license = false licenses.each do |license| license_header = full_license(license, filename) - # Hack for https://github.com/TextureGroup/Texture/issues/745 - # If it's already a "modified-post-Texture" file, leave it with it original copyright year. - if data.include? "Modifications to this file made after 4/13/2017" - correct_license = true - elsif data.start_with?(license_header) + if data.include? "Pinterest, Inc." correct_license = true end end diff --git a/Source/ASTextNode2.mm b/Source/ASTextNode2.mm index 54475368d..35b06026a 100644 --- a/Source/ASTextNode2.mm +++ b/Source/ASTextNode2.mm @@ -359,20 +359,17 @@ - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer [self prepareAttributedString:mutableText]; - // Apply background color if needed before drawing. To access the backgroundColor we need to be on the main thread - UIColor *backgroundColor = self.backgroundColor; - if (CGColorGetAlpha(backgroundColor.CGColor) > 0) { - [mutableText addAttribute:NSBackgroundColorAttributeName value:backgroundColor range:NSMakeRange(0, mutableText.length)]; - } - return @{ - @"container": copiedContainer, - @"text": mutableText - }; + @"container": copiedContainer, + @"text": mutableText, + @"bgColor": self.backgroundColor + }; } /** * If it can't find a compatible layout, this method creates one. + * + * NOTE: Be careful to copy `text` if needed. */ + (ASTextLayout *)compatibleLayoutWithContainer:(ASTextContainer *)container text:(NSAttributedString *)text @@ -391,7 +388,7 @@ + (ASTextLayout *)compatibleLayoutWithContainer:(ASTextContainer *)container cacheValue = [textLayoutCache objectForKey:text]; if (cacheValue == nil) { cacheValue = [[ASTextCacheValue alloc] init]; - [textLayoutCache setObject:cacheValue forKey:text]; + [textLayoutCache setObject:cacheValue forKey:[text copy]]; } cacheValue; }); @@ -458,15 +455,25 @@ + (ASTextLayout *)compatibleLayoutWithContainer:(ASTextContainer *)container return layout; } -+ (void)drawRect:(CGRect)bounds withParameters:(NSDictionary *)layoutDict isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing; ++ (void)drawRect:(CGRect)bounds withParameters:(NSDictionary *)layoutDict isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing { ASTextContainer *container = layoutDict[@"container"]; NSAttributedString *text = layoutDict[@"text"]; + UIColor *bgColor = layoutDict[@"bgColor"]; ASTextLayout *layout = [self compatibleLayoutWithContainer:container text:text]; if (isCancelledBlock()) { return; } + + // Fill background color. + // They may have already drawn into this context in the pre-context block + // so unfortunately we have to use the normal blend mode, not copy. + if (CGColorGetAlpha(bgColor.CGColor) > 0) { + [bgColor setFill]; + UIRectFillUsingBlendMode(bounds, kCGBlendModeNormal); + } + CGContextRef context = UIGraphicsGetCurrentContext(); ASDisplayNodeAssert(context, @"This is no good without a context."); @@ -941,11 +948,21 @@ - (UIEdgeInsets)shadowPadding - (void)setPointSizeScaleFactors:(NSArray *)scaleFactors { AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - _pointSizeScaleFactors = [scaleFactors copy]; + { + ASDN::MutexLocker l(__instanceLock__); + if (ASObjectIsEqual(scaleFactors, _pointSizeScaleFactors)) { + return; + } + + _pointSizeScaleFactors = [scaleFactors copy]; + } + + [self setNeedsLayout]; } - (NSArray *)pointSizeScaleFactors { + ASDN::MutexLocker l(__instanceLock__); return _pointSizeScaleFactors; } From a41cbb48b32dc56777d62b617a60c3c355f15258 Mon Sep 17 00:00:00 2001 From: appleguy Date: Tue, 13 Mar 2018 01:03:18 -0700 Subject: [PATCH 105/133] [ASWrapperCellNode] Introduce a new class allowing more control of UIKit passthrough cells. (#797) * - [ASWrapperCellNode] Introduce a new class allowing more control of UIKit passthrough cells. A few minor fixes to Collections behavior as well, including a new isSynchronized API. The difference from processingUpdates is that after Synchronized, all animations have also completed (or runloop turn if animations disabled, so .collectionViewLayout can be relied on being fully in sync). More upstreaming to come after this can land... * Fix -[ASDataController clearData] to take no action before initial data loading. * Empty commit to kick CI * Spacing change to kick CI (since an empty commit doesn't work...) * Tweak ASDataController changes to handle an edge case in _editingTransactionQueueCount management. * Avoid excess cyclic calls to onDidFinishProcessingUpdates: by avoiding ASMainSerialQueue. * Reverting my initial change as it wasn't the right approach, following the real fix before this. --- CHANGELOG.md | 1 + Source/ASCellNode.h | 2 +- Source/ASCellNode.mm | 19 +++ Source/ASCollectionNode+Beta.h | 13 ++ Source/ASCollectionNode.h | 26 +++- Source/ASCollectionNode.mm | 128 ++++++++++++++++++- Source/ASCollectionView.h | 8 +- Source/ASCollectionView.mm | 180 ++++++++++++++++----------- Source/ASTableNode.h | 2 +- Source/ASTableNode.mm | 5 +- Source/ASTableView.h | 2 +- Source/ASTableView.mm | 2 +- Source/Details/ASDataController.h | 15 ++- Source/Details/ASDataController.mm | 101 ++++++++++++--- Source/Private/ASCellNode+Internal.h | 16 ++- 15 files changed, 415 insertions(+), 105 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2311fb0e..e27918bad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * Add your own contributions to the next release on the line below this with your name. - [tvOS] Fixes errors when building against tvOS SDK [Alex Hill](https://github.com/alexhillc) [#728](https://github.com/TextureGroup/Texture/pull/728) - [ASDisplayNode] Add unit tests for layout z-order changes (with an open issue to fix). +- [ASWrapperCellNode] Introduce a new class allowing more control of UIKit passthrough cells. - [ASDisplayNode] Consolidate main thread initialization and allow apps to invoke it manually instead of +load. - [ASRunloopQueue] Introduce new runloop queue(ASCATransactionQueue) to coalesce Interface state update calls for view controller transitions. - [ASRangeController] Fix stability of "minimum" rangeMode if the app has more than one layout before scrolling. diff --git a/Source/ASCellNode.h b/Source/ASCellNode.h index b846e549d..87f72cf5c 100644 --- a/Source/ASCellNode.h +++ b/Source/ASCellNode.h @@ -221,7 +221,7 @@ typedef NS_ENUM(NSUInteger, ASCellNodeVisibilityEvent) { - (instancetype)initWithViewBlock:(ASDisplayNodeViewBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable; -- (void)setLayerBacked:(BOOL)layerBacked AS_UNAVAILABLE("ASCellNode does not support layer-backing"); +- (void)setLayerBacked:(BOOL)layerBacked AS_UNAVAILABLE("ASCellNode does not support layer-backing, although subnodes may be layer-backed."); @end diff --git a/Source/ASCellNode.mm b/Source/ASCellNode.mm index cd3bed447..c93da2db6 100644 --- a/Source/ASCellNode.mm +++ b/Source/ASCellNode.mm @@ -352,6 +352,25 @@ - (BOOL)supportsLayerBacking return NO; } +- (BOOL)shouldUseUIKitCell +{ + return NO; +} + +@end + + +#pragma mark - +#pragma mark ASWrapperCellNode + +// TODO: Consider if other calls, such as willDisplayCell, should be bridged to this class. +@implementation ASWrapperCellNode : ASCellNode + +- (BOOL)shouldUseUIKitCell +{ + return YES; +} + @end diff --git a/Source/ASCollectionNode+Beta.h b/Source/ASCollectionNode+Beta.h index 952227896..2c8163b71 100644 --- a/Source/ASCollectionNode+Beta.h +++ b/Source/ASCollectionNode+Beta.h @@ -57,6 +57,19 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign) BOOL usesSynchronousDataLoading; +/** + * Returns YES if the ASCollectionNode contents are completely synchronized with the underlying collection-view layout. + */ +@property (nonatomic, readonly, getter=isSynchronized) BOOL synchronized; + +/** + * Schedules a block to be performed (on the main thread) as soon as the completion block is called + * on performBatchUpdates:. + * + * When isSynchronized == YES, the block is run block immediately (before the method returns). + */ +- (void)onDidFinishSynchronizing:(void (^)(void))didFinishSynchronizing; + - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id)layoutFacilitator; - (instancetype)initWithLayoutDelegate:(id)layoutDelegate layoutFacilitator:(nullable id)layoutFacilitator; diff --git a/Source/ASCollectionNode.h b/Source/ASCollectionNode.h index 4a56217cf..b25372efc 100644 --- a/Source/ASCollectionNode.h +++ b/Source/ASCollectionNode.h @@ -109,6 +109,30 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, assign) BOOL allowsMultipleSelection; +/** + * A Boolean value that determines whether bouncing always occurs when vertical scrolling reaches the end of the content. + * The default value of this property is NO. + */ +@property (nonatomic, assign) BOOL alwaysBounceVertical; + +/** + * A Boolean value that determines whether bouncing always occurs when horizontal scrolling reaches the end of the content view. + * The default value of this property is NO. + */ +@property (nonatomic, assign) BOOL alwaysBounceHorizontal; + +/** + * A Boolean value that controls whether the vertical scroll indicator is visible. + * The default value of this property is YES. + */ +@property (nonatomic, assign) BOOL showsVerticalScrollIndicator; + +/** + * A Boolean value that controls whether the horizontal scroll indicator is visible. + * The default value of this property is NO. + */ +@property (nonatomic, assign) BOOL showsHorizontalScrollIndicator; + /** * The layout used to organize the node's items. * @@ -284,7 +308,7 @@ NS_ASSUME_NONNULL_BEGIN * * Calling -waitUntilAllUpdatesAreProcessed is one way to flush any pending update completion blocks. */ -- (void)onDidFinishProcessingUpdates:(nullable void (^)(void))didFinishProcessingUpdates; +- (void)onDidFinishProcessingUpdates:(void (^)(void))didFinishProcessingUpdates; /** * Blocks execution of the main thread until all section and item updates are committed to the view. This method must be called from the main thread. diff --git a/Source/ASCollectionNode.mm b/Source/ASCollectionNode.mm index d0cc5f208..a7f9e9bf7 100644 --- a/Source/ASCollectionNode.mm +++ b/Source/ASCollectionNode.mm @@ -49,9 +49,13 @@ @interface _ASCollectionPendingState : NSObject @property (nonatomic, assign) BOOL usesSynchronousDataLoading; @property (nonatomic, assign) CGFloat leadingScreensForBatching; @property (weak, nonatomic) id layoutInspector; +@property (nonatomic, assign) BOOL alwaysBounceVertical; +@property (nonatomic, assign) BOOL alwaysBounceHorizontal; @property (nonatomic, assign) UIEdgeInsets contentInset; @property (nonatomic, assign) CGPoint contentOffset; @property (nonatomic, assign) BOOL animatesContentOffset; +@property (nonatomic, assign) BOOL showsVerticalScrollIndicator; +@property (nonatomic, assign) BOOL showsHorizontalScrollIndicator; @end @implementation _ASCollectionPendingState @@ -203,13 +207,28 @@ - (void)didLoad view.allowsMultipleSelection = pendingState.allowsMultipleSelection; view.usesSynchronousDataLoading = pendingState.usesSynchronousDataLoading; view.layoutInspector = pendingState.layoutInspector; - view.contentInset = pendingState.contentInset; - + + // Only apply these flags if they're enabled; the view might come with them turned on. + if (pendingState.alwaysBounceVertical) { + view.alwaysBounceVertical = YES; + } + if (pendingState.alwaysBounceHorizontal) { + view.alwaysBounceHorizontal = YES; + } + + UIEdgeInsets contentInset = pendingState.contentInset; + if (!UIEdgeInsetsEqualToEdgeInsets(contentInset, UIEdgeInsetsZero)) { + view.contentInset = contentInset; + } + + CGPoint contentOffset = pendingState.contentOffset; + if (!CGPointEqualToPoint(contentOffset, CGPointZero)) { + [view setContentOffset:contentOffset animated:pendingState.animatesContentOffset]; + } + if (pendingState.rangeMode != ASLayoutRangeModeUnspecified) { [view.rangeController updateCurrentRangeWithMode:pendingState.rangeMode]; } - - [view setContentOffset:pendingState.contentOffset animated:pendingState.animatesContentOffset]; // Don't need to set collectionViewLayout to the view as the layout was already used to init the view in view block. } @@ -235,10 +254,11 @@ - (void)interfaceStateDidChange:(ASInterfaceState)newState fromState:(ASInterfac - (void)didEnterPreloadState { [super didEnterPreloadState]; + // ASCollectionNode is often nested inside of other collections. In this case, ASHierarchyState's RangeManaged bit will be set. // Intentionally allocate the view here and trigger a layout pass on it, which in turn will trigger the intial data load. // We can get rid of this call later when ASDataController, ASRangeController and ASCollectionLayout can operate without the view. // TODO (ASCL) If this node supports async layout, kick off the initial data load without allocating the view - if (CGRectEqualToRect(self.bounds, CGRectZero) == NO) { + if (ASHierarchyStateIncludesRangeManaged(self.hierarchyState) && CGRectEqualToRect(self.bounds, CGRectZero) == NO) { [[self view] layoutIfNeeded]; } } @@ -435,6 +455,82 @@ - (BOOL)allowsMultipleSelection } } +- (void)setAlwaysBounceVertical:(BOOL)alwaysBounceVertical +{ + if ([self pendingState]) { + _pendingState.alwaysBounceVertical = alwaysBounceVertical; + } else { + ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); + self.view.alwaysBounceVertical = alwaysBounceVertical; + } +} + +- (BOOL)alwaysBounceVertical +{ + if ([self pendingState]) { + return _pendingState.alwaysBounceVertical; + } else { + return self.view.alwaysBounceVertical; + } +} + +- (void)setAlwaysBounceHorizontal:(BOOL)alwaysBounceHorizontal +{ + if ([self pendingState]) { + _pendingState.alwaysBounceHorizontal = alwaysBounceHorizontal; + } else { + ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); + self.view.alwaysBounceHorizontal = alwaysBounceHorizontal; + } +} + +- (BOOL)alwaysBounceHorizontal +{ + if ([self pendingState]) { + return _pendingState.alwaysBounceHorizontal; + } else { + return self.view.alwaysBounceHorizontal; + } +} + +- (void)setShowsVerticalScrollIndicator:(BOOL)showsVerticalScrollIndicator +{ + if ([self pendingState]) { + _pendingState.showsVerticalScrollIndicator = showsVerticalScrollIndicator; + } else { + ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); + self.view.showsVerticalScrollIndicator = showsVerticalScrollIndicator; + } +} + +- (BOOL)showsVerticalScrollIndicator +{ + if ([self pendingState]) { + return _pendingState.showsVerticalScrollIndicator; + } else { + return self.view.showsVerticalScrollIndicator; + } +} + +- (void)setShowsHorizontalScrollIndicator:(BOOL)showsHorizontalScrollIndicator +{ + if ([self pendingState]) { + _pendingState.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator; + } else { + ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist"); + self.view.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator; + } +} + +- (BOOL)showsHorizontalScrollIndicator +{ + if ([self pendingState]) { + return _pendingState.showsHorizontalScrollIndicator; + } else { + return self.view.showsHorizontalScrollIndicator; + } +} + - (void)setCollectionViewLayout:(UICollectionViewLayout *)layout { if ([self pendingState]) { @@ -745,8 +841,11 @@ - (BOOL)isProcessingUpdates return (self.nodeLoaded ? [self.view isProcessingUpdates] : NO); } -- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion +- (void)onDidFinishProcessingUpdates:(void (^)())completion { + if (!completion) { + return; + } if (!self.nodeLoaded) { completion(); } else { @@ -754,6 +853,23 @@ - (void)onDidFinishProcessingUpdates:(nullable void (^)())completion } } +- (BOOL)isSynchronized +{ + return (self.nodeLoaded ? [self.view isSynchronized] : YES); +} + +- (void)onDidFinishSynchronizing:(void (^)())completion +{ + if (!completion) { + return; + } + if (!self.nodeLoaded) { + completion(); + } else { + [self.view onDidFinishSynchronizing:completion]; + } +} + - (void)waitUntilAllUpdatesAreProcessed { ASDisplayNodeAssertMainThread(); diff --git a/Source/ASCollectionView.h b/Source/ASCollectionView.h index 9f552e44c..594d88888 100644 --- a/Source/ASCollectionView.h +++ b/Source/ASCollectionView.h @@ -296,9 +296,15 @@ NS_ASSUME_NONNULL_BEGIN * See ASCollectionNode.h for full documentation of these methods. */ @property (nonatomic, readonly) BOOL isProcessingUpdates; -- (void)onDidFinishProcessingUpdates:(nullable void (^)(void))completion; +- (void)onDidFinishProcessingUpdates:(void (^)(void))completion; - (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use -[ASCollectionNode waitUntilAllUpdatesAreProcessed] instead."); +/** + * See ASCollectionNode.h for full documentation of these methods. + */ +@property (nonatomic, readonly, getter=isSynchronized) BOOL synchronized; +- (void)onDidFinishSynchronizing:(void (^)(void))completion; + /** * Registers the given kind of supplementary node for use in creating node-backed supplementary views. * diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 451322564..72d8b78d4 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -374,7 +374,7 @@ - (BOOL)isProcessingUpdates return [_dataController isProcessingUpdates]; } -- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion +- (void)onDidFinishProcessingUpdates:(void (^)())completion { [_dataController onDidFinishProcessingUpdates:completion]; } @@ -391,6 +391,16 @@ - (void)waitUntilAllUpdatesAreCommitted [_dataController waitUntilAllUpdatesAreProcessed]; } +- (BOOL)isSynchronized +{ + return [_dataController isSynchronized]; +} + +- (void)onDidFinishSynchronizing:(void (^)())completion +{ + [_dataController onDidFinishSynchronizing:completion]; +} + - (void)setDataSource:(id)dataSource { // UIKit can internally generate a call to this method upon changing the asyncDataSource; only assert for non-nil. We also allow this when we're doing interop. @@ -477,6 +487,7 @@ - (void)setAsyncDataSource:(id)asyncDataSource if (_layoutInspectorFlags.didChangeCollectionViewDataSource) { [layoutInspector didChangeCollectionViewDataSource:asyncDataSource]; } + [self _asyncDelegateOrDataSourceDidChange]; } - (id)asyncDelegate @@ -558,6 +569,15 @@ - (void)setAsyncDelegate:(id)asyncDelegate if (_layoutInspectorFlags.didChangeCollectionViewDelegate) { [layoutInspector didChangeCollectionViewDelegate:asyncDelegate]; } + [self _asyncDelegateOrDataSourceDidChange]; +} + +- (void)_asyncDelegateOrDataSourceDidChange +{ + ASDisplayNodeAssertMainThread(); + if (_asyncDataSource == nil && _asyncDelegate == nil) { + [_dataController clearData]; + } } - (void)setCollectionViewLayout:(nonnull UICollectionViewLayout *)collectionViewLayout @@ -644,18 +664,21 @@ - (BOOL)zeroContentInsets - (CGSize)sizeForElement:(ASCollectionElement *)element { ASDisplayNodeAssertMainThread(); - if (element == nil) { + ASCellNode *node = element.node; + if (element == nil || node == nil) { return CGSizeZero; } - ASCellNode *node = element.node; BOOL useUIKitCell = node.shouldUseUIKitCell; if (useUIKitCell) { - // In this case, we should use the exact value that was stashed earlier by calling sizeForItem:, referenceSizeFor*, etc. - // Although the node would use the preferredSize in layoutThatFits, we can skip this because there's no constrainedSize. - ASDisplayNodeAssert([node.superclass isSubclassOfClass:[ASCellNode class]] == NO, - @"Placeholder cells for UIKit passthrough should be generic ASCellNodes: %@", node); - return node.style.preferredSize; + ASWrapperCellNode *wrapperNode = (ASWrapperCellNode *)node; + if (wrapperNode.sizeForItemBlock) { + return wrapperNode.sizeForItemBlock(wrapperNode, element.constrainedSize.max); + } else { + // In this case, we should use the exact value that was stashed earlier by calling sizeForItem:, referenceSizeFor*, etc. + // Although the node would use the preferredSize in layoutThatFits, we can skip this because there's no constrainedSize. + return wrapperNode.style.preferredSize; + } } else { return [node layoutThatFits:element.constrainedSize].size; } @@ -781,7 +804,11 @@ - (void)invalidateFlowLayoutDelegateMetrics { // For UIKit passthrough cells of either type, re-fetch their sizes from the standard UIKit delegate methods. ASCellNode *node = element.node; if (node.shouldUseUIKitCell) { - NSIndexPath *indexPath = [self indexPathForNode:node]; + ASWrapperCellNode *wrapperNode = (ASWrapperCellNode *)node; + if (wrapperNode.sizeForItemBlock) { + continue; + } + NSIndexPath *indexPath = [_dataController.pendingMap indexPathForElement:element]; NSString *kind = [element supplementaryElementKind]; CGSize previousSize = node.style.preferredSize; CGSize size = [self _sizeForUIKitCellWithKind:kind atIndexPath:indexPath]; @@ -818,7 +845,7 @@ - (CGSize)_sizeForUIKitCellWithKind:(NSString *)kind atIndexPath:(NSIndexPath *) if (kind == nil) { ASDisplayNodeAssert(_asyncDataSourceFlags.interop, @"This code should not be called except for UIKit passthrough compatibility"); SEL sizeForItem = @selector(collectionView:layout:sizeForItemAtIndexPath:); - if ([_asyncDelegate respondsToSelector:sizeForItem]) { + if (indexPath && [_asyncDelegate respondsToSelector:sizeForItem]) { size = [(id)_asyncDelegate collectionView:self layout:l sizeForItemAtIndexPath:indexPath]; } else { size = ASFlowLayoutDefault(l, itemSize, CGSizeZero); @@ -826,7 +853,7 @@ - (CGSize)_sizeForUIKitCellWithKind:(NSString *)kind atIndexPath:(NSIndexPath *) } else if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { ASDisplayNodeAssert(_asyncDataSourceFlags.interopViewForSupplementaryElement, @"This code should not be called except for UIKit passthrough compatibility"); SEL sizeForHeader = @selector(collectionView:layout:referenceSizeForHeaderInSection:); - if ([_asyncDelegate respondsToSelector:sizeForHeader]) { + if (indexPath && [_asyncDelegate respondsToSelector:sizeForHeader]) { size = [(id)_asyncDelegate collectionView:self layout:l referenceSizeForHeaderInSection:indexPath.section]; } else { size = ASFlowLayoutDefault(l, headerReferenceSize, CGSizeZero); @@ -834,7 +861,7 @@ - (CGSize)_sizeForUIKitCellWithKind:(NSString *)kind atIndexPath:(NSIndexPath *) } else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { ASDisplayNodeAssert(_asyncDataSourceFlags.interopViewForSupplementaryElement, @"This code should not be called except for UIKit passthrough compatibility"); SEL sizeForFooter = @selector(collectionView:layout:referenceSizeForFooterInSection:); - if ([_asyncDelegate respondsToSelector:sizeForFooter]) { + if (indexPath && [_asyncDelegate respondsToSelector:sizeForFooter]) { size = [(id)_asyncDelegate collectionView:self layout:l referenceSizeForFooterInSection:indexPath.section]; } else { size = ASFlowLayoutDefault(l, footerReferenceSize, CGSizeZero); @@ -1105,9 +1132,12 @@ - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView UICollectionReusableView *view = nil; ASCollectionElement *element = [_dataController.visibleMap supplementaryElementOfKind:kind atIndexPath:indexPath]; ASCellNode *node = element.node; + ASWrapperCellNode *wrapperNode = (node.shouldUseUIKitCell ? (ASWrapperCellNode *)node : nil); + BOOL shouldDequeueExternally = _asyncDataSourceFlags.interopAlwaysDequeue || (_asyncDataSourceFlags.interopViewForSupplementaryElement && wrapperNode); - BOOL shouldDequeueExternally = _asyncDataSourceFlags.interopViewForSupplementaryElement && (_asyncDataSourceFlags.interopAlwaysDequeue || node.shouldUseUIKitCell); - if (shouldDequeueExternally) { + if (wrapperNode.viewForSupplementaryBlock) { + view = wrapperNode.viewForSupplementaryBlock(wrapperNode); + } else if (shouldDequeueExternally) { // This codepath is used for both IGListKit mode, and app-level UICollectionView interop. view = [(id)_asyncDataSource collectionView:collectionView viewForSupplementaryElementOfKind:kind atIndexPath:indexPath]; } else { @@ -1131,15 +1161,19 @@ - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cell UICollectionViewCell *cell = nil; ASCollectionElement *element = [_dataController.visibleMap elementForItemAtIndexPath:indexPath]; ASCellNode *node = element.node; + ASWrapperCellNode *wrapperNode = (node.shouldUseUIKitCell ? (ASWrapperCellNode *)node : nil); + BOOL shouldDequeueExternally = _asyncDataSourceFlags.interopAlwaysDequeue || (_asyncDataSourceFlags.interop && wrapperNode); - BOOL shouldDequeueExternally = _asyncDataSourceFlags.interopAlwaysDequeue || (_asyncDataSourceFlags.interop && node.shouldUseUIKitCell); - if (shouldDequeueExternally) { + if (wrapperNode.cellForItemBlock) { + cell = wrapperNode.cellForItemBlock(wrapperNode); + } else if (shouldDequeueExternally) { cell = [(id)_asyncDataSource collectionView:collectionView cellForItemAtIndexPath:indexPath]; } else { cell = [self dequeueReusableCellWithReuseIdentifier:kReuseIdentifier forIndexPath:indexPath]; } ASDisplayNodeAssert(element != nil, @"Element should exist. indexPath = %@, collectionDataSource = %@", indexPath, self); + ASDisplayNodeAssert(cell != nil, @"UICollectionViewCell must not be nil. indexPath = %@, collectionDataSource = %@", indexPath, self); if (_ASCollectionViewCell *asCell = ASDynamicCastStrict(cell, _ASCollectionViewCell)) { asCell.element = element; @@ -1828,39 +1862,32 @@ - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAt if (_asyncDataSourceFlags.collectionNodeNodeBlockForItem) { GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); block = [_asyncDataSource collectionNode:collectionNode nodeBlockForItemAtIndexPath:indexPath]; - } else if (_asyncDataSourceFlags.collectionNodeNodeForItem) { + } + if (!block && !cell && _asyncDataSourceFlags.collectionNodeNodeForItem) { GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); cell = [_asyncDataSource collectionNode:collectionNode nodeForItemAtIndexPath:indexPath]; + } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - } else if (_asyncDataSourceFlags.collectionViewNodeBlockForItem) { + if (!block && !cell && _asyncDataSourceFlags.collectionViewNodeBlockForItem) { block = [_asyncDataSource collectionView:self nodeBlockForItemAtIndexPath:indexPath]; - } else if (_asyncDataSourceFlags.collectionViewNodeForItem) { + } + if (!block && !cell && _asyncDataSourceFlags.collectionViewNodeForItem) { cell = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; } #pragma clang diagnostic pop - // Handle nil node block or cell - if (cell && [cell isKindOfClass:[ASCellNode class]]) { - block = ^{ - return cell; - }; - } - if (block == nil) { - if (_asyncDataSourceFlags.interop) { - CGSize preferredSize = [self _sizeForUIKitCellWithKind:nil atIndexPath:indexPath]; - block = ^{ - ASCellNode *node = [[ASCellNode alloc] init]; - node.shouldUseUIKitCell = YES; - node.style.preferredSize = preferredSize; - return node; - }; - } else { - ASDisplayNodeFailAssert(@"ASCollection could not get a node block for item at index path %@: %@, %@. If you are trying to display a UICollectionViewCell, make sure your dataSource conforms to the protocol!", indexPath, cell, block); - block = ^{ - return [[ASCellNode alloc] init]; - }; + if (cell == nil || ASDynamicCast(cell, ASCellNode) == nil) { + // In this case, either the client is expecting a UIKit passthrough cell to be created automatically, + // or it is an error. + if (_asyncDataSourceFlags.interop) { + cell = [[ASWrapperCellNode alloc] init]; + cell.style.preferredSize = [self _sizeForUIKitCellWithKind:nil atIndexPath:indexPath]; + } else { + ASDisplayNodeFailAssert(@"ASCollection could not get a node block for item at index path %@: %@, %@. If you are trying to display a UICollectionViewCell, make sure your dataSource conforms to the protocol!", indexPath, cell, block); + cell = [[ASCellNode alloc] init]; + } } } @@ -1868,17 +1895,18 @@ - (ASCellNodeBlock)dataController:(ASDataController *)dataController nodeBlockAt __weak __typeof__(self) weakSelf = self; return ^{ __typeof__(self) strongSelf = weakSelf; - ASCellNode *node = (block != nil ? block() : [[ASCellNode alloc] init]); + ASCellNode *node = (block ? block() : cell); + ASDisplayNodeAssert([node isKindOfClass:[ASCellNode class]], @"ASCollectionNode provided a non-ASCellNode! %@, %@", node, strongSelf); [node enterHierarchyState:ASHierarchyStateRangeManaged]; + if (node.interactionDelegate == nil) { node.interactionDelegate = strongSelf; } if (strongSelf.inverted) { - node.transform = CATransform3DMakeScale(1, -1, 1) ; + node.transform = CATransform3DMakeScale(1, -1, 1); } return node; }; - return block; } - (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section @@ -1923,45 +1951,50 @@ - (BOOL)dataController:(ASDataController *)dataController presentedSizeForElemen #pragma mark - ASDataControllerSource optional methods -- (ASCellNodeBlock)dataController:(ASDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +- (ASCellNodeBlock)dataController:(ASDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath shouldAsyncLayout:(BOOL *)shouldAsyncLayout { ASDisplayNodeAssertMainThread(); - ASCellNodeBlock nodeBlock = nil; - ASCellNode *node = nil; + ASCellNodeBlock block = nil; + ASCellNode *cell = nil; if (_asyncDataSourceFlags.collectionNodeNodeBlockForSupplementaryElement) { GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); - nodeBlock = [_asyncDataSource collectionNode:collectionNode nodeBlockForSupplementaryElementOfKind:kind atIndexPath:indexPath]; - } else if (_asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement) { + block = [_asyncDataSource collectionNode:collectionNode nodeBlockForSupplementaryElementOfKind:kind atIndexPath:indexPath]; + } + if (!block && !cell && _asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement) { GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; }); - node = [_asyncDataSource collectionNode:collectionNode nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath]; - } else if (_asyncDataSourceFlags.collectionViewNodeForSupplementaryElement) { + cell = [_asyncDataSource collectionNode:collectionNode nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath]; + } + if (!block && !cell && _asyncDataSourceFlags.collectionViewNodeForSupplementaryElement) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - node = [_asyncDataSource collectionView:self nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath]; + cell = [_asyncDataSource collectionView:self nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath]; #pragma clang diagnostic pop } - if (nodeBlock == nil) { - if (node) { - nodeBlock = ^{ return node; }; - } else { + if (block == nil) { + if (cell == nil || ASDynamicCast(cell, ASCellNode) == nil) { // In this case, the app code returned nil for the node and the nodeBlock. - // If the UIKit method is implemented, then we should use it. Otherwise the CGSizeZero default will cause UIKit to not show it. - CGSize preferredSize = CGSizeZero; + // If the UIKit method is implemented, then we should use a passthrough cell. + // Otherwise the CGSizeZero default will cause UIKit to not show it (so this isn't an error like the cellForItem case). + BOOL useUIKitCell = _asyncDataSourceFlags.interopViewForSupplementaryElement; if (useUIKitCell) { - preferredSize = [self _sizeForUIKitCellWithKind:kind atIndexPath:indexPath]; + cell = [[ASWrapperCellNode alloc] init]; + cell.style.preferredSize = [self _sizeForUIKitCellWithKind:kind atIndexPath:indexPath]; + } else { + cell = [[ASCellNode alloc] init]; } - nodeBlock = ^{ - ASCellNode *node = [[ASCellNode alloc] init]; - node.shouldUseUIKitCell = useUIKitCell; - node.style.preferredSize = preferredSize; - return node; - }; } + + // This condition is intended to run for either cells received from the datasource, or created just above. + if (cell.shouldUseUIKitCell) { + *shouldAsyncLayout = NO; + } + + block = ^{ return cell; }; } - return nodeBlock; + return block; } - (NSArray *)dataController:(ASDataController *)dataController supplementaryNodeKindsInSections:(NSIndexSet *)sections @@ -2097,6 +2130,15 @@ - (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet [_layoutFacilitator collectionViewWillPerformBatchUpdates]; __block NSUInteger numberOfUpdates = 0; + id completion = ^(BOOL finished){ + as_activity_scope(as_activity_create("Handle collection update completion", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT)); + as_log_verbose(ASCollectionLog(), "Update animation finished %{public}@", self.collectionNode); + // Flush any range changes that happened as part of the update animations ending. + [_rangeController updateIfNeeded]; + [self _scheduleCheckForBatchFetchingForNumberOfChanges:numberOfUpdates]; + [changeSet executeCompletionHandlerWithFinished:finished]; + }; + [self _superPerformBatchUpdates:^{ updates(); @@ -2129,14 +2171,8 @@ - (void)rangeController:(ASRangeController *)rangeController updateWithChangeSet [super insertItemsAtIndexPaths:change.indexPaths]; numberOfUpdates++; } - } completion:^(BOOL finished){ - as_activity_scope(as_activity_create("Handle collection update completion", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT)); - as_log_verbose(ASCollectionLog(), "Update animation finished %{public}@", self.collectionNode); - // Flush any range changes that happened as part of the update animations ending. - [_rangeController updateIfNeeded]; - [self _scheduleCheckForBatchFetchingForNumberOfChanges:numberOfUpdates]; - [changeSet executeCompletionHandlerWithFinished:finished]; - }]; + } completion:completion]; + as_log_debug(ASCollectionLog(), "Completed batch update %{public}@", self.collectionNode); // Flush any range changes that happened as part of submitting the update. diff --git a/Source/ASTableNode.h b/Source/ASTableNode.h index aae4f375d..dc13110dd 100644 --- a/Source/ASTableNode.h +++ b/Source/ASTableNode.h @@ -238,7 +238,7 @@ NS_ASSUME_NONNULL_BEGIN * * Calling -waitUntilAllUpdatesAreProcessed is one way to flush any pending update completion blocks. */ -- (void)onDidFinishProcessingUpdates:(nullable void (^)(void))didFinishProcessingUpdates; +- (void)onDidFinishProcessingUpdates:(void (^)(void))didFinishProcessingUpdates; /** * Blocks execution of the main thread until all section and item updates are committed to the view. This method must be called from the main thread. diff --git a/Source/ASTableNode.mm b/Source/ASTableNode.mm index c35f90a87..6420d8f70 100644 --- a/Source/ASTableNode.mm +++ b/Source/ASTableNode.mm @@ -776,8 +776,11 @@ - (BOOL)isProcessingUpdates return (self.nodeLoaded ? [self.view isProcessingUpdates] : NO); } -- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion +- (void)onDidFinishProcessingUpdates:(void (^)())completion { + if (!completion) { + return; + } if (!self.nodeLoaded) { completion(); } else { diff --git a/Source/ASTableView.h b/Source/ASTableView.h index d99ae5b21..38b5bffb1 100644 --- a/Source/ASTableView.h +++ b/Source/ASTableView.h @@ -219,7 +219,7 @@ NS_ASSUME_NONNULL_BEGIN * See ASTableNode.h for full documentation of these methods. */ @property (nonatomic, readonly) BOOL isProcessingUpdates; -- (void)onDidFinishProcessingUpdates:(nullable void (^)(void))completion; +- (void)onDidFinishProcessingUpdates:(void (^)(void))completion; - (void)waitUntilAllUpdatesAreCommitted ASDISPLAYNODE_DEPRECATED_MSG("Use -[ASTableNode waitUntilAllUpdatesAreProcessed] instead."); - (void)insertSections:(NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation ASDISPLAYNODE_DEPRECATED_MSG("Use ASTableNode method instead."); diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 5c8308444..58b3d033d 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -725,7 +725,7 @@ - (BOOL)isProcessingUpdates return [_dataController isProcessingUpdates]; } -- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion +- (void)onDidFinishProcessingUpdates:(void (^)())completion { [_dataController onDidFinishProcessingUpdates:completion]; } diff --git a/Source/Details/ASDataController.h b/Source/Details/ASDataController.h index acc98bcee..8792637ba 100644 --- a/Source/Details/ASDataController.h +++ b/Source/Details/ASDataController.h @@ -91,7 +91,7 @@ extern NSString * const ASCollectionInvalidUpdateException; - (NSUInteger)dataController:(ASDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section; -- (ASCellNodeBlock)dataController:(ASDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; +- (ASCellNodeBlock)dataController:(ASDataController *)dataController supplementaryNodeBlockOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath shouldAsyncLayout:(BOOL *)shouldAsyncLayout; /** The constrained size range for layout. Called only if no data controller layout delegate is provided. @@ -261,9 +261,15 @@ extern NSString * const ASCollectionInvalidUpdateException; * See ASCollectionNode.h for full documentation of these methods. */ @property (nonatomic, readonly) BOOL isProcessingUpdates; -- (void)onDidFinishProcessingUpdates:(nullable void (^)(void))completion; +- (void)onDidFinishProcessingUpdates:(void (^)(void))completion; - (void)waitUntilAllUpdatesAreProcessed; +/** + * See ASCollectionNode.h for full documentation of these methods. + */ +@property (nonatomic, readonly, getter=isSynchronized) BOOL synchronized; +- (void)onDidFinishSynchronizing:(void (^)(void))completion; + /** * Notifies the data controller object that its environment has changed. The object will request its environment delegate for new information * and propagate the information to all visible elements, including ones that are being prepared in background. @@ -274,6 +280,11 @@ extern NSString * const ASCollectionInvalidUpdateException; */ - (void)environmentDidChange; +/** + * Reset visibleMap and pendingMap when asyncDataSource and asyncDelegate of collection view become nil. + */ +- (void)clearData; + @end NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASDataController.mm b/Source/Details/ASDataController.mm index 512b844fa..877904769 100644 --- a/Source/Details/ASDataController.mm +++ b/Source/Details/ASDataController.mm @@ -17,6 +17,8 @@ #import +#include + #import #import #import @@ -54,6 +56,8 @@ typedef dispatch_block_t ASDataControllerCompletionBlock; +typedef void (^ASDataControllerSynchronizationBlock)(); + @interface ASDataController () { id _layoutDelegate; @@ -65,10 +69,14 @@ @interface ASDataController () { ASMainSerialQueue *_mainSerialQueue; dispatch_queue_t _editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. - dispatch_group_t _editingTransactionGroup; // Group of all edit transaction blocks. Useful for waiting. + dispatch_group_t _editingTransactionGroup; // Group of all edit transaction blocks. Useful for waiting. + std::atomic _editingTransactionGroupCount; BOOL _initialReloadDataHasBeenCalled; + BOOL _synchronized; + NSMutableSet *_onDidFinishSynchronizingBlocks; + struct { unsigned int supplementaryNodeKindsInSections:1; unsigned int supplementaryNodesOfKindInSection:1; @@ -98,7 +106,7 @@ - (instancetype)initWithDataSource:(id)dataSource node:( _dataSourceFlags.supplementaryNodeKindsInSections = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodeKindsInSections:)]; _dataSourceFlags.supplementaryNodesOfKindInSection = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodesOfKind:inSection:)]; - _dataSourceFlags.supplementaryNodeBlockOfKindAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]; + _dataSourceFlags.supplementaryNodeBlockOfKindAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:shouldAsyncLayout:)]; _dataSourceFlags.constrainedSizeForNodeAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:constrainedSizeForNodeAtIndexPath:)]; _dataSourceFlags.constrainedSizeForSupplementaryNodeOfKindAtIndexPath = [_dataSource respondsToSelector:@selector(dataController:constrainedSizeForSupplementaryNodeOfKind:atIndexPath:)]; _dataSourceFlags.contextForSection = [_dataSource respondsToSelector:@selector(dataController:contextForSection:)]; @@ -112,6 +120,9 @@ - (instancetype)initWithDataSource:(id)dataSource node:( _nextSectionID = 0; _mainSerialQueue = [[ASMainSerialQueue alloc] init]; + + _synchronized = YES; + _onDidFinishSynchronizingBlocks = [NSMutableSet set]; const char *queueName = [[NSString stringWithFormat:@"org.AsyncDisplayKit.ASDataController.editingTransactionQueue:%p", self] cStringUsingEncoding:NSASCIIStringEncoding]; _editingTransactionQueue = dispatch_queue_create(queueName, DISPATCH_QUEUE_SERIAL); @@ -352,7 +363,8 @@ - (void)_insertElementsIntoMap:(ASMutableElementMap *)map nodeBlock = [dataSource dataController:self nodeBlockAtIndexPath:indexPath]; } } else { - nodeBlock = [dataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath]; + BOOL shouldAsyncLayout = YES; + nodeBlock = [dataSource dataController:self supplementaryNodeBlockOfKind:kind atIndexPath:indexPath shouldAsyncLayout:&shouldAsyncLayout]; } ASSizeRange constrainedSize = ASSizeRangeUnconstrained; @@ -440,35 +452,71 @@ - (void)waitUntilAllUpdatesAreProcessed - (BOOL)isProcessingUpdates { ASDisplayNodeAssertMainThread(); - if (_mainSerialQueue.numberOfScheduledBlocks > 0) { - return YES; - } else if (dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_NOW) != 0) { - // After waiting for zero duration, a nonzero value is returned if blocks are still running. - return YES; - } - // Both the _mainSerialQueue and _editingTransactionQueue are drained; we are fully quiesced. - return NO; +#if ASDISPLAYNODE_ASSERTIONS_ENABLED + // Using dispatch_group_wait is much more expensive than our manually managed count, but it's crucial they always match. + BOOL editingTransactionQueueBusy = dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_NOW) != 0; + ASDisplayNodeAssert(editingTransactionQueueBusy == (_editingTransactionGroupCount > 0), + @"editingTransactionQueueBusy = %@, but _editingTransactionGroupCount = %d !", + editingTransactionQueueBusy ? @"YES" : @"NO", (int)_editingTransactionGroupCount); +#endif + + return _mainSerialQueue.numberOfScheduledBlocks > 0 || _editingTransactionGroupCount > 0; } -- (void)onDidFinishProcessingUpdates:(nullable void (^)())completion +- (void)onDidFinishProcessingUpdates:(void (^)())completion { ASDisplayNodeAssertMainThread(); + if (!completion) { + return; + } if ([self isProcessingUpdates] == NO) { ASPerformBlockOnMainThread(completion); } else { dispatch_async(_editingTransactionQueue, ^{ // Retry the block. If we're done processing updates, it'll run immediately, otherwise // wait again for updates to quiesce completely. - [_mainSerialQueue performBlockOnMainThread:^{ + // Don't use _mainSerialQueue so that we don't affect -isProcessingUpdates. + dispatch_async(dispatch_get_main_queue(), ^{ [self onDidFinishProcessingUpdates:completion]; - }]; + }); }); } } +- (BOOL)isSynchronized { + return _synchronized; +} + +- (void)onDidFinishSynchronizing:(void (^)())completion { + ASDisplayNodeAssertMainThread(); + if (!completion) { + return; + } + if ([self isSynchronized]) { + ASPerformBlockOnMainThread(completion); + } else { + // Hang on to the completion block so that it gets called the next time view is synchronized to data. + [_onDidFinishSynchronizingBlocks addObject:[completion copy]]; + } +} + - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet { ASDisplayNodeAssertMainThread(); + + _synchronized = NO; + + [changeSet addCompletionHandler:^(BOOL finished) { + _synchronized = YES; + [self onDidFinishProcessingUpdates:^{ + if (_synchronized) { + for (ASDataControllerSynchronizationBlock block in _onDidFinishSynchronizingBlocks) { + block(); + } + [_onDidFinishSynchronizingBlocks removeAllObjects]; + } + }]; + }]; if (changeSet.includesReloadData) { if (_initialReloadDataHasBeenCalled) { @@ -558,6 +606,7 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet as_log_debug(ASCollectionLog(), "New content: %@", newMap.smallDescription); Class layoutDelegateClass = [self.layoutDelegate class]; + ++_editingTransactionGroupCount; dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{ __block __unused os_activity_scope_state_s preparationScope = {}; // unused if deployment target < iOS10 as_activity_scope_enter(as_activity_create("Prepare nodes for collection update", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT), &preparationScope); @@ -577,6 +626,7 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet self.visibleMap = newMap; }]; }]; + --_editingTransactionGroupCount; }; // Step 3: Call the layout delegate if possible. Otherwise, allocate and layout all elements @@ -584,9 +634,17 @@ - (void)updateWithChangeSet:(_ASHierarchyChangeSet *)changeSet [layoutDelegateClass calculateLayoutWithContext:layoutContext]; completion(); } else { - NSArray *elementsToProcess = ASArrayByFlatMapping(newMap, - ASCollectionElement *element, - (element.nodeIfAllocated.calculatedLayout == nil ? element : nil)); + NSMutableArray *elementsToProcess = [NSMutableArray array]; + for (ASCollectionElement *element in newMap) { + ASCellNode *nodeIfAllocated = element.nodeIfAllocated; + if (nodeIfAllocated.shouldUseUIKitCell) { + // If the node exists and we know it is a passthrough cell, we know it will never have a .calculatedLayout. + continue; + } else if (nodeIfAllocated.calculatedLayout == nil) { + // If the node hasn't been allocated, or it doesn't have a valid layout, let's process it. + [elementsToProcess addObject:element]; + } + } [self _allocateNodesFromElements:elementsToProcess completion:completion]; } }); @@ -822,6 +880,15 @@ - (void)environmentDidChange }); } +- (void)clearData +{ + ASDisplayNodeAssertMainThread(); + if (_initialReloadDataHasBeenCalled) { + [self waitUntilAllUpdatesAreProcessed]; + self.visibleMap = self.pendingMap = [[ASElementMap alloc] init]; + } +} + # pragma mark - Helper methods - (void)_scheduleBlockOnMainSerialQueue:(dispatch_block_t)block diff --git a/Source/Private/ASCellNode+Internal.h b/Source/Private/ASCellNode+Internal.h index 9135dd5fc..d23dc173d 100644 --- a/Source/Private/ASCellNode+Internal.h +++ b/Source/Private/ASCellNode+Internal.h @@ -63,7 +63,21 @@ NS_ASSUME_NONNULL_BEGIN @property (atomic, weak, nullable) id owningNode; -@property (nonatomic, assign) BOOL shouldUseUIKitCell; +@property (nonatomic, readonly) BOOL shouldUseUIKitCell; + +@end + +@class ASWrapperCellNode; + +typedef CGSize (^ASSizeForItemBlock)(ASWrapperCellNode *node, CGSize collectionSize); +typedef UICollectionViewCell * _Nonnull(^ASCellForItemBlock)(ASWrapperCellNode *node); +typedef UICollectionReusableView * _Nonnull(^ASViewForSupplementaryBlock)(ASWrapperCellNode *node); + +@interface ASWrapperCellNode : ASCellNode + +@property (nonatomic, copy, readonly) ASSizeForItemBlock sizeForItemBlock; +@property (nonatomic, copy, readonly) ASCellForItemBlock cellForItemBlock; +@property (nonatomic, copy, readonly) ASViewForSupplementaryBlock viewForSupplementaryBlock; @end From e6c98d364ff1633c12e8ee228771135e3955c765 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 16 Mar 2018 15:13:26 -0700 Subject: [PATCH 106/133] [NoCopyRendering] Use vm instead of malloc (#833) * [Contexts] Use mmap directly for possible perf gain and to tag the memory as CGImage * Wrap the mmap in an object * Go straight to dataprovider * Tweak it * Remove wrong comment * Finish that comment * Address warnings --- AsyncDisplayKit.xcodeproj/project.pbxproj | 10 ++- CHANGELOG.md | 7 +- Source/ASCGImageBuffer.h | 31 ++++++++ Source/ASCGImageBuffer.m | 92 +++++++++++++++++++++++ Source/Details/ASGraphicsContext.h | 2 +- Source/Details/ASGraphicsContext.m | 19 ++--- 6 files changed, 147 insertions(+), 14 deletions(-) create mode 100644 Source/ASCGImageBuffer.h create mode 100644 Source/ASCGImageBuffer.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 06db270dc..6cc4d105c 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -357,6 +357,8 @@ CC6AA2DB1E9F03B900978E87 /* ASDisplayNode+Ancestry.m in Sources */ = {isa = PBXBuildFile; fileRef = CC6AA2D91E9F03B900978E87 /* ASDisplayNode+Ancestry.m */; }; 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, ); }; }; + CC84C7F220474C5300A3851B /* ASCGImageBuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = CC84C7F020474C5300A3851B /* ASCGImageBuffer.h */; }; + CC84C7F320474C5300A3851B /* ASCGImageBuffer.m in Sources */ = {isa = PBXBuildFile; fileRef = CC84C7F120474C5300A3851B /* ASCGImageBuffer.m */; }; CC87BB951DA8193C0090E380 /* ASCellNode+Internal.h in Headers */ = {isa = PBXBuildFile; fileRef = CC87BB941DA8193C0090E380 /* ASCellNode+Internal.h */; settings = {ATTRIBUTES = (Private, ); }; }; CC8B05D61D73836400F54286 /* ASPerformanceTestContext.m in Sources */ = {isa = PBXBuildFile; fileRef = CC8B05D51D73836400F54286 /* ASPerformanceTestContext.m */; }; CC8B05D81D73979700F54286 /* ASTextNodePerformanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC8B05D71D73979700F54286 /* ASTextNodePerformanceTests.m */; }; @@ -853,6 +855,8 @@ 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 = ""; }; + CC84C7F020474C5300A3851B /* ASCGImageBuffer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASCGImageBuffer.h; sourceTree = ""; }; + CC84C7F120474C5300A3851B /* ASCGImageBuffer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASCGImageBuffer.m; sourceTree = ""; }; CC87BB941DA8193C0090E380 /* ASCellNode+Internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASCellNode+Internal.h"; sourceTree = ""; }; CC8B05D41D73836400F54286 /* ASPerformanceTestContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPerformanceTestContext.h; sourceTree = ""; }; CC8B05D51D73836400F54286 /* ASPerformanceTestContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPerformanceTestContext.m; sourceTree = ""; }; @@ -1078,6 +1082,8 @@ 058D09B1195D04C000B7D73C /* Source */ = { isa = PBXGroup; children = ( + CC84C7F020474C5300A3851B /* ASCGImageBuffer.h */, + CC84C7F120474C5300A3851B /* ASCGImageBuffer.m */, CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */, CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.m */, CCE04B1D1E313E99006AEBBB /* Collection Data Adapter */, @@ -1900,6 +1906,7 @@ DEC146B71C37A16A004A0EE7 /* ASCollectionInternal.h in Headers */, 68B8A4E21CBDB958007E4543 /* ASWeakProxy.h in Headers */, 9F98C0271DBE29FC00476D92 /* ASControlTargetAction.h in Headers */, + CC84C7F220474C5300A3851B /* ASCGImageBuffer.h in Headers */, 695943401D70815300B0EE1F /* ASDisplayNodeLayout.h in Headers */, 0442850E1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.h in Headers */, DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */, @@ -2045,7 +2052,7 @@ attributes = { CLASSPREFIX = AS; LastUpgradeCheck = 0820; - ORGANIZATIONNAME = Facebook; + ORGANIZATIONNAME = Pinterest; TargetAttributes = { 057D02BE1AC0A66700C7AC3C = { CreatedOnToolsVersion = 6.2; @@ -2411,6 +2418,7 @@ 697796611D8AC8D3007E93D7 /* ASLayoutSpec+Subclasses.mm in Sources */, B350623B1B010EFD0018CF92 /* NSMutableAttributedString+TextKitAdditions.m in Sources */, CCA282CD1E9EB73E0037E8B7 /* ASTipNode.m in Sources */, + CC84C7F320474C5300A3851B /* ASCGImageBuffer.m in Sources */, 044284FD1BAA365100D16268 /* UICollectionViewLayout+ASConvenience.m in Sources */, CC0F885B1E42807F00576FED /* ASCollectionViewFlowLayoutInspector.m in Sources */, 690ED5981E36D118000627C0 /* ASControlNode+tvOS.m in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index e27918bad..17d91da24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,10 +32,11 @@ - Make `ASCellNode` tint color apply to table view cell accessories. [Vladyslav Chapaev](https://github.com/ShogunPhyched) [#764](https://github.com/TextureGroup/Texture/pull/764) - Fix ASTextNode2 is accessing backgroundColor off main while sizing / layout is happening. [Michael Schneider](https://github.com/maicki) [#794](https://github.com/TextureGroup/Texture/pull/778/) - Pass scrollViewWillEndDragging delegation through in ASIGListAdapterDataSource for IGListKit integration. [#796](https://github.com/TextureGroup/Texture/pull/796) -- Fix UIResponder handling with view backing ASDisplayNode. [Michael Schneider](https://github.com/maicki) [#789] (https://github.com/TextureGroup/Texture/pull/789/) -- Optimized thread-local storage by replacing pthread_specific with C11 thread-local variables. [Adlai Holler](https://github.com/Adlai-Holler) [#811] (https://github.com/TextureGroup/Texture/pull/811/) -- Fixed a thread-sanitizer warning in ASTextNode. [Adlai Holler](https://github.com/Adlai-Holler) [#830] (https://github.com/TextureGroup/Texture/pull/830/) +- Fix UIResponder handling with view backing ASDisplayNode. [Michael Schneider](https://github.com/maicki) [#789](https://github.com/TextureGroup/Texture/pull/789/) +- Optimized thread-local storage by replacing pthread_specific with C11 thread-local variables. [Adlai Holler](https://github.com/Adlai-Holler) [#811](https://github.com/TextureGroup/Texture/pull/811/) +- Fixed a thread-sanitizer warning in ASTextNode. [Adlai Holler](https://github.com/Adlai-Holler) [#830](https://github.com/TextureGroup/Texture/pull/830/) - Fix ASTextNode2 handling background color incorrectly. [Adlai Holler](https://github.com/Adlai-Holler) [#831] (https://github.com/TextureGroup/Texture/pull/831/) +- [NoCopyRendering] Improved performance & fixed image memory not being tagged in Instruments. [Adlai Holler](https://github.com/Adlai-Holler) [#833](https://github.com/TextureGroup/Texture/pull/833/) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASCGImageBuffer.h b/Source/ASCGImageBuffer.h new file mode 100644 index 000000000..47d4b8d30 --- /dev/null +++ b/Source/ASCGImageBuffer.h @@ -0,0 +1,31 @@ +// +// ASCGImageBuffer.h +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +AS_SUBCLASSING_RESTRICTED +@interface ASCGImageBuffer : NSObject + +- (instancetype)initWithLength:(NSUInteger)length; + +@property (readonly) void *mutableBytes NS_RETURNS_INNER_POINTER; + +/// Don't do any drawing or call any methods after calling this. +- (CGDataProviderRef)createDataProviderAndInvalidate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ASCGImageBuffer.m b/Source/ASCGImageBuffer.m new file mode 100644 index 000000000..f1b9d961a --- /dev/null +++ b/Source/ASCGImageBuffer.m @@ -0,0 +1,92 @@ +// +// ASCGImageBuffer.m +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import "ASCGImageBuffer.h" + +#import +#import +#import +#import + +/** + * The behavior of this class is modeled on the private function + * _CGDataProviderCreateWithCopyOfData, which is the function used + * by CGBitmapContextCreateImage. + * + * If the buffer is larger than a page, we use mmap and mark it as + * read-only when they are finished drawing. Then we wrap the VM + * in an NSData + */ +@implementation ASCGImageBuffer { + BOOL _createdData; + BOOL _isVM; + NSUInteger _length; +} + +- (instancetype)initWithLength:(NSUInteger)length +{ + if (self = [super init]) { + _length = length; + _isVM = NO;//(length >= vm_page_size); + if (_isVM) { + _mutableBytes = mmap(NULL, length, PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, VM_MAKE_TAG(VM_MEMORY_COREGRAPHICS_DATA), 0); + if (_mutableBytes == MAP_FAILED) { + NSAssert(NO, @"Failed to map for CG image data."); + _isVM = NO; + } + } + + // Check the VM flag again because we may have failed above. + if (!_isVM) { + _mutableBytes = malloc(length); + } + } + return self; +} + +- (void)dealloc +{ + if (!_createdData) { + [ASCGImageBuffer deallocateBuffer:_mutableBytes length:_length isVM:_isVM]; + } +} + +- (CGDataProviderRef)createDataProviderAndInvalidate +{ + NSAssert(!_createdData, @"Should not create data provider from buffer multiple times."); + _createdData = YES; + + // Mark the pages as read-only. + if (_isVM) { + __unused kern_return_t result = vm_protect(mach_task_self(), (vm_address_t)_mutableBytes, _length, true, VM_PROT_READ); + NSAssert(result == noErr, @"Error marking buffer as read-only: %@", [NSError errorWithDomain:NSMachErrorDomain code:result userInfo:nil]); + } + + // Wrap in an NSData + BOOL isVM = _isVM; + NSData *d = [[NSData alloc] initWithBytesNoCopy:_mutableBytes length:_length deallocator:^(void * _Nonnull bytes, NSUInteger length) { + [ASCGImageBuffer deallocateBuffer:bytes length:length isVM:isVM]; + }]; + return CGDataProviderCreateWithCFData((__bridge CFDataRef)d); +} + ++ (void)deallocateBuffer:(void *)buf length:(NSUInteger)length isVM:(BOOL)isVM +{ + if (isVM) { + __unused kern_return_t result = vm_deallocate(mach_task_self(), (vm_address_t)buf, length); + NSAssert(result == noErr, @"Failed to unmap cg image buffer: %@", [NSError errorWithDomain:NSMachErrorDomain code:result userInfo:nil]); + } else { + free(buf); + } +} + +@end diff --git a/Source/Details/ASGraphicsContext.h b/Source/Details/ASGraphicsContext.h index 26183d595..b7f808c97 100644 --- a/Source/Details/ASGraphicsContext.h +++ b/Source/Details/ASGraphicsContext.h @@ -52,7 +52,7 @@ extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGF * * Behavior is the same as UIGraphicsGetImageFromCurrentImageContext followed by UIGraphicsEndImageContext. */ -extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext(void); +extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext(void) NS_RETURNS_RETAINED; /** * Call this if you want to end the current context without making an image. diff --git a/Source/Details/ASGraphicsContext.m b/Source/Details/ASGraphicsContext.m index 3aaa14c09..70294d10a 100644 --- a/Source/Details/ASGraphicsContext.m +++ b/Source/Details/ASGraphicsContext.m @@ -11,6 +11,7 @@ // #import "ASGraphicsContext.h" +#import #import #import #import @@ -105,12 +106,13 @@ extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGF // We create our own buffer, and wrap the context around that. This way we can prevent // the copy that usually gets made when you form a CGImage from the context. - NSMutableData *data = [[NSMutableData alloc] initWithLength:bufferSize]; - CGContextRef context = CGBitmapContextCreate(data.mutableBytes, intWidth, intHeight, bitsPerComponent, bytesPerRow, colorspace, bitmapInfo); + ASCGImageBuffer *buffer = [[ASCGImageBuffer alloc] initWithLength:bufferSize]; + + CGContextRef context = CGBitmapContextCreate(buffer.mutableBytes, intWidth, intHeight, bitsPerComponent, bytesPerRow, colorspace, bitmapInfo); // Transfer ownership of the data to the context. So that if the context // is destroyed before we create an image from it, the data will be released. - objc_setAssociatedObject((__bridge id)context, &__contextDataAssociationKey, data, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject((__bridge id)context, &__contextDataAssociationKey, buffer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); // Set the CTM to account for iOS orientation & specified scale. // If only we could use CGContextSetBaseCTM. It doesn't @@ -128,7 +130,7 @@ extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGF CGContextRelease(context); } -extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext() +extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext() NS_RETURNS_RETAINED { if (!ASNoCopyRenderingBlockAndCheckEnabled()) { UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); @@ -159,11 +161,10 @@ extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGF UIGraphicsEndImageContext(); }); - // Retrieve our data and wrap it in a CGDataProvider. - // Don't worry, the provider doesn't copy the data – it just retains it. - NSMutableData *data = objc_getAssociatedObject((__bridge id)context, &__contextDataAssociationKey); - ASDisplayNodeCAssertNotNil(data, nil); - CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data); + // Retrieve our buffer and create a CGDataProvider from it. + ASCGImageBuffer *buffer = objc_getAssociatedObject((__bridge id)context, &__contextDataAssociationKey); + ASDisplayNodeCAssertNotNil(buffer, nil); + CGDataProviderRef provider = [buffer createDataProviderAndInvalidate]; // Create the CGImage. Options taken from CGBitmapContextCreateImage. CGImageRef cgImg = CGImageCreate(CGBitmapContextGetWidth(context), CGBitmapContextGetHeight(context), CGBitmapContextGetBitsPerComponent(context), CGBitmapContextGetBitsPerPixel(context), CGBitmapContextGetBytesPerRow(context), imageColorSpace, CGBitmapContextGetBitmapInfo(context), provider, NULL, true, kCGRenderingIntentDefault); From 15b695c917a0dba7d5bdd77b5bd21b4556bc4a7c Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Fri, 16 Mar 2018 15:21:38 -0700 Subject: [PATCH 107/133] Put back a flag that I removed (#839) --- Source/ASCGImageBuffer.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ASCGImageBuffer.m b/Source/ASCGImageBuffer.m index f1b9d961a..215dbc696 100644 --- a/Source/ASCGImageBuffer.m +++ b/Source/ASCGImageBuffer.m @@ -36,7 +36,7 @@ - (instancetype)initWithLength:(NSUInteger)length { if (self = [super init]) { _length = length; - _isVM = NO;//(length >= vm_page_size); + _isVM = (length >= vm_page_size); if (_isVM) { _mutableBytes = mmap(NULL, length, PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, VM_MAKE_TAG(VM_MEMORY_COREGRAPHICS_DATA), 0); if (_mutableBytes == MAP_FAILED) { From 0b101135ba9033f7dd48889f25b2b99087034928 Mon Sep 17 00:00:00 2001 From: Michael Schneider Date: Fri, 16 Mar 2018 20:08:19 -0700 Subject: [PATCH 108/133] Handle nil backgroundColor in ASTextNode2 #trivial (#841) * Handle nil backgroundColor * Small improvement --- Source/ASTextNode2.mm | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Source/ASTextNode2.mm b/Source/ASTextNode2.mm index 35b06026a..39553c538 100644 --- a/Source/ASTextNode2.mm +++ b/Source/ASTextNode2.mm @@ -360,10 +360,10 @@ - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer [self prepareAttributedString:mutableText]; return @{ - @"container": copiedContainer, - @"text": mutableText, - @"bgColor": self.backgroundColor - }; + @"container": copiedContainer, + @"text": mutableText, + @"bgColor": self.backgroundColor ?: [NSNull null] + }; } /** @@ -467,9 +467,13 @@ + (void)drawRect:(CGRect)bounds withParameters:(NSDictionary *)layoutDict isCanc } // Fill background color. + if (bgColor == (id)[NSNull null]) { + bgColor = nil; + } + // They may have already drawn into this context in the pre-context block // so unfortunately we have to use the normal blend mode, not copy. - if (CGColorGetAlpha(bgColor.CGColor) > 0) { + if (bgColor && CGColorGetAlpha(bgColor.CGColor) > 0) { [bgColor setFill]; UIRectFillUsingBlendMode(bounds, kCGBlendModeNormal); } From 5385d8b3edc305c6d90a6f2aea48d0c573158f04 Mon Sep 17 00:00:00 2001 From: Ariel Elkin Date: Tue, 20 Mar 2018 16:32:52 +0100 Subject: [PATCH 109/133] Update layout2-layout-element-properties.md (#844) --- docs/_docs/layout2-layout-element-properties.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_docs/layout2-layout-element-properties.md b/docs/_docs/layout2-layout-element-properties.md index 2efb71be6..21ed56b11 100755 --- a/docs/_docs/layout2-layout-element-properties.md +++ b/docs/_docs/layout2-layout-element-properties.md @@ -30,11 +30,11 @@ nextPage: layout2-api-sizing.html Additional space to place after this object in the stacking direction. - `BOOL .style.flexGrow` + `CGFloat .style.flexGrow` If the sum of childrens' stack dimensions is less than the minimum size, should this object grow? - `BOOL .style.flexShrink` + `CGFloat .style.flexShrink` If the sum of childrens' stack dimensions is greater than the maximum size, should this object shrink? From e954b1045a5fa58a6751cb1d82eb18915af371aa Mon Sep 17 00:00:00 2001 From: Yevgen Pogribnyi Date: Wed, 21 Mar 2018 14:44:30 +0200 Subject: [PATCH 110/133] [ASPrimitiveTraitCollection] Always treat preferredContentSize as a potential nil #trivial (#757) * Fix ASPrimitiveTraitCollection initialization on iOS 9. Add nil-value checks for preferredContentSizeCategory. * * Mark -[ASTraitCollection init] as deprecated. * Mark [ASViewController overrideDisplayTraitsWithWindowSize] as deprecated. Code review changes: * Remove unneeded nonnull annotations * Add null check in ASTraitCollection constructor implementation * Codestyle * Add some documentation about ASPrimitiveTraitCollection vs ASTraitCollection usage * Rename safeContentSizeCategory to AS_safeContentSizeCategory. Remove safePrimitiveContentSizeCategory in favour of AS_safeContentSizeCategory. --- Source/ASViewController.h | 2 +- Source/Details/ASTraitCollection.h | 36 ++++- Source/Details/ASTraitCollection.m | 216 +++++++++++++++++------------ 3 files changed, 161 insertions(+), 93 deletions(-) diff --git a/Source/ASViewController.h b/Source/ASViewController.h index bdd343b30..28fd134f4 100644 --- a/Source/ASViewController.h +++ b/Source/ASViewController.h @@ -63,7 +63,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Set this block to customize the ASDisplayTraits returned when the VC transitions to the given window size. */ -@property (nonatomic, copy) ASDisplayTraitsForTraitWindowSizeBlock overrideDisplayTraitsWithWindowSize; +@property (nonatomic, copy) ASDisplayTraitsForTraitWindowSizeBlock overrideDisplayTraitsWithWindowSize ASDISPLAYNODE_DEPRECATED_MSG("This property is actually never accessed inside the framework"); /** * @abstract Passthrough property to the the .interfaceState of the node. diff --git a/Source/Details/ASTraitCollection.h b/Source/Details/ASTraitCollection.h index 7f8ae3a47..1932b487a 100644 --- a/Source/Details/ASTraitCollection.h +++ b/Source/Details/ASTraitCollection.h @@ -58,6 +58,14 @@ extern ASPrimitiveContentSizeCategory ASPrimitiveContentSizeCategoryMake(UIConte #pragma mark - ASPrimitiveTraitCollection +/** + * @abstract This is an internal struct-representation of ASTraitCollection. + * + * @discussion This struct is for internal use only. Framework users should always use ASTraitCollection. + * + * If you use ASPrimitiveTraitCollection, please do make sure to initialize it with ASPrimitiveTraitCollectionMakeDefault() + * or ASPrimitiveTraitCollectionFromUITraitCollection(UITraitCollection*). + */ typedef struct ASPrimitiveTraitCollection { UIUserInterfaceSizeClass horizontalSizeClass; UIUserInterfaceSizeClass verticalSizeClass; @@ -112,17 +120,23 @@ ASDISPLAYNODE_EXTERN_C_END @protocol ASTraitEnvironment /** - * Returns a struct-representation of the environment's ASEnvironmentDisplayTraits. This only exists as a internal - * convenience method. Users should access the trait collections through the NSObject based asyncTraitCollection API + * @abstract Returns a struct-representation of the environment's ASEnvironmentDisplayTraits. + * + * @discussion This only exists as an internal convenience method. Users should access the trait collections through + * the NSObject based asyncTraitCollection API */ - (ASPrimitiveTraitCollection)primitiveTraitCollection; /** - * Sets a trait collection on this environment state. + * @abstract Sets a trait collection on this environment state. + * + * @discussion This only exists as an internal convenience method. Users should not override trait collection using it. + * Use [ASViewController overrideDisplayTraitsWithTraitCollection] block instead. */ - (void)setPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traitCollection; /** + * @abstract Returns the thread-safe UITraitCollection equivalent. */ - (ASTraitCollection *)asyncTraitCollection; @@ -179,8 +193,6 @@ AS_SUBCLASSING_RESTRICTED @property (nonatomic, assign, readonly) CGSize containerSize; -+ (ASTraitCollection *)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits; - + (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection containerSize:(CGSize)windowSize; @@ -211,13 +223,25 @@ AS_SUBCLASSING_RESTRICTED containerSize:(CGSize)windowSize; #endif -- (ASPrimitiveTraitCollection)primitiveTraitCollection; - (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection; @end +/** + * These are internal helper methods. Should never be called by the framework users. + */ +@interface ASTraitCollection (PrimitiveTraits) + ++ (ASTraitCollection *)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits; + +- (ASPrimitiveTraitCollection)primitiveTraitCollection; + +@end + @interface ASTraitCollection (Deprecated) +- (instancetype)init ASDISPLAYNODE_DEPRECATED_MSG("The default constructor of this class is going to become unavailable. Use other constructors instead."); + + (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass diff --git a/Source/Details/ASTraitCollection.m b/Source/Details/ASTraitCollection.m index 8755fc57c..194fe8278 100644 --- a/Source/Details/ASTraitCollection.m +++ b/Source/Details/ASTraitCollection.m @@ -32,6 +32,10 @@ ASDISPLAYNODE_INLINE UIContentSizeCategory AS_UIContentSizeCategoryUnspecified() } } +ASDISPLAYNODE_INLINE UIContentSizeCategory _Nonnull AS_safeContentSizeCategory(UIContentSizeCategory _Nullable sizeCategory) { + return sizeCategory ? sizeCategory : AS_UIContentSizeCategoryUnspecified(); +} + ASPrimitiveContentSizeCategory ASPrimitiveContentSizeCategoryMake(UIContentSizeCategory sizeCategory) { if ([sizeCategory isEqualToString:UIContentSizeCategoryExtraSmall]) { return UIContentSizeCategoryExtraSmall; @@ -115,13 +119,15 @@ ASPrimitiveTraitCollection ASPrimitiveTraitCollectionFromUITraitCollection(UITra #if TARGET_OS_TV environmentTraitCollection.userInterfaceStyle = traitCollection.userInterfaceStyle; #endif + } else { + environmentTraitCollection.displayGamut = UIDisplayGamutSRGB; // We're on iOS 9 or lower, so this is not a P3 device. } return environmentTraitCollection; } BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTraitCollection lhs, ASPrimitiveTraitCollection rhs) { - UIContentSizeCategory leftSizeCategory = (UIContentSizeCategory)lhs.preferredContentSizeCategory; - UIContentSizeCategory rightSizeCategory = (UIContentSizeCategory)rhs.preferredContentSizeCategory; + UIContentSizeCategory leftSizeCategory = AS_safeContentSizeCategory(lhs.preferredContentSizeCategory); + UIContentSizeCategory rightSizeCategory = AS_safeContentSizeCategory(rhs.preferredContentSizeCategory); return lhs.verticalSizeClass == rhs.verticalSizeClass && @@ -230,7 +236,7 @@ BOOL ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(ASPrimitiveTr #if TARGET_OS_TV [props addObject:@{ @"userInterfaceStyle": AS_NSStringFromUIUserInterfaceStyle(traits.userInterfaceStyle) }]; #endif - [props addObject:@{ @"preferredContentSizeCategory": (UIContentSizeCategory)traits.preferredContentSizeCategory }]; + [props addObject:@{ @"preferredContentSizeCategory": AS_safeContentSizeCategory(traits.preferredContentSizeCategory) }]; [props addObject:@{ @"containerSize": NSStringFromCGSize(traits.containerSize) }]; return ASObjectDescriptionMakeWithoutObject(props); } @@ -249,7 +255,7 @@ - (instancetype)initWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontal forceTouchCapability:(UIForceTouchCapability)forceTouchCapability layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection userInterfaceStyle:(UIUserInterfaceStyle)userInterfaceStyle - preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + preferredContentSizeCategory:(UIContentSizeCategory _Nonnull)preferredContentSizeCategory containerSize:(CGSize)windowSize { self = [super init]; @@ -262,7 +268,7 @@ - (instancetype)initWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontal _forceTouchCapability = forceTouchCapability; _layoutDirection = layoutDirection; _userInterfaceStyle = userInterfaceStyle; - _preferredContentSizeCategory = preferredContentSizeCategory; + _preferredContentSizeCategory = AS_safeContentSizeCategory(preferredContentSizeCategory); // guard against misuse _containerSize = windowSize; } return self; @@ -276,7 +282,7 @@ + (instancetype)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass forceTouchCapability:(UIForceTouchCapability)forceTouchCapability layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection userInterfaceStyle:(UIUserInterfaceStyle)userInterfaceStyle - preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + preferredContentSizeCategory:(UIContentSizeCategory _Nonnull)preferredContentSizeCategory containerSize:(CGSize)windowSize { return [[self alloc] initWithHorizontalSizeClass:horizontalSizeClass @@ -300,7 +306,7 @@ - (instancetype)initWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontal userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom forceTouchCapability:(UIForceTouchCapability)forceTouchCapability layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection - preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + preferredContentSizeCategory:(UIContentSizeCategory _Nonnull)preferredContentSizeCategory containerSize:(CGSize)windowSize { self = [super init]; @@ -312,7 +318,7 @@ - (instancetype)initWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontal _userInterfaceIdiom = userInterfaceIdiom; _forceTouchCapability = forceTouchCapability; _layoutDirection = layoutDirection; - _preferredContentSizeCategory = preferredContentSizeCategory; + _preferredContentSizeCategory = AS_safeContentSizeCategory(preferredContentSizeCategory); // guard against misuse _containerSize = windowSize; } return self; @@ -325,7 +331,7 @@ + (instancetype)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom forceTouchCapability:(UIForceTouchCapability)forceTouchCapability layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection - preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory + preferredContentSizeCategory:(UIContentSizeCategory _Nonnull)preferredContentSizeCategory containerSize:(CGSize)windowSize { return [[self alloc] initWithHorizontalSizeClass:horizontalSizeClass @@ -341,63 +347,6 @@ + (instancetype)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass #endif -+ (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale - userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom - horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass - verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass - forceTouchCapability:(UIForceTouchCapability)forceTouchCapability - containerSize:(CGSize)windowSize -{ -#if TARGET_OS_TV - return [self traitCollectionWithHorizontalSizeClass:horizontalSizeClass - verticalSizeClass:verticalSizeClass - displayScale:displayScale - displayGamut:UIDisplayGamutUnspecified - userInterfaceIdiom:userInterfaceIdiom - forceTouchCapability:forceTouchCapability - layoutDirection:UITraitEnvironmentLayoutDirectionUnspecified - userInterfaceStyle:UIUserInterfaceStyleUnspecified - preferredContentSizeCategory:AS_UIContentSizeCategoryUnspecified() - containerSize:windowSize]; -#else - return [self traitCollectionWithHorizontalSizeClass:horizontalSizeClass - verticalSizeClass:verticalSizeClass - displayScale:displayScale - displayGamut:UIDisplayGamutUnspecified - userInterfaceIdiom:userInterfaceIdiom - forceTouchCapability:forceTouchCapability - layoutDirection:UITraitEnvironmentLayoutDirectionUnspecified - preferredContentSizeCategory:AS_UIContentSizeCategoryUnspecified() - containerSize:windowSize]; -#endif -} - -+ (instancetype)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits -{ -#if TARGET_OS_TV - return [self traitCollectionWithHorizontalSizeClass:traits.horizontalSizeClass - verticalSizeClass:traits.verticalSizeClass - displayScale:traits.displayScale - displayGamut:traits.displayGamut - userInterfaceIdiom:traits.userInterfaceIdiom - forceTouchCapability:traits.forceTouchCapability - layoutDirection:traits.layoutDirection - userInterfaceStyle:traits.userInterfaceStyle - preferredContentSizeCategory:(UIContentSizeCategory)traits.preferredContentSizeCategory - containerSize:traits.containerSize]; -#else - return [self traitCollectionWithHorizontalSizeClass:traits.horizontalSizeClass - verticalSizeClass:traits.verticalSizeClass - displayScale:traits.displayScale - displayGamut:traits.displayGamut - userInterfaceIdiom:traits.userInterfaceIdiom - forceTouchCapability:traits.forceTouchCapability - layoutDirection:traits.layoutDirection - preferredContentSizeCategory:(UIContentSizeCategory)traits.preferredContentSizeCategory - containerSize:traits.containerSize]; -#endif -} - + (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection containerSize:(CGSize)windowSize { @@ -409,7 +358,7 @@ + (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitC + (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection containerSize:(CGSize)windowSize - fallbackContentSizeCategory:(UIContentSizeCategory)fallbackContentSizeCategory + fallbackContentSizeCategory:(UIContentSizeCategory _Nonnull)fallbackContentSizeCategory { UIDisplayGamut displayGamut; UITraitEnvironmentLayoutDirection layoutDirection; @@ -425,7 +374,7 @@ + (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitC userInterfaceStyle = traitCollection.userInterfaceStyle; #endif } else { - displayGamut = UIDisplayGamutUnspecified; + displayGamut = UIDisplayGamutSRGB; // We're on iOS 9 or lower, so this is not a P3 device. layoutDirection = UITraitEnvironmentLayoutDirectionUnspecified; sizeCategory = fallbackContentSizeCategory; #if TARGET_OS_TV @@ -457,26 +406,12 @@ + (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitC #endif } -- (ASPrimitiveTraitCollection)primitiveTraitCollection -{ - return (ASPrimitiveTraitCollection) { - .horizontalSizeClass = self.horizontalSizeClass, - .verticalSizeClass = self.verticalSizeClass, - .displayScale = self.displayScale, - .displayGamut = self.displayGamut, - .userInterfaceIdiom = self.userInterfaceIdiom, - .forceTouchCapability = self.forceTouchCapability, - .layoutDirection = self.layoutDirection, - #if TARGET_OS_TV - .userInterfaceStyle = self.userInterfaceStyle, - #endif - .preferredContentSizeCategory = ASPrimitiveContentSizeCategoryMake(self.preferredContentSizeCategory), - .containerSize = self.containerSize, - }; -} - - (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection { + if (traitCollection == nil) { + return NO; + } + if (self == traitCollection) { return YES; } @@ -497,3 +432,112 @@ - (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection } @end + +@implementation ASTraitCollection (PrimitiveTraits) + ++ (instancetype)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits +{ +#if TARGET_OS_TV + return [self traitCollectionWithHorizontalSizeClass:traits.horizontalSizeClass + verticalSizeClass:traits.verticalSizeClass + displayScale:traits.displayScale + displayGamut:traits.displayGamut + userInterfaceIdiom:traits.userInterfaceIdiom + forceTouchCapability:traits.forceTouchCapability + layoutDirection:traits.layoutDirection + userInterfaceStyle:traits.userInterfaceStyle + preferredContentSizeCategory:AS_safeContentSizeCategory(traits.preferredContentSizeCategory) + containerSize:traits.containerSize]; +#else + return [self traitCollectionWithHorizontalSizeClass:traits.horizontalSizeClass + verticalSizeClass:traits.verticalSizeClass + displayScale:traits.displayScale + displayGamut:traits.displayGamut + userInterfaceIdiom:traits.userInterfaceIdiom + forceTouchCapability:traits.forceTouchCapability + layoutDirection:traits.layoutDirection + preferredContentSizeCategory:AS_safeContentSizeCategory(traits.preferredContentSizeCategory) + containerSize:traits.containerSize]; +#endif +} + +- (ASPrimitiveTraitCollection)primitiveTraitCollection +{ + return (ASPrimitiveTraitCollection) { + .horizontalSizeClass = self.horizontalSizeClass, + .verticalSizeClass = self.verticalSizeClass, + .displayScale = self.displayScale, + .displayGamut = self.displayGamut, + .userInterfaceIdiom = self.userInterfaceIdiom, + .forceTouchCapability = self.forceTouchCapability, + .layoutDirection = self.layoutDirection, +#if TARGET_OS_TV + .userInterfaceStyle = self.userInterfaceStyle, +#endif + .preferredContentSizeCategory = ASPrimitiveContentSizeCategoryMake(self.preferredContentSizeCategory), + .containerSize = self.containerSize, + }; +} + +@end + +@implementation ASTraitCollection (Deprecated) + +- (instancetype)init +{ +#if TARGET_OS_TV + return [self initWithHorizontalSizeClass:UIUserInterfaceSizeClassUnspecified + verticalSizeClass:UIUserInterfaceSizeClassUnspecified + displayScale:0 + displayGamut:UIDisplayGamutUnspecified + userInterfaceIdiom:UIUserInterfaceIdiomUnspecified + forceTouchCapability:UIForceTouchCapabilityUnknown + layoutDirection:UITraitEnvironmentLayoutDirectionUnspecified + userInterfaceStyle:UIUserInterfaceStyleUnspecified + preferredContentSizeCategory:AS_UIContentSizeCategoryUnspecified() + containerSize:CGSizeZero]; +#else + return [self initWithHorizontalSizeClass:UIUserInterfaceSizeClassUnspecified + verticalSizeClass:UIUserInterfaceSizeClassUnspecified + displayScale:0 + displayGamut:UIDisplayGamutUnspecified + userInterfaceIdiom:UIUserInterfaceIdiomUnspecified + forceTouchCapability:UIForceTouchCapabilityUnknown + layoutDirection:UITraitEnvironmentLayoutDirectionUnspecified + preferredContentSizeCategory:AS_UIContentSizeCategoryUnspecified() + containerSize:CGSizeZero]; +#endif +} + ++ (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale + userInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom + horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass + verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass + forceTouchCapability:(UIForceTouchCapability)forceTouchCapability + containerSize:(CGSize)windowSize +{ +#if TARGET_OS_TV + return [self traitCollectionWithHorizontalSizeClass:horizontalSizeClass + verticalSizeClass:verticalSizeClass + displayScale:displayScale + displayGamut:UIDisplayGamutUnspecified + userInterfaceIdiom:userInterfaceIdiom + forceTouchCapability:forceTouchCapability + layoutDirection:UITraitEnvironmentLayoutDirectionUnspecified + userInterfaceStyle:UIUserInterfaceStyleUnspecified + preferredContentSizeCategory:AS_UIContentSizeCategoryUnspecified() + containerSize:windowSize]; +#else + return [self traitCollectionWithHorizontalSizeClass:horizontalSizeClass + verticalSizeClass:verticalSizeClass + displayScale:displayScale + displayGamut:UIDisplayGamutUnspecified + userInterfaceIdiom:userInterfaceIdiom + forceTouchCapability:forceTouchCapability + layoutDirection:UITraitEnvironmentLayoutDirectionUnspecified + preferredContentSizeCategory:AS_UIContentSizeCategoryUnspecified() + containerSize:windowSize]; +#endif +} + +@end From 2b12654810fe579fa5909d20828574fe0a499ac0 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 21 Mar 2018 08:13:09 -0700 Subject: [PATCH 111/133] Use NS_RETURNS_RETAINED macro to save time (#843) * Use NS_RETURNS_RETAINED macro to save time * Update changelog and do the thing with the license headers --- CHANGELOG.md | 3 +- Source/ASDisplayNode.mm | 2 +- Source/ASMapNode.mm | 2 +- Source/ASMultiplexImageNode.h | 2 +- Source/ASMultiplexImageNode.mm | 2 +- Source/ASRunLoopQueue.h | 2 + Source/ASRunLoopQueue.mm | 4 +- Source/ASTextNode2.mm | 2 +- Source/Debug/AsyncDisplayKit+Debug.m | 6 +-- Source/Details/ASBasicImageDownloader.h | 1 + Source/Details/ASIntegerMap.h | 4 +- Source/Details/ASIntegerMap.mm | 6 +-- Source/Details/ASPINRemoteImageDownloader.h | 1 + Source/Details/ASPINRemoteImageDownloader.m | 2 +- Source/Details/ASPageTable.h | 6 +-- Source/Details/ASPageTable.m | 8 ++-- Source/Details/ASTraitCollection.h | 12 +++--- Source/Details/ASTraitCollection.m | 12 +++--- Source/Details/ASWeakProxy.h | 2 +- Source/Details/ASWeakProxy.m | 2 +- Source/Layout/ASAbsoluteLayoutSpec.h | 4 +- Source/Layout/ASAbsoluteLayoutSpec.mm | 4 +- Source/Layout/ASAsciiArtBoxCreator.m | 2 +- Source/Layout/ASBackgroundLayoutSpec.h | 2 +- Source/Layout/ASBackgroundLayoutSpec.mm | 2 +- Source/Layout/ASCenterLayoutSpec.h | 2 +- Source/Layout/ASCenterLayoutSpec.mm | 2 +- Source/Layout/ASCornerLayoutSpec.h | 2 +- Source/Layout/ASCornerLayoutSpec.mm | 2 +- Source/Layout/ASInsetLayoutSpec.h | 2 +- Source/Layout/ASInsetLayoutSpec.mm | 2 +- Source/Layout/ASLayout.h | 8 ++-- Source/Layout/ASLayout.mm | 8 ++-- Source/Layout/ASLayoutSpec.h | 4 +- Source/Layout/ASLayoutSpec.mm | 4 +- Source/Layout/ASOverlayLayoutSpec.h | 2 +- Source/Layout/ASOverlayLayoutSpec.mm | 2 +- Source/Layout/ASRatioLayoutSpec.h | 2 +- Source/Layout/ASRatioLayoutSpec.mm | 2 +- Source/Layout/ASRelativeLayoutSpec.h | 2 +- Source/Layout/ASRelativeLayoutSpec.mm | 2 +- Source/Layout/ASStackLayoutSpec.h | 10 ++--- Source/Layout/ASStackLayoutSpec.mm | 10 ++--- Source/Private/ASRectMap.h | 2 +- Source/Private/ASRectMap.mm | 2 +- .../TextExperiment/Component/ASTextInput.h | 28 ++++++++------ .../TextExperiment/Component/ASTextInput.m | 28 ++++++++------ .../TextExperiment/Component/ASTextLayout.h | 22 +++++++---- .../TextExperiment/Component/ASTextLayout.m | 22 +++++++---- .../TextExperiment/Component/ASTextLine.h | 20 ++++++---- .../TextExperiment/Component/ASTextLine.m | 22 +++++++---- .../TextExperiment/String/ASTextAttribute.h | 38 +++++++++++-------- .../TextExperiment/String/ASTextAttribute.m | 38 +++++++++++-------- Source/TextKit/ASTextKitComponents.h | 4 +- Source/TextKit/ASTextKitComponents.mm | 4 +- Source/TextKit/ASTextKitCoreTextAdditions.h | 2 +- Source/TextKit/ASTextKitCoreTextAdditions.m | 2 +- Source/UIImage+ASConvenience.h | 10 ++--- Source/UIImage+ASConvenience.m | 12 +++--- Source/_ASTransitionContext.h | 2 +- Source/_ASTransitionContext.m | 2 +- 61 files changed, 239 insertions(+), 184 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17d91da24..cc3991080 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,8 +35,9 @@ - Fix UIResponder handling with view backing ASDisplayNode. [Michael Schneider](https://github.com/maicki) [#789](https://github.com/TextureGroup/Texture/pull/789/) - Optimized thread-local storage by replacing pthread_specific with C11 thread-local variables. [Adlai Holler](https://github.com/Adlai-Holler) [#811](https://github.com/TextureGroup/Texture/pull/811/) - Fixed a thread-sanitizer warning in ASTextNode. [Adlai Holler](https://github.com/Adlai-Holler) [#830](https://github.com/TextureGroup/Texture/pull/830/) -- Fix ASTextNode2 handling background color incorrectly. [Adlai Holler](https://github.com/Adlai-Holler) [#831] (https://github.com/TextureGroup/Texture/pull/831/) +- Fix ASTextNode2 handling background color incorrectly. [Adlai Holler](https://github.com/Adlai-Holler) [#831](https://github.com/TextureGroup/Texture/pull/831/) - [NoCopyRendering] Improved performance & fixed image memory not being tagged in Instruments. [Adlai Holler](https://github.com/Adlai-Holler) [#833](https://github.com/TextureGroup/Texture/pull/833/) +- Use `NS_RETURNS_RETAINED` macro to make our methods a tiny bit faster. [Adlai Holler](https://github.com/Adlai-Holler) [#843](https://github.com/TextureGroup/Texture/pull/843/) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 26b8f91d8..60eea7fb2 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -462,7 +462,7 @@ - (void)_scheduleIvarsForMainDeallocation * * Result is of type NSValue<[Ivar]> */ -+ (NSValue * _Nonnull)_ivarsThatMayNeedMainDeallocation ++ (NSValue * _Nonnull)_ivarsThatMayNeedMainDeallocation NS_RETURNS_RETAINED { static NSCache *ivarsCache; static dispatch_once_t onceToken; diff --git a/Source/ASMapNode.mm b/Source/ASMapNode.mm index 4697a2c05..e3e23df15 100644 --- a/Source/ASMapNode.mm +++ b/Source/ASMapNode.mm @@ -263,7 +263,7 @@ - (void)takeSnapshot }]; } -+ (UIImage *)defaultPinImageWithCenterOffset:(CGPoint *)centerOffset ++ (UIImage *)defaultPinImageWithCenterOffset:(CGPoint *)centerOffset NS_RETURNS_RETAINED { static MKAnnotationView *pin; static dispatch_once_t onceToken; diff --git a/Source/ASMultiplexImageNode.h b/Source/ASMultiplexImageNode.h index b3aa331e7..e4349d8cc 100644 --- a/Source/ASMultiplexImageNode.h +++ b/Source/ASMultiplexImageNode.h @@ -270,7 +270,7 @@ didFinishDownloadingImageWithIdentifier:(ASImageIdentifier)imageIdentifier + (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode - options:(PHImageRequestOptions *)options AS_WARN_UNUSED_RESULT API_AVAILABLE(ios(8.0), tvos(10.0)); + options:(PHImageRequestOptions *)options NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT API_AVAILABLE(ios(8.0), tvos(10.0)); @end diff --git a/Source/ASMultiplexImageNode.mm b/Source/ASMultiplexImageNode.mm index 12a2647e1..9edf93b6a 100644 --- a/Source/ASMultiplexImageNode.mm +++ b/Source/ASMultiplexImageNode.mm @@ -894,7 +894,7 @@ - (void)_finishedLoadingImage:(UIImage *)image forIdentifier:(id)imageIdentifier @implementation NSURL (ASPhotosFrameworkURLs) -+ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(PHImageRequestOptions *)options ++ (NSURL *)URLWithAssetLocalIdentifier:(NSString *)assetLocalIdentifier targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(PHImageRequestOptions *)options NS_RETURNS_RETAINED { ASPhotosFrameworkImageRequest *request = [[ASPhotosFrameworkImageRequest alloc] initWithAssetIdentifier:assetLocalIdentifier]; request.options = options; diff --git a/Source/ASRunLoopQueue.h b/Source/ASRunLoopQueue.h index 02711910b..266cd71f3 100644 --- a/Source/ASRunLoopQueue.h +++ b/Source/ASRunLoopQueue.h @@ -68,6 +68,7 @@ AS_SUBCLASSING_RESTRICTED * Each node will only be called once per transaction commit to reflect interface change. */ @property (class, atomic, readonly) ASCATransactionQueue *sharedQueue; ++ (ASCATransactionQueue *)sharedQueue NS_RETURNS_RETAINED; - (void)enqueue:(id)object; @@ -83,6 +84,7 @@ AS_SUBCLASSING_RESTRICTED @interface ASDeallocQueue : NSObject @property (class, atomic, readonly) ASDeallocQueue *sharedDeallocationQueue; ++ (ASDeallocQueue *)sharedDeallocationQueue NS_RETURNS_RETAINED; - (void)test_drain; diff --git a/Source/ASRunLoopQueue.mm b/Source/ASRunLoopQueue.mm index 12265d1df..782618cc3 100644 --- a/Source/ASRunLoopQueue.mm +++ b/Source/ASRunLoopQueue.mm @@ -45,7 +45,7 @@ @implementation ASDeallocQueue { ASDN::RecursiveMutex _queueLock; } -+ (ASDeallocQueue *)sharedDeallocationQueue ++ (ASDeallocQueue *)sharedDeallocationQueue NS_RETURNS_RETAINED { static ASDeallocQueue *deallocQueue = nil; static dispatch_once_t onceToken; @@ -516,7 +516,7 @@ @implementation ASCATransactionQueue // and kASASCATransactionQueuePostOrder will apply interface change immediately. static int const kASASCATransactionQueuePostOrder = 3000000; -+ (ASCATransactionQueue *)sharedQueue ++ (ASCATransactionQueue *)sharedQueue NS_RETURNS_RETAINED { static dispatch_once_t onceToken; static ASCATransactionQueue *sharedQueue; diff --git a/Source/ASTextNode2.mm b/Source/ASTextNode2.mm index 39553c538..cced8e577 100644 --- a/Source/ASTextNode2.mm +++ b/Source/ASTextNode2.mm @@ -372,7 +372,7 @@ - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer * NOTE: Be careful to copy `text` if needed. */ + (ASTextLayout *)compatibleLayoutWithContainer:(ASTextContainer *)container - text:(NSAttributedString *)text + text:(NSAttributedString *)text NS_RETURNS_RETAINED { // Allocate layoutCacheLock on the heap to prevent destruction at app exit (https://github.com/TextureGroup/Texture/issues/136) diff --git a/Source/Debug/AsyncDisplayKit+Debug.m b/Source/Debug/AsyncDisplayKit+Debug.m index 3225700e0..add247b14 100644 --- a/Source/Debug/AsyncDisplayKit+Debug.m +++ b/Source/Debug/AsyncDisplayKit+Debug.m @@ -213,7 +213,7 @@ - (void)drawEdgeIfClippedWithEdges:(UIRectEdge)rectEdge color:(UIColor *)color b @interface _ASRangeDebugOverlayView : UIView -+ (instancetype)sharedInstance; ++ (instancetype)sharedInstance NS_RETURNS_RETAINED; - (void)addRangeController:(ASRangeController *)rangeController; @@ -311,7 +311,7 @@ + (UIWindow *)keyWindow return [[NSClassFromString(@"UIApplication") sharedApplication] keyWindow]; } -+ (instancetype)sharedInstance ++ (_ASRangeDebugOverlayView *)sharedInstance NS_RETURNS_RETAINED { static _ASRangeDebugOverlayView *__rangeDebugOverlay = nil; @@ -752,7 +752,7 @@ - (ASImageNode *)createRangeNodeWithColor:(UIColor *)color return rangeBarImageNode; } -+ (NSAttributedString *)whiteAttributedStringFromString:(NSString *)string withSize:(CGFloat)size ++ (NSAttributedString *)whiteAttributedStringFromString:(NSString *)string withSize:(CGFloat)size NS_RETURNS_RETAINED { NSDictionary *attributes = @{NSForegroundColorAttributeName : [UIColor whiteColor], NSFontAttributeName : [UIFont systemFontOfSize:size]}; diff --git a/Source/Details/ASBasicImageDownloader.h b/Source/Details/ASBasicImageDownloader.h index d1f8862d4..5d9a7b384 100644 --- a/Source/Details/ASBasicImageDownloader.h +++ b/Source/Details/ASBasicImageDownloader.h @@ -34,6 +34,7 @@ NS_ASSUME_NONNULL_BEGIN * @note It is strongly recommended you include PINRemoteImage and use @c ASPINRemoteImageDownloader instead. */ @property (class, readonly) ASBasicImageDownloader *sharedImageDownloader; ++ (ASBasicImageDownloader *)sharedImageDownloader NS_RETURNS_RETAINED; + (instancetype)new __attribute__((unavailable("+[ASBasicImageDownloader sharedImageDownloader] must be used."))); - (instancetype)init __attribute__((unavailable("+[ASBasicImageDownloader sharedImageDownloader] must be used."))); diff --git a/Source/Details/ASIntegerMap.h b/Source/Details/ASIntegerMap.h index 2b94a8fce..86e288942 100644 --- a/Source/Details/ASIntegerMap.h +++ b/Source/Details/ASIntegerMap.h @@ -29,7 +29,7 @@ AS_SUBCLASSING_RESTRICTED */ + (ASIntegerMap *)mapForUpdateWithOldCount:(NSInteger)oldCount deleted:(nullable NSIndexSet *)deleted - inserted:(nullable NSIndexSet *)inserted; + inserted:(nullable NSIndexSet *)inserted NS_RETURNS_RETAINED; /** * A singleton that maps each integer to itself. Its inverse is itself. @@ -37,6 +37,7 @@ AS_SUBCLASSING_RESTRICTED * Note: You cannot mutate this. */ @property (class, atomic, readonly) ASIntegerMap *identityMap; ++ (ASIntegerMap *)identityMap NS_RETURNS_RETAINED; /** * A singleton that returns NSNotFound for all keys. Its inverse is itself. @@ -44,6 +45,7 @@ AS_SUBCLASSING_RESTRICTED * Note: You cannot mutate this. */ @property (class, atomic, readonly) ASIntegerMap *emptyMap; ++ (ASIntegerMap *)emptyMap NS_RETURNS_RETAINED; /** * Retrieves the integer for a given key, or NSNotFound if the key is not found. diff --git a/Source/Details/ASIntegerMap.mm b/Source/Details/ASIntegerMap.mm index 4208b71a6..19f0bbe4a 100644 --- a/Source/Details/ASIntegerMap.mm +++ b/Source/Details/ASIntegerMap.mm @@ -31,7 +31,7 @@ @implementation ASIntegerMap { #pragma mark - Singleton -+ (ASIntegerMap *)identityMap ++ (ASIntegerMap *)identityMap NS_RETURNS_RETAINED { static ASIntegerMap *identityMap; static dispatch_once_t onceToken; @@ -43,7 +43,7 @@ + (ASIntegerMap *)identityMap return identityMap; } -+ (ASIntegerMap *)emptyMap ++ (ASIntegerMap *)emptyMap NS_RETURNS_RETAINED { static ASIntegerMap *emptyMap; static dispatch_once_t onceToken; @@ -55,7 +55,7 @@ + (ASIntegerMap *)emptyMap return emptyMap; } -+ (ASIntegerMap *)mapForUpdateWithOldCount:(NSInteger)oldCount deleted:(NSIndexSet *)deletions inserted:(NSIndexSet *)insertions ++ (ASIntegerMap *)mapForUpdateWithOldCount:(NSInteger)oldCount deleted:(NSIndexSet *)deletions inserted:(NSIndexSet *)insertions NS_RETURNS_RETAINED { if (oldCount == 0) { return ASIntegerMap.emptyMap; diff --git a/Source/Details/ASPINRemoteImageDownloader.h b/Source/Details/ASPINRemoteImageDownloader.h index abccd9f84..14b207dd9 100644 --- a/Source/Details/ASPINRemoteImageDownloader.h +++ b/Source/Details/ASPINRemoteImageDownloader.h @@ -35,6 +35,7 @@ NS_ASSUME_NONNULL_BEGIN * available. It uses PINRemoteImage's features to provide caching and progressive image downloads. */ @property (class, readonly) ASPINRemoteImageDownloader *sharedDownloader; ++ (ASPINRemoteImageDownloader *)sharedDownloader NS_RETURNS_RETAINED; /** diff --git a/Source/Details/ASPINRemoteImageDownloader.m b/Source/Details/ASPINRemoteImageDownloader.m index 3d050fd9e..9061026ec 100644 --- a/Source/Details/ASPINRemoteImageDownloader.m +++ b/Source/Details/ASPINRemoteImageDownloader.m @@ -114,7 +114,7 @@ @interface ASPINRemoteImageDownloader () @implementation ASPINRemoteImageDownloader -+ (ASPINRemoteImageDownloader *)sharedDownloader ++ (ASPINRemoteImageDownloader *)sharedDownloader NS_RETURNS_RETAINED { static dispatch_once_t onceToken = 0; diff --git a/Source/Details/ASPageTable.h b/Source/Details/ASPageTable.h index e295e0256..b512f0dc6 100644 --- a/Source/Details/ASPageTable.h +++ b/Source/Details/ASPageTable.h @@ -82,12 +82,12 @@ typedef ASPageTable *> AS /** * Creates a new page table with (NSMapTableStrongMemory | NSMapTableObjectPointerPersonality) for values. */ -+ (ASPageTable *)pageTableForStrongObjectPointers; ++ (ASPageTable *)pageTableForStrongObjectPointers NS_RETURNS_RETAINED; /** * Creates a new page table with (NSMapTableWeakMemory | NSMapTableObjectPointerPersonality) for values. */ -+ (ASPageTable *)pageTableForWeakObjectPointers; ++ (ASPageTable *)pageTableForWeakObjectPointers NS_RETURNS_RETAINED; /** * Builds a new page to layout attributes from the given layout attributes. @@ -98,7 +98,7 @@ typedef ASPageTable *> AS * * @param pageSize The size of each page. */ -+ (ASPageToLayoutAttributesTable *)pageTableWithLayoutAttributes:(id)layoutAttributesEnumerator contentSize:(CGSize)contentSize pageSize:(CGSize)pageSize; ++ (ASPageToLayoutAttributesTable *)pageTableWithLayoutAttributes:(id)layoutAttributesEnumerator contentSize:(CGSize)contentSize pageSize:(CGSize)pageSize NS_RETURNS_RETAINED; /** * Retrieves the object for a given page, or nil if the page is not found. diff --git a/Source/Details/ASPageTable.m b/Source/Details/ASPageTable.m index 5cbf758dd..6855b41b6 100644 --- a/Source/Details/ASPageTable.m +++ b/Source/Details/ASPageTable.m @@ -79,7 +79,7 @@ extern CGRect ASPageCoordinateGetPageRect(ASPageCoordinate pageCoordinate, CGSiz @implementation NSMapTable (ASPageTableMethods) -+ (instancetype)pageTableWithValuePointerFunctions:(NSPointerFunctions *)valueFuncs ++ (instancetype)pageTableWithValuePointerFunctions:(NSPointerFunctions *)valueFuncs NS_RETURNS_RETAINED { static NSPointerFunctions *pageCoordinatesFuncs; static dispatch_once_t onceToken; @@ -90,7 +90,7 @@ + (instancetype)pageTableWithValuePointerFunctions:(NSPointerFunctions *)valueFu return [[NSMapTable alloc] initWithKeyPointerFunctions:pageCoordinatesFuncs valuePointerFunctions:valueFuncs capacity:0]; } -+ (ASPageTable *)pageTableForStrongObjectPointers ++ (ASPageTable *)pageTableForStrongObjectPointers NS_RETURNS_RETAINED { static NSPointerFunctions *strongObjectPointerFuncs; static dispatch_once_t onceToken; @@ -100,7 +100,7 @@ + (ASPageTable *)pageTableForStrongObjectPointers return [self pageTableWithValuePointerFunctions:strongObjectPointerFuncs]; } -+ (ASPageTable *)pageTableForWeakObjectPointers ++ (ASPageTable *)pageTableForWeakObjectPointers NS_RETURNS_RETAINED { static NSPointerFunctions *weakObjectPointerFuncs; static dispatch_once_t onceToken; @@ -110,7 +110,7 @@ + (ASPageTable *)pageTableForWeakObjectPointers return [self pageTableWithValuePointerFunctions:weakObjectPointerFuncs]; } -+ (ASPageToLayoutAttributesTable *)pageTableWithLayoutAttributes:(id)layoutAttributesEnumerator contentSize:(CGSize)contentSize pageSize:(CGSize)pageSize ++ (ASPageToLayoutAttributesTable *)pageTableWithLayoutAttributes:(id)layoutAttributesEnumerator contentSize:(CGSize)contentSize pageSize:(CGSize)pageSize NS_RETURNS_RETAINED { ASPageToLayoutAttributesTable *result = [ASPageTable pageTableForStrongObjectPointers]; for (UICollectionViewLayoutAttributes *attrs in layoutAttributesEnumerator) { diff --git a/Source/Details/ASTraitCollection.h b/Source/Details/ASTraitCollection.h index 1932b487a..287bf5e43 100644 --- a/Source/Details/ASTraitCollection.h +++ b/Source/Details/ASTraitCollection.h @@ -194,11 +194,11 @@ AS_SUBCLASSING_RESTRICTED @property (nonatomic, assign, readonly) CGSize containerSize; + (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection - containerSize:(CGSize)windowSize; + containerSize:(CGSize)windowSize NS_RETURNS_RETAINED; + (ASTraitCollection *)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection containerSize:(CGSize)windowSize - fallbackContentSizeCategory:(UIContentSizeCategory)fallbackContentSizeCategory; + fallbackContentSizeCategory:(UIContentSizeCategory)fallbackContentSizeCategory NS_RETURNS_RETAINED; #if TARGET_OS_TV + (ASTraitCollection *)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass @@ -210,7 +210,7 @@ AS_SUBCLASSING_RESTRICTED layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection userInterfaceStyle:(UIUserInterfaceStyle)userInterfaceStyle preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory - containerSize:(CGSize)windowSize; + containerSize:(CGSize)windowSize NS_RETURNS_RETAINED; #else + (ASTraitCollection *)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass @@ -220,7 +220,7 @@ AS_SUBCLASSING_RESTRICTED forceTouchCapability:(UIForceTouchCapability)forceTouchCapability layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection preferredContentSizeCategory:(UIContentSizeCategory)preferredContentSizeCategory - containerSize:(CGSize)windowSize; + containerSize:(CGSize)windowSize NS_RETURNS_RETAINED; #endif - (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection; @@ -232,7 +232,7 @@ AS_SUBCLASSING_RESTRICTED */ @interface ASTraitCollection (PrimitiveTraits) -+ (ASTraitCollection *)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits; ++ (ASTraitCollection *)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits NS_RETURNS_RETAINED; - (ASPrimitiveTraitCollection)primitiveTraitCollection; @@ -248,7 +248,7 @@ AS_SUBCLASSING_RESTRICTED verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass forceTouchCapability:(UIForceTouchCapability)forceTouchCapability containerSize:(CGSize)windowSize - ASDISPLAYNODE_DEPRECATED_MSG("Use full version of this method instead."); + NS_RETURNS_RETAINED ASDISPLAYNODE_DEPRECATED_MSG("Use full version of this method instead."); @end diff --git a/Source/Details/ASTraitCollection.m b/Source/Details/ASTraitCollection.m index 194fe8278..9b1902c9c 100644 --- a/Source/Details/ASTraitCollection.m +++ b/Source/Details/ASTraitCollection.m @@ -283,7 +283,7 @@ + (instancetype)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection userInterfaceStyle:(UIUserInterfaceStyle)userInterfaceStyle preferredContentSizeCategory:(UIContentSizeCategory _Nonnull)preferredContentSizeCategory - containerSize:(CGSize)windowSize + containerSize:(CGSize)windowSize NS_RETURNS_RETAINED { return [[self alloc] initWithHorizontalSizeClass:horizontalSizeClass verticalSizeClass:verticalSizeClass @@ -332,7 +332,7 @@ + (instancetype)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass forceTouchCapability:(UIForceTouchCapability)forceTouchCapability layoutDirection:(UITraitEnvironmentLayoutDirection)layoutDirection preferredContentSizeCategory:(UIContentSizeCategory _Nonnull)preferredContentSizeCategory - containerSize:(CGSize)windowSize + containerSize:(CGSize)windowSize NS_RETURNS_RETAINED { return [[self alloc] initWithHorizontalSizeClass:horizontalSizeClass verticalSizeClass:verticalSizeClass @@ -348,7 +348,7 @@ + (instancetype)traitCollectionWithHorizontalSizeClass:(UIUserInterfaceSizeClass #endif + (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection - containerSize:(CGSize)windowSize + containerSize:(CGSize)windowSize NS_RETURNS_RETAINED { return [self traitCollectionWithUITraitCollection:traitCollection containerSize:windowSize @@ -358,7 +358,7 @@ + (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitC + (instancetype)traitCollectionWithUITraitCollection:(UITraitCollection *)traitCollection containerSize:(CGSize)windowSize - fallbackContentSizeCategory:(UIContentSizeCategory _Nonnull)fallbackContentSizeCategory + fallbackContentSizeCategory:(UIContentSizeCategory _Nonnull)fallbackContentSizeCategory NS_RETURNS_RETAINED { UIDisplayGamut displayGamut; UITraitEnvironmentLayoutDirection layoutDirection; @@ -435,7 +435,7 @@ - (BOOL)isEqualToTraitCollection:(ASTraitCollection *)traitCollection @implementation ASTraitCollection (PrimitiveTraits) -+ (instancetype)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits ++ (instancetype)traitCollectionWithASPrimitiveTraitCollection:(ASPrimitiveTraitCollection)traits NS_RETURNS_RETAINED { #if TARGET_OS_TV return [self traitCollectionWithHorizontalSizeClass:traits.horizontalSizeClass @@ -514,7 +514,7 @@ + (ASTraitCollection *)traitCollectionWithDisplayScale:(CGFloat)displayScale horizontalSizeClass:(UIUserInterfaceSizeClass)horizontalSizeClass verticalSizeClass:(UIUserInterfaceSizeClass)verticalSizeClass forceTouchCapability:(UIForceTouchCapability)forceTouchCapability - containerSize:(CGSize)windowSize + containerSize:(CGSize)windowSize NS_RETURNS_RETAINED { #if TARGET_OS_TV return [self traitCollectionWithHorizontalSizeClass:horizontalSizeClass diff --git a/Source/Details/ASWeakProxy.h b/Source/Details/ASWeakProxy.h index 978204fbf..4a2490513 100644 --- a/Source/Details/ASWeakProxy.h +++ b/Source/Details/ASWeakProxy.h @@ -35,6 +35,6 @@ AS_SUBCLASSING_RESTRICTED * * @return an instance of ASWeakProxy */ -+ (instancetype)weakProxyWithTarget:(id)target; ++ (instancetype)weakProxyWithTarget:(id)target NS_RETURNS_RETAINED; @end diff --git a/Source/Details/ASWeakProxy.m b/Source/Details/ASWeakProxy.m index 9db965541..721191bc0 100644 --- a/Source/Details/ASWeakProxy.m +++ b/Source/Details/ASWeakProxy.m @@ -29,7 +29,7 @@ - (instancetype)initWithTarget:(id)target return self; } -+ (instancetype)weakProxyWithTarget:(id)target ++ (instancetype)weakProxyWithTarget:(id)target NS_RETURNS_RETAINED { return [[ASWeakProxy alloc] initWithTarget:target]; } diff --git a/Source/Layout/ASAbsoluteLayoutSpec.h b/Source/Layout/ASAbsoluteLayoutSpec.h index 0c250ec68..1080980c9 100644 --- a/Source/Layout/ASAbsoluteLayoutSpec.h +++ b/Source/Layout/ASAbsoluteLayoutSpec.h @@ -41,12 +41,12 @@ NS_ASSUME_NONNULL_BEGIN @param sizing How much space the spec will take up @param children Children to be positioned at fixed positions */ -+ (instancetype)absoluteLayoutSpecWithSizing:(ASAbsoluteLayoutSpecSizing)sizing children:(NSArray> *)children AS_WARN_UNUSED_RESULT; ++ (instancetype)absoluteLayoutSpecWithSizing:(ASAbsoluteLayoutSpecSizing)sizing children:(NSArray> *)children NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; /** @param children Children to be positioned at fixed positions */ -+ (instancetype)absoluteLayoutSpecWithChildren:(NSArray> *)children AS_WARN_UNUSED_RESULT; ++ (instancetype)absoluteLayoutSpecWithChildren:(NSArray> *)children NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; @end diff --git a/Source/Layout/ASAbsoluteLayoutSpec.mm b/Source/Layout/ASAbsoluteLayoutSpec.mm index 96ab7a705..eede9ab76 100644 --- a/Source/Layout/ASAbsoluteLayoutSpec.mm +++ b/Source/Layout/ASAbsoluteLayoutSpec.mm @@ -28,12 +28,12 @@ @implementation ASAbsoluteLayoutSpec #pragma mark - Class -+ (instancetype)absoluteLayoutSpecWithChildren:(NSArray *)children ++ (instancetype)absoluteLayoutSpecWithChildren:(NSArray *)children NS_RETURNS_RETAINED { return [[self alloc] initWithChildren:children]; } -+ (instancetype)absoluteLayoutSpecWithSizing:(ASAbsoluteLayoutSpecSizing)sizing children:(NSArray> *)children ++ (instancetype)absoluteLayoutSpecWithSizing:(ASAbsoluteLayoutSpecSizing)sizing children:(NSArray> *)children NS_RETURNS_RETAINED { return [[self alloc] initWithSizing:sizing children:children]; } diff --git a/Source/Layout/ASAsciiArtBoxCreator.m b/Source/Layout/ASAsciiArtBoxCreator.m index 293127a64..e018978bd 100644 --- a/Source/Layout/ASAsciiArtBoxCreator.m +++ b/Source/Layout/ASAsciiArtBoxCreator.m @@ -35,7 +35,7 @@ @interface NSString(PIDebugBox) @implementation NSString(PIDebugBox) -+ (instancetype)debugbox_stringWithString:(NSString *)stringToRepeat repeatedCount:(NSUInteger)repeatCount ++ (instancetype)debugbox_stringWithString:(NSString *)stringToRepeat repeatedCount:(NSUInteger)repeatCount NS_RETURNS_RETAINED { NSMutableString *string = [[NSMutableString alloc] initWithCapacity:[stringToRepeat length] * repeatCount]; for (NSUInteger index = 0; index < repeatCount; index++) { diff --git a/Source/Layout/ASBackgroundLayoutSpec.h b/Source/Layout/ASBackgroundLayoutSpec.h index 882084d05..616d68fea 100644 --- a/Source/Layout/ASBackgroundLayoutSpec.h +++ b/Source/Layout/ASBackgroundLayoutSpec.h @@ -35,7 +35,7 @@ NS_ASSUME_NONNULL_BEGIN * @param child A child that is laid out to determine the size of this spec. * @param background A layoutElement object that is laid out behind the child. */ -+ (instancetype)backgroundLayoutSpecWithChild:(id)child background:(id)background AS_WARN_UNUSED_RESULT; ++ (instancetype)backgroundLayoutSpecWithChild:(id)child background:(id)background NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; @end diff --git a/Source/Layout/ASBackgroundLayoutSpec.mm b/Source/Layout/ASBackgroundLayoutSpec.mm index e8c50901d..1cdf270a7 100644 --- a/Source/Layout/ASBackgroundLayoutSpec.mm +++ b/Source/Layout/ASBackgroundLayoutSpec.mm @@ -28,7 +28,7 @@ @implementation ASBackgroundLayoutSpec #pragma mark - Class -+ (instancetype)backgroundLayoutSpecWithChild:(id)child background:(id)background; ++ (instancetype)backgroundLayoutSpecWithChild:(id)child background:(id)background NS_RETURNS_RETAINED { return [[self alloc] initWithChild:child background:background]; } diff --git a/Source/Layout/ASCenterLayoutSpec.h b/Source/Layout/ASCenterLayoutSpec.h index f51a44b3a..825b375fb 100644 --- a/Source/Layout/ASCenterLayoutSpec.h +++ b/Source/Layout/ASCenterLayoutSpec.h @@ -71,7 +71,7 @@ NS_ASSUME_NONNULL_BEGIN */ + (instancetype)centerLayoutSpecWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions sizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions - child:(id)child AS_WARN_UNUSED_RESULT; + child:(id)child NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; @end diff --git a/Source/Layout/ASCenterLayoutSpec.mm b/Source/Layout/ASCenterLayoutSpec.mm index 0017a2a8e..230768faa 100644 --- a/Source/Layout/ASCenterLayoutSpec.mm +++ b/Source/Layout/ASCenterLayoutSpec.mm @@ -42,7 +42,7 @@ - (instancetype)initWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)cen + (instancetype)centerLayoutSpecWithCenteringOptions:(ASCenterLayoutSpecCenteringOptions)centeringOptions sizingOptions:(ASCenterLayoutSpecSizingOptions)sizingOptions - child:(id)child + child:(id)child NS_RETURNS_RETAINED { return [[self alloc] initWithCenteringOptions:centeringOptions sizingOptions:sizingOptions child:child]; } diff --git a/Source/Layout/ASCornerLayoutSpec.h b/Source/Layout/ASCornerLayoutSpec.h index 23d09d017..cb5406611 100644 --- a/Source/Layout/ASCornerLayoutSpec.h +++ b/Source/Layout/ASCornerLayoutSpec.h @@ -49,7 +49,7 @@ NS_ASSUME_NONNULL_BEGIN @param location The corner position option. @return An ASCornerLayoutSpec object with a given child and an layoutElement that act as corner. */ -+ (instancetype)cornerLayoutSpecWithChild:(id )child corner:(id )corner location:(ASCornerLayoutLocation)location AS_WARN_UNUSED_RESULT; ++ (instancetype)cornerLayoutSpecWithChild:(id )child corner:(id )corner location:(ASCornerLayoutLocation)location NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; /** A layoutElement object that is laid out to a corner on the child. diff --git a/Source/Layout/ASCornerLayoutSpec.mm b/Source/Layout/ASCornerLayoutSpec.mm index d1104089b..d2b88c70b 100644 --- a/Source/Layout/ASCornerLayoutSpec.mm +++ b/Source/Layout/ASCornerLayoutSpec.mm @@ -65,7 +65,7 @@ - (instancetype)initWithChild:(id )child corner:(id )child corner:(id )corner location:(ASCornerLayoutLocation)location ++ (instancetype)cornerLayoutSpecWithChild:(id )child corner:(id )corner location:(ASCornerLayoutLocation)location NS_RETURNS_RETAINED { return [[self alloc] initWithChild:child corner:corner location:location]; } diff --git a/Source/Layout/ASInsetLayoutSpec.h b/Source/Layout/ASInsetLayoutSpec.h index f12e54ff1..61b37b5e6 100644 --- a/Source/Layout/ASInsetLayoutSpec.h +++ b/Source/Layout/ASInsetLayoutSpec.h @@ -44,7 +44,7 @@ NS_ASSUME_NONNULL_BEGIN @param insets The amount of space to inset on each side. @param child The wrapped child to inset. */ -+ (instancetype)insetLayoutSpecWithInsets:(UIEdgeInsets)insets child:(id)child AS_WARN_UNUSED_RESULT; ++ (instancetype)insetLayoutSpecWithInsets:(UIEdgeInsets)insets child:(id)child NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; @end diff --git a/Source/Layout/ASInsetLayoutSpec.mm b/Source/Layout/ASInsetLayoutSpec.mm index 5db1cf03e..23e0feace 100644 --- a/Source/Layout/ASInsetLayoutSpec.mm +++ b/Source/Layout/ASInsetLayoutSpec.mm @@ -59,7 +59,7 @@ - (instancetype)initWithInsets:(UIEdgeInsets)insets child:(id)c return self; } -+ (instancetype)insetLayoutSpecWithInsets:(UIEdgeInsets)insets child:(id)child ++ (instancetype)insetLayoutSpecWithInsets:(UIEdgeInsets)insets child:(id)child NS_RETURNS_RETAINED { return [[self alloc] initWithInsets:insets child:child]; } diff --git a/Source/Layout/ASLayout.h b/Source/Layout/ASLayout.h index fa1371985..81c88ddc0 100644 --- a/Source/Layout/ASLayout.h +++ b/Source/Layout/ASLayout.h @@ -108,7 +108,7 @@ ASDISPLAYNODE_EXTERN_C_END + (instancetype)layoutWithLayoutElement:(id)layoutElement size:(CGSize)size position:(CGPoint)position - sublayouts:(nullable NSArray *)sublayouts AS_WARN_UNUSED_RESULT; + sublayouts:(nullable NSArray *)sublayouts NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; /** * Convenience initializer that has CGPointNull position. @@ -122,7 +122,7 @@ ASDISPLAYNODE_EXTERN_C_END */ + (instancetype)layoutWithLayoutElement:(id)layoutElement size:(CGSize)size - sublayouts:(nullable NSArray *)sublayouts AS_WARN_UNUSED_RESULT; + sublayouts:(nullable NSArray *)sublayouts NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; /** * Convenience that has CGPointNull position and no sublayouts. @@ -133,11 +133,11 @@ ASDISPLAYNODE_EXTERN_C_END * @param size The size of this layout. */ + (instancetype)layoutWithLayoutElement:(id)layoutElement - size:(CGSize)size AS_WARN_UNUSED_RESULT; + size:(CGSize)size NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; /** * Traverses the existing layout tree and generates a new tree that represents only ASDisplayNode layouts */ -- (ASLayout *)filteredNodeLayoutTree AS_WARN_UNUSED_RESULT; +- (ASLayout *)filteredNodeLayoutTree NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; @end diff --git a/Source/Layout/ASLayout.mm b/Source/Layout/ASLayout.mm index f6fbe9127..1a1b419f4 100644 --- a/Source/Layout/ASLayout.mm +++ b/Source/Layout/ASLayout.mm @@ -166,7 +166,7 @@ - (instancetype)init + (instancetype)layoutWithLayoutElement:(id)layoutElement size:(CGSize)size position:(CGPoint)position - sublayouts:(nullable NSArray *)sublayouts + sublayouts:(nullable NSArray *)sublayouts NS_RETURNS_RETAINED { return [[self alloc] initWithLayoutElement:layoutElement size:size @@ -176,7 +176,7 @@ + (instancetype)layoutWithLayoutElement:(id)layoutElement + (instancetype)layoutWithLayoutElement:(id)layoutElement size:(CGSize)size - sublayouts:(nullable NSArray *)sublayouts + sublayouts:(nullable NSArray *)sublayouts NS_RETURNS_RETAINED { return [self layoutWithLayoutElement:layoutElement size:size @@ -184,7 +184,7 @@ + (instancetype)layoutWithLayoutElement:(id)layoutElement sublayouts:sublayouts]; } -+ (instancetype)layoutWithLayoutElement:(id)layoutElement size:(CGSize)size ++ (instancetype)layoutWithLayoutElement:(id)layoutElement size:(CGSize)size NS_RETURNS_RETAINED { return [self layoutWithLayoutElement:layoutElement size:size @@ -216,7 +216,7 @@ - (void)setRetainSublayoutLayoutElements:(BOOL)retainSublayoutLayoutElements #pragma mark - Layout Flattening -- (ASLayout *)filteredNodeLayoutTree +- (ASLayout *)filteredNodeLayoutTree NS_RETURNS_RETAINED { if (ASLayoutIsFlattened(self)) { // All flattened layouts must have this flag enabled diff --git a/Source/Layout/ASLayoutSpec.h b/Source/Layout/ASLayoutSpec.h index ac02bacd0..6a49792de 100644 --- a/Source/Layout/ASLayoutSpec.h +++ b/Source/Layout/ASLayoutSpec.h @@ -71,12 +71,12 @@ NS_ASSUME_NONNULL_BEGIN /* * Returns an ASWrapperLayoutSpec object with the given layoutElement as child. */ -+ (instancetype)wrapperWithLayoutElement:(id)layoutElement AS_WARN_UNUSED_RESULT; ++ (instancetype)wrapperWithLayoutElement:(id)layoutElement NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; /* * Returns an ASWrapperLayoutSpec object with the given layoutElements as children. */ -+ (instancetype)wrapperWithLayoutElements:(NSArray> *)layoutElements AS_WARN_UNUSED_RESULT; ++ (instancetype)wrapperWithLayoutElements:(NSArray> *)layoutElements NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; /* * Returns an ASWrapperLayoutSpec object initialized with the given layoutElement as child. diff --git a/Source/Layout/ASLayoutSpec.mm b/Source/Layout/ASLayoutSpec.mm index 76901d1ac..fa275d21d 100644 --- a/Source/Layout/ASLayoutSpec.mm +++ b/Source/Layout/ASLayoutSpec.mm @@ -265,7 +265,7 @@ - (NSString *)asciiArtName @implementation ASWrapperLayoutSpec -+ (instancetype)wrapperWithLayoutElement:(id)layoutElement ++ (instancetype)wrapperWithLayoutElement:(id)layoutElement NS_RETURNS_RETAINED { return [[self alloc] initWithLayoutElement:layoutElement]; } @@ -279,7 +279,7 @@ - (instancetype)initWithLayoutElement:(id)layoutElement return self; } -+ (instancetype)wrapperWithLayoutElements:(NSArray> *)layoutElements ++ (instancetype)wrapperWithLayoutElements:(NSArray> *)layoutElements NS_RETURNS_RETAINED { return [[self alloc] initWithLayoutElements:layoutElements]; } diff --git a/Source/Layout/ASOverlayLayoutSpec.h b/Source/Layout/ASOverlayLayoutSpec.h index 54324f4ed..086252075 100644 --- a/Source/Layout/ASOverlayLayoutSpec.h +++ b/Source/Layout/ASOverlayLayoutSpec.h @@ -35,7 +35,7 @@ NS_ASSUME_NONNULL_BEGIN * @param child A child that is laid out to determine the size of this spec. * @param overlay A layoutElement object that is laid out over the child. */ -+ (instancetype)overlayLayoutSpecWithChild:(id)child overlay:(id)overlay AS_WARN_UNUSED_RESULT; ++ (instancetype)overlayLayoutSpecWithChild:(id)child overlay:(id)overlay NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; @end diff --git a/Source/Layout/ASOverlayLayoutSpec.mm b/Source/Layout/ASOverlayLayoutSpec.mm index 9ac5d1e56..77f8239da 100644 --- a/Source/Layout/ASOverlayLayoutSpec.mm +++ b/Source/Layout/ASOverlayLayoutSpec.mm @@ -26,7 +26,7 @@ @implementation ASOverlayLayoutSpec #pragma mark - Class -+ (instancetype)overlayLayoutSpecWithChild:(id)child overlay:(id)overlay ++ (instancetype)overlayLayoutSpecWithChild:(id)child overlay:(id)overlay NS_RETURNS_RETAINED { return [[self alloc] initWithChild:child overlay:overlay]; } diff --git a/Source/Layout/ASRatioLayoutSpec.h b/Source/Layout/ASRatioLayoutSpec.h index f5338d4da..b6f9987b5 100644 --- a/Source/Layout/ASRatioLayoutSpec.h +++ b/Source/Layout/ASRatioLayoutSpec.h @@ -43,7 +43,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) CGFloat ratio; -+ (instancetype)ratioLayoutSpecWithRatio:(CGFloat)ratio child:(id)child AS_WARN_UNUSED_RESULT; ++ (instancetype)ratioLayoutSpecWithRatio:(CGFloat)ratio child:(id)child NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; @end diff --git a/Source/Layout/ASRatioLayoutSpec.mm b/Source/Layout/ASRatioLayoutSpec.mm index 294d11064..aec3a9c8f 100644 --- a/Source/Layout/ASRatioLayoutSpec.mm +++ b/Source/Layout/ASRatioLayoutSpec.mm @@ -35,7 +35,7 @@ @implementation ASRatioLayoutSpec #pragma mark - Lifecycle -+ (instancetype)ratioLayoutSpecWithRatio:(CGFloat)ratio child:(id)child ++ (instancetype)ratioLayoutSpecWithRatio:(CGFloat)ratio child:(id)child NS_RETURNS_RETAINED { return [[self alloc] initWithRatio:ratio child:child]; } diff --git a/Source/Layout/ASRelativeLayoutSpec.h b/Source/Layout/ASRelativeLayoutSpec.h index 27860b00e..cd3f14c9f 100644 --- a/Source/Layout/ASRelativeLayoutSpec.h +++ b/Source/Layout/ASRelativeLayoutSpec.h @@ -74,7 +74,7 @@ NS_ASSUME_NONNULL_BEGIN + (instancetype)relativePositionLayoutSpecWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption - child:(id)child AS_WARN_UNUSED_RESULT; + child:(id)child NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; /*! * @discussion convenience initializer for a ASRelativeLayoutSpec diff --git a/Source/Layout/ASRelativeLayoutSpec.mm b/Source/Layout/ASRelativeLayoutSpec.mm index ea5f37df5..b14795d27 100644 --- a/Source/Layout/ASRelativeLayoutSpec.mm +++ b/Source/Layout/ASRelativeLayoutSpec.mm @@ -36,7 +36,7 @@ - (instancetype)initWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizon return self; } -+ (instancetype)relativePositionLayoutSpecWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption child:(id)child ++ (instancetype)relativePositionLayoutSpecWithHorizontalPosition:(ASRelativeLayoutSpecPosition)horizontalPosition verticalPosition:(ASRelativeLayoutSpecPosition)verticalPosition sizingOption:(ASRelativeLayoutSpecSizingOption)sizingOption child:(id)child NS_RETURNS_RETAINED { return [[self alloc] initWithHorizontalPosition:horizontalPosition verticalPosition:verticalPosition sizingOption:sizingOption child:child]; } diff --git a/Source/Layout/ASStackLayoutSpec.h b/Source/Layout/ASStackLayoutSpec.h index b4a27697e..44721f53d 100644 --- a/Source/Layout/ASStackLayoutSpec.h +++ b/Source/Layout/ASStackLayoutSpec.h @@ -88,7 +88,7 @@ NS_ASSUME_NONNULL_BEGIN spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems - children:(NSArray> *)children AS_WARN_UNUSED_RESULT; + children:(NSArray> *)children NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; /** @param direction The direction of the stack view (horizontal or vertical) @@ -105,7 +105,7 @@ NS_ASSUME_NONNULL_BEGIN alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent - children:(NSArray> *)children AS_WARN_UNUSED_RESULT; + children:(NSArray> *)children NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; /** @param direction The direction of the stack view (horizontal or vertical) @@ -124,17 +124,17 @@ NS_ASSUME_NONNULL_BEGIN flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent lineSpacing:(CGFloat)lineSpacing - children:(NSArray> *)children AS_WARN_UNUSED_RESULT; + children:(NSArray> *)children NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; /** * @return A stack layout spec with direction of ASStackLayoutDirectionVertical **/ -+ (instancetype)verticalStackLayoutSpec AS_WARN_UNUSED_RESULT; ++ (instancetype)verticalStackLayoutSpec NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; /** * @return A stack layout spec with direction of ASStackLayoutDirectionHorizontal **/ -+ (instancetype)horizontalStackLayoutSpec AS_WARN_UNUSED_RESULT; ++ (instancetype)horizontalStackLayoutSpec NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; @end diff --git a/Source/Layout/ASStackLayoutSpec.mm b/Source/Layout/ASStackLayoutSpec.mm index fc89d6f97..756912d80 100644 --- a/Source/Layout/ASStackLayoutSpec.mm +++ b/Source/Layout/ASStackLayoutSpec.mm @@ -36,29 +36,29 @@ - (instancetype)init return [self initWithDirection:ASStackLayoutDirectionHorizontal spacing:0.0 justifyContent:ASStackLayoutJustifyContentStart alignItems:ASStackLayoutAlignItemsStretch flexWrap:ASStackLayoutFlexWrapNoWrap alignContent:ASStackLayoutAlignContentStart lineSpacing:0.0 children:nil]; } -+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray *)children ++ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems children:(NSArray *)children NS_RETURNS_RETAINED { return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:ASStackLayoutFlexWrapNoWrap alignContent:ASStackLayoutAlignContentStart lineSpacing: 0.0 children:children]; } -+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent children:(NSArray> *)children ++ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent children:(NSArray> *)children NS_RETURNS_RETAINED { return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:flexWrap alignContent:alignContent lineSpacing:0.0 children:children]; } -+ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent lineSpacing:(CGFloat)lineSpacing children:(NSArray> *)children ++ (instancetype)stackLayoutSpecWithDirection:(ASStackLayoutDirection)direction spacing:(CGFloat)spacing justifyContent:(ASStackLayoutJustifyContent)justifyContent alignItems:(ASStackLayoutAlignItems)alignItems flexWrap:(ASStackLayoutFlexWrap)flexWrap alignContent:(ASStackLayoutAlignContent)alignContent lineSpacing:(CGFloat)lineSpacing children:(NSArray> *)children NS_RETURNS_RETAINED { return [[self alloc] initWithDirection:direction spacing:spacing justifyContent:justifyContent alignItems:alignItems flexWrap:flexWrap alignContent:alignContent lineSpacing:lineSpacing children:children]; } -+ (instancetype)verticalStackLayoutSpec ++ (instancetype)verticalStackLayoutSpec NS_RETURNS_RETAINED { ASStackLayoutSpec *stackLayoutSpec = [[self alloc] init]; stackLayoutSpec.direction = ASStackLayoutDirectionVertical; return stackLayoutSpec; } -+ (instancetype)horizontalStackLayoutSpec ++ (instancetype)horizontalStackLayoutSpec NS_RETURNS_RETAINED { ASStackLayoutSpec *stackLayoutSpec = [[self alloc] init]; stackLayoutSpec.direction = ASStackLayoutDirectionHorizontal; diff --git a/Source/Private/ASRectMap.h b/Source/Private/ASRectMap.h index fd756b333..e4b05d7cb 100644 --- a/Source/Private/ASRectMap.h +++ b/Source/Private/ASRectMap.h @@ -23,7 +23,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Creates a new rect map. The keys are never retained. */ -+ (ASRectMap *)rectMapForWeakObjectPointers; ++ (ASRectMap *)rectMapForWeakObjectPointers NS_RETURNS_RETAINED; /** * Retrieves the rect for a given key, or CGRectNull if the key is not found. diff --git a/Source/Private/ASRectMap.mm b/Source/Private/ASRectMap.mm index cb7681080..2e4398c32 100644 --- a/Source/Private/ASRectMap.mm +++ b/Source/Private/ASRectMap.mm @@ -19,7 +19,7 @@ @implementation ASRectMap { std::unordered_map _map; } -+ (ASRectMap *)rectMapForWeakObjectPointers ++ (ASRectMap *)rectMapForWeakObjectPointers NS_RETURNS_RETAINED { return [[self alloc] init]; } diff --git a/Source/Private/TextExperiment/Component/ASTextInput.h b/Source/Private/TextExperiment/Component/ASTextInput.h index 6b2aa79bb..061eef310 100755 --- a/Source/Private/TextExperiment/Component/ASTextInput.h +++ b/Source/Private/TextExperiment/Component/ASTextInput.h @@ -1,12 +1,18 @@ // // ASTextInput.h -// Modified from YYText +// Texture // -// Created by ibireme on 15/4/17. -// Copyright (c) 2015 ibireme. +// 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 /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// This source code is licensed under the MIT-style license found in the -// LICENSE file in the root directory of this source tree. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import @@ -36,8 +42,8 @@ typedef NS_ENUM(NSInteger, ASTextAffinity) { @property (nonatomic, readonly) NSInteger offset; @property (nonatomic, readonly) ASTextAffinity affinity; -+ (instancetype)positionWithOffset:(NSInteger)offset; -+ (instancetype)positionWithOffset:(NSInteger)offset affinity:(ASTextAffinity) affinity; ++ (instancetype)positionWithOffset:(NSInteger)offset NS_RETURNS_RETAINED; ++ (instancetype)positionWithOffset:(NSInteger)offset affinity:(ASTextAffinity) affinity NS_RETURNS_RETAINED; - (NSComparisonResult)compare:(id)otherPosition; @@ -57,10 +63,10 @@ typedef NS_ENUM(NSInteger, ASTextAffinity) { @property (nonatomic, readonly) ASTextPosition *end; @property (nonatomic, readonly, getter=isEmpty) BOOL empty; -+ (instancetype)rangeWithRange:(NSRange)range; -+ (instancetype)rangeWithRange:(NSRange)range affinity:(ASTextAffinity) affinity; -+ (instancetype)rangeWithStart:(ASTextPosition *)start end:(ASTextPosition *)end; -+ (instancetype)defaultRange; ///< <{0,0} Forward> ++ (instancetype)rangeWithRange:(NSRange)range NS_RETURNS_RETAINED; ++ (instancetype)rangeWithRange:(NSRange)range affinity:(ASTextAffinity) affinity NS_RETURNS_RETAINED; ++ (instancetype)rangeWithStart:(ASTextPosition *)start end:(ASTextPosition *)end NS_RETURNS_RETAINED; ++ (instancetype)defaultRange NS_RETURNS_RETAINED; ///< <{0,0} Forward> - (NSRange)asRange; diff --git a/Source/Private/TextExperiment/Component/ASTextInput.m b/Source/Private/TextExperiment/Component/ASTextInput.m index fa1c53b22..a56426b16 100755 --- a/Source/Private/TextExperiment/Component/ASTextInput.m +++ b/Source/Private/TextExperiment/Component/ASTextInput.m @@ -1,12 +1,18 @@ // // ASTextInput.m -// Modified from YYText +// Texture // -// Created by ibireme on 15/4/17. -// Copyright (c) 2015 ibireme. +// 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 /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// This source code is licensed under the MIT-style license found in the -// LICENSE file in the root directory of this source tree. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import @@ -15,11 +21,11 @@ @implementation ASTextPosition -+ (instancetype)positionWithOffset:(NSInteger)offset { ++ (instancetype)positionWithOffset:(NSInteger)offset NS_RETURNS_RETAINED { return [self positionWithOffset:offset affinity:ASTextAffinityForward]; } -+ (instancetype)positionWithOffset:(NSInteger)offset affinity:(ASTextAffinity)affinity { ++ (instancetype)positionWithOffset:(NSInteger)offset affinity:(ASTextAffinity)affinity NS_RETURNS_RETAINED { ASTextPosition *p = [self new]; p->_offset = offset; p->_affinity = affinity; @@ -85,17 +91,17 @@ - (NSRange)asRange { return NSMakeRange(_start.offset, _end.offset - _start.offset); } -+ (instancetype)rangeWithRange:(NSRange)range { ++ (instancetype)rangeWithRange:(NSRange)range NS_RETURNS_RETAINED { return [self rangeWithRange:range affinity:ASTextAffinityForward]; } -+ (instancetype)rangeWithRange:(NSRange)range affinity:(ASTextAffinity)affinity { ++ (instancetype)rangeWithRange:(NSRange)range affinity:(ASTextAffinity)affinity NS_RETURNS_RETAINED { ASTextPosition *start = [ASTextPosition positionWithOffset:range.location affinity:affinity]; ASTextPosition *end = [ASTextPosition positionWithOffset:range.location + range.length affinity:affinity]; return [self rangeWithStart:start end:end]; } -+ (instancetype)rangeWithStart:(ASTextPosition *)start end:(ASTextPosition *)end { ++ (instancetype)rangeWithStart:(ASTextPosition *)start end:(ASTextPosition *)end NS_RETURNS_RETAINED { if (!start || !end) return nil; if ([start compare:end] == NSOrderedDescending) { ASTEXT_SWAP(start, end); @@ -106,7 +112,7 @@ + (instancetype)rangeWithStart:(ASTextPosition *)start end:(ASTextPosition *)end return range; } -+ (instancetype)defaultRange { ++ (instancetype)defaultRange NS_RETURNS_RETAINED { return [self new]; } diff --git a/Source/Private/TextExperiment/Component/ASTextLayout.h b/Source/Private/TextExperiment/Component/ASTextLayout.h index e2cd9873e..5de6f057c 100755 --- a/Source/Private/TextExperiment/Component/ASTextLayout.h +++ b/Source/Private/TextExperiment/Component/ASTextLayout.h @@ -1,12 +1,18 @@ // // ASTextLayout.h -// Modified from YYText +// Texture // -// Created by ibireme on 15/3/3. -// Copyright (c) 2015 ibireme. +// 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 /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// This source code is licensed under the MIT-style license found in the -// LICENSE file in the root directory of this source tree. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import @@ -53,13 +59,13 @@ extern const CGSize ASTextContainerMaxSize; @interface ASTextContainer : NSObject /// Creates a container with the specified size. @param size The size. -+ (instancetype)containerWithSize:(CGSize)size; ++ (instancetype)containerWithSize:(CGSize)size NS_RETURNS_RETAINED; /// Creates a container with the specified size and insets. @param size The size. @param insets The text insets. -+ (instancetype)containerWithSize:(CGSize)size insets:(UIEdgeInsets)insets; ++ (instancetype)containerWithSize:(CGSize)size insets:(UIEdgeInsets)insets NS_RETURNS_RETAINED; /// Creates a container with the specified path. @param path The path. -+ (instancetype)containerWithPath:(nullable UIBezierPath *)path; ++ (instancetype)containerWithPath:(nullable UIBezierPath *)path NS_RETURNS_RETAINED; /// The constrained size. (if the size is larger than ASTextContainerMaxSize, it will be clipped) @property CGSize size; diff --git a/Source/Private/TextExperiment/Component/ASTextLayout.m b/Source/Private/TextExperiment/Component/ASTextLayout.m index fa61946c5..3707e3d94 100755 --- a/Source/Private/TextExperiment/Component/ASTextLayout.m +++ b/Source/Private/TextExperiment/Component/ASTextLayout.m @@ -1,12 +1,18 @@ // // ASTextLayout.m -// Modified from YYText +// Texture // -// Created by ibireme on 15/3/3. -// Copyright (c) 2015 ibireme. +// 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 /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// This source code is licensed under the MIT-style license found in the -// LICENSE file in the root directory of this source tree. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import @@ -99,18 +105,18 @@ @implementation ASTextContainer { id _linePositionModifier; } -+ (instancetype)containerWithSize:(CGSize)size { ++ (instancetype)containerWithSize:(CGSize)size NS_RETURNS_RETAINED { return [self containerWithSize:size insets:UIEdgeInsetsZero]; } -+ (instancetype)containerWithSize:(CGSize)size insets:(UIEdgeInsets)insets { ++ (instancetype)containerWithSize:(CGSize)size insets:(UIEdgeInsets)insets NS_RETURNS_RETAINED { ASTextContainer *one = [self new]; one.size = ASTextClipCGSize(size); one.insets = insets; return one; } -+ (instancetype)containerWithPath:(UIBezierPath *)path { ++ (instancetype)containerWithPath:(UIBezierPath *)path NS_RETURNS_RETAINED { ASTextContainer *one = [self new]; one.path = path; return one; diff --git a/Source/Private/TextExperiment/Component/ASTextLine.h b/Source/Private/TextExperiment/Component/ASTextLine.h index 10d2685db..2befb4581 100755 --- a/Source/Private/TextExperiment/Component/ASTextLine.h +++ b/Source/Private/TextExperiment/Component/ASTextLine.h @@ -1,12 +1,18 @@ // // ASTextLine.h -// Modified from YYText +// Texture // -// Created by ibireme on 15/3/10. -// Copyright (c) 2015 ibireme. +// 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 /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// This source code is licensed under the MIT-style license found in the -// LICENSE file in the root directory of this source tree. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import @@ -22,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN */ @interface ASTextLine : NSObject -+ (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical; ++ (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical NS_RETURNS_RETAINED; @property (nonatomic) NSUInteger index; ///< line index @property (nonatomic) NSUInteger row; ///< line row @@ -73,7 +79,7 @@ typedef NS_ENUM(NSUInteger, ASTextRunGlyphDrawMode) { @interface ASTextRunGlyphRange : NSObject @property (nonatomic) NSRange glyphRangeInRun; @property (nonatomic) ASTextRunGlyphDrawMode drawMode; -+ (instancetype)rangeWithRange:(NSRange)range drawMode:(ASTextRunGlyphDrawMode)mode; ++ (instancetype)rangeWithRange:(NSRange)range drawMode:(ASTextRunGlyphDrawMode)mode NS_RETURNS_RETAINED; @end NS_ASSUME_NONNULL_END diff --git a/Source/Private/TextExperiment/Component/ASTextLine.m b/Source/Private/TextExperiment/Component/ASTextLine.m index 14d9f7fd2..a0b8a173d 100755 --- a/Source/Private/TextExperiment/Component/ASTextLine.m +++ b/Source/Private/TextExperiment/Component/ASTextLine.m @@ -1,12 +1,18 @@ // -// ASYTextLine.m -// Modified from YYText +// ASTextLine.m +// Texture // -// Created by ibireme on 15/3/3. -// Copyright (c) 2015 ibireme. +// 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 /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// This source code is licensed under the MIT-style license found in the -// LICENSE file in the root directory of this source tree. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import @@ -16,7 +22,7 @@ @implementation ASTextLine { CGFloat _firstGlyphPos; // first glyph position for baseline, typically 0. } -+ (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical { ++ (instancetype)lineWithCTLine:(CTLineRef)CTLine position:(CGPoint)position vertical:(BOOL)isVertical NS_RETURNS_RETAINED { if (!CTLine) return nil; ASTextLine *line = [self new]; line->_position = position; @@ -157,7 +163,7 @@ - (NSString *)description { @implementation ASTextRunGlyphRange -+ (instancetype)rangeWithRange:(NSRange)range drawMode:(ASTextRunGlyphDrawMode)mode { ++ (instancetype)rangeWithRange:(NSRange)range drawMode:(ASTextRunGlyphDrawMode)mode NS_RETURNS_RETAINED { ASTextRunGlyphRange *one = [self new]; one.glyphRangeInRun = range; one.drawMode = mode; diff --git a/Source/Private/TextExperiment/String/ASTextAttribute.h b/Source/Private/TextExperiment/String/ASTextAttribute.h index 80cdf64ec..87716e9ce 100755 --- a/Source/Private/TextExperiment/String/ASTextAttribute.h +++ b/Source/Private/TextExperiment/String/ASTextAttribute.h @@ -1,12 +1,18 @@ // // ASTextAttribute.h -// Modified from YYText +// Texture // -// Created by ibireme on 14/10/26. -// Copyright (c) 2015 ibireme. +// 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 /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// This source code is licensed under the MIT-style license found in the -// LICENSE file in the root directory of this source tree. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import @@ -173,7 +179,7 @@ typedef void(^ASTextAction)(UIView *containerView, NSAttributedString *text, NSR Example: If :) is replace by a custom emoji (such as😊), the backed string can be set to @":)". */ @interface ASTextBackedString : NSObject -+ (instancetype)stringWithString:(nullable NSString *)string; ++ (instancetype)stringWithString:(nullable NSString *)string NS_RETURNS_RETAINED; @property (nullable, nonatomic, copy) NSString *string; ///< backed string @end @@ -188,7 +194,7 @@ typedef void(^ASTextAction)(UIView *containerView, NSAttributedString *text, NSR selection and edit. */ @interface ASTextBinding : NSObject -+ (instancetype)bindingWithDeleteConfirm:(BOOL)deleteConfirm; ++ (instancetype)bindingWithDeleteConfirm:(BOOL)deleteConfirm NS_RETURNS_RETAINED; @property (nonatomic) BOOL deleteConfirm; ///< confirm the range when delete in ASTextView @end @@ -201,7 +207,7 @@ typedef void(^ASTextAction)(UIView *containerView, NSAttributedString *text, NSR It's similar to `NSShadow`, but offers more options. */ @interface ASTextShadow : NSObject -+ (instancetype)shadowWithColor:(nullable UIColor *)color offset:(CGSize)offset radius:(CGFloat)radius; ++ (instancetype)shadowWithColor:(nullable UIColor *)color offset:(CGSize)offset radius:(CGFloat)radius NS_RETURNS_RETAINED; @property (nullable, nonatomic, strong) UIColor *color; ///< shadow color @property (nonatomic) CGSize offset; ///< shadow offset @@ -209,7 +215,7 @@ typedef void(^ASTextAction)(UIView *containerView, NSAttributedString *text, NSR @property (nonatomic) CGBlendMode blendMode; ///< shadow blend mode @property (nullable, nonatomic, strong) ASTextShadow *subShadow; ///< a sub shadow which will be added above the parent shadow -+ (instancetype)shadowWithNSShadow:(NSShadow *)nsShadow; ///< convert NSShadow to ASTextShadow ++ (instancetype)shadowWithNSShadow:(NSShadow *)nsShadow NS_RETURNS_RETAINED; ///< convert NSShadow to ASTextShadow - (NSShadow *)nsShadow; ///< convert ASTextShadow to NSShadow @end @@ -223,8 +229,8 @@ typedef void(^ASTextAction)(UIView *containerView, NSAttributedString *text, NSR when it's used as strikethrough, the line is drawn above text glyphs. */ @interface ASTextDecoration : NSObject -+ (instancetype)decorationWithStyle:(ASTextLineStyle)style; -+ (instancetype)decorationWithStyle:(ASTextLineStyle)style width:(nullable NSNumber *)width color:(nullable UIColor *)color; ++ (instancetype)decorationWithStyle:(ASTextLineStyle)style NS_RETURNS_RETAINED; ++ (instancetype)decorationWithStyle:(ASTextLineStyle)style width:(nullable NSNumber *)width color:(nullable UIColor *)color NS_RETURNS_RETAINED; @property (nonatomic) ASTextLineStyle style; ///< line style @property (nullable, nonatomic, strong) NSNumber *width; ///< line width (nil means automatic width) @property (nullable, nonatomic, strong) UIColor *color; ///< line color (nil means automatic color) @@ -246,8 +252,8 @@ typedef void(^ASTextAction)(UIView *containerView, NSAttributedString *text, NSR ╰──────╯ */ @interface ASTextBorder : NSObject -+ (instancetype)borderWithLineStyle:(ASTextLineStyle)lineStyle lineWidth:(CGFloat)width strokeColor:(nullable UIColor *)color; -+ (instancetype)borderWithFillColor:(nullable UIColor *)color cornerRadius:(CGFloat)cornerRadius; ++ (instancetype)borderWithLineStyle:(ASTextLineStyle)lineStyle lineWidth:(CGFloat)width strokeColor:(nullable UIColor *)color NS_RETURNS_RETAINED; ++ (instancetype)borderWithFillColor:(nullable UIColor *)color cornerRadius:(CGFloat)cornerRadius NS_RETURNS_RETAINED; @property (nonatomic) ASTextLineStyle lineStyle; ///< border line style @property (nonatomic) CGFloat strokeWidth; ///< border line width @property (nullable, nonatomic, strong) UIColor *strokeColor; ///< border line color @@ -270,7 +276,7 @@ typedef void(^ASTextAction)(UIView *containerView, NSAttributedString *text, NSR then it will be added to the text container's view or layer. */ @interface ASTextAttachment : NSObject -+ (instancetype)attachmentWithContent:(nullable id)content; ++ (instancetype)attachmentWithContent:(nullable id)content NS_RETURNS_RETAINED; @property (nullable, nonatomic, strong) id content; ///< Supported type: UIImage, UIView, CALayer @property (nonatomic) UIViewContentMode contentMode; ///< Content display mode. @property (nonatomic) UIEdgeInsets contentInsets; ///< The insets when drawing content. @@ -303,14 +309,14 @@ typedef void(^ASTextAction)(UIView *containerView, NSAttributedString *text, NSR @param attributes The attributes which will replace original attributes when highlight, If the value is NSNull, it will removed when highlight. */ -+ (instancetype)highlightWithAttributes:(nullable NSDictionary *)attributes; ++ (instancetype)highlightWithAttributes:(nullable NSDictionary *)attributes NS_RETURNS_RETAINED; /** Convenience methods to create a default highlight with the specifeid background color. @param color The background border color. */ -+ (instancetype)highlightWithBackgroundColor:(nullable UIColor *)color; ++ (instancetype)highlightWithBackgroundColor:(nullable UIColor *)color NS_RETURNS_RETAINED; // Convenience methods below to set the `attributes`. - (void)setFont:(nullable UIFont *)font; diff --git a/Source/Private/TextExperiment/String/ASTextAttribute.m b/Source/Private/TextExperiment/String/ASTextAttribute.m index 76ff0df38..f5bb64224 100755 --- a/Source/Private/TextExperiment/String/ASTextAttribute.m +++ b/Source/Private/TextExperiment/String/ASTextAttribute.m @@ -1,12 +1,18 @@ // // ASTextAttribute.m -// Modified from YYText +// Texture // -// Created by ibireme on 14/10/26. -// Copyright (c) 2015 ibireme. +// 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 /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// This source code is licensed under the MIT-style license found in the -// LICENSE file in the root directory of this source tree. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import "ASTextAttribute.h" @@ -109,7 +115,7 @@ ASTextAttributeType ASTextAttributeGetType(NSString *name){ @implementation ASTextBackedString -+ (instancetype)stringWithString:(NSString *)string { ++ (instancetype)stringWithString:(NSString *)string NS_RETURNS_RETAINED { ASTextBackedString *one = [self new]; one.string = string; return one; @@ -136,7 +142,7 @@ - (id)copyWithZone:(NSZone *)zone { @implementation ASTextBinding -+ (instancetype)bindingWithDeleteConfirm:(BOOL)deleteConfirm { ++ (instancetype)bindingWithDeleteConfirm:(BOOL)deleteConfirm NS_RETURNS_RETAINED { ASTextBinding *one = [self new]; one.deleteConfirm = deleteConfirm; return one; @@ -163,7 +169,7 @@ - (id)copyWithZone:(NSZone *)zone { @implementation ASTextShadow -+ (instancetype)shadowWithColor:(UIColor *)color offset:(CGSize)offset radius:(CGFloat)radius { ++ (instancetype)shadowWithColor:(UIColor *)color offset:(CGSize)offset radius:(CGFloat)radius NS_RETURNS_RETAINED { ASTextShadow *one = [self new]; one.color = color; one.offset = offset; @@ -171,7 +177,7 @@ + (instancetype)shadowWithColor:(UIColor *)color offset:(CGSize)offset radius:(C return one; } -+ (instancetype)shadowWithNSShadow:(NSShadow *)nsShadow { ++ (instancetype)shadowWithNSShadow:(NSShadow *)nsShadow NS_RETURNS_RETAINED { if (!nsShadow) return nil; ASTextShadow *shadow = [self new]; shadow.offset = nsShadow.shadowOffset; @@ -232,12 +238,12 @@ - (instancetype)init { return self; } -+ (instancetype)decorationWithStyle:(ASTextLineStyle)style { ++ (instancetype)decorationWithStyle:(ASTextLineStyle)style NS_RETURNS_RETAINED { ASTextDecoration *one = [self new]; one.style = style; return one; } -+ (instancetype)decorationWithStyle:(ASTextLineStyle)style width:(NSNumber *)width color:(UIColor *)color { ++ (instancetype)decorationWithStyle:(ASTextLineStyle)style width:(NSNumber *)width color:(UIColor *)color NS_RETURNS_RETAINED { ASTextDecoration *one = [self new]; one.style = style; one.width = width; @@ -272,7 +278,7 @@ - (id)copyWithZone:(NSZone *)zone { @implementation ASTextBorder -+ (instancetype)borderWithLineStyle:(ASTextLineStyle)lineStyle lineWidth:(CGFloat)width strokeColor:(UIColor *)color { ++ (instancetype)borderWithLineStyle:(ASTextLineStyle)lineStyle lineWidth:(CGFloat)width strokeColor:(UIColor *)color NS_RETURNS_RETAINED { ASTextBorder *one = [self new]; one.lineStyle = lineStyle; one.strokeWidth = width; @@ -280,7 +286,7 @@ + (instancetype)borderWithLineStyle:(ASTextLineStyle)lineStyle lineWidth:(CGFloa return one; } -+ (instancetype)borderWithFillColor:(UIColor *)color cornerRadius:(CGFloat)cornerRadius { ++ (instancetype)borderWithFillColor:(UIColor *)color cornerRadius:(CGFloat)cornerRadius NS_RETURNS_RETAINED { ASTextBorder *one = [self new]; one.fillColor = color; one.cornerRadius = cornerRadius; @@ -336,7 +342,7 @@ - (id)copyWithZone:(NSZone *)zone { @implementation ASTextAttachment -+ (instancetype)attachmentWithContent:(id)content { ++ (instancetype)attachmentWithContent:(id)content NS_RETURNS_RETAINED { ASTextAttachment *one = [self new]; one.content = content; return one; @@ -373,13 +379,13 @@ - (id)copyWithZone:(NSZone *)zone { @implementation ASTextHighlight -+ (instancetype)highlightWithAttributes:(NSDictionary *)attributes { ++ (instancetype)highlightWithAttributes:(NSDictionary *)attributes NS_RETURNS_RETAINED { ASTextHighlight *one = [self new]; one.attributes = attributes; return one; } -+ (instancetype)highlightWithBackgroundColor:(UIColor *)color { ++ (instancetype)highlightWithBackgroundColor:(UIColor *)color NS_RETURNS_RETAINED { ASTextBorder *highlightBorder = [ASTextBorder new]; highlightBorder.insets = UIEdgeInsetsMake(-2, -1, -2, -1); highlightBorder.cornerRadius = 3; diff --git a/Source/TextKit/ASTextKitComponents.h b/Source/TextKit/ASTextKitComponents.h index 20acc2c47..c516fe69e 100644 --- a/Source/TextKit/ASTextKitComponents.h +++ b/Source/TextKit/ASTextKitComponents.h @@ -37,7 +37,7 @@ AS_SUBCLASSING_RESTRICTED @discussion The returned components will be hooked up together, so they are ready for use as a system upon return. */ + (instancetype)componentsWithAttributedSeedString:(nullable NSAttributedString *)attributedSeedString - textContainerSize:(CGSize)textContainerSize; + textContainerSize:(CGSize)textContainerSize NS_RETURNS_RETAINED; /** @abstract Creates the stack of TextKit components. @@ -49,7 +49,7 @@ AS_SUBCLASSING_RESTRICTED */ + (instancetype)componentsWithTextStorage:(NSTextStorage *)textStorage textContainerSize:(CGSize)textContainerSize - layoutManager:(NSLayoutManager *)layoutManager; + layoutManager:(NSLayoutManager *)layoutManager NS_RETURNS_RETAINED; /** @abstract Returns the bounding size for the text view's text. diff --git a/Source/TextKit/ASTextKitComponents.mm b/Source/TextKit/ASTextKitComponents.mm index 7e4a7ae68..dbb611b78 100644 --- a/Source/TextKit/ASTextKitComponents.mm +++ b/Source/TextKit/ASTextKitComponents.mm @@ -65,7 +65,7 @@ @implementation ASTextKitComponents #pragma mark - Class + (instancetype)componentsWithAttributedSeedString:(NSAttributedString *)attributedSeedString - textContainerSize:(CGSize)textContainerSize + textContainerSize:(CGSize)textContainerSize NS_RETURNS_RETAINED { NSTextStorage *textStorage = attributedSeedString ? [[NSTextStorage alloc] initWithAttributedString:attributedSeedString] : [[NSTextStorage alloc] init]; @@ -76,7 +76,7 @@ + (instancetype)componentsWithAttributedSeedString:(NSAttributedString *)attribu + (instancetype)componentsWithTextStorage:(NSTextStorage *)textStorage textContainerSize:(CGSize)textContainerSize - layoutManager:(NSLayoutManager *)layoutManager + layoutManager:(NSLayoutManager *)layoutManager NS_RETURNS_RETAINED { ASTextKitComponents *components = [[self alloc] init]; diff --git a/Source/TextKit/ASTextKitCoreTextAdditions.h b/Source/TextKit/ASTextKitCoreTextAdditions.h index 94108961c..211250e8c 100644 --- a/Source/TextKit/ASTextKitCoreTextAdditions.h +++ b/Source/TextKit/ASTextKitCoreTextAdditions.h @@ -88,7 +88,7 @@ ASDISPLAYNODE_EXTERN_C_END @result An NSParagraphStyle initialized with as many of the paragraph specifiers from `coreTextParagraphStyle` as possible. */ -+ (instancetype)paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreTextParagraphStyle; ++ (instancetype)paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreTextParagraphStyle NS_RETURNS_RETAINED; @end diff --git a/Source/TextKit/ASTextKitCoreTextAdditions.m b/Source/TextKit/ASTextKitCoreTextAdditions.m index 7f48aff71..272cacd3d 100644 --- a/Source/TextKit/ASTextKitCoreTextAdditions.m +++ b/Source/TextKit/ASTextKitCoreTextAdditions.m @@ -172,7 +172,7 @@ BOOL ASAttributeWithNameIsUnsupportedCoreTextAttribute(NSString *attributeName) #pragma mark - @implementation NSParagraphStyle (ASTextKitCoreTextAdditions) -+ (instancetype)paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreTextParagraphStyle; ++ (instancetype)paragraphStyleWithCTParagraphStyle:(CTParagraphStyleRef)coreTextParagraphStyle NS_RETURNS_RETAINED { NSMutableParagraphStyle *newParagraphStyle = [[NSMutableParagraphStyle alloc] init]; diff --git a/Source/UIImage+ASConvenience.h b/Source/UIImage+ASConvenience.h index e9612c8b7..915571b7c 100644 --- a/Source/UIImage+ASConvenience.h +++ b/Source/UIImage+ASConvenience.h @@ -40,7 +40,7 @@ NS_ASSUME_NONNULL_BEGIN * @param imageName The name of the image to load * @return The loaded image or nil */ -+ (UIImage *)as_imageNamed:(NSString *)imageName; ++ (UIImage *)as_imageNamed:(NSString *)imageName NS_RETURNS_RETAINED; /** * A version of imageNamed that caches results because loading an image is expensive. @@ -51,7 +51,7 @@ NS_ASSUME_NONNULL_BEGIN * @param traitCollection The traits associated with the intended environment for the image. * @return The loaded image or nil */ -+ (UIImage *)as_imageNamed:(NSString *)imageName compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection; ++ (UIImage *)as_imageNamed:(NSString *)imageName compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection NS_RETURNS_RETAINED; @end @@ -76,7 +76,7 @@ NS_ASSUME_NONNULL_BEGIN */ + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius cornerColor:(nullable UIColor *)cornerColor - fillColor:(UIColor *)fillColor AS_WARN_UNUSED_RESULT; + fillColor:(UIColor *)fillColor NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; /** * This generates a flat-color, rounded-corner resizeable image with a border @@ -91,7 +91,7 @@ NS_ASSUME_NONNULL_BEGIN cornerColor:(UIColor *)cornerColor fillColor:(UIColor *)fillColor borderColor:(nullable UIColor *)borderColor - borderWidth:(CGFloat)borderWidth AS_WARN_UNUSED_RESULT; + borderWidth:(CGFloat)borderWidth NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; /** * This generates a flat-color, rounded-corner resizeable image with a border @@ -110,7 +110,7 @@ NS_ASSUME_NONNULL_BEGIN borderColor:(nullable UIColor *)borderColor borderWidth:(CGFloat)borderWidth roundedCorners:(UIRectCorner)roundedCorners - scale:(CGFloat)scale AS_WARN_UNUSED_RESULT; + scale:(CGFloat)scale NS_RETURNS_RETAINED AS_WARN_UNUSED_RESULT; @end diff --git a/Source/UIImage+ASConvenience.m b/Source/UIImage+ASConvenience.m index 35e6f0d56..c1d0751e3 100644 --- a/Source/UIImage+ASConvenience.m +++ b/Source/UIImage+ASConvenience.m @@ -24,7 +24,7 @@ @implementation UIImage (ASDKFastImageNamed) -UIImage *cachedImageNamed(NSString *imageName, UITraitCollection *traitCollection) +UIImage *cachedImageNamed(NSString *imageName, UITraitCollection *traitCollection) NS_RETURNS_RETAINED { static NSCache *imageCache = nil; static dispatch_once_t onceToken; @@ -55,12 +55,12 @@ @implementation UIImage (ASDKFastImageNamed) return image; } -+ (UIImage *)as_imageNamed:(NSString *)imageName ++ (UIImage *)as_imageNamed:(NSString *)imageName NS_RETURNS_RETAINED { return cachedImageNamed(imageName, nil); } -+ (UIImage *)as_imageNamed:(NSString *)imageName compatibleWithTraitCollection:(UITraitCollection *)traitCollection ++ (UIImage *)as_imageNamed:(NSString *)imageName compatibleWithTraitCollection:(UITraitCollection *)traitCollection NS_RETURNS_RETAINED { return cachedImageNamed(imageName, traitCollection); } @@ -73,7 +73,7 @@ @implementation UIImage (ASDKResizableRoundedRects) + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius cornerColor:(UIColor *)cornerColor - fillColor:(UIColor *)fillColor + fillColor:(UIColor *)fillColor NS_RETURNS_RETAINED { return [self as_resizableRoundedImageWithCornerRadius:cornerRadius cornerColor:cornerColor @@ -88,7 +88,7 @@ + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius cornerColor:(UIColor *)cornerColor fillColor:(UIColor *)fillColor borderColor:(UIColor *)borderColor - borderWidth:(CGFloat)borderWidth + borderWidth:(CGFloat)borderWidth NS_RETURNS_RETAINED { return [self as_resizableRoundedImageWithCornerRadius:cornerRadius cornerColor:cornerColor @@ -105,7 +105,7 @@ + (UIImage *)as_resizableRoundedImageWithCornerRadius:(CGFloat)cornerRadius borderColor:(UIColor *)borderColor borderWidth:(CGFloat)borderWidth roundedCorners:(UIRectCorner)roundedCorners - scale:(CGFloat)scale + scale:(CGFloat)scale NS_RETURNS_RETAINED { static NSCache *__pathCache = nil; static dispatch_once_t onceToken; diff --git a/Source/_ASTransitionContext.h b/Source/_ASTransitionContext.h index 2a8a6f14f..68eae7daa 100644 --- a/Source/_ASTransitionContext.h +++ b/Source/_ASTransitionContext.h @@ -53,5 +53,5 @@ @interface _ASAnimatedTransitionContext : NSObject @property (nonatomic, strong, readonly) ASDisplayNode *node; @property (nonatomic, assign, readonly) CGFloat alpha; -+ (instancetype)contextForNode:(ASDisplayNode *)node alpha:(CGFloat)alphaValue; ++ (instancetype)contextForNode:(ASDisplayNode *)node alpha:(CGFloat)alphaValue NS_RETURNS_RETAINED; @end diff --git a/Source/_ASTransitionContext.m b/Source/_ASTransitionContext.m index da6350a4a..169e11eaf 100644 --- a/Source/_ASTransitionContext.m +++ b/Source/_ASTransitionContext.m @@ -101,7 +101,7 @@ @interface _ASAnimatedTransitionContext () @implementation _ASAnimatedTransitionContext -+ (instancetype)contextForNode:(ASDisplayNode *)node alpha:(CGFloat)alpha ++ (instancetype)contextForNode:(ASDisplayNode *)node alpha:(CGFloat)alpha NS_RETURNS_RETAINED { _ASAnimatedTransitionContext *context = [[_ASAnimatedTransitionContext alloc] init]; context.node = node; From 255aec5839a5600ac268db9976f98e5b8e35af50 Mon Sep 17 00:00:00 2001 From: Oleksiy Ivanov Date: Fri, 23 Mar 2018 22:57:22 -0700 Subject: [PATCH 112/133] Order items in XCode project navigator by name (#835) * Order items in XCode project navigator by name It is a bit hard to find file in folders with long list of file, for example Tests. Sorting items by name makes this a bit easier and brings order. If there are child folders, they are ordered before files. Keeping folders sorted by name may be reasonable alternative as well. * Re-sort after merge. ASNetworkImageLoadInfo.h is still here, not sure why showing removed in this commit. Had this file duplicated reference previously? --- AsyncDisplayKit.xcodeproj/project.pbxproj | 306 +++++++++++----------- 1 file changed, 153 insertions(+), 153 deletions(-) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 6cc4d105c..e50e895c4 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -1082,44 +1082,45 @@ 058D09B1195D04C000B7D73C /* Source */ = { isa = PBXGroup; children = ( - CC84C7F020474C5300A3851B /* ASCGImageBuffer.h */, - CC84C7F120474C5300A3851B /* ASCGImageBuffer.m */, - CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */, - CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.m */, + 058D0A42195D058D00B7D73C /* Base */, CCE04B1D1E313E99006AEBBB /* Collection Data Adapter */, + DE89C1691DCEB9CC00D49D74 /* Debug */, + 058D09E1195D050800B7D73C /* Details */, + AC6456051B0A333200CF11B8 /* Layout */, + 058D0A01195D050800B7D73C /* Private */, + 058D09B2195D04C000B7D73C /* Supporting Files */, + 257754661BED245B00737CA5 /* TextKit */, + 690ED5911E36D118000627C0 /* tvOS */, CC58AA4A1E398E1D002C8CB4 /* ASBlockTypes.h */, - DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */, - DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */, - 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */, - 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */, - AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */, - AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */, - 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */, - 8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */, + DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */, + DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */, 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */, AC6456071B0A335000CF11B8 /* ASCellNode.mm */, + CC84C7F020474C5300A3851B /* ASCGImageBuffer.h */, + CC84C7F120474C5300A3851B /* ASCGImageBuffer.m */, + DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */, + DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */, 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */, 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */, B13CA0FF1C52004900E031AB /* ASCollectionNode+Beta.h */, AC3C4A4F1A1139C100143C57 /* ASCollectionView.h */, AC3C4A501A1139C100143C57 /* ASCollectionView.mm */, - AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */, B13CA0F61C519E9400E031AB /* ASCollectionViewLayoutFacilitatorProtocol.h */, - DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */, - DEC146B51C37A16A004A0EE7 /* ASCollectionInternal.m */, + AC3C4A531A113EEC00143C57 /* ASCollectionViewProtocols.h */, + DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */, 058D09D5195D050800B7D73C /* ASControlNode.h */, 058D09D6195D050800B7D73C /* ASControlNode.mm */, - DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */, - DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */, 058D09D7195D050800B7D73C /* ASControlNode+Subclasses.h */, 058D09D8195D050800B7D73C /* ASDisplayNode.h */, 058D09D9195D050800B7D73C /* ASDisplayNode.mm */, + CC6AA2D81E9F03B900978E87 /* ASDisplayNode+Ancestry.h */, + CC6AA2D91E9F03B900978E87 /* ASDisplayNode+Ancestry.m */, 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */, - 90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */, - 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */, CC034A071E60BEB400626263 /* ASDisplayNode+Convenience.h */, CC034A081E60BEB400626263 /* ASDisplayNode+Convenience.m */, 69BCE3D71EC6513B007DCCAD /* ASDisplayNode+Layout.mm */, + 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */, + 90FC784E1E4BFE1B00383C5A /* ASDisplayNode+Yoga.mm */, 058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */, 058D09DC195D050800B7D73C /* ASDisplayNodeExtras.mm */, 0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */, @@ -1127,27 +1128,29 @@ 058D09DD195D050800B7D73C /* ASImageNode.h */, 058D09DE195D050800B7D73C /* ASImageNode.mm */, 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */, - 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */, - 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */, + 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */, + 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */, 0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */, 0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */, 68FC85DC1CE29AB700EDD713 /* ASNavigationController.h */, 68FC85DD1CE29AB700EDD713 /* ASNavigationController.m */, - CC6AA2D81E9F03B900978E87 /* ASDisplayNode+Ancestry.h */, - CC6AA2D91E9F03B900978E87 /* ASDisplayNode+Ancestry.m */, + CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */, + CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.m */, 055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */, 055B9FA71A1C154B00035D6D /* ASNetworkImageNode.mm */, 698371D91E4379CD00437585 /* ASNodeController+Beta.h */, 698371DA1E4379CD00437585 /* ASNodeController+Beta.m */, + DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */, + DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */, 25E327541C16819500A2170C /* ASPagerNode.h */, 25E327551C16819500A2170C /* ASPagerNode.m */, E54E00711F1D3828000B30D7 /* ASPagerNode+Beta.h */, A2763D771CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h */, A2763D781CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.m */, CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */, - ACE87A2B1D73696800D7FF06 /* ASSectionContext.h */, D785F6601A74327E00291744 /* ASScrollNode.h */, D785F6611A74327E00291744 /* ASScrollNode.mm */, + ACE87A2B1D73696800D7FF06 /* ASSectionContext.h */, 68FC85E01CE29B7E00EDD713 /* ASTabBarController.h */, 68FC85E11CE29B7E00EDD713 /* ASTabBarController.m */, B0F880581BEAEC7500D17647 /* ASTableNode.h */, @@ -1158,26 +1161,23 @@ AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */, 0574D5E119C110610097DC25 /* ASTableViewProtocols.h */, 058D09DF195D050800B7D73C /* ASTextNode.h */, - A373200E1C571B050011FC94 /* ASTextNode+Beta.h */, 058D09E0195D050800B7D73C /* ASTextNode.mm */, + A373200E1C571B050011FC94 /* ASTextNode+Beta.h */, 909C4C731F09C98B00D6B76F /* ASTextNode2.h */, 909C4C741F09C98B00D6B76F /* ASTextNode2.mm */, + AEEC47DF1C20C2DD00EC1693 /* ASVideoNode.h */, + AEEC47E01C20C2DD00EC1693 /* ASVideoNode.mm */, + 8BDA5FC31CDBDDE1007D13B2 /* ASVideoPlayerNode.h */, + 8BDA5FC41CDBDDE1007D13B2 /* ASVideoPlayerNode.mm */, ACC945A81BA9E7A0005E1FB8 /* ASViewController.h */, 9CFFC6BF1CCAC73C006A6476 /* ASViewController.mm */, - 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */, - DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */, 68FC85E71CE29C7D00EDD713 /* ASVisibilityProtocols.h */, 68FC85E81CE29C7D00EDD713 /* ASVisibilityProtocols.m */, + 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */, + 8021EC1A1D2B00B100799119 /* UIImage+ASConvenience.h */, + 8021EC1B1D2B00B100799119 /* UIImage+ASConvenience.m */, CC55A70B1E529FA200594372 /* UIResponder+AsyncDisplayKit.h */, CC55A70C1E529FA200594372 /* UIResponder+AsyncDisplayKit.m */, - 058D0A42195D058D00B7D73C /* Base */, - DE89C1691DCEB9CC00D49D74 /* Debug */, - 058D09E1195D050800B7D73C /* Details */, - 058D0A01195D050800B7D73C /* Private */, - AC6456051B0A333200CF11B8 /* Layout */, - 257754661BED245B00737CA5 /* TextKit */, - 690ED5911E36D118000627C0 /* tvOS */, - 058D09B2195D04C000B7D73C /* Supporting Files */, ); path = Source; sourceTree = ""; @@ -1185,8 +1185,8 @@ 058D09B2195D04C000B7D73C /* Supporting Files */ = { isa = PBXGroup; children = ( - CC57EAF91E394EA40034C595 /* Info.plist */, 044285011BAA3CC700D16268 /* AsyncDisplayKit.modulemap */, + CC57EAF91E394EA40034C595 /* Info.plist */, ); name = "Supporting Files"; sourceTree = ""; @@ -1194,83 +1194,83 @@ 058D09C5195D04C000B7D73C /* Tests */ = { isa = PBXGroup; children = ( - CC583ABF1EF9BAB400134156 /* Common */, - CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */, - BB5FC3CD1F9BA688007F191E /* ASNavigationControllerTests.m */, - BB5FC3D01F9C9389007F191E /* ASTabBarControllerTests.m */, - CC11F9791DB181180024D77B /* ASNetworkImageNodeTests.m */, - CC051F1E1D7A286A006434CB /* ASCALayerTests.m */, - CCE4F9B21F0D60AC00062E4E /* ASIntegerMapTests.m */, - CCE4F9B41F0DA4F300062E4E /* ASLayoutEngineTests.mm */, - CCE4F9B61F0DBA5000062E4E /* ASLayoutTestNode.h */, - CCE4F9B71F0DBA5000062E4E /* ASLayoutTestNode.mm */, - CCE4F9BC1F0ECE5200062E4E /* ASTLayoutFixture.h */, - CCE4F9BD1F0ECE5200062E4E /* ASTLayoutFixture.mm */, - CC8B05D71D73979700F54286 /* ASTextNodePerformanceTests.m */, - CC8B05D41D73836400F54286 /* ASPerformanceTestContext.h */, - CC8B05D51D73836400F54286 /* ASPerformanceTestContext.m */, - CC54A81D1D7008B300296A24 /* ASDispatchTests.m */, - CCA221D21D6FA7EF00AF6A0F /* ASViewControllerTests.m */, - CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */, - CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */, - E52AC9BE1FEA915D00AA4040 /* ASRectMapTests.m */, - 83A7D95D1D446A6E00BF333E /* ASWeakMapTests.m */, - DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */, DBC452DD1C5C6A6A00B16017 /* ArrayDiffingTests.m */, - CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */, - CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */, - 057D02C01AC0A66700C7AC3C /* AsyncDisplayKitTestHost */, - 056D21501ABCEDA1001107EF /* ASSnapshotTestCase.h */, - 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.m */, - 056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.m */, - ACF6ED531B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm */, - 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */, - ACF6ED551B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm */, - ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */, - 1A6C000F1FAB4ED400D05926 /* ASCornerLayoutSpecSnapshotTests.mm */, - 696FCB301D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm */, - ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */, - ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */, AC026B571BD3F61800BBC17E /* ASAbsoluteLayoutSpecSnapshotTests.m */, - 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */, - ACF6ED571B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.h */, - ACF6ED581B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m */, - 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */, + 696FCB301D6E46050093471E /* ASBackgroundLayoutSpecSnapshotTests.mm */, 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */, - CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */, + 242995D21B29743C00090100 /* ASBasicImageDownloaderTests.m */, 296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.m */, + CC3B208F1C3F892D00798563 /* ASBridgedPropertiesTests.mm */, + CC051F1E1D7A286A006434CB /* ASCALayerTests.m */, + ACF6ED531B178DC700DA7C62 /* ASCenterLayoutSpecSnapshotTests.mm */, + CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */, + 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */, 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.mm */, - AE6987C01DD04E1000B9E458 /* ASPagerNodeTests.m */, 2911485B1A77147A005D0878 /* ASControlNodeTests.m */, + 1A6C000F1FAB4ED400D05926 /* ASCornerLayoutSpecSnapshotTests.mm */, ACF6ED541B178DC700DA7C62 /* ASDimensionTests.mm */, + CC54A81D1D7008B300296A24 /* ASDispatchTests.m */, 058D0A2D195D057000B7D73C /* ASDisplayLayerTests.m */, 058D0A2E195D057000B7D73C /* ASDisplayNodeAppearanceTests.m */, - 058D0A2F195D057000B7D73C /* ASDisplayNodeTests.mm */, + F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.m */, + DBC453211C5FD97200B16017 /* ASDisplayNodeImplicitHierarchyTests.m */, 69B225661D72535E00B25B22 /* ASDisplayNodeLayoutTests.mm */, + CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */, + 058D0A2F195D057000B7D73C /* ASDisplayNodeTests.mm */, 058D0A30195D057000B7D73C /* ASDisplayNodeTestsHelper.h */, 058D0A31195D057000B7D73C /* ASDisplayNodeTestsHelper.m */, 697B31591CFE4B410049936F /* ASEditableTextNodeTests.m */, + 056D21541ABCEF50001107EF /* ASImageNodeSnapshotTests.m */, + ACF6ED551B178DC700DA7C62 /* ASInsetLayoutSpecSnapshotTests.mm */, + CCE4F9B21F0D60AC00062E4E /* ASIntegerMapTests.m */, + 69FEE53C1D95A9AF0086F066 /* ASLayoutElementStyleTests.m */, + CCE4F9B41F0DA4F300062E4E /* ASLayoutEngineTests.mm */, E51B78BD1F01A0EE00E32604 /* ASLayoutFlatteningTests.m */, + ACF6ED571B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.h */, + ACF6ED581B178DC700DA7C62 /* ASLayoutSpecSnapshotTestsHelper.m */, + 699B83501E3C1BA500433FA4 /* ASLayoutSpecTests.m */, + CCE4F9B61F0DBA5000062E4E /* ASLayoutTestNode.h */, + CCE4F9B71F0DBA5000062E4E /* ASLayoutTestNode.mm */, 052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */, 058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m */, + BB5FC3CD1F9BA688007F191E /* ASNavigationControllerTests.m */, + CC11F9791DB181180024D77B /* ASNetworkImageNodeTests.m */, + ACF6ED591B178DC700DA7C62 /* ASOverlayLayoutSpecSnapshotTests.mm */, + AE6987C01DD04E1000B9E458 /* ASPagerNodeTests.m */, + CC8B05D41D73836400F54286 /* ASPerformanceTestContext.h */, + CC8B05D51D73836400F54286 /* ASPerformanceTestContext.m */, + CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */, + ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */, + E52AC9BE1FEA915D00AA4040 /* ASRectMapTests.m */, + 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */, + 4E9127681F64157600499623 /* ASRunLoopQueueTests.m */, E586F96B1F9F9E2900ECE00E /* ASScrollNodeTests.m */, + 056D21501ABCEDA1001107EF /* ASSnapshotTestCase.h */, + 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.m */, + ACF6ED5B1B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm */, + BB5FC3D01F9C9389007F191E /* ASTabBarControllerTests.m */, 3C9C128419E616EF00E942A0 /* ASTableViewTests.mm */, CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */, 058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m */, - 254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */, 254C6B531BF8FF2A003EC431 /* ASTextKitTests.mm */, + 254C6B511BF8FE6D003EC431 /* ASTextKitTruncationTests.mm */, + CC8B05D71D73979700F54286 /* ASTextNodePerformanceTests.m */, + 81E95C131D62639600336598 /* ASTextNodeSnapshotTests.m */, 058D0A36195D057000B7D73C /* ASTextNodeTests.m */, 058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */, + CCE4F9BC1F0ECE5200062E4E /* ASTLayoutFixture.h */, + CCE4F9BD1F0ECE5200062E4E /* ASTLayoutFixture.mm */, + 4496D0721FA9EA6B001CC8D5 /* ASTraitCollectionTests.m */, + CC0AEEA31D66316E005D1C78 /* ASUICollectionViewTests.m */, AEEC47E31C21D3D200EC1693 /* ASVideoNodeTests.m */, - F711994D1D20C21100568860 /* ASDisplayNodeExtrasTests.m */, + CCA221D21D6FA7EF00AF6A0F /* ASViewControllerTests.m */, + 83A7D95D1D446A6E00BF333E /* ASWeakMapTests.m */, + CC3B208D1C3F7D0A00798563 /* ASWeakSetTests.m */, + 695BE2541DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm */, + 057D02C01AC0A66700C7AC3C /* AsyncDisplayKitTestHost */, + CC583ABF1EF9BAB400134156 /* Common */, 058D09C6195D04C000B7D73C /* Supporting Files */, 052EE06A1A15A0D8002C6279 /* TestResources */, - 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */, - 69FEE53C1D95A9AF0086F066 /* ASLayoutElementStyleTests.m */, - 695BE2541DC1245C008E6EA5 /* ASWrapperSpecSnapshotTests.mm */, - 699B83501E3C1BA500433FA4 /* ASLayoutSpecTests.m */, - 4E9127681F64157600499623 /* ASRunLoopQueueTests.m */, - 4496D0721FA9EA6B001CC8D5 /* ASTraitCollectionTests.m */, ); path = Tests; sourceTree = ""; @@ -1289,14 +1289,13 @@ 058D09E1195D050800B7D73C /* Details */ = { isa = PBXGroup; children = ( - CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */, - CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.m */, - CC5601391F06E9A700DC4FBE /* ASIntegerMap.h */, - CC56013A1F06E9A700DC4FBE /* ASIntegerMap.mm */, - CC0F885E1E4280B800576FED /* _ASCollectionViewCell.h */, - CC0F885D1E4280B800576FED /* _ASCollectionViewCell.m */, + E5B077EB1E6843AF00C24B5B /* Collection Layout */, + 25B171EA1C12242700508A7A /* Data Controller */, + 058D09F7195D050800B7D73C /* Transactions */, 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */, 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */, + CC0F885E1E4280B800576FED /* _ASCollectionViewCell.h */, + CC0F885D1E4280B800576FED /* _ASCollectionViewCell.m */, 058D09E2195D050800B7D73C /* _ASDisplayLayer.h */, 058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */, 058D09E4195D050800B7D73C /* _ASDisplayView.h */, @@ -1310,21 +1309,23 @@ 299DA1A71A828D2900162D41 /* ASBatchContext.h */, 299DA1A81A828D2900162D41 /* ASBatchContext.mm */, E5C347B01ECB3D9200EC4BE4 /* ASBatchFetchingDelegate.h */, - 68C215561DE10D330019C4BC /* ASCollectionViewLayoutInspector.h */, - 68C215571DE10D330019C4BC /* ASCollectionViewLayoutInspector.m */, 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */, 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.m */, + 68C215561DE10D330019C4BC /* ASCollectionViewLayoutInspector.h */, + 68C215571DE10D330019C4BC /* ASCollectionViewLayoutInspector.m */, 696F01EA1DD2AF450049FBD5 /* ASEventLog.h */, 696F01EB1DD2AF450049FBD5 /* ASEventLog.mm */, + CCDC9B4B200991D10063C1F8 /* ASGraphicsContext.h */, + CCDC9B4C200991D10063C1F8 /* ASGraphicsContext.m */, E5B225271F1790B5001E1431 /* ASHashing.h */, E5B225261F1790B5001E1431 /* ASHashing.m */, - 4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */, - 4640521C1A3F83C40061C0BA /* ASTableLayoutController.m */, 058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */, 058D09E7195D050800B7D73C /* ASHighlightOverlayLayer.mm */, 68355B371CB57A5A001D4E68 /* ASImageContainerProtocolCategories.h */, 68355B381CB57A5A001D4E68 /* ASImageContainerProtocolCategories.m */, 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */, + CC5601391F06E9A700DC4FBE /* ASIntegerMap.h */, + CC56013A1F06E9A700DC4FBE /* ASIntegerMap.mm */, 4640521D1A3F83C40061C0BA /* ASLayoutController.h */, 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */, 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */, @@ -1344,6 +1345,8 @@ 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */, 296A0A311A951715005ACEAA /* ASScrollDirection.h */, 205F0E111B371BD7007741D0 /* ASScrollDirection.m */, + 4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */, + 4640521C1A3F83C40061C0BA /* ASTableLayoutController.m */, 058D0A12195D050800B7D73C /* ASThread.h */, CC4C2A751D88E3BF0039ACAB /* ASTraceEvent.h */, CC4C2A761D88E3BF0039ACAB /* ASTraceEvent.m */, @@ -1355,15 +1358,12 @@ CC3B20881C3F7A5400798563 /* ASWeakSet.m */, 205F0E1F1B376416007741D0 /* CoreGraphics+ASConvenience.h */, 205F0E201B376416007741D0 /* CoreGraphics+ASConvenience.m */, - 25B171EA1C12242700508A7A /* Data Controller */, - E5B077EB1E6843AF00C24B5B /* Collection Layout */, DBC452D91C5BF64600B16017 /* NSArray+Diffing.h */, DBC452DA1C5BF64600B16017 /* NSArray+Diffing.m */, CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */, CC4981BB1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m */, 058D09F5195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.h */, 058D09F6195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m */, - 058D09F7195D050800B7D73C /* Transactions */, 205F0E0D1B371875007741D0 /* UICollectionViewLayout+ASConvenience.h */, 205F0E0E1B371875007741D0 /* UICollectionViewLayout+ASConvenience.m */, 058D09FF195D050800B7D73C /* UIView+ASConvenience.h */, @@ -1376,9 +1376,9 @@ children = ( 058D09F8195D050800B7D73C /* _ASAsyncTransaction.h */, 058D09F9195D050800B7D73C /* _ASAsyncTransaction.mm */, - 058D09FA195D050800B7D73C /* _ASAsyncTransactionContainer+Private.h */, 058D09FB195D050800B7D73C /* _ASAsyncTransactionContainer.h */, 058D09FC195D050800B7D73C /* _ASAsyncTransactionContainer.m */, + 058D09FA195D050800B7D73C /* _ASAsyncTransactionContainer+Private.h */, 058D09FD195D050800B7D73C /* _ASAsyncTransactionGroup.h */, 058D09FE195D050800B7D73C /* _ASAsyncTransactionGroup.m */, ); @@ -1388,30 +1388,10 @@ 058D0A01195D050800B7D73C /* Private */ = { isa = PBXGroup; children = ( - CCED5E402020D41600395C40 /* ASNetworkImageLoadInfo+Private.h */, - CCA282CE1E9EBF6C0037E8B7 /* ASTipsWindow.h */, - CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.m */, - CCA282C61E9EB64B0037E8B7 /* ASDisplayNodeTipState.h */, - CCA282C71E9EB64B0037E8B7 /* ASDisplayNodeTipState.m */, - CCA282CA1E9EB73E0037E8B7 /* ASTipNode.h */, - CCA282CB1E9EB73E0037E8B7 /* ASTipNode.m */, - CCA282BE1E9EAE010037E8B7 /* ASTip.h */, - CCA282BF1E9EAE010037E8B7 /* ASTip.m */, - CCA282C21E9EAE630037E8B7 /* ASLayerBackingTipProvider.h */, - CCA282C31E9EAE630037E8B7 /* ASLayerBackingTipProvider.m */, - CCA282BA1E9EABDD0037E8B7 /* ASTipProvider.h */, - CCA282BB1E9EABDD0037E8B7 /* ASTipProvider.m */, - CCA282B21E9EA7310037E8B7 /* ASTipsController.h */, - CCA282B31E9EA7310037E8B7 /* ASTipsController.m */, - CC2F65EC1E5FFB1600DA57C9 /* ASMutableElementMap.h */, - CC2F65ED1E5FFB1600DA57C9 /* ASMutableElementMap.m */, - E52AC9B91FEA90EB00AA4040 /* ASRectMap.h */, - E52AC9B81FEA90EB00AA4040 /* ASRectMap.mm */, - CC55A70F1E52A0F200594372 /* ASResponderChainEnumerator.h */, - CC55A7101E52A0F200594372 /* ASResponderChainEnumerator.m */, - 6947B0BB1E36B4E30007C478 /* Layout */, CCE04B2A1E313EDA006AEBBB /* Collection Data Adapter */, E52F8AEE1EAE659600B5A912 /* Collection Layout */, + 6947B0BB1E36B4E30007C478 /* Layout */, + CCCCCCC11EC3EF060087FE10 /* TextExperiment */, 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */, 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */, AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */, @@ -1446,22 +1426,42 @@ 058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */, 6959433D1D70815300B0EE1F /* ASDisplayNodeLayout.h */, 6959433C1D70815300B0EE1F /* ASDisplayNodeLayout.mm */, - 6900C5F31E8072DA00BCD75C /* ASImageNode+Private.h */, + CCA282C61E9EB64B0037E8B7 /* ASDisplayNodeTipState.h */, + CCA282C71E9EB64B0037E8B7 /* ASDisplayNodeTipState.m */, 68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */, 058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */, 058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */, + 6900C5F31E8072DA00BCD75C /* ASImageNode+Private.h */, ACF6ED431B17847A00DA7C62 /* ASInternalHelpers.h */, ACF6ED441B17847A00DA7C62 /* ASInternalHelpers.m */, + CCA282C21E9EAE630037E8B7 /* ASLayerBackingTipProvider.h */, + CCA282C31E9EAE630037E8B7 /* ASLayerBackingTipProvider.m */, E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */, E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */, - 0442850B1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.h */, - 0442850C1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.m */, + CC2F65EC1E5FFB1600DA57C9 /* ASMutableElementMap.h */, + CC2F65ED1E5FFB1600DA57C9 /* ASMutableElementMap.m */, + CCED5E402020D41600395C40 /* ASNetworkImageLoadInfo+Private.h */, CC3B20811C3F76D600798563 /* ASPendingStateController.h */, CC3B20821C3F76D600798563 /* ASPendingStateController.mm */, + E52AC9B91FEA90EB00AA4040 /* ASRectMap.h */, + E52AC9B81FEA90EB00AA4040 /* ASRectMap.mm */, + CC55A70F1E52A0F200594372 /* ASResponderChainEnumerator.h */, + CC55A7101E52A0F200594372 /* ASResponderChainEnumerator.m */, CC512B841DAC45C60054848E /* ASTableView+Undeprecated.h */, + CCA282BE1E9EAE010037E8B7 /* ASTip.h */, + CCA282BF1E9EAE010037E8B7 /* ASTip.m */, + CCA282CA1E9EB73E0037E8B7 /* ASTipNode.h */, + CCA282CB1E9EB73E0037E8B7 /* ASTipNode.m */, + CCA282BA1E9EABDD0037E8B7 /* ASTipProvider.h */, + CCA282BB1E9EABDD0037E8B7 /* ASTipProvider.m */, + CCA282B21E9EA7310037E8B7 /* ASTipsController.h */, + CCA282B31E9EA7310037E8B7 /* ASTipsController.m */, + CCA282CE1E9EBF6C0037E8B7 /* ASTipsWindow.h */, + CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.m */, + 0442850B1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.h */, + 0442850C1BAA64EC00D16268 /* ASTwoDimensionalArrayUtils.m */, 83A7D9581D44542100BF333E /* ASWeakMap.h */, 83A7D9591D44542100BF333E /* ASWeakMap.m */, - CCCCCCC11EC3EF060087FE10 /* TextExperiment */, ); path = Private; sourceTree = ""; @@ -1486,19 +1486,18 @@ children = ( B30BF6501C5964B0004FCD53 /* ASLayoutManager.h */, B30BF6511C5964B0004FCD53 /* ASLayoutManager.m */, + 257754951BEE44CD00737CA5 /* ASTextKitAttributes.h */, + 257754941BEE44CD00737CA5 /* ASTextKitAttributes.mm */, 257754BA1BEE458E00737CA5 /* ASTextKitComponents.h */, 257754B71BEE458D00737CA5 /* ASTextKitComponents.mm */, - 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */, - 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */, - 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */, - 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */, - 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */, - 257754941BEE44CD00737CA5 /* ASTextKitAttributes.mm */, - 257754951BEE44CD00737CA5 /* ASTextKitAttributes.h */, 257754961BEE44CD00737CA5 /* ASTextKitContext.h */, 257754971BEE44CD00737CA5 /* ASTextKitContext.mm */, + 257754BB1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h */, + 257754B81BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m */, 257754981BEE44CD00737CA5 /* ASTextKitEntityAttribute.h */, 257754991BEE44CD00737CA5 /* ASTextKitEntityAttribute.m */, + A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */, + 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */, 257754931BEE44CD00737CA5 /* ASTextKitRenderer.h */, 2577549A1BEE44CD00737CA5 /* ASTextKitRenderer.mm */, 2577549B1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.h */, @@ -1509,9 +1508,10 @@ 257754A01BEE44CD00737CA5 /* ASTextKitShadower.mm */, 257754A11BEE44CD00737CA5 /* ASTextKitTailTruncater.h */, 257754A21BEE44CD00737CA5 /* ASTextKitTailTruncater.mm */, - A32FEDD31C501B6A004F642A /* ASTextKitFontSizeAdjuster.h */, - 9C8898BA1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm */, 257754A31BEE44CD00737CA5 /* ASTextKitTruncating.h */, + 257754BC1BEE458E00737CA5 /* ASTextNodeTypes.h */, + 257754B91BEE458E00737CA5 /* ASTextNodeWordKerner.h */, + 257754BD1BEE458E00737CA5 /* ASTextNodeWordKerner.m */, ); name = TextKit; sourceTree = ""; @@ -1519,12 +1519,12 @@ 25B171EA1C12242700508A7A /* Data Controller */ = { isa = PBXGroup; children = ( - DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */, - DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */, - 464052191A3F83C40061C0BA /* ASDataController.h */, - 4640521A1A3F83C40061C0BA /* ASDataController.mm */, E5711A2A1C840C81009619D4 /* ASCollectionElement.h */, E5711A2D1C840C96009619D4 /* ASCollectionElement.mm */, + 464052191A3F83C40061C0BA /* ASDataController.h */, + 4640521A1A3F83C40061C0BA /* ASDataController.mm */, + DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */, + DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */, E5B077FD1E69F4EB00C24B5B /* ASElementMap.h */, E5B077FE1E69F4EB00C24B5B /* ASElementMap.m */, AC6145401D8AFAE8003D62A2 /* ASSection.h */, @@ -1608,11 +1608,11 @@ CC583ABF1EF9BAB400134156 /* Common */ = { isa = PBXGroup; children = ( - CCE4F9BB1F0EA67F00062E4E /* debugbreak.h */, CC583AC01EF9BAB400134156 /* ASDisplayNode+OCMock.m */, CC583AC11EF9BAB400134156 /* ASTestCase.h */, CC583AC21EF9BAB400134156 /* ASTestCase.m */, CC583AC31EF9BAB400134156 /* ASXCTExtensions.h */, + CCE4F9BB1F0EA67F00062E4E /* debugbreak.h */, CC583AC41EF9BAB400134156 /* NSInvocation+ASTestHelpers.h */, CC583AC51EF9BAB400134156 /* NSInvocation+ASTestHelpers.m */, CC583AC61EF9BAB400134156 /* OCMockObject+ASAdditions.h */, @@ -1673,9 +1673,9 @@ CCE04B1D1E313E99006AEBBB /* Collection Data Adapter */ = { isa = PBXGroup; children = ( + CCF92DCE1E315FC50019E9C6 /* IGListKit Support */, CCE04B1E1E313EA7006AEBBB /* ASSectionController.h */, CCE04B2B1E314A32006AEBBB /* ASSupplementaryNodeSource.h */, - CCF92DCE1E315FC50019E9C6 /* IGListKit Support */, ); name = "Collection Data Adapter"; sourceTree = ""; @@ -1703,10 +1703,10 @@ DE89C1691DCEB9CC00D49D74 /* Debug */ = { isa = PBXGroup; children = ( - CCA282B61E9EA8E40037E8B7 /* AsyncDisplayKit+Tips.h */, - CCA282B71E9EA8E40037E8B7 /* AsyncDisplayKit+Tips.m */, 764D83D21C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h */, 764D83D31C8EA515009B4FB8 /* AsyncDisplayKit+Debug.m */, + CCA282B61E9EA8E40037E8B7 /* AsyncDisplayKit+Tips.h */, + CCA282B71E9EA8E40037E8B7 /* AsyncDisplayKit+Tips.m */, ); path = Debug; sourceTree = ""; @@ -1714,6 +1714,10 @@ E52F8AEE1EAE659600B5A912 /* Collection Layout */ = { isa = PBXGroup; children = ( + E5667E8B1F33871300FA6FC0 /* _ASCollectionGalleryLayoutInfo.h */, + E5667E8D1F33872700FA6FC0 /* _ASCollectionGalleryLayoutInfo.m */, + E5775AFB1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h */, + E5775AFD1F13CF7400CAC9BC /* _ASCollectionGalleryLayoutItem.mm */, E58E9E471E941DA5004CFC59 /* ASCollectionLayout.h */, E58E9E481E941DA5004CFC59 /* ASCollectionLayout.mm */, E5775B011F16759300CAC9BC /* ASCollectionLayoutCache.h */, @@ -1722,10 +1726,6 @@ E5855DEE1EBB4D83003639AE /* ASCollectionLayoutDefines.h */, E5855DED1EBB4D83003639AE /* ASCollectionLayoutDefines.m */, E5775AFF1F13D25400CAC9BC /* ASCollectionLayoutState+Private.h */, - E5667E8B1F33871300FA6FC0 /* _ASCollectionGalleryLayoutInfo.h */, - E5667E8D1F33872700FA6FC0 /* _ASCollectionGalleryLayoutInfo.m */, - E5775AFB1F13CE9F00CAC9BC /* _ASCollectionGalleryLayoutItem.h */, - E5775AFD1F13CF7400CAC9BC /* _ASCollectionGalleryLayoutItem.mm */, ); name = "Collection Layout"; sourceTree = ""; @@ -1733,15 +1733,15 @@ E5B077EB1E6843AF00C24B5B /* Collection Layout */ = { isa = PBXGroup; children = ( - E58E9E3F1E941D74004CFC59 /* ASCollectionLayoutContext.h */, - E58E9E401E941D74004CFC59 /* ASCollectionLayoutContext.m */, - E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */, - E5E281751E71C845006B67C2 /* ASCollectionLayoutState.mm */, - E58E9E411E941D74004CFC59 /* ASCollectionLayoutDelegate.h */, E58E9E3D1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.h */, E58E9E3E1E941D74004CFC59 /* ASCollectionFlowLayoutDelegate.m */, E5E2D72D1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h */, E5E2D72F1EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm */, + E58E9E3F1E941D74004CFC59 /* ASCollectionLayoutContext.h */, + E58E9E401E941D74004CFC59 /* ASCollectionLayoutContext.m */, + E58E9E411E941D74004CFC59 /* ASCollectionLayoutDelegate.h */, + E5E281731E71C833006B67C2 /* ASCollectionLayoutState.h */, + E5E281751E71C845006B67C2 /* ASCollectionLayoutState.mm */, E54E81FA1EB357BD00FFE8E1 /* ASPageTable.h */, E54E81FB1EB357BD00FFE8E1 /* ASPageTable.m */, ); From 0f9b1e6789509b642bffdbdacd3af139ab0b23bf Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sun, 25 Mar 2018 10:46:04 -0700 Subject: [PATCH 113/133] Make objects conform to NSLocking (#851) * Make display node, layout spec, and style conform to NSLocking so that users/subclasses can access their locks * Update the changelog * Align slashes * Put it back, when we're in ASDisplayNode * Go a little further * Put back the changes I didn't mean to commit * Kick the CI * Fix yoga build * Put back non-locking change * Address comments from Scott --- AsyncDisplayKit.xcodeproj/project.pbxproj | 4 - CHANGELOG.md | 1 + Source/ASButtonNode.mm | 58 ++--- Source/ASControlNode.mm | 58 +++-- Source/ASDisplayNode+Layout.mm | 5 +- Source/ASDisplayNode+Yoga.mm | 6 +- Source/ASDisplayNode.h | 2 +- Source/ASDisplayNode.mm | 16 +- Source/ASImageNode+AnimatedImage.mm | 20 +- Source/ASImageNode.mm | 7 +- Source/ASMapNode.mm | 26 +-- Source/ASMultiplexImageNode.mm | 14 +- Source/ASNetworkImageNode.mm | 123 +++++----- Source/ASScrollNode.mm | 15 +- Source/ASTextNode.mm | 212 +++++------------- Source/ASTextNode2.mm | 120 +++++----- Source/ASVideoNode.mm | 104 +++++---- Source/ASVideoPlayerNode.mm | 27 +-- Source/Base/ASBaseDefines.h | 23 ++ Source/Details/ASThread.h | 72 +++++- Source/Layout/ASLayoutElement.h | 2 +- Source/Layout/ASLayoutElement.mm | 12 + Source/Layout/ASLayoutSpec.h | 2 +- Source/Layout/ASLayoutSpec.mm | 12 + Source/Private/ASDisplayNode+AsyncDisplay.mm | 2 +- .../Private/ASDisplayNode+FrameworkPrivate.h | 2 +- .../ASDisplayNode+FrameworkSubclasses.h | 40 ---- Source/Private/ASDisplayNode+UIViewBridge.mm | 1 - Source/Private/ASDisplayNodeInternal.h | 2 + 29 files changed, 461 insertions(+), 527 deletions(-) delete mode 100644 Source/Private/ASDisplayNode+FrameworkSubclasses.h diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index e50e895c4..be2a0441f 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -424,7 +424,6 @@ DBDB83971C6E879900D0098C /* ASPagerFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */; }; DE4843DC1C93EAC100A1F33B /* ASLayoutTransition.h in Headers */ = {isa = PBXBuildFile; fileRef = E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */; settings = {ATTRIBUTES = (Private, ); }; }; DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; settings = {ATTRIBUTES = (Private, ); }; }; - DE7EF4F81DFF77720082B84A /* ASDisplayNode+FrameworkSubclasses.h in Headers */ = {isa = PBXBuildFile; fileRef = DE7EF4F71DFF77720082B84A /* ASDisplayNode+FrameworkSubclasses.h */; settings = {ATTRIBUTES = (Private, ); }; }; DE84918D1C8FFF2B003D89E9 /* ASRunLoopQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */; settings = {ATTRIBUTES = (Public, ); }; }; DE84918E1C8FFF9F003D89E9 /* ASRunLoopQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */; }; DE8BEAC21C2DF3FC00D57C12 /* ASDelegateProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -933,7 +932,6 @@ DBDB83921C6E879900D0098C /* ASPagerFlowLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPagerFlowLayout.h; sourceTree = ""; }; DBDB83931C6E879900D0098C /* ASPagerFlowLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASPagerFlowLayout.m; sourceTree = ""; }; DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = ""; }; - DE7EF4F71DFF77720082B84A /* ASDisplayNode+FrameworkSubclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkSubclasses.h"; sourceTree = ""; }; DE8BEABF1C2DF3FC00D57C12 /* ASDelegateProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDelegateProxy.h; sourceTree = ""; }; DE8BEAC01C2DF3FC00D57C12 /* ASDelegateProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDelegateProxy.m; sourceTree = ""; }; DEC146B41C37A16A004A0EE7 /* ASCollectionInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASCollectionInternal.h; path = Details/ASCollectionInternal.h; sourceTree = ""; }; @@ -1421,7 +1419,6 @@ 058D0A09195D050800B7D73C /* ASDisplayNode+DebugTiming.h */, 058D0A0A195D050800B7D73C /* ASDisplayNode+DebugTiming.mm */, DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */, - DE7EF4F71DFF77720082B84A /* ASDisplayNode+FrameworkSubclasses.h */, 058D0A0B195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm */, 058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */, 6959433D1D70815300B0EE1F /* ASDisplayNodeLayout.h */, @@ -1849,7 +1846,6 @@ 34EFC7691B701CE100AD841F /* ASLayoutElement.h in Headers */, 698DFF471E36B7E9002891F1 /* ASLayoutSpecUtilities.h in Headers */, 9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */, - DE7EF4F81DFF77720082B84A /* ASDisplayNode+FrameworkSubclasses.h in Headers */, CCCCCCD51EC3EF060087FE10 /* ASTextDebugOption.h in Headers */, CC034A091E60BEB400626263 /* ASDisplayNode+Convenience.h in Headers */, 254C6B7E1BF94DF4003EC431 /* ASTextKitTailTruncater.h in Headers */, diff --git a/CHANGELOG.md b/CHANGELOG.md index cc3991080..1d37be067 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ - Fix ASTextNode2 handling background color incorrectly. [Adlai Holler](https://github.com/Adlai-Holler) [#831](https://github.com/TextureGroup/Texture/pull/831/) - [NoCopyRendering] Improved performance & fixed image memory not being tagged in Instruments. [Adlai Holler](https://github.com/Adlai-Holler) [#833](https://github.com/TextureGroup/Texture/pull/833/) - Use `NS_RETURNS_RETAINED` macro to make our methods a tiny bit faster. [Adlai Holler](https://github.com/Adlai-Holler) [#843](https://github.com/TextureGroup/Texture/pull/843/) +- `ASDisplayNode, ASLayoutSpec, and ASLayoutElementStyle` now conform to `NSLocking`. They act as recursive locks. Useful locking macros have been added as `ASThread.h`. Subclasses / client code can lock these objects but should be careful as usual when dealing with locks. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Source/ASButtonNode.mm b/Source/ASButtonNode.mm index 41e411caf..7f50dd91a 100644 --- a/Source/ASButtonNode.mm +++ b/Source/ASButtonNode.mm @@ -18,7 +18,7 @@ #import #import #import -#import +#import #import #import #import @@ -161,7 +161,7 @@ - (void)setDisplaysAsynchronously:(BOOL)displaysAsynchronously - (void)updateImage { - __instanceLock__.lock(); + [self lock]; UIImage *newImage; if (self.enabled == NO && _disabledImage) { @@ -178,18 +178,18 @@ - (void)updateImage if ((_imageNode != nil || newImage != nil) && newImage != self.imageNode.image) { _imageNode.image = newImage; - __instanceLock__.unlock(); + [self unlock]; [self setNeedsLayout]; return; } - __instanceLock__.unlock(); + [self unlock]; } - (void)updateTitle { - __instanceLock__.lock(); + [self lock]; NSAttributedString *newTitle; if (self.enabled == NO && _disabledAttributedTitle) { @@ -207,19 +207,19 @@ - (void)updateTitle // Calling self.titleNode is essential here because _titleNode is lazily created by the getter. if ((_titleNode != nil || newTitle.length > 0) && [self.titleNode.attributedText isEqualToAttributedString:newTitle] == NO) { _titleNode.attributedText = newTitle; - __instanceLock__.unlock(); + [self unlock]; self.accessibilityLabel = _titleNode.accessibilityLabel; [self setNeedsLayout]; return; } - __instanceLock__.unlock(); + [self unlock]; } - (void)updateBackgroundImage { - __instanceLock__.lock(); + [self lock]; UIImage *newImage; if (self.enabled == NO && _disabledBackgroundImage) { @@ -236,25 +236,25 @@ - (void)updateBackgroundImage if ((_backgroundImageNode != nil || newImage != nil) && newImage != self.backgroundImageNode.image) { _backgroundImageNode.image = newImage; - __instanceLock__.unlock(); + [self unlock]; [self setNeedsLayout]; return; } - __instanceLock__.unlock(); + [self unlock]; } - (CGFloat)contentSpacing { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _contentSpacing; } - (void)setContentSpacing:(CGFloat)contentSpacing { { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (contentSpacing == _contentSpacing) { return; } @@ -267,14 +267,14 @@ - (void)setContentSpacing:(CGFloat)contentSpacing - (BOOL)laysOutHorizontally { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _laysOutHorizontally; } - (void)setLaysOutHorizontally:(BOOL)laysOutHorizontally { { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (laysOutHorizontally == _laysOutHorizontally) { return; } @@ -287,49 +287,49 @@ - (void)setLaysOutHorizontally:(BOOL)laysOutHorizontally - (ASVerticalAlignment)contentVerticalAlignment { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _contentVerticalAlignment; } - (void)setContentVerticalAlignment:(ASVerticalAlignment)contentVerticalAlignment { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _contentVerticalAlignment = contentVerticalAlignment; } - (ASHorizontalAlignment)contentHorizontalAlignment { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _contentHorizontalAlignment; } - (void)setContentHorizontalAlignment:(ASHorizontalAlignment)contentHorizontalAlignment { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _contentHorizontalAlignment = contentHorizontalAlignment; } - (UIEdgeInsets)contentEdgeInsets { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _contentEdgeInsets; } - (void)setContentEdgeInsets:(UIEdgeInsets)contentEdgeInsets { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _contentEdgeInsets = contentEdgeInsets; } - (ASButtonNodeImageAlignment)imageAlignment { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _imageAlignment; } - (void)setImageAlignment:(ASButtonNodeImageAlignment)imageAlignment { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _imageAlignment = imageAlignment; } @@ -349,7 +349,7 @@ - (void)setTitle:(NSString *)title withFont:(UIFont *)font withColor:(UIColor *) - (NSAttributedString *)attributedTitleForState:(UIControlState)state { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); switch (state) { case UIControlStateNormal: return _normalAttributedTitle; @@ -374,7 +374,7 @@ - (NSAttributedString *)attributedTitleForState:(UIControlState)state - (void)setAttributedTitle:(NSAttributedString *)title forState:(UIControlState)state { { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); switch (state) { case UIControlStateNormal: _normalAttributedTitle = [title copy]; @@ -406,7 +406,7 @@ - (void)setAttributedTitle:(NSAttributedString *)title forState:(UIControlState) - (UIImage *)imageForState:(UIControlState)state { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); switch (state) { case UIControlStateNormal: return _normalImage; @@ -431,7 +431,7 @@ - (UIImage *)imageForState:(UIControlState)state - (void)setImage:(UIImage *)image forState:(UIControlState)state { { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); switch (state) { case UIControlStateNormal: _normalImage = image; @@ -463,7 +463,7 @@ - (void)setImage:(UIImage *)image forState:(UIControlState)state - (UIImage *)backgroundImageForState:(UIControlState)state { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); switch (state) { case UIControlStateNormal: return _normalBackgroundImage; @@ -488,7 +488,7 @@ - (UIImage *)backgroundImageForState:(UIControlState)state - (void)setBackgroundImage:(UIImage *)image forState:(UIControlState)state { { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); switch (state) { case UIControlStateNormal: _normalBackgroundImage = image; @@ -525,7 +525,7 @@ - (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize ASLayoutSpec *spec; ASStackLayoutSpec *stack = [[ASStackLayoutSpec alloc] init]; { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); stack.direction = _laysOutHorizontally ? ASStackLayoutDirectionHorizontal : ASStackLayoutDirectionVertical; stack.spacing = _contentSpacing; stack.horizontalAlignment = _contentHorizontalAlignment; diff --git a/Source/ASControlNode.mm b/Source/ASControlNode.mm index 702161721..697abf8d0 100644 --- a/Source/ASControlNode.mm +++ b/Source/ASControlNode.mm @@ -38,8 +38,6 @@ @interface ASControlNode () { @private - ASDN::RecursiveMutex _controlLock; - // Control Attributes BOOL _enabled; BOOL _highlighted; @@ -298,7 +296,7 @@ - (void)addTarget:(id)target action:(SEL)action forControlEvents:(ASControlNodeE // ASControlNode cannot be layer backed if adding a target ASDisplayNodeAssert(!self.isLayerBacked, @"ASControlNode is layer backed, will never be able to call target in target:action: pair."); - ASDN::MutexLocker l(_controlLock); + ASLockScopeSelf(); if (!_controlEventDispatchTable) { _controlEventDispatchTable = [[NSMutableDictionary alloc] initWithCapacity:kASControlNodeEventDispatchTableInitialCapacity]; // enough to handle common types without re-hashing the dictionary when adding entries. @@ -352,7 +350,7 @@ - (NSArray *)actionsForTarget:(id)target forControlEvent:(ASControlNodeEvent)con NSParameterAssert(target); NSParameterAssert(controlEvent != 0 && controlEvent != ASControlNodeEventAllEvents); - ASDN::MutexLocker l(_controlLock); + ASLockScopeSelf(); // Grab the event target action array for this event. NSMutableArray *eventTargetActionArray = _controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)]; @@ -374,7 +372,7 @@ - (NSArray *)actionsForTarget:(id)target forControlEvent:(ASControlNodeEvent)con - (NSSet *)allTargets { - ASDN::MutexLocker l(_controlLock); + ASLockScopeSelf(); NSMutableSet *targets = [[NSMutableSet alloc] init]; @@ -393,7 +391,7 @@ - (void)removeTarget:(id)target action:(SEL)action forControlEvents:(ASControlNo { NSParameterAssert(controlEventMask != 0); - ASDN::MutexLocker l(_controlLock); + ASLockScopeSelf(); // Enumerate the events in the mask, removing the target-action pair for each control event included in controlEventMask. _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEventMask, ^ @@ -435,31 +433,31 @@ - (void)sendActionsForControlEvents:(ASControlNodeEvent)controlEvents withEvent: NSMutableArray *resolvedEventTargetActionArray = [[NSMutableArray alloc] init]; - _controlLock.lock(); - - // Enumerate the events in the mask, invoking the target-action pairs for each. - _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEvents, ^ - (ASControlNodeEvent controlEvent) - { - // Iterate on each target action pair - for (ASControlTargetAction *targetAction in _controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)]) { - ASControlTargetAction *resolvedTargetAction = [[ASControlTargetAction alloc] init]; - resolvedTargetAction.action = targetAction.action; - resolvedTargetAction.target = targetAction.target; - - // NSNull means that a nil target was set, so start at self and travel the responder chain - if (!resolvedTargetAction.target && targetAction.createdWithNoTarget) { - // if the target cannot perform the action, travel the responder chain to try to find something that does - resolvedTargetAction.target = [self.view targetForAction:resolvedTargetAction.action withSender:self]; - } - - if (resolvedTargetAction.target) { - [resolvedEventTargetActionArray addObject:resolvedTargetAction]; + { + ASLockScopeSelf(); + + // Enumerate the events in the mask, invoking the target-action pairs for each. + _ASEnumerateControlEventsIncludedInMaskWithBlock(controlEvents, ^ + (ASControlNodeEvent controlEvent) + { + // Iterate on each target action pair + for (ASControlTargetAction *targetAction in _controlEventDispatchTable[_ASControlNodeEventKeyForControlEvent(controlEvent)]) { + ASControlTargetAction *resolvedTargetAction = [[ASControlTargetAction alloc] init]; + resolvedTargetAction.action = targetAction.action; + resolvedTargetAction.target = targetAction.target; + + // NSNull means that a nil target was set, so start at self and travel the responder chain + if (!resolvedTargetAction.target && targetAction.createdWithNoTarget) { + // if the target cannot perform the action, travel the responder chain to try to find something that does + resolvedTargetAction.target = [self.view targetForAction:resolvedTargetAction.action withSender:self]; + } + + if (resolvedTargetAction.target) { + [resolvedEventTargetActionArray addObject:resolvedTargetAction]; + } } - } - }); - - _controlLock.unlock(); + }); + } //We don't want to hold the lock while calling out, we could potentially walk up the ownership tree causing a deadlock. #pragma clang diagnostic push diff --git a/Source/ASDisplayNode+Layout.mm b/Source/ASDisplayNode+Layout.mm index 084f4f912..d656a8983 100644 --- a/Source/ASDisplayNode+Layout.mm +++ b/Source/ASDisplayNode+Layout.mm @@ -17,14 +17,13 @@ #import #import +#import +#import #import #import #import #import -#import - -#pragma mark - #pragma mark - ASDisplayNode (ASLayoutElement) @implementation ASDisplayNode (ASLayoutElement) diff --git a/Source/ASDisplayNode+Yoga.mm b/Source/ASDisplayNode+Yoga.mm index eca586b57..078559639 100644 --- a/Source/ASDisplayNode+Yoga.mm +++ b/Source/ASDisplayNode+Yoga.mm @@ -24,7 +24,7 @@ #import #import #import -#import +#import #import #import @@ -157,7 +157,7 @@ - (ASLayout *)layoutForYogaNode - (void)setupYogaCalculatedLayout { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); YGNodeRef yogaNode = self.style.yogaNode; uint32_t childCount = YGNodeGetChildCount(yogaNode); @@ -267,7 +267,7 @@ - (void)calculateLayoutFromYogaRoot:(ASSizeRange)rootConstrainedSize return; } - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); // Prepare all children for the layout pass with the current Yoga tree configuration. ASDisplayNodePerformBlockOnEveryYogaChild(self, ^(ASDisplayNode * _Nonnull node) { diff --git a/Source/ASDisplayNode.h b/Source/ASDisplayNode.h index 49f2d917c..851155dea 100644 --- a/Source/ASDisplayNode.h +++ b/Source/ASDisplayNode.h @@ -127,7 +127,7 @@ extern NSInteger const ASDefaultDrawingPriority; * */ -@interface ASDisplayNode : NSObject +@interface ASDisplayNode : NSObject /** @name Initializing a node object */ diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 60eea7fb2..98f3f1b80 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -18,7 +18,6 @@ #import #import -#import #import #import #import @@ -35,6 +34,9 @@ #import #import #import +#import +#import +#import #import #import #import @@ -93,7 +95,7 @@ BOOL ASDisplayNodeNeedsSpecialPropertiesHandling(BOOL isSynchronous, BOOL isLaye _ASPendingState *ASDisplayNodeGetPendingState(ASDisplayNode *node) { - ASDN::MutexLocker l(node->__instanceLock__); + ASLockScope(node); _ASPendingState *result = node->_pendingViewState; if (result == nil) { result = [[_ASPendingState alloc] init]; @@ -359,6 +361,16 @@ - (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)layerBlock didLoadBl return self; } +- (void)lock +{ + __instanceLock__.lock(); +} + +- (void)unlock +{ + __instanceLock__.unlock(); +} + - (void)setViewBlock:(ASDisplayNodeViewBlock)viewBlock { ASDisplayNodeAssertFalse(self.nodeLoaded); diff --git a/Source/ASImageNode+AnimatedImage.mm b/Source/ASImageNode+AnimatedImage.mm index a9ef4d33f..cca62928c 100644 --- a/Source/ASImageNode+AnimatedImage.mm +++ b/Source/ASImageNode+AnimatedImage.mm @@ -20,7 +20,6 @@ #import #import #import -#import #import #import #import @@ -28,6 +27,7 @@ #import #import #import +#import #import #define ASAnimatedImageDebug 0 @@ -44,7 +44,7 @@ @implementation ASImageNode (AnimatedImage) - (void)setAnimatedImage:(id )animatedImage { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); [self _locked_setAnimatedImage:animatedImage]; } @@ -95,13 +95,13 @@ - (void)animatedImageSet:(id )newAnimatedImage previous - (id )animatedImage { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _animatedImage; } - (void)setAnimatedImagePaused:(BOOL)animatedImagePaused { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _animatedImagePaused = animatedImagePaused; @@ -110,14 +110,14 @@ - (void)setAnimatedImagePaused:(BOOL)animatedImagePaused - (BOOL)animatedImagePaused { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _animatedImagePaused; } - (void)setCoverImageCompleted:(UIImage *)coverImage { if (ASInterfaceStateIncludesDisplay(self.interfaceState)) { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); [self _locked_setCoverImageCompleted:coverImage]; } } @@ -135,7 +135,7 @@ - (void)_locked_setCoverImageCompleted:(UIImage *)coverImage - (void)setCoverImage:(UIImage *)coverImage { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); [self _locked_setCoverImage:coverImage]; } @@ -176,7 +176,7 @@ - (void)setAnimatedImageRunLoopMode:(NSString *)runLoopMode - (void)setShouldAnimate:(BOOL)shouldAnimate { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); [self _locked_setShouldAnimate:shouldAnimate]; } @@ -209,7 +209,7 @@ - (void)startAnimating { ASDisplayNodeAssertMainThread(); - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); [self _locked_startAnimating]; } @@ -251,7 +251,7 @@ - (void)stopAnimating { ASDisplayNodeAssertMainThread(); - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); [self _locked_stopAnimating]; } diff --git a/Source/ASImageNode.mm b/Source/ASImageNode.mm index b1a561eea..b0266ebc3 100644 --- a/Source/ASImageNode.mm +++ b/Source/ASImageNode.mm @@ -22,7 +22,8 @@ #import #import #import -#import +#import +#import #import #import #import @@ -305,7 +306,7 @@ - (void)setPlaceholderColor:(UIColor *)placeholderColor - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); ASImageNodeDrawParameters *drawParameters = [[ASImageNodeDrawParameters alloc] init]; drawParameters->_image = [self _locked_Image]; @@ -325,7 +326,7 @@ - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer // Hack for now to retain the weak entry that was created while this drawing happened drawParameters->_didDrawBlock = ^(ASWeakMapEntry *entry){ - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _weakCacheEntry = entry; }; diff --git a/Source/ASMapNode.mm b/Source/ASMapNode.mm index e3e23df15..70a8951de 100644 --- a/Source/ASMapNode.mm +++ b/Source/ASMapNode.mm @@ -22,12 +22,13 @@ #import -#import +#import #import #import #import #import #import +#import @interface ASMapNode() { @@ -107,14 +108,14 @@ - (void)didExitPreloadState - (BOOL)isLiveMap { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _liveMap; } - (void)setLiveMap:(BOOL)liveMap { ASDisplayNodeAssert(!self.isLayerBacked, @"ASMapNode can not use the interactive map feature whilst .isLayerBacked = YES, set .layerBacked = NO to use the interactive map feature."); - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (liveMap == _liveMap) { return; } @@ -126,19 +127,19 @@ - (void)setLiveMap:(BOOL)liveMap - (BOOL)needsMapReloadOnBoundsChange { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _needsMapReloadOnBoundsChange; } - (void)setNeedsMapReloadOnBoundsChange:(BOOL)needsMapReloadOnBoundsChange { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _needsMapReloadOnBoundsChange = needsMapReloadOnBoundsChange; } - (MKMapSnapshotOptions *)options { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (!_options) { _options = [[MKMapSnapshotOptions alloc] init]; _options.region = MKCoordinateRegionForMapRect(MKMapRectWorld); @@ -152,7 +153,7 @@ - (MKMapSnapshotOptions *)options - (void)setOptions:(MKMapSnapshotOptions *)options { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (!_options || ![options isEqual:_options]) { _options = options; if (self.isLiveMap) { @@ -324,7 +325,7 @@ - (void)removeLiveMap - (NSArray *)annotations { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _annotations; } @@ -332,7 +333,7 @@ - (void)setAnnotations:(NSArray *)annotations { annotations = [annotations copy] ? : @[]; - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _annotations = annotations; ASMapNodeShowAnnotationsOptions showAnnotationsOptions = self.showAnnotationsOptions; if (self.isLiveMap) { @@ -353,7 +354,7 @@ - (void)setAnnotations:(NSArray *)annotations } } --(MKCoordinateRegion)regionToFitAnnotations:(NSArray> *)annotations +- (MKCoordinateRegion)regionToFitAnnotations:(NSArray> *)annotations { if([annotations count] == 0) return MKCoordinateRegionForMapRect(MKMapRectWorld); @@ -377,12 +378,11 @@ -(MKCoordinateRegion)regionToFitAnnotations:(NSArray> *)annotat } -(ASMapNodeShowAnnotationsOptions)showAnnotationsOptions { - ASDN::MutexLocker l(__instanceLock__); - return _showAnnotationsOptions; + return ASLockedSelf(_showAnnotationsOptions); } -(void)setShowAnnotationsOptions:(ASMapNodeShowAnnotationsOptions)showAnnotationsOptions { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _showAnnotationsOptions = showAnnotationsOptions; } diff --git a/Source/ASMultiplexImageNode.mm b/Source/ASMultiplexImageNode.mm index 9edf93b6a..50057dd52 100644 --- a/Source/ASMultiplexImageNode.mm +++ b/Source/ASMultiplexImageNode.mm @@ -22,12 +22,14 @@ #endif #import -#import #import +#import +#import #import #import #import #import +#import #if AS_PIN_REMOTE_IMAGE #import @@ -357,23 +359,21 @@ - (void)setDataSource:(id )dataSource - (void)setShouldRenderProgressImages:(BOOL)shouldRenderProgressImages { - __instanceLock__.lock(); + [self lock]; if (shouldRenderProgressImages == _shouldRenderProgressImages) { - __instanceLock__.unlock(); + [self unlock]; return; } _shouldRenderProgressImages = shouldRenderProgressImages; - - __instanceLock__.unlock(); + [self unlock]; [self _updateProgressImageBlockOnDownloaderIfNeeded]; } - (BOOL)shouldRenderProgressImages { - ASDN::MutexLocker l(__instanceLock__); - return _shouldRenderProgressImages; + return ASLockedSelf(_shouldRenderProgressImages); } #pragma mark - diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index f7c430a46..6f61da7b9 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -20,7 +20,8 @@ #import #import #import -#import +#import +#import #import #import #import @@ -29,13 +30,15 @@ #import #import +#import + #if AS_PIN_REMOTE_IMAGE #import #endif @interface ASNetworkImageNode () { - // Only access any of these with __instanceLock__. + // Only access any of these while locked. __weak id _delegate; NSURL *_URL; @@ -126,7 +129,7 @@ - (void)dealloc /// Setter for public image property. It has the side effect of setting an internal _imageWasSetExternally that prevents setting an image internally. Setting an image internally should happen with the _setImage: method - (void)setImage:(UIImage *)image { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); [self _locked_setImage:image]; } @@ -152,7 +155,7 @@ - (void)_locked_setImage:(UIImage *)image /// Setter for private image property. See @c _locked_setImage why this is needed - (void)_setImage:(UIImage *)image { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); [self _locked__setImage:image]; } @@ -181,7 +184,7 @@ - (void)setURL:(NSURL *)URL - (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset { { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (ASObjectIsEqual(URL, _URL)) { return; @@ -210,13 +213,12 @@ - (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset - (NSURL *)URL { - ASDN::MutexLocker l(__instanceLock__); - return _URL; + return ASLockedSelf(_URL); } - (void)setDefaultImage:(UIImage *)defaultImage { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); [self _locked_setDefaultImage:defaultImage]; } @@ -237,20 +239,18 @@ - (void)_locked_setDefaultImage:(UIImage *)defaultImage - (UIImage *)defaultImage { - ASDN::MutexLocker l(__instanceLock__); - return _defaultImage; + return ASLockedSelf(_defaultImage); } - (void)setCurrentImageQuality:(CGFloat)currentImageQuality { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _currentImageQuality = currentImageQuality; } - (CGFloat)currentImageQuality { - ASDN::MutexLocker l(__instanceLock__); - return _currentImageQuality; + return ASLockedSelf(_currentImageQuality); } /** @@ -262,29 +262,25 @@ - (CGFloat)currentImageQuality - (void)_setCurrentImageQuality:(CGFloat)imageQuality { dispatch_async(dispatch_get_main_queue(), ^{ - // As the setting of the image quality is dispatched the lock is gone by the time the block is executing. - // Therefore we have to grab the lock again - __instanceLock__.lock(); - _currentImageQuality = imageQuality; - __instanceLock__.unlock(); + self.currentImageQuality = imageQuality; }); } - (void)setRenderedImageQuality:(CGFloat)renderedImageQuality { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _renderedImageQuality = renderedImageQuality; } - (CGFloat)renderedImageQuality { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _renderedImageQuality; } - (void)setDelegate:(id)delegate { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _delegate = delegate; _delegateFlags.delegateDidStartFetchingData = [delegate respondsToSelector:@selector(imageNodeDidStartFetchingData:)]; @@ -296,14 +292,14 @@ - (void)setDelegate:(id)delegate - (id)delegate { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _delegate; } - (void)setShouldRenderProgressImages:(BOOL)shouldRenderProgressImages { { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (shouldRenderProgressImages == _shouldRenderProgressImages) { return; } @@ -315,13 +311,13 @@ - (void)setShouldRenderProgressImages:(BOOL)shouldRenderProgressImages - (BOOL)shouldRenderProgressImages { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _shouldRenderProgressImages; } - (BOOL)placeholderShouldPersist { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return (self.image == nil && self.animatedImage == nil && _URL != nil); } @@ -332,7 +328,7 @@ - (void)displayWillStartAsynchronously:(BOOL)asynchronously [super displayWillStartAsynchronously:asynchronously]; if (asynchronously == NO && _cacheFlags.cacheSupportsSynchronousFetch) { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); NSURL *url = _URL; if (_imageLoaded == NO && url && _downloadIdentifier == nil) { @@ -344,11 +340,11 @@ - (void)displayWillStartAsynchronously:(BOOL)asynchronously // Call out to the delegate. if (_delegateFlags.delegateDidLoadImageWithInfo) { - ASDN::MutexUnlocker l(__instanceLock__); + ASUnlockScope(self); auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:url sourceType:ASNetworkImageSourceSynchronousCache downloadIdentifier:nil userInfo:nil]; [_delegate imageNode:self didLoadImage:result info:info]; } else if (_delegateFlags.delegateDidLoadImage) { - ASDN::MutexUnlocker l(__instanceLock__); + ASUnlockScope(self); [_delegate imageNode:self didLoadImage:result]; } } @@ -359,9 +355,7 @@ - (void)displayWillStartAsynchronously:(BOOL)asynchronously [self didEnterPreloadState]; if (self.image == nil && _downloaderFlags.downloaderImplementsSetPriority) { - __instanceLock__.lock(); - id downloadIdentifier = _downloadIdentifier; - __instanceLock__.unlock(); + id downloadIdentifier = ASLockedSelf(_downloadIdentifier); if (downloadIdentifier != nil) { [_downloader setPriority:ASImageDownloaderPriorityImminent withDownloadIdentifier:downloadIdentifier]; } @@ -374,12 +368,10 @@ - (void)didEnterVisibleState { [super didEnterVisibleState]; - __instanceLock__.lock(); - id downloadIdentifier = nil; - if (_downloaderFlags.downloaderImplementsSetPriority) { - downloadIdentifier = _downloadIdentifier; - } - __instanceLock__.unlock(); + id downloadIdentifier = ({ + ASLockScopeSelf(); + _downloaderFlags.downloaderImplementsSetPriority ? _downloadIdentifier : nil; + }); if (downloadIdentifier != nil) { [_downloader setPriority:ASImageDownloaderPriorityVisible withDownloadIdentifier:downloadIdentifier]; @@ -392,12 +384,10 @@ - (void)didExitVisibleState { [super didExitVisibleState]; - __instanceLock__.lock(); - id downloadIdentifier = nil; - if (_downloaderFlags.downloaderImplementsSetPriority) { - downloadIdentifier = _downloadIdentifier; - } - __instanceLock__.unlock(); + id downloadIdentifier = ({ + ASLockScopeSelf(); + _downloaderFlags.downloaderImplementsSetPriority ? _downloadIdentifier : nil; + }); if (downloadIdentifier != nil) { [_downloader setPriority:ASImageDownloaderPriorityPreload withDownloadIdentifier:downloadIdentifier]; @@ -410,11 +400,8 @@ - (void)didExitPreloadState { [super didExitPreloadState]; - __instanceLock__.lock(); - BOOL imageWasSetExternally = _imageWasSetExternally; - __instanceLock__.unlock(); // If the image was set explicitly we don't want to remove it while exiting the preload state - if (imageWasSetExternally) { + if (ASLockedSelf(_imageWasSetExternally)) { return; } @@ -433,7 +420,7 @@ - (void)didEnterPreloadState - (void)handleProgressImage:(UIImage *)progressImage progress:(CGFloat)progress downloadIdentifier:(nullable id)downloadIdentifier { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); // Getting a result back for a different download identifier, download must not have been successfully canceled if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { @@ -453,12 +440,12 @@ - (void)_updateProgressImageBlockOnDownloaderIfNeeded } // Read state. - __instanceLock__.lock(); + [self lock]; BOOL shouldRender = _shouldRenderProgressImages && ASInterfaceStateIncludesVisible(_interfaceState); id oldDownloadIDForProgressBlock = _downloadIdentifierForProgressBlock; id newDownloadIDForProgressBlock = shouldRender ? _downloadIdentifier : nil; BOOL clearAndReattempt = NO; - __instanceLock__.unlock(); + [self unlock]; // If we're already bound to the correct download, we're done. if (ASObjectIsEqual(oldDownloadIDForProgressBlock, newDownloadIDForProgressBlock)) { @@ -482,7 +469,7 @@ - (void)_updateProgressImageBlockOnDownloaderIfNeeded // Update state local state with lock held. { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); // Check if the oldDownloadIDForProgressBlock still is the same as the _downloadIdentifierForProgressBlock if (_downloadIdentifierForProgressBlock == oldDownloadIDForProgressBlock) { _downloadIdentifierForProgressBlock = newDownloadIDForProgressBlock; @@ -505,7 +492,7 @@ - (void)_updateProgressImageBlockOnDownloaderIfNeeded - (void)_cancelDownloadAndClearImageWithResumePossibility:(BOOL)storeResume { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); [self _locked_cancelDownloadAndClearImageWithResumePossibility:storeResume]; } @@ -529,7 +516,7 @@ - (void)_locked_cancelDownloadAndClearImageWithResumePossibility:(BOOL)storeResu - (void)_cancelImageDownloadWithResumePossibility:(BOOL)storeResume { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); [self _locked_cancelImageDownloadWithResumePossibility:storeResume]; } @@ -564,7 +551,7 @@ - (void)_downloadImageWithCompletion:(void (^)(id ima // We need to reobtain the lock after and ensure that the task we've kicked off still matches our URL. If not, we need to cancel // it and try again. { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); url = _URL; } @@ -580,7 +567,7 @@ - (void)_downloadImageWithCompletion:(void (^)(id ima as_log_verbose(ASImageLoadingLog(), "Downloading image for %@ url: %@", self, url); { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (ASObjectIsEqual(_URL, url)) { // The download we kicked off is correct, no need to do any more work. _downloadIdentifier = downloadIdentifier; @@ -606,13 +593,13 @@ - (void)_downloadImageWithCompletion:(void (^)(id ima - (void)_lazilyLoadImageIfNecessary { - __instanceLock__.lock(); + [self lock]; __weak id delegate = _delegate; BOOL delegateDidStartFetchingData = _delegateFlags.delegateDidStartFetchingData; BOOL isImageLoaded = _imageLoaded; NSURL *URL = _URL; id currentDownloadIdentifier = _downloadIdentifier; - __instanceLock__.unlock(); + [self unlock]; if (!isImageLoaded && URL != nil && currentDownloadIdentifier == nil) { if (delegateDidStartFetchingData) { @@ -621,7 +608,7 @@ - (void)_lazilyLoadImageIfNecessary if (URL.isFileURL) { dispatch_async(dispatch_get_main_queue(), ^{ - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); // Bail out if not the same URL anymore if (!ASObjectIsEqual(URL, _URL)) { @@ -668,11 +655,11 @@ - (void)_lazilyLoadImageIfNecessary [self _setCurrentImageQuality:1.0]; if (_delegateFlags.delegateDidLoadImageWithInfo) { - ASDN::MutexUnlocker u(__instanceLock__); + ASUnlockScope(self); auto info = [[ASNetworkImageLoadInfo alloc] initWithURL:URL sourceType:ASNetworkImageSourceFileURL downloadIdentifier:nil userInfo:nil]; [delegate imageNode:self didLoadImage:self.image info:info]; } else if (_delegateFlags.delegateDidLoadImage) { - ASDN::MutexUnlocker u(__instanceLock__); + ASUnlockScope(self); [delegate imageNode:self didLoadImage:self.image]; } }); @@ -688,7 +675,7 @@ - (void)_lazilyLoadImageIfNecessary as_log_verbose(ASImageLoadingLog(), "Downloaded image for %@ img: %@ url: %@", self, [imageContainer asdk_image], URL); // Grab the lock for the rest of the block - ASDN::MutexLocker l(strongSelf->__instanceLock__); + ASLockScope(strongSelf); //Getting a result back for a different download identifier, download must not have been successfully canceled if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { @@ -752,17 +739,13 @@ - (void)_lazilyLoadImageIfNecessary // lock in here if (_cache != nil) { NSUUID *cacheUUID = [NSUUID UUID]; - __instanceLock__.lock(); - _cacheUUID = cacheUUID; - __instanceLock__.unlock(); + ASLockedSelf(_cacheUUID = cacheUUID); as_log_verbose(ASImageLoadingLog(), "Decaching image for %@ url: %@", self, URL); ASImageCacherCompletion completion = ^(id imageContainer) { // If the cache UUID changed, that means this request was cancelled. - __instanceLock__.lock(); - NSUUID *currentCacheUUID = _cacheUUID; - __instanceLock__.unlock(); + auto currentCacheUUID = ASLockedSelf(_cacheUUID); if (!ASObjectIsEqual(currentCacheUUID, cacheUUID)) { return; @@ -797,7 +780,8 @@ - (void)displayDidFinish id delegate = nil; - __instanceLock__.lock(); + { + ASLockScopeSelf(); if (_delegateFlags.delegateDidFinishDecoding && self.layer.contents != nil) { /* We store the image quality in _currentImageQuality whenever _image is set. On the following displayDidFinish, we'll know that _currentImageQuality is the quality of the image that has just finished rendering. In order for this to be accurate, we @@ -812,8 +796,7 @@ - (void)displayDidFinish // Assign the delegate to be used delegate = _delegate; } - - __instanceLock__.unlock(); + } if (delegate != nil) { [delegate imageNodeDidFinishDecoding:self]; diff --git a/Source/ASScrollNode.mm b/Source/ASScrollNode.mm index 3ff008809..a7458f19d 100644 --- a/Source/ASScrollNode.mm +++ b/Source/ASScrollNode.mm @@ -18,9 +18,10 @@ #import #import #import -#import +#import #import #import +#import @interface ASScrollView : UIScrollView @end @@ -81,7 +82,7 @@ - (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize restrictedToSize:(ASLayoutElementSize)size relativeToParentSize:(CGSize)parentSize { - ASDN::MutexLocker l(__instanceLock__); // Lock for using our instance variables. + ASLockScopeSelf(); // Lock for using our instance variables. ASSizeRange contentConstrainedSize = constrainedSize; if (ASScrollDirectionContainsVerticalDirection(_scrollableDirections)) { @@ -123,7 +124,7 @@ - (void)layout { [super layout]; - ASDN::MutexLocker l(__instanceLock__); // Lock for using our two instance variables. + ASLockScopeSelf(); // Lock for using our two instance variables. if (_automaticallyManagesContentSize) { CGSize contentSize = _contentCalculatedSizeFromLayout; @@ -137,13 +138,13 @@ - (void)layout - (BOOL)automaticallyManagesContentSize { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _automaticallyManagesContentSize; } - (void)setAutomaticallyManagesContentSize:(BOOL)automaticallyManagesContentSize { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _automaticallyManagesContentSize = automaticallyManagesContentSize; if (_automaticallyManagesContentSize == YES && ASScrollDirectionContainsVerticalDirection(_scrollableDirections) == NO @@ -156,13 +157,13 @@ - (void)setAutomaticallyManagesContentSize:(BOOL)automaticallyManagesContentSize - (ASScrollDirection)scrollableDirections { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _scrollableDirections; } - (void)setScrollableDirections:(ASScrollDirection)scrollableDirections { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (_scrollableDirections != scrollableDirections) { _scrollableDirections = scrollableDirections; [self setNeedsLayout]; diff --git a/Source/ASTextNode.mm b/Source/ASTextNode.mm index 0b6421e7a..b57d7ccea 100644 --- a/Source/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -21,11 +21,12 @@ #if !ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE #import -#include +#import #import #import -#import +#import +#import #import #import #import @@ -40,6 +41,7 @@ #import #import #import +#import /** * If set, we will record all values set to attributedText into an array @@ -323,13 +325,13 @@ - (BOOL)supportsLayerBacking - (ASTextKitRenderer *)_renderer { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return [self _locked_renderer]; } - (ASTextKitRenderer *)_rendererWithBounds:(CGRect)bounds { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return [self _locked_rendererWithBounds:bounds]; } @@ -365,27 +367,19 @@ - (ASTextKitAttributes)_locked_rendererAttributes - (void)setTextContainerInset:(UIEdgeInsets)textContainerInset { - __instanceLock__.lock(); - BOOL needsUpdate = !UIEdgeInsetsEqualToEdgeInsets(textContainerInset, _textContainerInset); - if (needsUpdate) { - _textContainerInset = textContainerInset; - } - __instanceLock__.unlock(); - - if (needsUpdate) { + if (ASLockedSelfCompareAssignCustom(_textContainerInset, textContainerInset, UIEdgeInsetsEqualToEdgeInsets)) { [self setNeedsLayout]; } } - (UIEdgeInsets)textContainerInset { - ASDN::MutexLocker l(__instanceLock__); - return _textContainerInset; + return ASLockedSelf(_textContainerInset); } - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); ASDisplayNodeAssert(constrainedSize.width >= 0, @"Constrained width for text (%f) is too narrow", constrainedSize.width); ASDisplayNodeAssert(constrainedSize.height >= 0, @"Constrained height for text (%f) is too short", constrainedSize.height); @@ -434,7 +428,7 @@ + (CGFloat)ascenderWithAttributedString:(NSAttributedString *)attributedString - (NSAttributedString *)attributedText { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _attributedText; } @@ -447,7 +441,7 @@ - (void)setAttributedText:(NSAttributedString *)attributedText // Don't hold textLock for too long. { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (ASObjectIsEqual(attributedText, _attributedText)) { return; } @@ -483,32 +477,22 @@ - (void)setAttributedText:(NSAttributedString *)attributedText - (void)setExclusionPaths:(NSArray *)exclusionPaths { - { - ASDN::MutexLocker l(__instanceLock__); - - if (ASObjectIsEqual(exclusionPaths, _exclusionPaths)) { - return; - } - - _exclusionPaths = [exclusionPaths copy]; + if (ASLockedSelfCompareAssignCopy(_exclusionPaths, exclusionPaths)) { + [self setNeedsLayout]; + [self setNeedsDisplay]; } - - [self setNeedsLayout]; - [self setNeedsDisplay]; } - (NSArray *)exclusionPaths { - ASDN::MutexLocker l(__instanceLock__); - - return _exclusionPaths; + return ASLockedSelf(_exclusionPaths); } #pragma mark - Drawing - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return [[ASTextNodeDrawParameter alloc] initWithRendererAttributes:[self _locked_rendererAttributes] backgroundColor:self.backgroundColor @@ -560,7 +544,7 @@ - (id)_linkAttributeValueAtPoint:(CGPoint)point { ASDisplayNodeAssertMainThread(); - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); ASTextKitRenderer *renderer = [self _locked_renderer]; NSRange visibleRange = renderer.firstVisibleRange; @@ -687,14 +671,14 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer - (ASTextNodeHighlightStyle)highlightStyle { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _highlightStyle; } - (void)setHighlightStyle:(ASTextNodeHighlightStyle)highlightStyle { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _highlightStyle = highlightStyle; } @@ -778,7 +762,7 @@ - (void)_setHighlightRange:(NSRange)highlightRange forAttributeName:(NSString *) } if (highlightTargetLayer != nil) { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); ASTextKitRenderer *renderer = [self _locked_renderer]; NSArray *highlightRects = [renderer rectsForTextRange:highlightRange measureOption:ASTextKitRendererMeasureOptionBlock]; @@ -866,7 +850,7 @@ - (NSArray *)highlightRectsForTextRange:(NSRange)textRange - (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextKitRendererMeasureOption)measureOption { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); NSArray *rects = [[self _locked_renderer] rectsForTextRange:textRange measureOption:measureOption]; NSMutableArray *adjustedRects = [NSMutableArray array]; @@ -884,7 +868,7 @@ - (NSArray *)_rectsForTextRange:(NSRange)textRange measureOption:(ASTextKitRende - (CGRect)trailingRect { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); CGRect rect = [[self _locked_renderer] trailingRect]; return ASTextNodeAdjustRenderRectForShadowPadding(rect, self.shadowPadding); @@ -892,7 +876,7 @@ - (CGRect)trailingRect - (CGRect)frameForTextRange:(NSRange)textRange { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); CGRect frame = [[self _locked_renderer] frameForTextRange:textRange]; return ASTextNodeAdjustRenderRectForShadowPadding(frame, self.shadowPadding); @@ -902,12 +886,9 @@ - (CGRect)frameForTextRange:(NSRange)textRange - (void)setPlaceholderColor:(UIColor *)placeholderColor { - ASDN::MutexLocker l(__instanceLock__); - - _placeholderColor = placeholderColor; - - // prevent placeholders if we don't have a color - self.placeholderEnabled = placeholderColor != nil; + if (ASLockedSelfCompareAssignCopy(_placeholderColor, placeholderColor)) { + self.placeholderEnabled = CGColorGetAlpha(placeholderColor.CGColor) > 0; + } } - (UIImage *)placeholderImage @@ -919,7 +900,7 @@ - (UIImage *)placeholderImage return nil; } - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); ASGraphicsBeginImageContextWithOptions(size, NO, 1.0); [self.placeholderColor setFill]; @@ -1000,7 +981,7 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event if (inAdditionalTruncationMessage) { NSRange visibleRange = NSMakeRange(0, 0); { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); visibleRange = [self _locked_renderer].firstVisibleRange; } NSRange truncationMessageRange = [self _additionalTruncationMessageRangeWithVisibleRange:visibleRange]; @@ -1079,14 +1060,14 @@ - (void)_handleLongPress:(UILongPressGestureRecognizer *)longPressRecognizer - (BOOL)_pendingLinkTap { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return (_highlightedLinkAttributeValue != nil && ![self _pendingTruncationTap]) && _delegate != nil; } - (BOOL)_pendingTruncationTap { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return [_highlightedLinkAttributeName isEqualToString:ASTextNodeTruncationTokenAttributeName]; } @@ -1095,96 +1076,65 @@ - (BOOL)_pendingTruncationTap - (CGColorRef)shadowColor { - ASDN::MutexLocker l(__instanceLock__); - - return _shadowColor; + return ASLockedSelf(_shadowColor); } - (void)setShadowColor:(CGColorRef)shadowColor { - __instanceLock__.lock(); + [self lock]; if (_shadowColor != shadowColor && CGColorEqualToColor(shadowColor, _shadowColor) == NO) { CGColorRelease(_shadowColor); _shadowColor = CGColorRetain(shadowColor); _cachedShadowUIColor = [UIColor colorWithCGColor:shadowColor]; - __instanceLock__.unlock(); + [self unlock]; [self setNeedsDisplay]; return; } - __instanceLock__.unlock(); + [self unlock]; } - (CGSize)shadowOffset { - ASDN::MutexLocker l(__instanceLock__); - - return _shadowOffset; + return ASLockedSelf(_shadowOffset); } - (void)setShadowOffset:(CGSize)shadowOffset { - { - ASDN::MutexLocker l(__instanceLock__); - - if (CGSizeEqualToSize(_shadowOffset, shadowOffset)) { - return; - } - _shadowOffset = shadowOffset; + if (ASLockedSelfCompareAssignCustom(_shadowOffset, shadowOffset, CGSizeEqualToSize)) { + [self setNeedsDisplay]; } - - [self setNeedsDisplay]; } - (CGFloat)shadowOpacity { - ASDN::MutexLocker l(__instanceLock__); - - return _shadowOpacity; + return ASLockedSelf(_shadowOpacity); } - (void)setShadowOpacity:(CGFloat)shadowOpacity { - { - ASDN::MutexLocker l(__instanceLock__); - - if (_shadowOpacity == shadowOpacity) { - return; - } - - _shadowOpacity = shadowOpacity; + if (ASLockedSelfCompareAssign(_shadowOpacity, shadowOpacity)) { + [self setNeedsDisplay]; } - - [self setNeedsDisplay]; } - (CGFloat)shadowRadius { - ASDN::MutexLocker l(__instanceLock__); - - return _shadowRadius; + return ASLockedSelf(_shadowRadius); } - (void)setShadowRadius:(CGFloat)shadowRadius { - { - ASDN::MutexLocker l(__instanceLock__); - - if (_shadowRadius == shadowRadius) { - return; - } - - _shadowRadius = shadowRadius; + if (ASLockedSelfCompareAssign(_shadowRadius, shadowRadius)) { + [self setNeedsDisplay]; } - - [self setNeedsDisplay]; } - (UIEdgeInsets)shadowPadding { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return [self _locked_renderer].shadower.shadowPadding; } @@ -1202,96 +1152,52 @@ - (UIEdgeInsets)shadowPadding - (NSAttributedString *)truncationAttributedText { - ASDN::MutexLocker l(__instanceLock__); - return _truncationAttributedText; + return ASLockedSelf(_truncationAttributedText); } - (void)setTruncationAttributedText:(NSAttributedString *)truncationAttributedText { - { - ASDN::MutexLocker l(__instanceLock__); - - if (ASObjectIsEqual(_truncationAttributedText, truncationAttributedText)) { - return; - } - - _truncationAttributedText = [truncationAttributedText copy]; + if (ASLockedSelfCompareAssignCopy(_truncationAttributedText, truncationAttributedText)) { + [self _invalidateTruncationText]; } - - [self _invalidateTruncationText]; } - (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage { - { - ASDN::MutexLocker l(__instanceLock__); - - if (ASObjectIsEqual(_additionalTruncationMessage, additionalTruncationMessage)) { - return; - } - - _additionalTruncationMessage = [additionalTruncationMessage copy]; + if (ASLockedSelfCompareAssignCopy(_additionalTruncationMessage, additionalTruncationMessage)) { + [self _invalidateTruncationText]; } - - [self _invalidateTruncationText]; } - (void)setTruncationMode:(NSLineBreakMode)truncationMode { - { - ASDN::MutexLocker l(__instanceLock__); - - if (_truncationMode == truncationMode) { - return; - } - - _truncationMode = truncationMode; + if (ASLockedSelfCompareAssign(_truncationMode, truncationMode)) { + [self setNeedsDisplay]; } - - [self setNeedsDisplay]; } - (BOOL)isTruncated { - ASDN::MutexLocker l(__instanceLock__); - - return [[self _locked_renderer] isTruncated]; + return ASLockedSelf([[self _locked_renderer] isTruncated]); } - (void)setPointSizeScaleFactors:(NSArray *)pointSizeScaleFactors { - { - ASDN::MutexLocker l(__instanceLock__); - if ([_pointSizeScaleFactors isEqualToArray:pointSizeScaleFactors]) { - return; - } - - _pointSizeScaleFactors = pointSizeScaleFactors; + if (ASLockedSelfCompareAssignCopy(_pointSizeScaleFactors, pointSizeScaleFactors)) { + [self setNeedsDisplay]; } - - [self setNeedsDisplay]; } - (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines { - { - ASDN::MutexLocker l(__instanceLock__); - - if (_maximumNumberOfLines == maximumNumberOfLines) { - return; - } - - _maximumNumberOfLines = maximumNumberOfLines; + if (ASLockedSelfCompareAssign(_maximumNumberOfLines, maximumNumberOfLines)) { + [self setNeedsDisplay]; } - - [self setNeedsDisplay]; } - (NSUInteger)lineCount { - ASDN::MutexLocker l(__instanceLock__); - - return [[self _locked_renderer] lineCount]; + return ASLockedSelf([[self _locked_renderer] lineCount]); } #pragma mark - Truncation Message @@ -1299,7 +1205,7 @@ - (NSUInteger)lineCount - (void)_invalidateTruncationText { { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _composedTruncationText = nil; } @@ -1312,7 +1218,7 @@ - (void)_invalidateTruncationText */ - (NSRange)_additionalTruncationMessageRangeWithVisibleRange:(NSRange)visibleRange { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); // Check if we even have an additional truncation message. if (!_additionalTruncationMessage) { diff --git a/Source/ASTextNode2.mm b/Source/ASTextNode2.mm index cced8e577..0569a8988 100644 --- a/Source/ASTextNode2.mm +++ b/Source/ASTextNode2.mm @@ -17,7 +17,8 @@ #import #import -#import +#import +#import #import #import @@ -30,6 +31,7 @@ #import #import #import +#import @interface ASTextCacheValue : NSObject { @package @@ -259,7 +261,7 @@ + (CGFloat)ascenderWithAttributedString:(NSAttributedString *)attributedString - (NSAttributedString *)attributedText { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _attributedText; } @@ -272,7 +274,7 @@ - (void)setAttributedText:(NSAttributedString *)attributedText // Don't hold textLock for too long. { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (ASObjectIsEqual(attributedText, _attributedText)) { return; } @@ -321,7 +323,7 @@ - (NSArray *)exclusionPaths - (void)prepareAttributedString:(NSMutableAttributedString *)attributedString { - ASDN::MutexLocker lock(__instanceLock__); + ASLockScopeSelf(); // Apply paragraph style if needed [attributedString enumerateAttribute:NSParagraphStyleAttributeName inRange:NSMakeRange(0, attributedString.length) options:kNilOptions usingBlock:^(NSParagraphStyle *style, NSRange range, BOOL * _Nonnull stop) { @@ -503,7 +505,7 @@ - (id)_linkAttributeValueAtPoint:(CGPoint)point inAdditionalTruncationMessage:(out BOOL *)inAdditionalTruncationMessageOut forHighlighting:(BOOL)highlighting { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); // TODO: The copy and application of size shouldn't be required, but it is currently. // See discussion in https://github.com/TextureGroup/Texture/pull/396 @@ -589,14 +591,14 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer - (ASTextNodeHighlightStyle)highlightStyle { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _highlightStyle; } - (void)setHighlightStyle:(ASTextNodeHighlightStyle)highlightStyle { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _highlightStyle = highlightStyle; } @@ -679,7 +681,7 @@ - (CGRect)frameForTextRange:(NSRange)textRange - (void)setPlaceholderColor:(UIColor *)placeholderColor { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _placeholderColor = placeholderColor; @@ -749,7 +751,7 @@ - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event if (inAdditionalTruncationMessage) { NSRange visibleRange = NSMakeRange(0, 0); { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); // TODO: The copy and application of size shouldn't be required, but it is currently. // See discussion in https://github.com/TextureGroup/Texture/pull/396 ASTextContainer *containerCopy = [_textContainer copy]; @@ -835,14 +837,14 @@ - (void)_handleLongPress:(UILongPressGestureRecognizer *)longPressRecognizer - (BOOL)_pendingLinkTap { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return (_highlightedLinkAttributeValue != nil && ![self _pendingTruncationTap]) && _delegate != nil; } - (BOOL)_pendingTruncationTap { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return [_highlightedLinkAttributeName isEqualToString:ASTextNodeTruncationTokenAttributeName]; } @@ -857,30 +859,30 @@ - (BOOL)_pendingTruncationTap */ - (CGColorRef)shadowColor { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _shadowColor; } - (void)setShadowColor:(CGColorRef)shadowColor { - __instanceLock__.lock(); + [self lock]; if (_shadowColor != shadowColor && CGColorEqualToColor(shadowColor, _shadowColor) == NO) { CGColorRelease(_shadowColor); _shadowColor = CGColorRetain(shadowColor); - __instanceLock__.unlock(); + [self unlock]; [self setNeedsDisplay]; return; } - __instanceLock__.unlock(); + [self unlock]; } - (CGSize)shadowOffset { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _shadowOffset; } @@ -888,7 +890,7 @@ - (CGSize)shadowOffset - (void)setShadowOffset:(CGSize)shadowOffset { { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (CGSizeEqualToSize(_shadowOffset, shadowOffset)) { return; @@ -901,7 +903,7 @@ - (void)setShadowOffset:(CGSize)shadowOffset - (CGFloat)shadowOpacity { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _shadowOpacity; } @@ -909,7 +911,7 @@ - (CGFloat)shadowOpacity - (void)setShadowOpacity:(CGFloat)shadowOpacity { { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (_shadowOpacity == shadowOpacity) { return; @@ -923,7 +925,7 @@ - (void)setShadowOpacity:(CGFloat)shadowOpacity - (CGFloat)shadowRadius { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _shadowRadius; } @@ -931,7 +933,7 @@ - (CGFloat)shadowRadius - (void)setShadowRadius:(CGFloat)shadowRadius { { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (_shadowRadius == shadowRadius) { return; @@ -953,7 +955,7 @@ - (void)setPointSizeScaleFactors:(NSArray *)scaleFactors { AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (ASObjectIsEqual(scaleFactors, _pointSizeScaleFactors)) { return; } @@ -966,7 +968,7 @@ - (void)setPointSizeScaleFactors:(NSArray *)scaleFactors - (NSArray *)pointSizeScaleFactors { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _pointSizeScaleFactors; } @@ -985,67 +987,47 @@ - (NSArray *)pointSizeScaleFactors - (void)_ensureTruncationText { if (_textContainer.truncationToken == nil) { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _textContainer.truncationToken = [self _locked_composedTruncationText]; } } - (void)setTruncationAttributedText:(NSAttributedString *)truncationAttributedText { - { - ASDN::MutexLocker l(__instanceLock__); - - if (ASObjectIsEqual(_truncationAttributedText, truncationAttributedText)) { - return; - } - - _truncationAttributedText = [truncationAttributedText copy]; + if (ASLockedSelfCompareAssignCopy(_truncationAttributedText, truncationAttributedText)) { + [self _invalidateTruncationText]; } - - [self _invalidateTruncationText]; } - (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage { - { - ASDN::MutexLocker l(__instanceLock__); - - if (ASObjectIsEqual(_additionalTruncationMessage, additionalTruncationMessage)) { - return; - } - - _additionalTruncationMessage = [additionalTruncationMessage copy]; + if (ASLockedSelfCompareAssignCopy(_additionalTruncationMessage, additionalTruncationMessage)) { + [self _invalidateTruncationText]; } - - [self _invalidateTruncationText]; } - (void)setTruncationMode:(NSLineBreakMode)truncationMode { - ASDN::MutexLocker lock(__instanceLock__); - if (_truncationMode == truncationMode) { - return; - } - _truncationMode = truncationMode; - - ASTextTruncationType truncationType; - switch (truncationMode) { - case NSLineBreakByTruncatingHead: - truncationType = ASTextTruncationTypeStart; - break; - case NSLineBreakByTruncatingTail: - truncationType = ASTextTruncationTypeEnd; - break; - case NSLineBreakByTruncatingMiddle: - truncationType = ASTextTruncationTypeMiddle; - break; - default: - truncationType = ASTextTruncationTypeNone; + if (ASLockedSelfCompareAssign(_truncationMode, truncationMode)) { + ASTextTruncationType truncationType; + switch (truncationMode) { + case NSLineBreakByTruncatingHead: + truncationType = ASTextTruncationTypeStart; + break; + case NSLineBreakByTruncatingTail: + truncationType = ASTextTruncationTypeEnd; + break; + case NSLineBreakByTruncatingMiddle: + truncationType = ASTextTruncationTypeMiddle; + break; + default: + truncationType = ASTextTruncationTypeNone; + } + + _textContainer.truncationType = truncationType; + + [self setNeedsDisplay]; } - - _textContainer.truncationType = truncationType; - - [self setNeedsDisplay]; } - (BOOL)isTruncated @@ -1071,7 +1053,7 @@ - (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines - (NSUInteger)lineCount { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); return 0; } @@ -1090,7 +1072,7 @@ - (void)_invalidateTruncationText */ - (NSRange)_additionalTruncationMessageRangeWithVisibleRange:(NSRange)visibleRange { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); // Check if we even have an additional truncation message. if (!_additionalTruncationMessage) { diff --git a/Source/ASVideoNode.mm b/Source/ASVideoNode.mm index 9274d6fa5..85650e885 100644 --- a/Source/ASVideoNode.mm +++ b/Source/ASVideoNode.mm @@ -16,11 +16,13 @@ // #import -#import +#import +#import #import #import #import #import +#import static BOOL ASAssetIsEqual(AVAsset *asset1, AVAsset *asset2) { return ASObjectIsEqual(asset1, asset2) @@ -130,7 +132,7 @@ - (ASDisplayNode *)constructPlayerNode - (AVPlayerItem *)constructPlayerItem { ASDisplayNodeAssertMainThread(); - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); AVPlayerItem *playerItem = nil; if (_assetURL != nil) { @@ -257,9 +259,7 @@ - (void)layout - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { - __instanceLock__.lock(); - ASDisplayNode *playerNode = _playerNode; - __instanceLock__.unlock(); + ASDisplayNode *playerNode = ASLockedSelf(_playerNode); CGSize calculatedSize = constrainedSize; @@ -320,9 +320,7 @@ - (void)imageAtTime:(CMTime)imageTime completionHandler:(void(^)(UIImage *image) - (void)setVideoPlaceholderImage:(UIImage *)image { - __instanceLock__.lock(); - NSString *gravity = _gravity; - __instanceLock__.unlock(); + NSString *gravity = self.gravity; if (image != nil) { self.contentMode = ASContentModeFromVideoGravity(gravity); @@ -332,7 +330,7 @@ - (void)setVideoPlaceholderImage:(UIImage *)image - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (object == _currentPlayerItem) { if ([keyPath isEqualToString:kStatus]) { @@ -400,7 +398,7 @@ - (void)didEnterPreloadState { [super didEnterPreloadState]; - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); AVAsset *asset = self.asset; // Return immediately if the asset is nil; if (asset == nil || self.playerState != ASVideoNodePlayerStateUnknown) { @@ -441,7 +439,7 @@ - (void)didExitPreloadState [super didExitPreloadState]; { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); self.player = nil; self.currentItem = nil; @@ -453,15 +451,16 @@ - (void)didEnterVisibleState { [super didEnterVisibleState]; - __instanceLock__.lock(); BOOL shouldPlay = NO; - if (_shouldBePlaying || _shouldAutoplay) { - if (_player != nil && CMTIME_IS_VALID(_lastPlaybackTime)) { - [_player seekToTime:_lastPlaybackTime]; + { + ASLockScopeSelf(); + if (_shouldBePlaying || _shouldAutoplay) { + if (_player != nil && CMTIME_IS_VALID(_lastPlaybackTime)) { + [_player seekToTime:_lastPlaybackTime]; + } + shouldPlay = YES; } - shouldPlay = YES; } - __instanceLock__.unlock(); if (shouldPlay) { [self play]; @@ -472,7 +471,7 @@ - (void)didExitVisibleState { [super didExitVisibleState]; - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (_shouldBePlaying) { [self pause]; @@ -487,7 +486,7 @@ - (void)didExitVisibleState - (void)setPlayerState:(ASVideoNodePlayerState)playerState { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); ASVideoNodePlayerState oldState = _playerState; @@ -513,7 +512,7 @@ - (void)setAssetURL:(NSURL *)assetURL - (NSURL *)assetURL { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (_assetURL != nil) { return _assetURL; @@ -535,7 +534,7 @@ - (void)setAsset:(AVAsset *)asset - (AVAsset *)asset { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _asset; } @@ -546,7 +545,7 @@ - (void)setAndFetchAsset:(AVAsset *)asset url:(NSURL *)assetURL [self didExitPreloadState]; { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); self.videoPlaceholderImage = nil; _asset = asset; _assetURL = assetURL; @@ -557,7 +556,7 @@ - (void)setAndFetchAsset:(AVAsset *)asset url:(NSURL *)assetURL - (void)setVideoComposition:(AVVideoComposition *)videoComposition { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _videoComposition = videoComposition; _currentPlayerItem.videoComposition = videoComposition; @@ -565,13 +564,13 @@ - (void)setVideoComposition:(AVVideoComposition *)videoComposition - (AVVideoComposition *)videoComposition { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _videoComposition; } - (void)setAudioMix:(AVAudioMix *)audioMix { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _audioMix = audioMix; _currentPlayerItem.audioMix = audioMix; @@ -579,19 +578,19 @@ - (void)setAudioMix:(AVAudioMix *)audioMix - (AVAudioMix *)audioMix { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _audioMix; } - (AVPlayer *)player { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _player; } - (AVPlayerLayer *)playerLayer { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return (AVPlayerLayer *)_playerNode.layer; } @@ -618,7 +617,7 @@ - (void)setDelegate:(id)delegate - (void)setGravity:(NSString *)gravity { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (_playerNode.isNodeLoaded) { ((AVPlayerLayer *)_playerNode.layer).videoGravity = gravity; } @@ -628,19 +627,19 @@ - (void)setGravity:(NSString *)gravity - (NSString *)gravity { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _gravity; } - (BOOL)muted { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _muted; } - (void)setMuted:(BOOL)muted { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _player.muted = muted; _muted = muted; @@ -650,33 +649,31 @@ - (void)setMuted:(BOOL)muted - (void)play { - __instanceLock__.lock(); + ASLockScopeSelf(); if (![self isStateChangeValid:ASVideoNodePlayerStatePlaying]) { - __instanceLock__.unlock(); return; } if (_player == nil) { - __instanceLock__.unlock(); - [self setNeedsPreload]; - __instanceLock__.lock(); + ASUnlockScope(self); + [self setNeedsPreload]; } if (_playerNode == nil) { _playerNode = [self constructPlayerNode]; - __instanceLock__.unlock(); + { + ASUnlockScope(self); [self addSubnode:_playerNode]; - __instanceLock__.lock(); - + } + [self setNeedsLayout]; } [_player play]; _shouldBePlaying = YES; - __instanceLock__.unlock(); } - (BOOL)ready @@ -686,7 +683,7 @@ - (BOOL)ready - (void)pause { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (![self isStateChangeValid:ASVideoNodePlayerStatePaused]) { return; } @@ -696,7 +693,7 @@ - (void)pause - (BOOL)isPlaying { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return (_player.rate > 0 && !_player.error); } @@ -713,7 +710,7 @@ - (BOOL)isStateChangeValid:(ASVideoNodePlayerState)state - (void)resetToPlaceholder { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (_playerNode != nil) { [_playerNode removeFromSupernode]; @@ -778,13 +775,13 @@ - (void)errorWhilePlaying:(NSNotification *)notification - (AVPlayerItem *)currentItem { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _currentPlayerItem; } - (void)setCurrentItem:(AVPlayerItem *)currentItem { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); [self removePlayerItemObservers:_currentPlayerItem]; @@ -797,22 +794,23 @@ - (void)setCurrentItem:(AVPlayerItem *)currentItem - (ASDisplayNode *)playerNode { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _playerNode; } - (void)setPlayerNode:(ASDisplayNode *)playerNode { - __instanceLock__.lock(); - _playerNode = playerNode; - __instanceLock__.unlock(); + { + ASLockScopeSelf(); + _playerNode = playerNode; + } [self setNeedsLayout]; } - (void)setPlayer:(AVPlayer *)player { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); [self removePlayerObservers:_player]; @@ -827,13 +825,13 @@ - (void)setPlayer:(AVPlayer *)player - (BOOL)shouldBePlaying { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); return _shouldBePlaying; } - (void)setShouldBePlaying:(BOOL)shouldBePlaying { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); _shouldBePlaying = shouldBePlaying; } diff --git a/Source/ASVideoPlayerNode.mm b/Source/ASVideoPlayerNode.mm index e6f766b41..7859448a6 100644 --- a/Source/ASVideoPlayerNode.mm +++ b/Source/ASVideoPlayerNode.mm @@ -25,7 +25,8 @@ #import #import -#import +#import +#import static void *ASVideoPlayerNodeContext = &ASVideoPlayerNodeContext; @@ -167,7 +168,7 @@ - (NSURL *)assetURL { NSURL *url = nil; { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if ([_pendingAsset isKindOfClass:AVURLAsset.class]) { url = ((AVURLAsset *)_pendingAsset).URL; } @@ -180,7 +181,7 @@ - (void)setAsset:(AVAsset *)asset { ASDisplayNodeAssertMainThread(); - __instanceLock__.lock(); + [self lock]; // Clean out pending asset _pendingAsset = nil; @@ -188,22 +189,18 @@ - (void)setAsset:(AVAsset *)asset // Set asset based on interface state if ((ASInterfaceStateIncludesPreload(self.interfaceState))) { // Don't hold the lock while accessing the subnode - __instanceLock__.unlock(); + [self unlock]; _videoNode.asset = asset; return; } _pendingAsset = asset; - __instanceLock__.unlock(); + [self unlock]; } - (AVAsset *)asset { - __instanceLock__.lock(); - AVAsset *asset = _pendingAsset; - __instanceLock__.unlock(); - - return asset ?: _videoNode.asset; + return ASLockedSelf(_pendingAsset) ?: _videoNode.asset; } #pragma mark - ASDisplayNode @@ -212,7 +209,7 @@ - (void)didLoad { [super didLoad]; { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); [self createControls]; } } @@ -223,7 +220,7 @@ - (void)didEnterPreloadState AVAsset *pendingAsset = nil; { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); pendingAsset = _pendingAsset; _pendingAsset = nil; } @@ -244,7 +241,7 @@ - (BOOL)supportsLayerBacking - (void)createControls { { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (_controlsDisabled) { return; @@ -610,7 +607,7 @@ - (void)togglePlayPause - (void)showSpinner { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (!_spinnerNode) { @@ -643,7 +640,7 @@ - (void)showSpinner - (void)removeSpinner { - ASDN::MutexLocker l(__instanceLock__); + ASLockScopeSelf(); if (!_spinnerNode) { return; diff --git a/Source/Base/ASBaseDefines.h b/Source/Base/ASBaseDefines.h index 21b85026d..6706b6da4 100755 --- a/Source/Base/ASBaseDefines.h +++ b/Source/Base/ASBaseDefines.h @@ -232,6 +232,29 @@ ((c *) ([__val class] == [c class] ? __val : nil));\ }) +// Compare two primitives, assign if different. Returns whether the assignment happened. +#define ASCompareAssign(lvalue, newValue) ({ \ + BOOL result = (lvalue != newValue); \ + if (result) { lvalue = newValue; } \ + result; \ +}) + +#define ASCompareAssignObjects(lvalue, newValue) \ + ASCompareAssignCustom(lvalue, newValue, ASObjectIsEqual) + +// e.g. ASCompareAssignCustom(_myInsets, insets, UIEdgeInsetsEqualToEdgeInsets) +#define ASCompareAssignCustom(lvalue, newValue, isequal) ({ \ + BOOL result = !(isequal(lvalue, newValue)); \ + if (result) { lvalue = newValue; } \ + result; \ +}) + +#define ASCompareAssignCopy(lvalue, newValue) ({ \ + BOOL result = !ASObjectIsEqual(lvalue, newValue); \ + if (result) { lvalue = [newValue copyWithZone:NULL]; } \ + result; \ +}) + /** * Create a new set by mapping `collection` over `work`, ignoring nil. */ diff --git a/Source/Details/ASThread.h b/Source/Details/ASThread.h index 989db9a26..6ee8a551b 100644 --- a/Source/Details/ASThread.h +++ b/Source/Details/ASThread.h @@ -15,8 +15,6 @@ // http://www.apache.org/licenses/LICENSE-2.0 // -#pragma once - #import #import #import @@ -26,11 +24,68 @@ #import -static inline BOOL ASDisplayNodeThreadIsMain() +ASDISPLAYNODE_INLINE BOOL ASDisplayNodeThreadIsMain() { return 0 != pthread_main_np(); } +/** + * Adds the lock to the current scope. + * + * A C version of the C++ lockers. Pass in any id. + * One benefit this has over C++ lockers is that the lock is retained. We + * had bugs in the past where an object would be deallocated while someone + * had locked its instanceLock, and we'd get a crash. This macro + * retains the locked object until it can be unlocked, which is nice. + */ +#define ASLockScope(nsLocking) \ + id __lockToken __attribute__((cleanup(_ASLockScopeCleanup))) NS_VALID_UNTIL_END_OF_SCOPE = nsLocking; \ + [__lockToken lock]; + +/// Same as ASLockScope(1) but lock isn't retained (be careful). +#define ASLockScopeUnowned(nsLocking) \ + __unsafe_unretained id __lockToken __attribute__((cleanup(_ASLockScopeUnownedCleanup))) = nsLocking; \ + [__lockToken lock]; + +ASDISPLAYNODE_INLINE void _ASLockScopeCleanup(id __strong * const lockPtr) { + [*lockPtr unlock]; +} + +ASDISPLAYNODE_INLINE void _ASLockScopeUnownedCleanup(id __unsafe_unretained * const lockPtr) { + [*lockPtr unlock]; +} + +/** + * Same as ASLockScope(1) but it uses self, so we can skip retain/release. + */ +#define ASLockScopeSelf() ASLockScopeUnowned(self) + +/// One-liner while holding the lock. +#define ASLocked(nsLocking, expr) ({ ASLockScope(nsLocking); expr; }) + +/// Faster self-version. +#define ASLockedSelf(expr) ({ ASLockScopeSelf(); expr; }) + +#define ASLockedSelfCompareAssign(lvalue, newValue) \ + ASLockedSelf(ASCompareAssign(lvalue, newValue)) + +#define ASLockedSelfCompareAssignObjects(lvalue, newValue) \ + ASLockedSelf(ASCompareAssignObjects(lvalue, newValue)) + +#define ASLockedSelfCompareAssignCustom(lvalue, newValue, isequal) \ + ASLockedSelf(ASCompareAssignCustom(lvalue, newValue, isequal)) + +#define ASLockedSelfCompareAssignCopy(lvalue, obj) \ + ASLockedSelf(ASCompareAssignCopy(lvalue, obj)) + +#define ASUnlockScope(nsLocking) \ + id __lockToken __attribute__((cleanup(_ASUnlockScopeCleanup))) NS_VALID_UNTIL_END_OF_SCOPE = nsLocking; \ + [__lockToken unlock]; + +ASDISPLAYNODE_INLINE void _ASUnlockScopeCleanup(id __strong *lockPtr) { + [*lockPtr lock]; +} + #ifdef __cplusplus #define TIME_LOCKER 0 @@ -55,13 +110,10 @@ static inline BOOL ASDisplayNodeThreadIsMain() // This MUST always execute, even when assertions are disabled. Otherwise all lock operations become no-ops! // (To be explicit, do not turn this into an NSAssert, assert(), or any other kind of statement where the // evaluation of x_ can be compiled out.) -#define ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(x_) do { \ - _Pragma("clang diagnostic push"); \ - _Pragma("clang diagnostic ignored \"-Wunused-variable\""); \ - volatile int res = (x_); \ - ASDisplayNodeCAssert(res == 0, @"Expected %@ to return 0, got %d instead", @#x_, res); \ - _Pragma("clang diagnostic pop"); \ -} while (0) +#define ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(x_) ({ \ + __unused int res = (x_); \ + ASDisplayNodeCAssert(res == 0, @"Expected %s to return 0, got %d instead. Error: %s", #x_, res, strerror(res)); \ +}) /** * Assert if the current thread owns a mutex. diff --git a/Source/Layout/ASLayoutElement.h b/Source/Layout/ASLayoutElement.h index e84c54569..01af4353a 100644 --- a/Source/Layout/ASLayoutElement.h +++ b/Source/Layout/ASLayoutElement.h @@ -174,7 +174,7 @@ extern NSString * const ASLayoutElementStyleLayoutPositionProperty; - (void)style:(__kindof ASLayoutElementStyle *)style propertyDidChange:(NSString *)propertyName; @end -@interface ASLayoutElementStyle : NSObject +@interface ASLayoutElementStyle : NSObject /** * @abstract Initializes the layoutElement style with a specified delegate diff --git a/Source/Layout/ASLayoutElement.mm b/Source/Layout/ASLayoutElement.mm index bf5c61373..8baba5326 100644 --- a/Source/Layout/ASLayoutElement.mm +++ b/Source/Layout/ASLayoutElement.mm @@ -176,6 +176,18 @@ - (instancetype)init return self; } +#pragma mark - NSLocking + +- (void)lock +{ + __instanceLock__.lock(); +} + +- (void)unlock +{ + __instanceLock__.unlock(); +} + #pragma mark - ASLayoutElementStyleSize - (ASLayoutElementSize)size diff --git a/Source/Layout/ASLayoutSpec.h b/Source/Layout/ASLayoutSpec.h index 6a49792de..181ea70b2 100644 --- a/Source/Layout/ASLayoutSpec.h +++ b/Source/Layout/ASLayoutSpec.h @@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN /** * A layout spec is an immutable object that describes a layout, loosely inspired by React. */ -@interface ASLayoutSpec : NSObject +@interface ASLayoutSpec : NSObject /** * Creation of a layout spec should only happen by a user in layoutSpecThatFits:. During that method, a diff --git a/Source/Layout/ASLayoutSpec.mm b/Source/Layout/ASLayoutSpec.mm index fa275d21d..ff45add44 100644 --- a/Source/Layout/ASLayoutSpec.mm +++ b/Source/Layout/ASLayoutSpec.mm @@ -259,6 +259,18 @@ - (NSString *)asciiArtName return result; } +#pragma mark - NSLocking + +- (void)lock +{ + __instanceLock__.lock(); +} + +- (void)unlock +{ + __instanceLock__.unlock(); +} + @end #pragma mark - ASWrapperLayoutSpec diff --git a/Source/Private/ASDisplayNode+AsyncDisplay.mm b/Source/Private/ASDisplayNode+AsyncDisplay.mm index 96426d479..4d307439c 100644 --- a/Source/Private/ASDisplayNode+AsyncDisplay.mm +++ b/Source/Private/ASDisplayNode+AsyncDisplay.mm @@ -20,7 +20,7 @@ #import #import #import -#import +#import #import #import #import diff --git a/Source/Private/ASDisplayNode+FrameworkPrivate.h b/Source/Private/ASDisplayNode+FrameworkPrivate.h index b309bdcc2..2f3e7223d 100644 --- a/Source/Private/ASDisplayNode+FrameworkPrivate.h +++ b/Source/Private/ASDisplayNode+FrameworkPrivate.h @@ -234,7 +234,7 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyStateChange(ASHierarc * ASNetworkImageNode and ASMultiplexImageNode set this to YES, because they load data from a database or server, * and are expected to support a placeholder state given that display is often blocked on slow data fetching. */ -@property (nonatomic, assign) BOOL shouldBypassEnsureDisplay; +@property (atomic) BOOL shouldBypassEnsureDisplay; /** * @abstract Checks whether a node should be scheduled for display, considering its current and new interface states. diff --git a/Source/Private/ASDisplayNode+FrameworkSubclasses.h b/Source/Private/ASDisplayNode+FrameworkSubclasses.h deleted file mode 100644 index 9b083c550..000000000 --- a/Source/Private/ASDisplayNode+FrameworkSubclasses.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// ASDisplayNode+FrameworkSubclasses.h -// Texture -// -// 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 /ASDK-Licenses directory of this source tree. An additional -// grant of patent rights can be found in the PATENTS file in the same directory. -// -// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, -// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// - -// -// The following methods are ONLY for use by _ASDisplayLayer, _ASDisplayView, and ASDisplayNode. -// These methods must never be called or overridden by other classes. -// - -#import -#import - -// These are included because most internal subclasses need it. -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface ASDisplayNode () -{ - // Protects access to _view, _layer, _pendingViewState, _subnodes, _supernode, and other properties which are accessed from multiple threads. - @package - ASDN::RecursiveMutex __instanceLock__; -} -@end - -NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASDisplayNode+UIViewBridge.mm b/Source/Private/ASDisplayNode+UIViewBridge.mm index 21f091c3f..ed92a9f7e 100644 --- a/Source/Private/ASDisplayNode+UIViewBridge.mm +++ b/Source/Private/ASDisplayNode+UIViewBridge.mm @@ -21,7 +21,6 @@ #import #import #import -#import #import /** diff --git a/Source/Private/ASDisplayNodeInternal.h b/Source/Private/ASDisplayNodeInternal.h index ef2d460a3..5798d53e5 100644 --- a/Source/Private/ASDisplayNodeInternal.h +++ b/Source/Private/ASDisplayNodeInternal.h @@ -76,6 +76,8 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo @interface ASDisplayNode () <_ASTransitionContextCompletionDelegate> { @package + ASDN::RecursiveMutex __instanceLock__; + _ASPendingState *_pendingViewState; ASInterfaceState _pendingInterfaceState; UIView *_view; From bd24015691c9f87344c2fd3d0453b6d8792f3a06 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sun, 25 Mar 2018 12:31:32 -0700 Subject: [PATCH 114/133] [ASNetworkImageNode] Replace NSUUID sentinel with integer #trivial (#852) * Replace NSUUID sentinel with an integer * Update ASNetworkImageNode.mm --- Source/ASNetworkImageNode.mm | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index 6f61da7b9..76eaf7c21 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -44,7 +44,7 @@ @interface ASNetworkImageNode () NSURL *_URL; UIImage *_defaultImage; - NSUUID *_cacheUUID; + NSInteger _cacheSentinel; id _downloadIdentifier; // The download identifier that we have set a progress block on, if any. id _downloadIdentifierForProgressBlock; @@ -536,8 +536,7 @@ - (void)_locked_cancelImageDownloadWithResumePossibility:(BOOL)storeResume } } _downloadIdentifier = nil; - - _cacheUUID = nil; + _cacheSentinel++; } - (void)_downloadImageWithCompletion:(void (^)(id imageContainer, NSError*, id downloadIdentifier, id userInfo))finished @@ -685,7 +684,7 @@ - (void)_lazilyLoadImageIfNecessary //No longer in preload range, no point in setting the results (they won't be cleared in exit preload range) if (ASInterfaceStateIncludesPreload(strongSelf->_interfaceState) == NO) { strongSelf->_downloadIdentifier = nil; - strongSelf->_cacheUUID = nil; + strongSelf->_cacheSentinel++; return; } @@ -704,7 +703,7 @@ - (void)_lazilyLoadImageIfNecessary } strongSelf->_downloadIdentifier = nil; - strongSelf->_cacheUUID = nil; + strongSelf->_cacheSentinel++; void (^calloutBlock)(ASNetworkImageNode *inst); @@ -738,16 +737,13 @@ - (void)_lazilyLoadImageIfNecessary // As the _cache and _downloader is only set once in the intializer we don't have to use a // lock in here if (_cache != nil) { - NSUUID *cacheUUID = [NSUUID UUID]; - ASLockedSelf(_cacheUUID = cacheUUID); + NSInteger cacheSentinel = ASLockedSelf(++_cacheSentinel); as_log_verbose(ASImageLoadingLog(), "Decaching image for %@ url: %@", self, URL); ASImageCacherCompletion completion = ^(id imageContainer) { - // If the cache UUID changed, that means this request was cancelled. - auto currentCacheUUID = ASLockedSelf(_cacheUUID); - - if (!ASObjectIsEqual(currentCacheUUID, cacheUUID)) { + // If the cache sentinel changed, that means this request was cancelled. + if (ASLockedSelf(_cacheSentinel != cacheSentinel)) { return; } From 3d9fe8c3a769f006565a38ad8fc3d91d18019fe8 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Sun, 25 Mar 2018 12:32:00 -0700 Subject: [PATCH 115/133] Make cache support animated image (#850) * fix SIMULATE_WEB_RESPONSE not imported #449 * Fix to make rangeMode update in right time * support animated image for cache * Modify change log --- CHANGELOG.md | 1 + Source/Details/ASPINRemoteImageDownloader.m | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d37be067..79227ca03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASPINRemoteImageDownloader] Allow cache to provide animated image. [Max Wang](https://github.com/wsdwsd0829) [#850](https://github.com/TextureGroup/Texture/pull/850) - [tvOS] Fixes errors when building against tvOS SDK [Alex Hill](https://github.com/alexhillc) [#728](https://github.com/TextureGroup/Texture/pull/728) - [ASDisplayNode] Add unit tests for layout z-order changes (with an open issue to fix). - [ASWrapperCellNode] Introduce a new class allowing more control of UIKit passthrough cells. diff --git a/Source/Details/ASPINRemoteImageDownloader.m b/Source/Details/ASPINRemoteImageDownloader.m index 9061026ec..eff1cb6fd 100644 --- a/Source/Details/ASPINRemoteImageDownloader.m +++ b/Source/Details/ASPINRemoteImageDownloader.m @@ -204,7 +204,15 @@ - (void)cachedImageWithURL:(NSURL *)URL { [[self sharedPINRemoteImageManager] imageFromCacheWithURL:URL processorKey:nil options:PINRemoteImageManagerDownloadOptionsSkipDecode completion:^(PINRemoteImageManagerResult * _Nonnull result) { [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{ +#if PIN_ANIMATED_AVAILABLE + if (result.alternativeRepresentation) { + completion(result.alternativeRepresentation); + } else { + completion(result.image); + } +#else completion(result.image); +#endif }]; }]; } From 27fac9f586302160e414b984e6a9ceca3d724124 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Sun, 25 Mar 2018 12:52:57 -0700 Subject: [PATCH 116/133] Create a centralized configuration API (#747) * Update the dangerfile * Make a trivial change to test new dangerfile * Try out the new value with another trivial change * Add a configuration API to make a unified place for pulling config from clients safely * Specify properties for delegate * Finish removing text experiment global enable * Generate the config file * Clean up configuration to fix tests * Work on making it serializable * Finish it up * Fix example code * Update sample project * Clean up a few things * Align with new project order * Make it faster and update license header * Add an option to specify your config at compile time * Update another license header * Add a version field, and bring interface state coalescing into configuration * Update CA queue code * Update CATransactionQueue tests * Turn transaction queue on by default (for now, see comment) * Update the tests * Update the tests AGAIN * Remove unused ordered set --- AsyncDisplayKit.xcodeproj/project.pbxproj | 48 ++++++++++ CHANGELOG.md | 1 + Schemas/configuration.json | 23 +++++ Source/ASConfiguration.h | 62 ++++++++++++ Source/ASConfiguration.m | 67 +++++++++++++ Source/ASConfigurationDelegate.h | 31 ++++++ Source/ASDisplayNode.mm | 6 +- Source/ASExperimentalFeatures.h | 36 +++++++ Source/ASExperimentalFeatures.m | 45 +++++++++ Source/ASNetworkImageLoadInfo.h | 10 +- Source/ASNetworkImageLoadInfo.m | 10 +- Source/ASRunLoopQueue.h | 9 +- Source/ASRunLoopQueue.mm | 13 +-- Source/ASTextNode+Beta.h | 22 ----- Source/ASTextNode.h | 89 +---------------- Source/ASTextNode.mm | 87 +++++------------ Source/ASTextNode2.h | 23 +---- Source/ASTextNodeCommon.h | 86 +++++++++++++++++ Source/AsyncDisplayKit.h | 2 + Source/Base/ASAvailability.h | 7 +- Source/Details/ASElementMap.h | 5 + Source/Details/ASElementMap.m | 5 + Source/Details/ASGraphicsContext.h | 9 -- Source/Details/ASGraphicsContext.m | 32 +------ Source/Private/ASConfigurationInternal.h | 38 ++++++++ Source/Private/ASConfigurationInternal.m | 96 +++++++++++++++++++ .../Private/ASNetworkImageLoadInfo+Private.h | 10 +- Tests/ASCollectionViewTests.mm | 4 +- Tests/ASConfigurationTests.m | 77 +++++++++++++++ Tests/ASDisplayNodeImplicitHierarchyTests.m | 2 +- Tests/ASDisplayNodeTests.mm | 2 +- Tests/ASDisplayNodeTestsHelper.h | 4 +- Tests/ASDisplayNodeTestsHelper.m | 5 +- Tests/ASNetworkImageNodeTests.m | 8 +- Tests/ASPerformanceTestContext.h | 8 +- Tests/ASRunLoopQueueTests.m | 20 ++-- Tests/ASTextNodePerformanceTests.m | 8 +- Tests/ASTextNodeTests.m | 25 +++++ Tests/ASVideoNodeTests.m | 2 +- Tests/Common/ASTestCase.h | 16 +++- Tests/Common/ASTestCase.m | 11 ++- .../ASDKgram/Sample.xcodeproj/project.pbxproj | 4 + examples/ASDKgram/Sample/AppDelegate.m | 4 +- .../ASDKgram/Sample/TextureConfigDelegate.m | 41 ++++++++ examples/Kittens/Sample/AppDelegate.m | 19 ++-- examples/Kittens/Sample/ViewController.m | 26 ++--- 46 files changed, 838 insertions(+), 320 deletions(-) create mode 100644 Schemas/configuration.json create mode 100644 Source/ASConfiguration.h create mode 100644 Source/ASConfiguration.m create mode 100644 Source/ASConfigurationDelegate.h create mode 100644 Source/ASExperimentalFeatures.h create mode 100644 Source/ASExperimentalFeatures.m create mode 100644 Source/ASTextNodeCommon.h create mode 100644 Source/Private/ASConfigurationInternal.h create mode 100644 Source/Private/ASConfigurationInternal.m create mode 100644 Tests/ASConfigurationTests.m create mode 100644 examples/ASDKgram/Sample/TextureConfigDelegate.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index be2a0441f..76b69f741 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -327,6 +327,8 @@ CC0F886C1E4286FA00576FED /* ReferenceImages_64 in Resources */ = {isa = PBXBuildFile; fileRef = CC0F88691E4286FA00576FED /* ReferenceImages_64 */; }; CC0F886D1E4286FA00576FED /* ReferenceImages_iOS_10 in Resources */ = {isa = PBXBuildFile; fileRef = CC0F886A1E4286FA00576FED /* ReferenceImages_iOS_10 */; }; CC11F97A1DB181180024D77B /* ASNetworkImageNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC11F9791DB181180024D77B /* ASNetworkImageNodeTests.m */; }; + CC18248C200D49C800875940 /* ASTextNodeCommon.h in Headers */ = {isa = PBXBuildFile; fileRef = CC18248B200D49C800875940 /* ASTextNodeCommon.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CC224E962066CA6D00BBA57F /* configuration.json in Resources */ = {isa = PBXBuildFile; fileRef = CC224E952066CA6D00BBA57F /* configuration.json */; }; CC2F65EE1E5FFB1600DA57C9 /* ASMutableElementMap.h in Headers */ = {isa = PBXBuildFile; fileRef = CC2F65EC1E5FFB1600DA57C9 /* ASMutableElementMap.h */; }; CC2F65EF1E5FFB1600DA57C9 /* ASMutableElementMap.m in Sources */ = {isa = PBXBuildFile; fileRef = CC2F65ED1E5FFB1600DA57C9 /* ASMutableElementMap.m */; }; CC3B20841C3F76D600798563 /* ASPendingStateController.h in Headers */ = {isa = PBXBuildFile; fileRef = CC3B20811C3F76D600798563 /* ASPendingStateController.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -355,6 +357,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 */; }; + CC7AF196200D9BD500A21BDE /* ASExperimentalFeatures.h in Headers */ = {isa = PBXBuildFile; fileRef = CC7AF195200D9BD500A21BDE /* ASExperimentalFeatures.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CC7AF198200DAB2200A21BDE /* ASExperimentalFeatures.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7AF197200D9E8400A21BDE /* ASExperimentalFeatures.m */; }; 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, ); }; }; CC84C7F220474C5300A3851B /* ASCGImageBuffer.h in Headers */ = {isa = PBXBuildFile; fileRef = CC84C7F020474C5300A3851B /* ASCGImageBuffer.h */; }; @@ -413,6 +417,12 @@ CCED5E3E2020D36800395C40 /* ASNetworkImageLoadInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; CCED5E3F2020D36800395C40 /* ASNetworkImageLoadInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.m */; }; CCED5E412020D49D00395C40 /* ASNetworkImageLoadInfo+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CCED5E402020D41600395C40 /* ASNetworkImageLoadInfo+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; + CCEDDDCA200C2AC300FFCD0A /* ASConfigurationInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = CCEDDDC8200C2AC300FFCD0A /* ASConfigurationInternal.h */; }; + CCEDDDCB200C2AC300FFCD0A /* ASConfigurationInternal.m in Sources */ = {isa = PBXBuildFile; fileRef = CCEDDDC9200C2AC300FFCD0A /* ASConfigurationInternal.m */; }; + CCEDDDCD200C2CB900FFCD0A /* ASConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = CCEDDDCC200C2CB900FFCD0A /* ASConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CCEDDDCF200C42A200FFCD0A /* ASConfigurationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = CCEDDDCE200C42A200FFCD0A /* ASConfigurationDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CCEDDDD1200C488000FFCD0A /* ASConfiguration.m in Sources */ = {isa = PBXBuildFile; fileRef = CCEDDDD0200C488000FFCD0A /* ASConfiguration.m */; }; + CCEDDDD9200C518800FFCD0A /* ASConfigurationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCEDDDD8200C518800FFCD0A /* ASConfigurationTests.m */; }; CCF18FF41D2575E300DF5895 /* NSIndexSet+ASHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */; settings = {ATTRIBUTES = (Private, ); }; }; DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DB55C2651C641AE4004EDCF5 /* ASContextTransitioning.h */; settings = {ATTRIBUTES = (Public, ); }; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; @@ -816,6 +826,8 @@ CC0F88691E4286FA00576FED /* ReferenceImages_64 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ReferenceImages_64; sourceTree = ""; }; CC0F886A1E4286FA00576FED /* ReferenceImages_iOS_10 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ReferenceImages_iOS_10; sourceTree = ""; }; CC11F9791DB181180024D77B /* ASNetworkImageNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASNetworkImageNodeTests.m; sourceTree = ""; }; + CC18248B200D49C800875940 /* ASTextNodeCommon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASTextNodeCommon.h; sourceTree = ""; }; + CC224E952066CA6D00BBA57F /* configuration.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = configuration.json; sourceTree = ""; }; CC2E317F1DAC353700EEE891 /* ASCollectionView+Undeprecated.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASCollectionView+Undeprecated.h"; sourceTree = ""; }; CC2F65EC1E5FFB1600DA57C9 /* ASMutableElementMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMutableElementMap.h; sourceTree = ""; }; CC2F65ED1E5FFB1600DA57C9 /* ASMutableElementMap.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASMutableElementMap.m; sourceTree = ""; }; @@ -851,6 +863,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 = ""; }; + CC7AF195200D9BD500A21BDE /* ASExperimentalFeatures.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASExperimentalFeatures.h; sourceTree = ""; }; + CC7AF197200D9E8400A21BDE /* ASExperimentalFeatures.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASExperimentalFeatures.m; 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 = ""; }; @@ -919,6 +933,12 @@ CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASNetworkImageLoadInfo.h; sourceTree = ""; }; CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASNetworkImageLoadInfo.m; sourceTree = ""; }; CCED5E402020D41600395C40 /* ASNetworkImageLoadInfo+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ASNetworkImageLoadInfo+Private.h"; sourceTree = ""; }; + CCEDDDC8200C2AC300FFCD0A /* ASConfigurationInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASConfigurationInternal.h; sourceTree = ""; }; + CCEDDDC9200C2AC300FFCD0A /* ASConfigurationInternal.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASConfigurationInternal.m; sourceTree = ""; }; + CCEDDDCC200C2CB900FFCD0A /* ASConfiguration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASConfiguration.h; sourceTree = ""; }; + CCEDDDCE200C42A200FFCD0A /* ASConfigurationDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASConfigurationDelegate.h; sourceTree = ""; }; + CCEDDDD0200C488000FFCD0A /* ASConfiguration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASConfiguration.m; sourceTree = ""; }; + CCEDDDD8200C518800FFCD0A /* ASConfigurationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASConfigurationTests.m; sourceTree = ""; }; D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = ""; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASScrollNode.mm; sourceTree = ""; }; @@ -1043,6 +1063,7 @@ isa = PBXGroup; children = ( 058D09B1195D04C000B7D73C /* Source */, + CC224E942066CA6D00BBA57F /* Schemas */, 058D09C5195D04C000B7D73C /* Tests */, 058D09AE195D04C000B7D73C /* Frameworks */, 058D09AD195D04C000B7D73C /* Products */, @@ -1092,6 +1113,9 @@ CC58AA4A1E398E1D002C8CB4 /* ASBlockTypes.h */, DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */, DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */, + CCEDDDCC200C2CB900FFCD0A /* ASConfiguration.h */, + CCEDDDD0200C488000FFCD0A /* ASConfiguration.m */, + CCEDDDCE200C42A200FFCD0A /* ASConfigurationDelegate.h */, 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */, AC6456071B0A335000CF11B8 /* ASCellNode.mm */, CC84C7F020474C5300A3851B /* ASCGImageBuffer.h */, @@ -1123,6 +1147,8 @@ 058D09DC195D050800B7D73C /* ASDisplayNodeExtras.mm */, 0587F9BB1A7309ED00AFF0BA /* ASEditableTextNode.h */, 0587F9BC1A7309ED00AFF0BA /* ASEditableTextNode.mm */, + CC7AF195200D9BD500A21BDE /* ASExperimentalFeatures.h */, + CC7AF197200D9E8400A21BDE /* ASExperimentalFeatures.m */, 058D09DD195D050800B7D73C /* ASImageNode.h */, 058D09DE195D050800B7D73C /* ASImageNode.mm */, 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */, @@ -1158,6 +1184,7 @@ 055F1A3319ABD3E3004DAFF1 /* ASTableView.mm */, AC7A2C161BDE11DF0093FE1A /* ASTableViewInternal.h */, 0574D5E119C110610097DC25 /* ASTableViewProtocols.h */, + CC18248B200D49C800875940 /* ASTextNodeCommon.h */, 058D09DF195D050800B7D73C /* ASTextNode.h */, 058D09E0195D050800B7D73C /* ASTextNode.mm */, A373200E1C571B050011FC94 /* ASTextNode+Beta.h */, @@ -1204,6 +1231,7 @@ CCDD148A1EEDCD9D0020834E /* ASCollectionModernDataSourceTests.m */, 2538B6F21BC5D2A2003CA0B4 /* ASCollectionViewFlowLayoutInspectorTests.m */, 9F06E5CC1B4CAF4200F015D8 /* ASCollectionViewTests.mm */, + CCEDDDD8200C518800FFCD0A /* ASConfigurationTests.m */, 2911485B1A77147A005D0878 /* ASControlNodeTests.m */, 1A6C000F1FAB4ED400D05926 /* ASCornerLayoutSpecSnapshotTests.mm */, ACF6ED541B178DC700DA7C62 /* ASDimensionTests.mm */, @@ -1386,6 +1414,8 @@ 058D0A01195D050800B7D73C /* Private */ = { isa = PBXGroup; children = ( + CCEDDDC8200C2AC300FFCD0A /* ASConfigurationInternal.h */, + CCEDDDC9200C2AC300FFCD0A /* ASConfigurationInternal.m */, CCE04B2A1E313EDA006AEBBB /* Collection Data Adapter */, E52F8AEE1EAE659600B5A912 /* Collection Layout */, 6947B0BB1E36B4E30007C478 /* Layout */, @@ -1602,6 +1632,14 @@ path = Layout; sourceTree = ""; }; + CC224E942066CA6D00BBA57F /* Schemas */ = { + isa = PBXGroup; + children = ( + CC224E952066CA6D00BBA57F /* configuration.json */, + ); + path = Schemas; + sourceTree = ""; + }; CC583ABF1EF9BAB400134156 /* Common */ = { isa = PBXGroup; children = ( @@ -1777,6 +1815,7 @@ 696F01EC1DD2AF450049FBD5 /* ASEventLog.h in Headers */, 690C35641E055C7B00069B91 /* ASDimensionInternal.h in Headers */, 3917EBD41E9C2FC400D04A01 /* _ASCollectionReusableView.h in Headers */, + CC18248C200D49C800875940 /* ASTextNodeCommon.h in Headers */, 698371DB1E4379CD00437585 /* ASNodeController+Beta.h in Headers */, 6907C2581DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h in Headers */, 69E0E8A71D356C9400627613 /* ASEqualityHelpers.h in Headers */, @@ -1829,6 +1868,7 @@ CC6AA2DA1E9F03B900978E87 /* ASDisplayNode+Ancestry.h in Headers */, 8021EC1D1D2B00B100799119 /* UIImage+ASConvenience.h in Headers */, B35061FD1B010EFD0018CF92 /* ASDisplayNode+Subclasses.h in Headers */, + CCEDDDCD200C2CB900FFCD0A /* ASConfiguration.h in Headers */, B35061FB1B010EFD0018CF92 /* ASDisplayNode.h in Headers */, B35061FE1B010EFD0018CF92 /* ASDisplayNodeExtras.h in Headers */, CC0F88601E4280B800576FED /* _ASCollectionViewCell.h in Headers */, @@ -1841,6 +1881,7 @@ B35062021B010EFD0018CF92 /* ASImageNode.h in Headers */, B350621F1B010EFD0018CF92 /* ASImageProtocols.h in Headers */, 34EFC75F1B701C8600AD841F /* ASInsetLayoutSpec.h in Headers */, + CCEDDDCA200C2AC300FFCD0A /* ASConfigurationInternal.h in Headers */, 34EFC7671B701CD900AD841F /* ASLayout.h in Headers */, DBDB83951C6E879900D0098C /* ASPagerFlowLayout.h in Headers */, 34EFC7691B701CE100AD841F /* ASLayoutElement.h in Headers */, @@ -1942,6 +1983,7 @@ 34EFC76E1B701CF400AD841F /* ASRatioLayoutSpec.h in Headers */, DB55C2671C641AE4004EDCF5 /* ASContextTransitioning.h in Headers */, CCA282C41E9EAE630037E8B7 /* ASLayerBackingTipProvider.h in Headers */, + CCEDDDCF200C42A200FFCD0A /* ASConfigurationDelegate.h in Headers */, E5C347B31ECB40AA00EC4BE4 /* ASTableNode+Beta.h in Headers */, 6900C5F41E8072DA00BCD75C /* ASImageNode+Private.h in Headers */, 68B0277B1C1A79D60041016B /* ASDisplayNode+Beta.h in Headers */, @@ -1953,6 +1995,7 @@ CCCCCCDB1EC3EF060087FE10 /* ASTextLine.h in Headers */, 9C70F20E1CDBE9E5007D6C76 /* NSArray+Diffing.h in Headers */, CCCCCCE71EC3F0FC0087FE10 /* NSAttributedString+ASText.h in Headers */, + CC7AF196200D9BD500A21BDE /* ASExperimentalFeatures.h in Headers */, CCCCCCDF1EC3EF060087FE10 /* ASTextRunDelegate.h in Headers */, 9C49C3701B853961000B0DD5 /* ASStackLayoutElement.h in Headers */, 34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */, @@ -2105,6 +2148,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + CC224E962066CA6D00BBA57F /* configuration.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2175,6 +2219,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + CCEDDDD9200C518800FFCD0A /* ASConfigurationTests.m in Sources */, E51B78BF1F028ABF00E32604 /* ASLayoutFlatteningTests.m in Sources */, 4496D0731FA9EA6B001CC8D5 /* ASTraitCollectionTests.m in Sources */, 29CDC2E21AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m in Sources */, @@ -2315,6 +2360,7 @@ B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */, CCB1F95A1EFB60A5009C7475 /* ASLog.m in Sources */, 767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.m in Sources */, + CCEDDDCB200C2AC300FFCD0A /* ASConfigurationInternal.m in Sources */, CCCCCCD61EC3EF060087FE10 /* ASTextDebugOption.m in Sources */, 34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */, B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */, @@ -2353,6 +2399,7 @@ DECBD6EA1BE56E1900CF4905 /* ASButtonNode.mm in Sources */, CCCCCCE01EC3EF060087FE10 /* ASTextRunDelegate.m in Sources */, CCCCCCDA1EC3EF060087FE10 /* ASTextLayout.m in Sources */, + CCEDDDD1200C488000FFCD0A /* ASConfiguration.m in Sources */, 254C6B841BF94F8A003EC431 /* ASTextNodeWordKerner.m in Sources */, E5E2D7301EA780DF005C24C6 /* ASCollectionGalleryLayoutDelegate.mm in Sources */, 34EFC76B1B701CEB00AD841F /* ASLayoutSpec.mm in Sources */, @@ -2383,6 +2430,7 @@ E54E81FD1EB357BD00FFE8E1 /* ASPageTable.m in Sources */, 34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */, 7AB338661C55B3420055FDE8 /* ASRelativeLayoutSpec.mm in Sources */, + CC7AF198200DAB2200A21BDE /* ASExperimentalFeatures.m in Sources */, E5B2252E1F17E521001E1431 /* ASDispatch.m in Sources */, 696F01EE1DD2AF450049FBD5 /* ASEventLog.mm in Sources */, 9C70F2051CDA4F06007D6C76 /* ASTraitCollection.m in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 79227ca03..22c737c17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler) - Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler) - Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler) +- Added a configuration API – a unified place to turn on/off experimental Texture features. See `ASConfiguration.h` for info. [Adlai Holler](https://github.com/Adlai-Holler) - **Breaking** Changes to ASNetworkImageNode: [Adlai Holler](https://github.com/Adlai-Holler) - Modified `ASImageDownloaderCompletion` to add an optional `id userInfo` field. Your custom downloader can pass `nil`. - Modified the last argument to `-[ASNetworkImageNodeDelegate imageNode:didLoadImage:info:]` method from a struct to an object of new class `ASNetworkImageLoadInfo` which includes other metadata about the load operation. diff --git a/Schemas/configuration.json b/Schemas/configuration.json new file mode 100644 index 000000000..366b29233 --- /dev/null +++ b/Schemas/configuration.json @@ -0,0 +1,23 @@ +{ + "id": "configuration.json", + "title": "configuration", + "description" : "Schema definition of a Texture Configuration", + "$schema": "http://json-schema.org/schema#", + "type": "object", + "properties": { + "version" : { + "type" : "number" + }, + "experimental_features": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "exp_graphics_contexts", + "exp_text_node", + "exp_interface_state_coalesce" + ] + } + } + } +} diff --git a/Source/ASConfiguration.h b/Source/ASConfiguration.h new file mode 100644 index 000000000..b024bb7e9 --- /dev/null +++ b/Source/ASConfiguration.h @@ -0,0 +1,62 @@ +// +// ASConfiguration.h +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import +#import + +@protocol ASConfigurationDelegate; + +NS_ASSUME_NONNULL_BEGIN + +static NSInteger const ASConfigurationSchemaCurrentVersion = 1; + +AS_SUBCLASSING_RESTRICTED +@interface ASConfiguration : NSObject + +/** + * Initialize this configuration with the provided dictionary, + * or nil to create an empty configuration. + * + * The schema is located in `schemas/configuration.json`. + */ +- (instancetype)initWithDictionary:(nullable NSDictionary *)dictionary; + +/** + * The delegate for configuration-related events. + * Delegate methods are called from a serial queue. + */ +@property (nonatomic, strong, nullable) id delegate; + +/** + * The experimental features to enable in Texture. + * See ASExperimentalFeatures for functions to convert to/from a string array. + */ +@property (nonatomic) ASExperimentalFeatures experimentalFeatures; + +@end + +/** + * Implement this method in a category to make your + * configuration available to Texture. It will be read + * only once and copied. + * + * NOTE: To specify your configuration at compile-time, you can + * define AS_FIXED_CONFIG_JSON as a C-string of JSON. This method + * will then be implemented to parse that string and generate + * a configuration. + */ +@interface ASConfiguration (UserProvided) ++ (ASConfiguration *)textureConfiguration NS_RETURNS_RETAINED; +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ASConfiguration.m b/Source/ASConfiguration.m new file mode 100644 index 000000000..93c8c8fb0 --- /dev/null +++ b/Source/ASConfiguration.m @@ -0,0 +1,67 @@ +// +// ASConfiguration.m +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import + +/// Not too performance-sensitive here. + +/// Get this from C++, without the extra exception handling. +#define autotype __auto_type + +@implementation ASConfiguration + +- (instancetype)initWithDictionary:(NSDictionary *)dictionary +{ + if (self = [super init]) { + autotype featureStrings = ASDynamicCast(dictionary[@"experimental_features"], NSArray); + autotype version = ASDynamicCast(dictionary[@"version"], NSNumber).integerValue; + if (version != ASConfigurationSchemaCurrentVersion) { + NSLog(@"Texture warning: configuration schema is old version (%zd vs %zd)", version, ASConfigurationSchemaCurrentVersion); + } + self.experimentalFeatures = ASExperimentalFeaturesFromArray(featureStrings); + } + return self; +} + +- (id)copyWithZone:(NSZone *)zone +{ + ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil]; + config.experimentalFeatures = self.experimentalFeatures; + config.delegate = self.delegate; + return config; +} + +@end + +//#define AS_FIXED_CONFIG_JSON "{ \"version\" : 1, \"experimental_features\": [ \"exp_text_node\" ] }" + +#ifdef AS_FIXED_CONFIG_JSON + +@implementation ASConfiguration (UserProvided) + ++ (ASConfiguration *)textureConfiguration NS_RETURNS_RETAINED +{ + NSData *data = [@AS_FIXED_CONFIG_JSON dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSDictionary *d = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error]; + if (!d) { + NSAssert(NO, @"Error parsing fixed config string '%s': %@", AS_FIXED_CONFIG_JSON, error); + return nil; + } else { + return [[ASConfiguration alloc] initWithDictionary:d]; + } +} + +@end + +#endif // AS_FIXED_CONFIG_JSON diff --git a/Source/ASConfigurationDelegate.h b/Source/ASConfigurationDelegate.h new file mode 100644 index 000000000..88055f037 --- /dev/null +++ b/Source/ASConfigurationDelegate.h @@ -0,0 +1,31 @@ +// +// ASConfigurationDelegate.h +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Used to communicate configuration-related events to the client. + */ +@protocol ASConfigurationDelegate + +/** + * Texture performed its first behavior related to the feature(s). + * This can be useful for tracking the impact of the behavior (A/B testing). + */ +- (void)textureDidActivateExperimentalFeatures:(ASExperimentalFeatures)features; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 98f3f1b80..72eaf0331 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -2915,7 +2915,7 @@ - (void)didExitHierarchy } }; - if ([[ASCATransactionQueue sharedQueue] disabled]) { + if (!ASCATransactionQueue.sharedQueue.enabled) { dispatch_async(dispatch_get_main_queue(), exitVisibleInterfaceState); } else { exitVisibleInterfaceState(); @@ -2980,7 +2980,7 @@ - (ASInterfaceState)interfaceState - (void)setInterfaceState:(ASInterfaceState)newState { - if ([[ASCATransactionQueue sharedQueue] disabled]) { + if (!ASCATransactionQueue.sharedQueue.enabled) { [self applyPendingInterfaceState:newState]; } else { ASDN::MutexLocker l(__instanceLock__); @@ -3012,7 +3012,7 @@ - (void)applyPendingInterfaceState:(ASInterfaceState)newPendingState ASDN::MutexLocker l(__instanceLock__); // newPendingState will not be used when ASCATransactionQueue is enabled // and use _pendingInterfaceState instead for interfaceState update. - if ([[ASCATransactionQueue sharedQueue] disabled]) { + if (!ASCATransactionQueue.sharedQueue.enabled) { _pendingInterfaceState = newPendingState; } oldState = _interfaceState; diff --git a/Source/ASExperimentalFeatures.h b/Source/ASExperimentalFeatures.h new file mode 100644 index 000000000..9cb3d419e --- /dev/null +++ b/Source/ASExperimentalFeatures.h @@ -0,0 +1,36 @@ +// +// ASExperimentalFeatures.h +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN +ASDISPLAYNODE_EXTERN_C_BEGIN + +/** + * A bit mask of features. + */ +typedef NS_OPTIONS(NSUInteger, ASExperimentalFeatures) { + ASExperimentalGraphicsContexts = 1 << 0, // exp_graphics_contexts + ASExperimentalTextNode = 1 << 1, // exp_text_node + ASExperimentalInterfaceStateCoalescing = 1 << 2, // exp_interface_state_coalesce + ASExperimentalFeatureAll = 0xFFFFFFFF +}; + +/// Convert flags -> name array. +NSArray *ASExperimentalFeaturesGetNames(ASExperimentalFeatures flags); + +/// Convert name array -> flags. +ASExperimentalFeatures ASExperimentalFeaturesFromArray(NSArray *array); + +ASDISPLAYNODE_EXTERN_C_END +NS_ASSUME_NONNULL_END diff --git a/Source/ASExperimentalFeatures.m b/Source/ASExperimentalFeatures.m new file mode 100644 index 000000000..5ce20c70b --- /dev/null +++ b/Source/ASExperimentalFeatures.m @@ -0,0 +1,45 @@ +// +// ASExperimentalFeatures.m +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +NSArray *ASExperimentalFeaturesGetNames(ASExperimentalFeatures flags) +{ + NSArray *allNames = ASCreateOnce((@[@"exp_graphics_contexts", + @"exp_text_node", + @"exp_interface_state_coalesce"])); + + if (flags == ASExperimentalFeatureAll) { + return allNames; + } + + // Go through all names, testing each bit. + NSUInteger i = 0; + return ASArrayByFlatMapping(allNames, NSString *name, ({ + (flags & (1 << i++)) ? name : nil; + })); +} + +// O(N^2) but with counts this small, it's probably faster +// than hashing the strings. +ASExperimentalFeatures ASExperimentalFeaturesFromArray(NSArray *array) +{ + NSArray *allNames = ASExperimentalFeaturesGetNames(ASExperimentalFeatureAll); + ASExperimentalFeatures result = 0; + for (NSString *str in array) { + NSUInteger i = [allNames indexOfObject:str]; + if (i != NSNotFound) { + result |= (1 << i); + } + } + return result; +} diff --git a/Source/ASNetworkImageLoadInfo.h b/Source/ASNetworkImageLoadInfo.h index 51e23e375..6db366a9c 100644 --- a/Source/ASNetworkImageLoadInfo.h +++ b/Source/ASNetworkImageLoadInfo.h @@ -1,9 +1,13 @@ // // ASNetworkImageLoadInfo.h -// AsyncDisplayKit +// Texture // -// Created by Adlai on 1/30/18. -// Copyright © 2018 Facebook. All rights reserved. +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import diff --git a/Source/ASNetworkImageLoadInfo.m b/Source/ASNetworkImageLoadInfo.m index 69dfee760..00baa1bf7 100644 --- a/Source/ASNetworkImageLoadInfo.m +++ b/Source/ASNetworkImageLoadInfo.m @@ -1,9 +1,13 @@ // // ASNetworkImageLoadInfo.m -// AsyncDisplayKit +// Texture // -// Created by Adlai on 1/30/18. -// Copyright © 2018 Facebook. All rights reserved. +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import diff --git a/Source/ASRunLoopQueue.h b/Source/ASRunLoopQueue.h index 266cd71f3..291cc4abf 100644 --- a/Source/ASRunLoopQueue.h +++ b/Source/ASRunLoopQueue.h @@ -59,7 +59,9 @@ AS_SUBCLASSING_RESTRICTED @interface ASCATransactionQueue : ASAbstractRunLoopQueue @property (atomic, readonly) BOOL isEmpty; -@property (atomic, readonly) BOOL disabled; + +@property (atomic, readonly, getter=isEnabled) BOOL enabled; + /** * The queue to run on main run loop before CATransaction commit. * @@ -72,11 +74,6 @@ AS_SUBCLASSING_RESTRICTED - (void)enqueue:(id)object; -/** - * @abstract Apply a node's interfaceState immediately rather than adding to the queue. - */ -- (void)disable; - @end diff --git a/Source/ASRunLoopQueue.mm b/Source/ASRunLoopQueue.mm index 782618cc3..d30a15a39 100644 --- a/Source/ASRunLoopQueue.mm +++ b/Source/ASRunLoopQueue.mm @@ -16,6 +16,7 @@ // #import +#import #import #import #import @@ -494,7 +495,6 @@ @interface ASCATransactionQueue () { CFRunLoopObserverRef _postTransactionObserver; NSPointerArray *_internalQueue; ASDN::RecursiveMutex _internalQueueLock; - BOOL _disableInterfaceStateCoalesce; BOOL _CATransactionCommitInProgress; // In order to not pollute the top-level activities, each queue has 1 root activity. @@ -671,7 +671,7 @@ - (void)enqueue:(id)object return; } - if (_disableInterfaceStateCoalesce || _CATransactionCommitInProgress) { + if (!self.enabled || _CATransactionCommitInProgress) { [object prepareForCATransactionCommit]; return; } @@ -702,14 +702,9 @@ - (BOOL)isEmpty return _internalQueue.count == 0; } -- (void)disable +- (BOOL)isEnabled { - _disableInterfaceStateCoalesce = YES; -} - -- (BOOL)disabled -{ - return _disableInterfaceStateCoalesce; + return ASActivateExperimentalFeature(ASExperimentalInterfaceStateCoalescing); } @end diff --git a/Source/ASTextNode+Beta.h b/Source/ASTextNode+Beta.h index 8c7556811..d60e0c13c 100644 --- a/Source/ASTextNode+Beta.h +++ b/Source/ASTextNode+Beta.h @@ -21,20 +21,6 @@ NS_ASSUME_NONNULL_BEGIN -// When enabled, use ASTextNode2 for subclasses, random instances, or all instances of ASTextNode. -// See ASAvailability.h declaration of ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE for a compile-time option. -typedef NS_OPTIONS(NSUInteger, ASTextNodeExperimentOptions) { - // All subclass instances use the experimental implementation. - ASTextNodeExperimentSubclasses = 1 << 0, - // Random instances of ASTextNode (50% chance) (not subclasses) use experimental impl. - // Useful for profiling with apps that have no custom text node subclasses. - ASTextNodeExperimentRandomInstances = 1 << 1, - // All instances of ASTextNode itself use experimental implementation. Supersedes `.randomInstances`. - ASTextNodeExperimentAllInstances = 1 << 2, - // Add highlighting etc. for debugging. - ASTextNodeExperimentDebugging = 1 << 3 -}; - @interface ASTextNode () /** @@ -52,14 +38,6 @@ typedef NS_OPTIONS(NSUInteger, ASTextNodeExperimentOptions) { */ @property (nonatomic, assign) UIEdgeInsets textContainerInset; -/** - * Opt in to an experimental implementation of text node. The implementation may improve performance and correctness, - * but may not support all features and has not been thoroughly tested in production. - * - * @precondition You may not call this after allocating any text nodes. You may only call this once. - */ -+ (void)setExperimentOptions:(ASTextNodeExperimentOptions)options; - /** * Returns YES if this node is using the experimental implementation. NO otherwise. Will not change. */ diff --git a/Source/ASTextNode.h b/Source/ASTextNode.h index f3670bb0f..269ca8f81 100644 --- a/Source/ASTextNode.h +++ b/Source/ASTextNode.h @@ -17,31 +17,10 @@ #import #import -#if ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE - #import -#endif +#import NS_ASSUME_NONNULL_BEGIN -@protocol ASTextNodeDelegate; - -#if !ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE - -/** - * Highlight styles. - */ -typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { - /** - * Highlight style for text on a light background. - */ - ASTextNodeHighlightStyleLight, - - /** - * Highlight style for text on a dark background. - */ - ASTextNodeHighlightStyleDark -}; - /** @abstract Draws interactive rich text. @discussion Backed by TextKit. @@ -238,70 +217,6 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { @end -#else - -@interface ASTextNode : ASTextNode2 -@end - -#endif - -/** - * @abstract Text node delegate. - */ -@protocol ASTextNodeDelegate -@optional - -/** - @abstract Indicates to the delegate that a link was tapped within a text node. - @param textNode The ASTextNode containing the link that was tapped. - @param attribute The attribute that was tapped. Will not be nil. - @param value The value of the tapped attribute. - @param point The point within textNode, in textNode's coordinate system, that was tapped. - @param textRange The range of highlighted text. - */ -- (void)textNode:(ASTextNode *)textNode tappedLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point textRange:(NSRange)textRange; - -/** - @abstract Indicates to the delegate that a link was tapped within a text node. - @param textNode The ASTextNode containing the link that was tapped. - @param attribute The attribute that was tapped. Will not be nil. - @param value The value of the tapped attribute. - @param point The point within textNode, in textNode's coordinate system, that was tapped. - @param textRange The range of highlighted text. - @discussion In addition to implementing this method, the delegate must be set on the text - node before it is loaded (the recognizer is created in -didLoad) - */ -- (void)textNode:(ASTextNode *)textNode longPressedLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point textRange:(NSRange)textRange; - -//! @abstract Called when the text node's truncation string has been tapped. -- (void)textNodeTappedTruncationToken:(ASTextNode *)textNode; - -/** - @abstract Indicates to the text node if an attribute should be considered a link. - @param textNode The text node containing the entity attribute. - @param attribute The attribute that was tapped. Will not be nil. - @param value The value of the tapped attribute. - @param point The point within textNode, in textNode's coordinate system, that was touched to trigger a highlight. - @discussion If not implemented, the default value is YES. - @return YES if the entity attribute should be a link, NO otherwise. - */ -- (BOOL)textNode:(ASTextNode *)textNode shouldHighlightLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point; - -/** - @abstract Indicates to the text node if an attribute is a valid long-press target - @param textNode The text node containing the entity attribute. - @param attribute The attribute that was tapped. Will not be nil. - @param value The value of the tapped attribute. - @param point The point within textNode, in textNode's coordinate system, that was long-pressed. - @discussion If not implemented, the default value is NO. - @return YES if the entity attribute should be treated as a long-press target, NO otherwise. - */ -- (BOOL)textNode:(ASTextNode *)textNode shouldLongPressLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point; - -@end - -#if !ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE - @interface ASTextNode (Unavailable) - (instancetype)initWithLayerBlock:(ASDisplayNodeLayerBlock)viewBlock didLoadBlock:(nullable ASDisplayNodeDidLoadBlock)didLoadBlock __unavailable; @@ -334,6 +249,4 @@ typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { @end -#endif - NS_ASSUME_NONNULL_END diff --git a/Source/ASTextNode.mm b/Source/ASTextNode.mm index b57d7ccea..e98dfb261 100644 --- a/Source/ASTextNode.mm +++ b/Source/ASTextNode.mm @@ -18,7 +18,6 @@ #import #import -#if !ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE #import #import @@ -27,6 +26,7 @@ #import #import #import +#import #import #import #import @@ -1309,69 +1309,35 @@ + (void)_registerAttributedText:(NSAttributedString *)str } #endif -// Allocate _experimentLock on the heap to prevent destruction at app exit (https://github.com/TextureGroup/Texture/issues/136) -static ASDN::StaticMutex& _experimentLock = *new ASDN::StaticMutex; -static ASTextNodeExperimentOptions _experimentOptions; -static BOOL _hasAllocatedNode; - -+ (void)setExperimentOptions:(ASTextNodeExperimentOptions)options ++ (id)allocWithZone:(struct _NSZone *)zone { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - ASDN::StaticMutexLocker lock(_experimentLock); - - // They must call this before allocating any text nodes. - ASDisplayNodeAssertFalse(_hasAllocatedNode); - - _experimentOptions = options; - - // Set superclass of all subclasses to ASTextNode2 - if (options & ASTextNodeExperimentSubclasses) { - unsigned int classCount; - Class originalClass = [ASTextNode class]; - Class newClass = [ASTextNode2 class]; - Class *classes = objc_copyClassList(&classCount); - for (int i = 0; i < classCount; i++) { - Class c = classes[i]; - if (class_getSuperclass(c) == originalClass) { + // If they're not experimenting, just forward. + if (!ASActivateExperimentalFeature(ASExperimentalTextNode)) { + return [super allocWithZone:zone]; + } + + // We are plain ASTextNode. Just swap in an ASTextNode2 instead. + if (self == [ASTextNode class]) { + return (ASTextNode *)[ASTextNode2 allocWithZone:zone]; + } + + // We are descended from ASTextNode. We need to change the superclass for the + // ASTextNode subclass to ASTextNode2. + // Walk up the class hierarchy until we find ASTextNode. + Class s; + for (Class c = self; c != [ASTextNode class]; c = s) { + s = class_getSuperclass(c); + if (s == [ASTextNode class]) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - class_setSuperclass(c, newClass); + // Direct descendent. Update superclass of c and end. + class_setSuperclass(c, [ASTextNode2 class]); #pragma clang diagnostic pop - } - } - free(classes); + break; } - - if (options & ASTextNodeExperimentDebugging) { - [ASTextNode2 enableDebugging]; - } - }); -} - -+ (id)allocWithZone:(struct _NSZone *)zone -{ - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - ASDN::StaticMutexLocker lock(_experimentLock); - _hasAllocatedNode = YES; - }); - - // All instances || (random instances && rand() != 0) - BOOL useExperiment = (_experimentOptions & ASTextNodeExperimentAllInstances) - || ((_experimentOptions & ASTextNodeExperimentRandomInstances) - && (arc4random_uniform(2) != 0)); - - if (useExperiment) { - return (ASTextNode *)[ASTextNode2 allocWithZone:zone]; - } else { - return [super allocWithZone:zone]; } -} -- (BOOL)usingExperiment -{ - return NO; + return [super allocWithZone:zone]; } @end @@ -1399,10 +1365,3 @@ - (NSAttributedString *)truncationAttributedString } @end - -#else - -@implementation ASTextNode -@end - -#endif diff --git a/Source/ASTextNode2.h b/Source/ASTextNode2.h index aef3ac468..9eecbf546 100644 --- a/Source/ASTextNode2.h +++ b/Source/ASTextNode2.h @@ -11,28 +11,7 @@ // #import - -#if !ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE -// Import this to get ASTextNodeHighlightStyle -#import -#else -@protocol ASTextNodeDelegate; - -/** - * Highlight styles. - */ -typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { - /** - * Highlight style for text on a light background. - */ - ASTextNodeHighlightStyleLight, - - /** - * Highlight style for text on a dark background. - */ - ASTextNodeHighlightStyleDark -}; -#endif +#import NS_ASSUME_NONNULL_BEGIN diff --git a/Source/ASTextNodeCommon.h b/Source/ASTextNodeCommon.h new file mode 100644 index 000000000..0ff51b402 --- /dev/null +++ b/Source/ASTextNodeCommon.h @@ -0,0 +1,86 @@ +// +// ASTextNodeCommon.h +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +@class ASTextNode; + +/** + * Highlight styles. + */ +typedef NS_ENUM(NSUInteger, ASTextNodeHighlightStyle) { + /** + * Highlight style for text on a light background. + */ + ASTextNodeHighlightStyleLight, + + /** + * Highlight style for text on a dark background. + */ + ASTextNodeHighlightStyleDark +}; + +/** + * @abstract Text node delegate. + */ +@protocol ASTextNodeDelegate +@optional + +/** + @abstract Indicates to the delegate that a link was tapped within a text node. + @param textNode The ASTextNode containing the link that was tapped. + @param attribute The attribute that was tapped. Will not be nil. + @param value The value of the tapped attribute. + @param point The point within textNode, in textNode's coordinate system, that was tapped. + @param textRange The range of highlighted text. + */ +- (void)textNode:(ASTextNode *)textNode tappedLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point textRange:(NSRange)textRange; + +/** + @abstract Indicates to the delegate that a link was tapped within a text node. + @param textNode The ASTextNode containing the link that was tapped. + @param attribute The attribute that was tapped. Will not be nil. + @param value The value of the tapped attribute. + @param point The point within textNode, in textNode's coordinate system, that was tapped. + @param textRange The range of highlighted text. + @discussion In addition to implementing this method, the delegate must be set on the text + node before it is loaded (the recognizer is created in -didLoad) + */ +- (void)textNode:(ASTextNode *)textNode longPressedLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point textRange:(NSRange)textRange; + +//! @abstract Called when the text node's truncation string has been tapped. +- (void)textNodeTappedTruncationToken:(ASTextNode *)textNode; + +/** + @abstract Indicates to the text node if an attribute should be considered a link. + @param textNode The text node containing the entity attribute. + @param attribute The attribute that was tapped. Will not be nil. + @param value The value of the tapped attribute. + @param point The point within textNode, in textNode's coordinate system, that was touched to trigger a highlight. + @discussion If not implemented, the default value is YES. + @return YES if the entity attribute should be a link, NO otherwise. + */ +- (BOOL)textNode:(ASTextNode *)textNode shouldHighlightLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point; + +/** + @abstract Indicates to the text node if an attribute is a valid long-press target + @param textNode The text node containing the entity attribute. + @param attribute The attribute that was tapped. Will not be nil. + @param value The value of the tapped attribute. + @param point The point within textNode, in textNode's coordinate system, that was long-pressed. + @discussion If not implemented, the default value is NO. + @return YES if the entity attribute should be treated as a long-press target, NO otherwise. + */ +- (BOOL)textNode:(ASTextNode *)textNode shouldLongPressLinkAttribute:(NSString *)attribute value:(id)value atPoint:(CGPoint)point; + +@end + diff --git a/Source/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h index 67e1720b6..111a8f310 100644 --- a/Source/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -21,6 +21,8 @@ #import #import #import +#import +#import #import #import diff --git a/Source/Base/ASAvailability.h b/Source/Base/ASAvailability.h index f400dea3c..c05ddd3fa 100644 --- a/Source/Base/ASAvailability.h +++ b/Source/Base/ASAvailability.h @@ -56,11 +56,8 @@ #define YOGA __has_include(YOGA_HEADER_PATH) #endif -// When enabled, use ASTextNode2 for ALL instances of ASTextNode. -// This includes what ASButtonNode uses internally, as well as all app references to ASTextNode. -// See ASTextNode+Beta.h declaration of ASTextNodeExperimentOptions for more details. -#ifndef ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE - #define ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE 0 +#ifdef ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE + #error "ASTEXTNODE_EXPERIMENT_GLOBAL_ENABLE is unavailable. See ASConfiguration.h." #endif #define AS_PIN_REMOTE_IMAGE __has_include() diff --git a/Source/Details/ASElementMap.h b/Source/Details/ASElementMap.h index eed7eff26..a73f80a92 100644 --- a/Source/Details/ASElementMap.h +++ b/Source/Details/ASElementMap.h @@ -31,6 +31,11 @@ NS_ASSUME_NONNULL_BEGIN AS_SUBCLASSING_RESTRICTED @interface ASElementMap : NSObject +/** + * The total number of elements in this map. + */ +@property (readonly) NSUInteger count; + /** * The number of sections (of items) in this map. */ diff --git a/Source/Details/ASElementMap.m b/Source/Details/ASElementMap.m index 08e90d401..f5646de78 100644 --- a/Source/Details/ASElementMap.m +++ b/Source/Details/ASElementMap.m @@ -75,6 +75,11 @@ - (instancetype)initWithSections:(NSArray *)sections items:(ASColle return self; } +- (NSUInteger)count +{ + return _elementToIndexPathMap.count; +} + - (NSArray *)itemIndexPaths { return ASIndexPathsForTwoDimensionalArray(_sectionsOfItems); diff --git a/Source/Details/ASGraphicsContext.h b/Source/Details/ASGraphicsContext.h index b7f808c97..0713f104f 100644 --- a/Source/Details/ASGraphicsContext.h +++ b/Source/Details/ASGraphicsContext.h @@ -31,15 +31,6 @@ NS_ASSUME_NONNULL_BEGIN ASDISPLAYNODE_EXTERN_C_BEGIN -/** - * Call this to enable the experimental no-copy rendering. - * - * Returns YES if it was enabled, or NO + assert if it's too late because - * rendering has already started. In practice it's fine to call this - * during -didFinishLaunchingWithOptions:. - */ -extern BOOL ASEnableNoCopyRendering(void); - /** * Creates a one-shot context. * diff --git a/Source/Details/ASGraphicsContext.m b/Source/Details/ASGraphicsContext.m index 70294d10a..37810a106 100644 --- a/Source/Details/ASGraphicsContext.m +++ b/Source/Details/ASGraphicsContext.m @@ -13,36 +13,12 @@ #import "ASGraphicsContext.h" #import #import +#import #import #import #import -#import #import -#pragma mark - Feature Gating - -// Two flags that we atomically manipulate to control the feature. -typedef NS_OPTIONS(uint, ASNoCopyFlags) { - ASNoCopyEnabled = 1 << 0, - ASNoCopyBlocked = 1 << 1 -}; -static atomic_uint __noCopyFlags; - -// Check if it's blocked, and set the enabled flag if not. -extern BOOL ASEnableNoCopyRendering() -{ - ASNoCopyFlags expectedFlags = 0; - BOOL enabled = atomic_compare_exchange_strong(&__noCopyFlags, &expectedFlags, ASNoCopyEnabled); - ASDisplayNodeCAssert(enabled, @"Can't enable no-copy rendering after first render started."); - return enabled; -} - -// Check if it's enabled and set the "blocked" flag either way. -static BOOL ASNoCopyRenderingBlockAndCheckEnabled() { - ASNoCopyFlags oldFlags = atomic_fetch_or(&__noCopyFlags, ASNoCopyBlocked); - return (oldFlags & ASNoCopyEnabled) != 0; -} - /** * Our version of the private CGBitmapGetAlignedBytesPerRow function. * @@ -67,7 +43,7 @@ static size_t ASGraphicsGetAlignedBytesPerRow(size_t baseValue) { extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) { - if (!ASNoCopyRenderingBlockAndCheckEnabled()) { + if (!ASActivateExperimentalFeature(ASExperimentalGraphicsContexts)) { UIGraphicsBeginImageContextWithOptions(size, opaque, scale); return; } @@ -132,7 +108,7 @@ extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGF extern UIImage * _Nullable ASGraphicsGetImageAndEndCurrentContext() NS_RETURNS_RETAINED { - if (!ASNoCopyRenderingBlockAndCheckEnabled()) { + if (!ASActivateExperimentalFeature(ASExperimentalGraphicsContexts)) { UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; @@ -186,7 +162,7 @@ extern void ASGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGF extern void ASGraphicsEndImageContext() { - if (!ASNoCopyRenderingBlockAndCheckEnabled()) { + if (!ASActivateExperimentalFeature(ASExperimentalGraphicsContexts)) { UIGraphicsEndImageContext(); return; } diff --git a/Source/Private/ASConfigurationInternal.h b/Source/Private/ASConfigurationInternal.h new file mode 100644 index 000000000..f93af4bbc --- /dev/null +++ b/Source/Private/ASConfigurationInternal.h @@ -0,0 +1,38 @@ +// +// ASConfigurationInternal.h +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import + +NS_ASSUME_NONNULL_BEGIN +ASDISPLAYNODE_EXTERN_C_BEGIN + +/** + * Quickly check if an experiment is enabled and notify the delegate + * that it's been activated. + * + * The delegate will be notified asynchronously. + */ +BOOL ASActivateExperimentalFeature(ASExperimentalFeatures option); + +AS_SUBCLASSING_RESTRICTED +@interface ASConfigurationManager : NSObject + +/** + * No API for now. + * Just use ASActivateExperimentalFeature to access this efficiently. + */ + +@end + +NS_ASSUME_NONNULL_END +ASDISPLAYNODE_EXTERN_C_END diff --git a/Source/Private/ASConfigurationInternal.m b/Source/Private/ASConfigurationInternal.m new file mode 100644 index 000000000..e9f0705ec --- /dev/null +++ b/Source/Private/ASConfigurationInternal.m @@ -0,0 +1,96 @@ +// +// ASConfigurationInternal.m +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import "ASConfigurationInternal.h" +#import +#import +#import + +#define ASGetSharedConfigMgr() (__bridge ASConfigurationManager *)ASConfigurationManager.sharedInstance + +@implementation ASConfigurationManager { + ASConfiguration *_config; + dispatch_queue_t _delegateQueue; + _Atomic(ASExperimentalFeatures) _activatedExperiments; +} + +/// Return CFTypeRef to avoid retain/release on this singleton. ++ (CFTypeRef)sharedInstance +{ + static CFTypeRef inst; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + inst = (__bridge_retained CFTypeRef)[[ASConfigurationManager alloc] init]; + }); + return inst; +} + ++ (ASConfiguration *)defaultConfiguration NS_RETURNS_RETAINED +{ + ASConfiguration *config = [[ASConfiguration alloc] init]; + // On by default for now, pending fix for https://github.com/TextureGroup/Texture/issues/853 + config.experimentalFeatures = ASExperimentalInterfaceStateCoalescing; + return config; +} + +- (instancetype)init +{ + if (self = [super init]) { + _delegateQueue = dispatch_queue_create("org.TextureGroup.Texture.ConfigNotifyQueue", DISPATCH_QUEUE_SERIAL); + if ([ASConfiguration respondsToSelector:@selector(textureConfiguration)]) { + _config = [[ASConfiguration textureConfiguration] copy]; + } else { + _config = [ASConfigurationManager defaultConfiguration]; + } + } + return self; +} + +- (BOOL)activateExperimentalFeature:(ASExperimentalFeatures)requested +{ + if (_config == nil) { + return NO; + } + + NSAssert(__builtin_popcount(requested) == 1, @"Cannot activate multiple features at once with this method."); + + // If they're disabled, ignore them. + ASExperimentalFeatures enabled = requested & _config.experimentalFeatures; + ASExperimentalFeatures prevTriggered = atomic_fetch_or(&_activatedExperiments, enabled); + ASExperimentalFeatures newlyTriggered = enabled & ~prevTriggered; + + // Notify delegate if needed. + if (newlyTriggered != 0) { + __unsafe_unretained id del = _config.delegate; + dispatch_async(_delegateQueue, ^{ + [del textureDidActivateExperimentalFeatures:newlyTriggered]; + }); + } + + return (enabled != 0); +} + +#if DEBUG ++ (void)test_resetWithConfiguration:(ASConfiguration *)configuration +{ + ASConfigurationManager *inst = ASGetSharedConfigMgr(); + inst->_config = configuration ?: [self defaultConfiguration]; + atomic_store(&inst->_activatedExperiments, 0); +} +#endif + +@end + +BOOL ASActivateExperimentalFeature(ASExperimentalFeatures feature) +{ + return [ASGetSharedConfigMgr() activateExperimentalFeature:feature]; +} diff --git a/Source/Private/ASNetworkImageLoadInfo+Private.h b/Source/Private/ASNetworkImageLoadInfo+Private.h index db0435231..a77d48af6 100644 --- a/Source/Private/ASNetworkImageLoadInfo+Private.h +++ b/Source/Private/ASNetworkImageLoadInfo+Private.h @@ -1,9 +1,13 @@ // // ASNetworkImageLoadInfo+Private.h -// AsyncDisplayKit +// Texture // -// Created by Adlai on 1/30/18. -// Copyright © 2018 Facebook. All rights reserved. +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import diff --git a/Tests/ASCollectionViewTests.mm b/Tests/ASCollectionViewTests.mm index c3dc8a2c4..9ea6fe557 100644 --- a/Tests/ASCollectionViewTests.mm +++ b/Tests/ASCollectionViewTests.mm @@ -862,7 +862,7 @@ - (void)testThatDeletedItemsAreMarkedInvisible [cn waitUntilAllUpdatesAreProcessed]; [cn.view layoutIfNeeded]; ASCellNode *node = [cn nodeForItemAtIndexPath:[NSIndexPath indexPathForItem:0 inSection:0]]; - ASCATransactionQueueWait(); + ASCATransactionQueueWait(nil); XCTAssertTrue(node.visible); testController.asyncDelegate->_itemCounts = {0}; [cn deleteItemsAtIndexPaths: @[[NSIndexPath indexPathForItem:0 inSection:0]]]; @@ -1074,7 +1074,7 @@ - (void)testInitialRangeBounds for (NSInteger i = 0; i < c; i++) { NSIndexPath *ip = [NSIndexPath indexPathForItem:i inSection:s]; ASCellNode *node = [cn nodeForItemAtIndexPath:ip]; - ASCATransactionQueueWait(); + ASCATransactionQueueWait(nil); if (node.inPreloadState) { CGRect frame = [cn.view layoutAttributesForItemAtIndexPath:ip].frame; r = CGRectUnion(r, frame); diff --git a/Tests/ASConfigurationTests.m b/Tests/ASConfigurationTests.m new file mode 100644 index 000000000..41e0d0ed8 --- /dev/null +++ b/Tests/ASConfigurationTests.m @@ -0,0 +1,77 @@ +// +// ASConfigurationTests.m +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import "ASTestCase.h" +#import "ASConfiguration.h" +#import "ASConfigurationDelegate.h" +#import "ASConfigurationInternal.h" + +@interface ASConfigurationTests : ASTestCase + +@end + +@implementation ASConfigurationTests { + void (^onActivate)(ASConfigurationTests *self, ASExperimentalFeatures feature); +} + +- (void)testExperimentalFeatureConfig +{ + // Set the config + ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil]; + config.experimentalFeatures = ASExperimentalGraphicsContexts; + config.delegate = self; + [ASConfigurationManager test_resetWithConfiguration:config]; + + // Set an expectation for a callback, and assert we only get one. + XCTestExpectation *e = [self expectationWithDescription:@"Callback 1 done."]; + onActivate = ^(ASConfigurationTests *self, ASExperimentalFeatures feature) { + XCTAssertEqual(feature, ASExperimentalGraphicsContexts); + [e fulfill]; + // Next time it's a fail. + self->onActivate = ^(ASConfigurationTests *self, ASExperimentalFeatures feature) { + XCTFail(@"Too many callbacks."); + }; + }; + + // Now activate the graphics experiment and expect it works. + XCTAssertTrue(ASActivateExperimentalFeature(ASExperimentalGraphicsContexts)); + // We should get a callback here + // Now activate text node and expect it fails. + XCTAssertFalse(ASActivateExperimentalFeature(ASExperimentalTextNode)); + [self waitForExpectationsWithTimeout:3 handler:nil]; +} + +- (void)textureDidActivateExperimentalFeatures:(ASExperimentalFeatures)feature +{ + if (onActivate) { + onActivate(self, feature); + } +} + +- (void)testMappingNamesToFlags +{ + // Throw in a bad bit. + ASExperimentalFeatures features = ASExperimentalTextNode | ASExperimentalGraphicsContexts | (1 << 22); + NSArray *expectedNames = @[ @"exp_graphics_contexts", @"exp_text_node" ]; + XCTAssertEqualObjects(expectedNames, ASExperimentalFeaturesGetNames(features)); +} + +- (void)testMappingFlagsFromNames +{ + // Throw in a bad name. + NSArray *names = @[ @"exp_text_node", @"exp_graphics_contexts", @"__invalid_name" ]; + ASExperimentalFeatures expected = ASExperimentalGraphicsContexts | ASExperimentalTextNode; + XCTAssertEqual(expected, ASExperimentalFeaturesFromArray(names)); +} + +@end diff --git a/Tests/ASDisplayNodeImplicitHierarchyTests.m b/Tests/ASDisplayNodeImplicitHierarchyTests.m index d033ac4f6..9c39386df 100644 --- a/Tests/ASDisplayNodeImplicitHierarchyTests.m +++ b/Tests/ASDisplayNodeImplicitHierarchyTests.m @@ -131,7 +131,7 @@ - (void)testInitialNodeInsertionWhenEnterPreloadState ASDisplayNodeSizeToFitSizeRange(node, ASSizeRangeMake(CGSizeZero, CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX))); [node recursivelySetInterfaceState:ASInterfaceStatePreload]; - ASCATransactionQueueWait(); + ASCATransactionQueueWait(nil); // No premature view allocation XCTAssertFalse(node.isNodeLoaded); // Subnodes should be inserted, laid out and entered preload state diff --git a/Tests/ASDisplayNodeTests.mm b/Tests/ASDisplayNodeTests.mm index 1176a709c..50b340890 100644 --- a/Tests/ASDisplayNodeTests.mm +++ b/Tests/ASDisplayNodeTests.mm @@ -131,7 +131,7 @@ @implementation ASTestDisplayNode - (void)setInterfaceState:(ASInterfaceState)state { [super setInterfaceState:state]; - ASCATransactionQueueWait(); + ASCATransactionQueueWait(nil); } - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize diff --git a/Tests/ASDisplayNodeTestsHelper.h b/Tests/ASDisplayNodeTestsHelper.h index 98b4719c6..3e0bea5fb 100644 --- a/Tests/ASDisplayNodeTestsHelper.h +++ b/Tests/ASDisplayNodeTestsHelper.h @@ -18,7 +18,7 @@ #import #import -@class ASDisplayNode; +@class ASCATransactionQueue, ASDisplayNode; typedef BOOL (^as_condition_block_t)(void); @@ -28,6 +28,6 @@ BOOL ASDisplayNodeRunRunLoopUntilBlockIsTrue(as_condition_block_t block); void ASDisplayNodeSizeToFitSize(ASDisplayNode *node, CGSize size); void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange); -void ASCATransactionQueueWait(void); +void ASCATransactionQueueWait(ASCATransactionQueue *q); // nil means shared queue ASDISPLAYNODE_EXTERN_C_END diff --git a/Tests/ASDisplayNodeTestsHelper.m b/Tests/ASDisplayNodeTestsHelper.m index 0a37da1e4..6dbd9b794 100644 --- a/Tests/ASDisplayNodeTestsHelper.m +++ b/Tests/ASDisplayNodeTestsHelper.m @@ -64,12 +64,13 @@ void ASDisplayNodeSizeToFitSizeRange(ASDisplayNode *node, ASSizeRange sizeRange) node.bounds = (CGRect){.origin = CGPointZero, .size = sizeThatFits}; } -void ASCATransactionQueueWait(void) +void ASCATransactionQueueWait(ASCATransactionQueue *q) { + if (!q) { q = ASCATransactionQueue.sharedQueue; } NSDate *date = [NSDate dateWithTimeIntervalSinceNow:1]; BOOL whileResult = YES; while ([date timeIntervalSinceNow] > 0 && - (whileResult = ![[ASCATransactionQueue sharedQueue] isEmpty])) { + (whileResult = ![q isEmpty])) { [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow:0.01]]; } diff --git a/Tests/ASNetworkImageNodeTests.m b/Tests/ASNetworkImageNodeTests.m index 94dc668d5..498e79e8c 100644 --- a/Tests/ASNetworkImageNodeTests.m +++ b/Tests/ASNetworkImageNodeTests.m @@ -2,8 +2,12 @@ // ASNetworkImageNodeTests.m // Texture // -// Created by Adlai Holler on 10/14/16. -// Copyright © 2016 Facebook. All rights reserved. +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import diff --git a/Tests/ASPerformanceTestContext.h b/Tests/ASPerformanceTestContext.h index 42075f5fc..d60f53b1a 100644 --- a/Tests/ASPerformanceTestContext.h +++ b/Tests/ASPerformanceTestContext.h @@ -2,8 +2,12 @@ // ASPerformanceTestContext.h // Texture // -// Created by Adlai Holler on 8/28/16. -// Copyright © 2016 Facebook. All rights reserved. +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import diff --git a/Tests/ASRunLoopQueueTests.m b/Tests/ASRunLoopQueueTests.m index 2fc9c04b5..b195cbc4d 100644 --- a/Tests/ASRunLoopQueueTests.m +++ b/Tests/ASRunLoopQueueTests.m @@ -10,7 +10,8 @@ // http://www.apache.org/licenses/LICENSE-2.0 // -#import +#import "ASTestCase.h" + #import #import "ASDisplayNodeTestsHelper.h" @@ -27,7 +28,7 @@ - (void)prepareForCATransactionCommit } @end -@interface ASRunLoopQueueTests : XCTestCase +@interface ASRunLoopQueueTests : ASTestCase @end @@ -171,12 +172,18 @@ - (void)testWeakQueueWithAllDeallocatedObjectsIsDrained - (void)testASCATransactionQueueDisable { + // Disable coalescing. + ASConfiguration *config = [[ASConfiguration alloc] init]; + config.experimentalFeatures = kNilOptions; + [ASConfigurationManager test_resetWithConfiguration:config]; + ASCATransactionQueue *queue = [[ASCATransactionQueue alloc] init]; - [queue disable]; QueueObject *object = [[QueueObject alloc] init]; - [[ASCATransactionQueue sharedQueue] enqueue:object]; + XCTAssertFalse(object.queueObjectProcessed); + [queue enqueue:object]; + XCTAssertTrue(object.queueObjectProcessed); XCTAssertTrue([queue isEmpty]); - XCTAssertTrue([queue disabled]); + XCTAssertFalse(queue.enabled); } - (void)testASCATransactionQueueProcess @@ -185,8 +192,9 @@ - (void)testASCATransactionQueueProcess QueueObject *object = [[QueueObject alloc] init]; [queue enqueue:object]; XCTAssertFalse(object.queueObjectProcessed); - ASCATransactionQueueWait(); + ASCATransactionQueueWait(queue); XCTAssertTrue(object.queueObjectProcessed); + XCTAssertTrue(queue.enabled); } @end diff --git a/Tests/ASTextNodePerformanceTests.m b/Tests/ASTextNodePerformanceTests.m index aecf6d2fd..f5c62e53a 100644 --- a/Tests/ASTextNodePerformanceTests.m +++ b/Tests/ASTextNodePerformanceTests.m @@ -2,8 +2,12 @@ // ASTextNodePerformanceTests.m // Texture // -// Created by Adlai Holler on 8/28/16. -// Copyright © 2016 Facebook. All rights reserved. +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import diff --git a/Tests/ASTextNodeTests.m b/Tests/ASTextNodeTests.m index 5b1f2cf4d..8c90a6959 100644 --- a/Tests/ASTextNodeTests.m +++ b/Tests/ASTextNodeTests.m @@ -17,6 +17,8 @@ #import +#import "ASTestCase.h" + #import #import @@ -31,6 +33,10 @@ @interface ASTextNodeTestDelegate : NSObject @property (nonatomic, copy, readonly) NSString *tappedLinkAttribute; @property (nonatomic, assign, readonly) id tappedLinkValue; +@end +@interface ASTextNodeSubclass : ASTextNode +@end +@interface ASTextNodeSecondSubclass : ASTextNodeSubclass @end @implementation ASTextNodeTestDelegate @@ -235,4 +241,23 @@ - (void)testAddingExclusionPathsShouldInvalidateAndIncreaseTheSize XCTAssertGreaterThan(sizeWithExclusionPaths.height, sizeWithoutExclusionPaths.height, @"Setting exclusions paths should invalidate the calculated size and return a greater size"); } +- (void)testThatTheExperimentWorksCorrectly +{ + ASConfiguration *config = [ASConfiguration new]; + config.experimentalFeatures = ASExperimentalTextNode; + [ASConfigurationManager test_resetWithConfiguration:config]; + + ASTextNode *plainTextNode = [[ASTextNode alloc] init]; + XCTAssertEqualObjects(plainTextNode.class, [ASTextNode2 class]); + + ASTextNodeSecondSubclass *sc2 = [[ASTextNodeSecondSubclass alloc] init]; + XCTAssertEqualObjects([ASTextNodeSubclass superclass], [ASTextNode2 class]); + XCTAssertEqualObjects(sc2.superclass, [ASTextNodeSubclass class]); +} + +@end + +@implementation ASTextNodeSubclass +@end +@implementation ASTextNodeSecondSubclass @end diff --git a/Tests/ASVideoNodeTests.m b/Tests/ASVideoNodeTests.m index 96892b176..24bb5dd37 100644 --- a/Tests/ASVideoNodeTests.m +++ b/Tests/ASVideoNodeTests.m @@ -352,7 +352,7 @@ - (void)testVideoResumedWhenBufferIsLikelyToKeepUp [_videoNode setInterfaceState:ASInterfaceStateVisible | ASInterfaceStateDisplay | ASInterfaceStatePreload]; [_videoNode prepareToPlayAsset:assetMock withKeys:_requestedKeys]; - ASCATransactionQueueWait(); + ASCATransactionQueueWait(nil); [_videoNode pause]; _videoNode.shouldBePlaying = YES; XCTAssertFalse(_videoNode.isPlaying); diff --git a/Tests/Common/ASTestCase.h b/Tests/Common/ASTestCase.h index 54231b64e..bbcf07336 100644 --- a/Tests/Common/ASTestCase.h +++ b/Tests/Common/ASTestCase.h @@ -2,8 +2,13 @@ // ASTestCase.h // Texture // -// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0 (the "License"); +// 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 /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // @@ -16,6 +21,7 @@ #import #import #import "OCMockObject+ASAdditions.h" +#import "ASConfigurationInternal.h" NS_ASSUME_NONNULL_BEGIN @@ -25,4 +31,10 @@ NS_ASSUME_NONNULL_BEGIN @end +@interface ASConfigurationManager (Testing) + ++ (void)test_resetWithConfiguration:(nullable ASConfiguration *)configuration; + +@end + NS_ASSUME_NONNULL_END diff --git a/Tests/Common/ASTestCase.m b/Tests/Common/ASTestCase.m index 5bc720253..fb059e7ed 100644 --- a/Tests/Common/ASTestCase.m +++ b/Tests/Common/ASTestCase.m @@ -2,8 +2,13 @@ // ASTestCase.m // Texture // -// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0 (the "License"); +// 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 /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. +// +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // @@ -31,6 +36,8 @@ - (void)setUp - (void)tearDown { + [ASConfigurationManager test_resetWithConfiguration:nil]; + // Clear out all application windows. Note: the system will retain these sometimes on its // own but we'll do our best. for (UIWindow *window in [UIApplication sharedApplication].windows) { diff --git a/examples/ASDKgram/Sample.xcodeproj/project.pbxproj b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj index 743b9456e..a4b5b7722 100644 --- a/examples/ASDKgram/Sample.xcodeproj/project.pbxproj +++ b/examples/ASDKgram/Sample.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ CC5532171E15CC1E0011C01F /* ASCollectionSectionController.m in Sources */ = {isa = PBXBuildFile; fileRef = CC5532161E15CC1E0011C01F /* ASCollectionSectionController.m */; }; CC6350BB1E1C482D002BC613 /* TailLoadingNode.m in Sources */ = {isa = PBXBuildFile; fileRef = CC6350BA1E1C482D002BC613 /* TailLoadingNode.m */; }; CC85250F1E36B392008EABE6 /* FeedHeaderNode.m in Sources */ = {isa = PBXBuildFile; fileRef = CC85250E1E36B392008EABE6 /* FeedHeaderNode.m */; }; + CCEDDDD7200C4C0E00FFCD0A /* TextureConfigDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CCEDDDD6200C4C0E00FFCD0A /* TextureConfigDelegate.m */; }; E5F128F01E09625400B4335F /* PhotoFeedBaseController.m in Sources */ = {isa = PBXBuildFile; fileRef = E5F128EF1E09625400B4335F /* PhotoFeedBaseController.m */; }; /* End PBXBuildFile section */ @@ -98,6 +99,7 @@ CC6350BA1E1C482D002BC613 /* TailLoadingNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TailLoadingNode.m; sourceTree = ""; }; CC85250D1E36B392008EABE6 /* FeedHeaderNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FeedHeaderNode.h; sourceTree = ""; }; CC85250E1E36B392008EABE6 /* FeedHeaderNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FeedHeaderNode.m; sourceTree = ""; }; + CCEDDDD6200C4C0E00FFCD0A /* TextureConfigDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TextureConfigDelegate.m; sourceTree = ""; }; D09B5DF0BFB37583DE8F3142 /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; E5F128EE1E09612700B4335F /* PhotoFeedBaseController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PhotoFeedBaseController.h; sourceTree = ""; }; E5F128EF1E09625400B4335F /* PhotoFeedBaseController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PhotoFeedBaseController.m; sourceTree = ""; }; @@ -141,6 +143,7 @@ children = ( 768843511CAA37EF00D8629E /* AppDelegate.h */, 768843681CAA37EF00D8629E /* AppDelegate.m */, + CCEDDDD6200C4C0E00FFCD0A /* TextureConfigDelegate.m */, 76229A761CBB79E000B62CEF /* WindowWithStatusBarUnderlay.h */, 76229A771CBB79E000B62CEF /* WindowWithStatusBarUnderlay.m */, 767A5F141CAA3D8A004CDA8D /* Controller */, @@ -427,6 +430,7 @@ CC5532171E15CC1E0011C01F /* ASCollectionSectionController.m in Sources */, 768843801CAA37EF00D8629E /* AppDelegate.m in Sources */, 768843811CAA37EF00D8629E /* CommentFeedModel.m in Sources */, + CCEDDDD7200C4C0E00FFCD0A /* TextureConfigDelegate.m in Sources */, 7688438E1CAA37EF00D8629E /* PhotoFeedNodeController.m in Sources */, CC6350BB1E1C482D002BC613 /* TailLoadingNode.m in Sources */, CC85250F1E36B392008EABE6 /* FeedHeaderNode.m in Sources */, diff --git a/examples/ASDKgram/Sample/AppDelegate.m b/examples/ASDKgram/Sample/AppDelegate.m index 21c47a273..e1dd507ba 100644 --- a/examples/ASDKgram/Sample/AppDelegate.m +++ b/examples/ASDKgram/Sample/AppDelegate.m @@ -7,7 +7,7 @@ // LICENSE file in the /ASDK-Licenses directory of this source tree. An additional // grant of patent rights can be found in the PATENTS file in the same directory. // -// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present, +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, // Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -40,8 +40,6 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - ASEnableNoCopyRendering(); - // this UIWindow subclass is neccessary to make the status bar opaque _window = [[WindowWithStatusBarUnderlay alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; _window.backgroundColor = [UIColor whiteColor]; diff --git a/examples/ASDKgram/Sample/TextureConfigDelegate.m b/examples/ASDKgram/Sample/TextureConfigDelegate.m new file mode 100644 index 000000000..0905a646e --- /dev/null +++ b/examples/ASDKgram/Sample/TextureConfigDelegate.m @@ -0,0 +1,41 @@ +// +// TextureConfigDelegate.m +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +@interface TextureConfigDelegate : NSObject + +@end + +@implementation ASConfiguration (UserProvided) + ++ (ASConfiguration *)textureConfiguration +{ + ASConfiguration *config = [[ASConfiguration alloc] init]; + config.experimentalFeatures = ASExperimentalGraphicsContexts | ASExperimentalTextNode; + config.delegate = [[TextureConfigDelegate alloc] init]; + return config; +} + +@end + +@implementation TextureConfigDelegate + +- (void)textureDidActivateExperimentalFeatures:(ASExperimentalFeatures)features +{ + if (features & ASExperimentalGraphicsContexts) { + NSLog(@"Texture activated experimental graphics contexts."); + } +} + +@end + diff --git a/examples/Kittens/Sample/AppDelegate.m b/examples/Kittens/Sample/AppDelegate.m index 230173d51..e683d16d4 100644 --- a/examples/Kittens/Sample/AppDelegate.m +++ b/examples/Kittens/Sample/AppDelegate.m @@ -1,18 +1,18 @@ // // AppDelegate.m -// Sample +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// 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. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import "AppDelegate.h" @@ -24,7 +24,6 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - [ASTextNode setExperimentOptions:ASTextNodeExperimentRandomInstances]; self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; self.window.backgroundColor = [UIColor whiteColor]; self.window.rootViewController = [[UINavigationController alloc] initWithRootViewController:[[ViewController alloc] init]]; diff --git a/examples/Kittens/Sample/ViewController.m b/examples/Kittens/Sample/ViewController.m index 63a662327..38d01d6f5 100644 --- a/examples/Kittens/Sample/ViewController.m +++ b/examples/Kittens/Sample/ViewController.m @@ -1,18 +1,18 @@ // // ViewController.m -// Sample +// Texture // // 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. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// 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. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import "ViewController.h" @@ -35,12 +35,10 @@ @interface ViewController () // array of boxed CGSizes corresponding to placekitten.com kittens NSMutableArray *_kittenDataSource; - BOOL _dataSourceLocked; NSIndexPath *_blurbNodeIndexPath; } @property (nonatomic, strong) NSMutableArray *kittenDataSource; -@property (atomic, assign) BOOL dataSourceLocked; @end @@ -96,12 +94,6 @@ - (NSMutableArray *)createLitterWithSize:(NSInteger)litterSize return kittens; } -- (void)setKittenDataSource:(NSMutableArray *)kittenDataSource { - ASDisplayNodeAssert(!self.dataSourceLocked, @"Could not update data source when it is locked !"); - - _kittenDataSource = kittenDataSource; -} - - (void)toggleEditingMode { [_tableNode.view setEditing:!_tableNode.view.editing animated:YES]; From 063194cb1d8b3b4d5d7eaf3ef71a5c22eb205bf5 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Mon, 26 Mar 2018 10:46:52 -0700 Subject: [PATCH 117/133] Make ASBatchContext lock-free (#854) --- AsyncDisplayKit.xcodeproj/project.pbxproj | 8 ++--- .../{ASBatchContext.mm => ASBatchContext.m} | 30 +++++++------------ 2 files changed, 14 insertions(+), 24 deletions(-) rename Source/Details/{ASBatchContext.mm => ASBatchContext.m} (65%) diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 76b69f741..bf592de6a 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -264,7 +264,7 @@ B35062131B010EFD0018CF92 /* ASBasicImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */ = {isa = PBXBuildFile; fileRef = 054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */; }; B35062151B010EFD0018CF92 /* ASBatchContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 299DA1A71A828D2900162D41 /* ASBatchContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 299DA1A81A828D2900162D41 /* ASBatchContext.mm */; }; + B35062161B010EFD0018CF92 /* ASBatchContext.m in Sources */ = {isa = PBXBuildFile; fileRef = 299DA1A81A828D2900162D41 /* ASBatchContext.m */; }; B35062171B010EFD0018CF92 /* ASDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 464052191A3F83C40061C0BA /* ASDataController.h */; settings = {ATTRIBUTES = (Public, ); }; }; B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521A1A3F83C40061C0BA /* ASDataController.mm */; }; B350621B1B010EFD0018CF92 /* ASTableLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521B1A3F83C40061C0BA /* ASTableLayoutController.h */; settings = {ATTRIBUTES = (Private, ); }; }; @@ -643,7 +643,7 @@ 296A0A311A951715005ACEAA /* ASScrollDirection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASScrollDirection.h; path = Source/Details/ASScrollDirection.h; sourceTree = SOURCE_ROOT; }; 296A0A341A951ABF005ACEAA /* ASBatchFetchingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASBatchFetchingTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 299DA1A71A828D2900162D41 /* ASBatchContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBatchContext.h; sourceTree = ""; }; - 299DA1A81A828D2900162D41 /* ASBatchContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBatchContext.mm; sourceTree = ""; }; + 299DA1A81A828D2900162D41 /* ASBatchContext.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASBatchContext.m; sourceTree = ""; }; 29CDC2E11AAE70D000833CA4 /* ASBasicImageDownloaderContextTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = ASBasicImageDownloaderContextTests.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 3917EBD21E9C2FC400D04A01 /* _ASCollectionReusableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCollectionReusableView.h; sourceTree = ""; }; 3917EBD31E9C2FC400D04A01 /* _ASCollectionReusableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASCollectionReusableView.m; sourceTree = ""; }; @@ -1333,7 +1333,7 @@ 054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */, 054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */, 299DA1A71A828D2900162D41 /* ASBatchContext.h */, - 299DA1A81A828D2900162D41 /* ASBatchContext.mm */, + 299DA1A81A828D2900162D41 /* ASBatchContext.m */, E5C347B01ECB3D9200EC4BE4 /* ASBatchFetchingDelegate.h */, 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */, 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.m */, @@ -2339,7 +2339,7 @@ 34EFC7621B701CA400AD841F /* ASBackgroundLayoutSpec.mm in Sources */, DE8BEAC41C2DF3FC00D57C12 /* ASDelegateProxy.m in Sources */, B35062141B010EFD0018CF92 /* ASBasicImageDownloader.mm in Sources */, - B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */, + B35062161B010EFD0018CF92 /* ASBatchContext.m in Sources */, AC47D9421B3B891B00AAEE9D /* ASCellNode.mm in Sources */, E58E9E451E941D74004CFC59 /* ASCollectionLayoutContext.m in Sources */, 34EFC7641B701CC600AD841F /* ASCenterLayoutSpec.mm in Sources */, diff --git a/Source/Details/ASBatchContext.mm b/Source/Details/ASBatchContext.m similarity index 65% rename from Source/Details/ASBatchContext.mm rename to Source/Details/ASBatchContext.m index 8598126d2..5ac780ddc 100644 --- a/Source/Details/ASBatchContext.mm +++ b/Source/Details/ASBatchContext.m @@ -1,5 +1,5 @@ // -// ASBatchContext.mm +// ASBatchContext.m // Texture // // Copyright (c) 2014-present, Facebook, Inc. All rights reserved. @@ -18,7 +18,7 @@ #import #import -#import +#import typedef NS_ENUM(NSInteger, ASBatchContextState) { ASBatchContextStateFetching, @@ -26,54 +26,44 @@ typedef NS_ENUM(NSInteger, ASBatchContextState) { ASBatchContextStateCompleted }; -@interface ASBatchContext () -{ - ASBatchContextState _state; - ASDN::RecursiveMutex __instanceLock__; +@implementation ASBatchContext { + atomic_int _state; } -@end - -@implementation ASBatchContext - (instancetype)init { if (self = [super init]) { - _state = ASBatchContextStateCompleted; + _state = ATOMIC_VAR_INIT(ASBatchContextStateCompleted); } return self; } - (BOOL)isFetching { - ASDN::MutexLocker l(__instanceLock__); - return _state == ASBatchContextStateFetching; + return atomic_load(&_state) == ASBatchContextStateFetching; } - (BOOL)batchFetchingWasCancelled { - ASDN::MutexLocker l(__instanceLock__); - return _state == ASBatchContextStateCancelled; + return atomic_load(&_state) == ASBatchContextStateCancelled; } - (void)beginBatchFetching { - ASDN::MutexLocker l(__instanceLock__); - _state = ASBatchContextStateFetching; + atomic_store(&_state, ASBatchContextStateFetching); } - (void)completeBatchFetching:(BOOL)didComplete { if (didComplete) { as_log_debug(ASCollectionLog(), "Completed batch fetch with context %@", self); - ASDN::MutexLocker l(__instanceLock__); - _state = ASBatchContextStateCompleted; + atomic_store(&_state, ASBatchContextStateCompleted); } } - (void)cancelBatchFetching { - ASDN::MutexLocker l(__instanceLock__); - _state = ASBatchContextStateCancelled; + atomic_store(&_state, ASBatchContextStateCancelled); } @end From 7f01b89ddcb78f2a8e8c47904c0ab2308b3fffc1 Mon Sep 17 00:00:00 2001 From: Yevgen Pogribnyi Date: Tue, 27 Mar 2018 16:29:17 +0300 Subject: [PATCH 118/133] [ASDisplayNode] Provide safeAreaInsets and layoutMargins bridge (#685) * [ASDisplayNode] Add safeAreaInsets, layoutMargins and related properties to ASDisplayNode * Add layoutMargins bridged to the underlying view * Add safeAreaInsets bridged to the underlying view * Add fallback calculation of safeAreaInsets for old iOS versions * Add automaticallyRelayoutOnSafeAreaChanges and automaticallyRelayoutOnLayoutMarginsChanges properties * Add additionalSafeAreaInsets property to ASViewController for compatibility with old iOS versions * Provide safeAreaInsets for layer-backed nodes. This also fixes tests. * Fix crash when insetsLayoutMarginsFromSafeArea is set from a background thread * Changes requested at code review: * Update documentation for layoutMargins and safeAreaInsets properties. Suggest that users set the automaticallyRelayout* flags to ensure that their layout is synchronized to the margin's values. * Fix accessing ASDisplayNode internal structures without a lock. * Add shortcut in -[ASDisplayNode _fallbackUpdateSafeAreaOnChildren] to skip a child when possible. * Add shortcut in ASViewController to avoid fallback safe area insets recalculation in iOS 11. Fix fallback safe area insets recalculation when the additionalSafeAreaInsets are set. * Add debug check that a view controller's node is never reused without its view controller, so the viewControllerRoot flag value is always consistent. * Use getters instead of reading ivars directly in -layoutMarginsDidChange and -safeAreaInsetsDidChange. * Minor change in CHANGELOG * Minor change in ASDisplayNodeTests.mm --- CHANGELOG.md | 1 + Source/ASDisplayNode.h | 41 +++++++ Source/ASDisplayNode.mm | 109 +++++++++++++++++- Source/ASViewController.h | 5 + Source/ASViewController.mm | 38 ++++++ Source/Details/UIView+ASConvenience.h | 3 + Source/Details/_ASDisplayView.mm | 30 ++++- .../Private/ASDisplayNode+FrameworkPrivate.h | 19 +++ Source/Private/ASDisplayNode+UIViewBridge.mm | 107 +++++++++++++++++ Source/Private/ASDisplayNodeInternal.h | 14 +++ Source/Private/ASInternalHelpers.h | 9 ++ Source/Private/_ASPendingState.mm | 51 +++++++- Tests/ASDisplayNodeTests.mm | 61 ++++++++++ 13 files changed, 485 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22c737c17..241a9c3ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- [ASDisplayNode] Add safeAreaInsets, layoutMargins and related properties to ASDisplayNode, with full support for older OS versions [Yevgen Pogribnyi](https://github.com/ypogribnyi) [#685](https://github.com/TextureGroup/Texture/pull/685) - [ASPINRemoteImageDownloader] Allow cache to provide animated image. [Max Wang](https://github.com/wsdwsd0829) [#850](https://github.com/TextureGroup/Texture/pull/850) - [tvOS] Fixes errors when building against tvOS SDK [Alex Hill](https://github.com/alexhillc) [#728](https://github.com/TextureGroup/Texture/pull/728) - [ASDisplayNode] Add unit tests for layout z-order changes (with an open issue to fix). diff --git a/Source/ASDisplayNode.h b/Source/ASDisplayNode.h index 851155dea..8968a1f50 100644 --- a/Source/ASDisplayNode.h +++ b/Source/ASDisplayNode.h @@ -551,6 +551,20 @@ extern NSInteger const ASDefaultDrawingPriority; */ @property (nonatomic, readonly) BOOL supportsLayerBacking; +/** + * Whether or not the node layout should be automatically updated when it receives safeAreaInsetsDidChange. + * + * Defaults to NO. + */ +@property (nonatomic, assign) BOOL automaticallyRelayoutOnSafeAreaChanges; + +/** + * Whether or not the node layout should be automatically updated when it receives layoutMarginsDidChange. + * + * Defaults to NO. + */ +@property (nonatomic, assign) BOOL automaticallyRelayoutOnLayoutMarginsChanges; + @end /** @@ -725,6 +739,33 @@ extern NSInteger const ASDefaultDrawingPriority; @property (nonatomic, assign) BOOL autoresizesSubviews; // default==YES (undefined for layer-backed nodes) @property (nonatomic, assign) UIViewAutoresizing autoresizingMask; // default==UIViewAutoresizingNone (undefined for layer-backed nodes) +/** + * @abstract Content margins + * + * @discussion This property is bridged to its UIView counterpart. + * + * If your layout depends on this property, you should probably enable automaticallyRelayoutOnLayoutMarginsChanges to ensure + * that the layout gets automatically updated when the value of this property changes. Or you can override layoutMarginsDidChange + * and make all the necessary updates manually. + */ +@property (nonatomic, assign) UIEdgeInsets layoutMargins; +@property (nonatomic, assign) BOOL preservesSuperviewLayoutMargins; // default is NO - set to enable pass-through or cascading behavior of margins from this view’s parent to its children +- (void)layoutMarginsDidChange; + +/** + * @abstract Safe area insets + * + * @discussion This property is bridged to its UIVIew counterpart. + * + * If your layout depends on this property, you should probably enable automaticallyRelayoutOnSafeAreaChanges to ensure + * that the layout gets automatically updated when the value of this property changes. Or you can override safeAreaInsetsDidChange + * and make all the necessary updates manually. + */ +@property (nonatomic, readonly) UIEdgeInsets safeAreaInsets; +@property (nonatomic, assign) BOOL insetsLayoutMarginsFromSafeArea; // Default: YES +- (void)safeAreaInsetsDidChange; + + // UIResponder methods // By default these fall through to the underlying view, but can be overridden. - (BOOL)canBecomeFirstResponder; // default==NO diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 72eaf0331..adddc67df 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -282,6 +282,14 @@ - (void)_initializeInstance _flags.canClearContentsOfLayer = YES; _flags.canCallSetNeedsDisplayOfLayer = YES; + + _fallbackSafeAreaInsets = UIEdgeInsetsZero; + _fallbackInsetsLayoutMarginsFromSafeArea = YES; + _isViewControllerRoot = NO; + + _automaticallyRelayoutOnSafeAreaChanges = NO; + _automaticallyRelayoutOnLayoutMarginsChanges = NO; + ASDisplayNodeLogEvent(self, @"init"); } @@ -851,7 +859,104 @@ - (void)nodeViewDidAddGestureRecognizer _flags.viewEverHadAGestureRecognizerAttached = YES; } -#pragma mark UIResponder +- (UIEdgeInsets)fallbackSafeAreaInsets +{ + ASDN::MutexLocker l(__instanceLock__); + return _fallbackSafeAreaInsets; +} + +- (void)setFallbackSafeAreaInsets:(UIEdgeInsets)insets +{ + BOOL needsManualUpdate; + BOOL updatesLayoutMargins; + + { + ASDN::MutexLocker l(__instanceLock__); + ASDisplayNodeAssertThreadAffinity(self); + + if (UIEdgeInsetsEqualToEdgeInsets(insets, _fallbackSafeAreaInsets)) { + return; + } + + _fallbackSafeAreaInsets = insets; + needsManualUpdate = !AS_AT_LEAST_IOS11 || _flags.layerBacked; + updatesLayoutMargins = needsManualUpdate && [self _locked_insetsLayoutMarginsFromSafeArea]; + } + + if (needsManualUpdate) { + [self safeAreaInsetsDidChange]; + } + + if (updatesLayoutMargins) { + [self layoutMarginsDidChange]; + } +} + +- (void)_fallbackUpdateSafeAreaOnChildren +{ + ASDisplayNodeAssertThreadAffinity(self); + + UIEdgeInsets insets = self.safeAreaInsets; + CGRect bounds = self.bounds; + + for (ASDisplayNode *child in self.subnodes) { + if (AS_AT_LEAST_IOS11 && !child.layerBacked) { + // In iOS 11 view-backed nodes already know what their safe area is. + continue; + } + + if (child.viewControllerRoot) { + // Its safe area is controlled by a view controller. Don't override it. + continue; + } + + CGRect childFrame = child.frame; + UIEdgeInsets childInsets = UIEdgeInsetsMake(MAX(insets.top - (CGRectGetMinY(childFrame) - CGRectGetMinY(bounds)), 0), + MAX(insets.left - (CGRectGetMinX(childFrame) - CGRectGetMinX(bounds)), 0), + MAX(insets.bottom - (CGRectGetMaxY(bounds) - CGRectGetMaxY(childFrame)), 0), + MAX(insets.right - (CGRectGetMaxX(bounds) - CGRectGetMaxX(childFrame)), 0)); + + child.fallbackSafeAreaInsets = childInsets; + } +} + +- (BOOL)isViewControllerRoot +{ + ASDN::MutexLocker l(__instanceLock__); + return _isViewControllerRoot; +} + +- (void)setViewControllerRoot:(BOOL)flag +{ + ASDN::MutexLocker l(__instanceLock__); + _isViewControllerRoot = flag; +} + +- (BOOL)automaticallyRelayoutOnSafeAreaChanges +{ + ASDN::MutexLocker l(__instanceLock__); + return _automaticallyRelayoutOnSafeAreaChanges; +} + +- (void)setAutomaticallyRelayoutOnSafeAreaChanges:(BOOL)flag +{ + ASDN::MutexLocker l(__instanceLock__); + _automaticallyRelayoutOnSafeAreaChanges = flag; +} + +- (BOOL)automaticallyRelayoutOnLayoutMarginsChanges +{ + ASDN::MutexLocker l(__instanceLock__); + return _automaticallyRelayoutOnLayoutMarginsChanges; +} + +- (void)setAutomaticallyRelayoutOnLayoutMarginsChanges:(BOOL)flag +{ + ASDN::MutexLocker l(__instanceLock__); + _automaticallyRelayoutOnLayoutMarginsChanges = flag; +} + +#pragma mark - UIResponder #define HANDLE_NODE_RESPONDER_METHOD(__sel) \ /* All responder methods should be called on the main thread */ \ @@ -1042,6 +1147,8 @@ - (void)__layout [self layoutDidFinish]; }); } + + [self _fallbackUpdateSafeAreaOnChildren]; } - (void)layoutDidFinish diff --git a/Source/ASViewController.h b/Source/ASViewController.h index 28fd134f4..3d1c7e13a 100644 --- a/Source/ASViewController.h +++ b/Source/ASViewController.h @@ -78,6 +78,11 @@ NS_ASSUME_NONNULL_BEGIN // Refer to examples/SynchronousConcurrency, AsyncViewController.m @property (nonatomic, assign) BOOL neverShowPlaceholders; +/* Custom container UIViewController subclasses can use this property to add to the overlay + that UIViewController calculates for the safeAreaInsets for contained view controllers. + */ +@property(nonatomic) UIEdgeInsets additionalSafeAreaInsets; + @end @interface ASViewController (ASRangeControllerUpdateRangeProtocol) diff --git a/Source/ASViewController.mm b/Source/ASViewController.mm index 4e1e57c3a..8aded78c3 100644 --- a/Source/ASViewController.mm +++ b/Source/ASViewController.mm @@ -32,6 +32,7 @@ @implementation ASViewController NSInteger _visibilityDepth; BOOL _selfConformsToRangeModeProtocol; BOOL _nodeConformsToRangeModeProtocol; + UIEdgeInsets _fallbackAdditionalSafeAreaInsets; } - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil @@ -73,10 +74,14 @@ - (void)_initializeInstance if (_node == nil) { return; } + + _node.viewControllerRoot = YES; _selfConformsToRangeModeProtocol = [self conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]; _nodeConformsToRangeModeProtocol = [_node conformsToProtocol:@protocol(ASRangeControllerUpdateRangeProtocol)]; _automaticallyAdjustRangeModeBasedOnViewEvents = _selfConformsToRangeModeProtocol || _nodeConformsToRangeModeProtocol; + + _fallbackAdditionalSafeAreaInsets = UIEdgeInsetsZero; // In case the node will get loaded if (_node.nodeLoaded) { @@ -159,6 +164,20 @@ - (void)viewDidLayoutSubviews [_node recursivelyEnsureDisplaySynchronously:YES]; } [super viewDidLayoutSubviews]; + + if (!AS_AT_LEAST_IOS11) { + [self _updateNodeFallbackSafeArea]; + } +} + +- (void)_updateNodeFallbackSafeArea +{ + UIEdgeInsets safeArea = UIEdgeInsetsMake(self.topLayoutGuide.length, 0, self.bottomLayoutGuide.length, 0); + UIEdgeInsets additionalInsets = self.additionalSafeAreaInsets; + + safeArea = ASConcatInsets(safeArea, additionalInsets); + + _node.fallbackSafeAreaInsets = safeArea; } ASVisibilityDidMoveToParentViewController; @@ -264,6 +283,25 @@ - (ASInterfaceState)interfaceState return _node.interfaceState; } +- (UIEdgeInsets)additionalSafeAreaInsets +{ + if (AS_AVAILABLE_IOS(11.0)) { + return super.additionalSafeAreaInsets; + } + + return _fallbackAdditionalSafeAreaInsets; +} + +- (void)setAdditionalSafeAreaInsets:(UIEdgeInsets)additionalSafeAreaInsets +{ + if (AS_AVAILABLE_IOS(11.0)) { + [super setAdditionalSafeAreaInsets:additionalSafeAreaInsets]; + } else { + _fallbackAdditionalSafeAreaInsets = additionalSafeAreaInsets; + [self _updateNodeFallbackSafeArea]; + } +} + #pragma mark - ASTraitEnvironment - (ASPrimitiveTraitCollection)primitiveTraitCollectionForUITraitCollection:(UITraitCollection *)traitCollection diff --git a/Source/Details/UIView+ASConvenience.h b/Source/Details/UIView+ASConvenience.h index 452dcee6e..8c7687952 100644 --- a/Source/Details/UIView+ASConvenience.h +++ b/Source/Details/UIView+ASConvenience.h @@ -75,6 +75,9 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign, getter=isUserInteractionEnabled) BOOL userInteractionEnabled; @property (nonatomic, assign, getter=isExclusiveTouch) BOOL exclusiveTouch; @property (nonatomic, assign, getter=asyncdisplaykit_isAsyncTransactionContainer, setter = asyncdisplaykit_setAsyncTransactionContainer:) BOOL asyncdisplaykit_asyncTransactionContainer; +@property (nonatomic, assign) UIEdgeInsets layoutMargins; +@property (nonatomic, assign) BOOL preservesSuperviewLayoutMargins; +@property (nonatomic, assign) BOOL insetsLayoutMarginsFromSafeArea; /** Following properties of the UIAccessibility informal protocol are supported as well. diff --git a/Source/Details/_ASDisplayView.mm b/Source/Details/_ASDisplayView.mm index 2b72d1fd6..9315fa7af 100644 --- a/Source/Details/_ASDisplayView.mm +++ b/Source/Details/_ASDisplayView.mm @@ -21,11 +21,13 @@ #import #import #import +#import #import #import #import -#import #import +#import +#import #pragma mark - _ASDisplayViewMethodOverrides @@ -234,6 +236,16 @@ - (void)didMoveToSuperview self.keepalive_node = nil; } +#if DEBUG + // This is only to help detect issues when a root-of-view-controller node is reused separately from its view controller. + // Avoid overhead in release. + if (superview && node.viewControllerRoot) { + UIViewController *vc = [node closestViewController]; + + ASDisplayNodeAssert(vc != nil && [vc isKindOfClass:[ASViewController class]] && ((ASViewController*)vc).node == node, @"This node was once used as a view controller's node. You should not reuse it without its view controller."); + } +#endif + ASDisplayNode *supernode = node.supernode; ASDisplayNodeAssert(!supernode.isLayerBacked, @"Shouldn't be possible for superview's node to be layer-backed."); @@ -481,6 +493,22 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender return ([super canPerformAction:action withSender:sender] || [node respondsToSelector:action]); } +- (void)layoutMarginsDidChange +{ + ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. + [super layoutMarginsDidChange]; + + [node layoutMarginsDidChange]; +} + +- (void)safeAreaInsetsDidChange +{ + ASDisplayNode *node = _asyncdisplaykit_node; // Create strong reference to weak ivar. + [super safeAreaInsetsDidChange]; + + [node safeAreaInsetsDidChange]; +} + - (id)forwardingTargetForSelector:(SEL)aSelector { // Ideally, we would implement -targetForAction:withSender: and simply return the node where we don't respond personally. diff --git a/Source/Private/ASDisplayNode+FrameworkPrivate.h b/Source/Private/ASDisplayNode+FrameworkPrivate.h index 2f3e7223d..987fdfa76 100644 --- a/Source/Private/ASDisplayNode+FrameworkPrivate.h +++ b/Source/Private/ASDisplayNode+FrameworkPrivate.h @@ -241,6 +241,25 @@ __unused static NSString * _Nonnull NSStringFromASHierarchyStateChange(ASHierarc */ - (BOOL)shouldScheduleDisplayWithNewInterfaceState:(ASInterfaceState)newInterfaceState; +/** + * @abstract safeAreaInsets will fallback to this value if the corresponding UIKit property is not available + * (due to an old iOS version). + * + * @discussion This should be set by the owning view controller based on it's layout guides. + * If this is not a view controllet's node the value will be calculated automatically by the parent node. + */ +@property (nonatomic, assign) UIEdgeInsets fallbackSafeAreaInsets; + +/** + * @abstract Indicates if this node is a view controller's root node. Defaults to NO. + * + * @discussion Set to YES in -[ASViewController initWithNode:]. + * + * YES here only means that this node is used as an ASViewController node. It doesn't mean that this node is a root of + * ASDisplayNode hierarchy, e.g. when its view controller is parented by another ASViewController. + */ +@property (nonatomic, assign, getter=isViewControllerRoot) BOOL viewControllerRoot; + @end diff --git a/Source/Private/ASDisplayNode+UIViewBridge.mm b/Source/Private/ASDisplayNode+UIViewBridge.mm index ed92a9f7e..939ed642c 100644 --- a/Source/Private/ASDisplayNode+UIViewBridge.mm +++ b/Source/Private/ASDisplayNode+UIViewBridge.mm @@ -860,6 +860,103 @@ - (void)setSemanticContentAttribute:(UISemanticContentAttribute)semanticContentA #endif } +- (UIEdgeInsets)layoutMargins +{ + _bridge_prologue_read; + ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); + UIEdgeInsets margins = _getFromViewOnly(layoutMargins); + + if (!AS_AT_LEAST_IOS11 && self.insetsLayoutMarginsFromSafeArea) { + UIEdgeInsets safeArea = self.safeAreaInsets; + margins = ASConcatInsets(margins, safeArea); + } + + return margins; +} + +- (void)setLayoutMargins:(UIEdgeInsets)layoutMargins +{ + _bridge_prologue_write; + ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); + _setToViewOnly(layoutMargins, layoutMargins); +} + +- (BOOL)preservesSuperviewLayoutMargins +{ + _bridge_prologue_read; + ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); + return _getFromViewOnly(preservesSuperviewLayoutMargins); +} + +- (void)setPreservesSuperviewLayoutMargins:(BOOL)preservesSuperviewLayoutMargins +{ + _bridge_prologue_write; + ASDisplayNodeAssert(!_flags.layerBacked, @"Danger: this property is undefined on layer-backed nodes."); + _setToViewOnly(preservesSuperviewLayoutMargins, preservesSuperviewLayoutMargins); +} + +- (void)layoutMarginsDidChange +{ + ASDisplayNodeAssertMainThread(); + + if (self.automaticallyRelayoutOnLayoutMarginsChanges) { + [self setNeedsLayout]; + } +} + +- (UIEdgeInsets)safeAreaInsets +{ + _bridge_prologue_read; + + if (AS_AVAILABLE_IOS(11.0)) { + if (!_flags.layerBacked && __loaded(self)) { + return self.view.safeAreaInsets; + } + } + return _fallbackSafeAreaInsets; +} + +- (BOOL)insetsLayoutMarginsFromSafeArea +{ + _bridge_prologue_read; + + return [self _locked_insetsLayoutMarginsFromSafeArea]; +} + +- (void)setInsetsLayoutMarginsFromSafeArea:(BOOL)insetsLayoutMarginsFromSafeArea +{ + ASDisplayNodeAssertThreadAffinity(self); + BOOL shouldNotifyAboutUpdate; + { + _bridge_prologue_write; + + _fallbackInsetsLayoutMarginsFromSafeArea = insetsLayoutMarginsFromSafeArea; + + if (AS_AVAILABLE_IOS(11.0)) { + if (!_flags.layerBacked) { + _setToViewOnly(insetsLayoutMarginsFromSafeArea, insetsLayoutMarginsFromSafeArea); + } + } + + shouldNotifyAboutUpdate = __loaded(self) && (!AS_AT_LEAST_IOS11 || _flags.layerBacked); + } + + if (shouldNotifyAboutUpdate) { + [self layoutMarginsDidChange]; + } +} + +- (void)safeAreaInsetsDidChange +{ + ASDisplayNodeAssertMainThread(); + + if (self.automaticallyRelayoutOnSafeAreaChanges) { + [self setNeedsLayout]; + } + + [self _fallbackUpdateSafeAreaOnChildren]; +} + @end @implementation ASDisplayNode (InternalPropertyBridge) @@ -876,6 +973,16 @@ - (void)setLayerCornerRadius:(CGFloat)newLayerCornerRadius _setToLayer(cornerRadius, newLayerCornerRadius); } +- (BOOL)_locked_insetsLayoutMarginsFromSafeArea +{ + if (AS_AVAILABLE_IOS(11.0)) { + if (!_flags.layerBacked) { + return _getFromViewOnly(insetsLayoutMarginsFromSafeArea); + } + } + return _fallbackInsetsLayoutMarginsFromSafeArea; +} + @end #pragma mark - UIViewBridgeAccessibility diff --git a/Source/Private/ASDisplayNodeInternal.h b/Source/Private/ASDisplayNodeInternal.h index 5798d53e5..ab0e3faed 100644 --- a/Source/Private/ASDisplayNodeInternal.h +++ b/Source/Private/ASDisplayNodeInternal.h @@ -204,6 +204,15 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo UIBezierPath *_accessibilityPath; BOOL _isAccessibilityContainer; + // These properties are used on iOS 10 and lower, where safe area is not supported by UIKit. + UIEdgeInsets _fallbackSafeAreaInsets; + BOOL _fallbackInsetsLayoutMarginsFromSafeArea; + + BOOL _automaticallyRelayoutOnSafeAreaChanges; + BOOL _automaticallyRelayoutOnLayoutMarginsChanges; + + BOOL _isViewControllerRoot; + // performance measurement ASDisplayNodePerformanceMeasurementOptions _measurementOptions; NSTimeInterval _layoutSpecTotalTime; @@ -335,12 +344,17 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo */ - (void)nodeViewDidAddGestureRecognizer; +// Recalculates fallbackSafeAreaInsets for the subnodes +- (void)_fallbackUpdateSafeAreaOnChildren; + @end @interface ASDisplayNode (InternalPropertyBridge) @property (nonatomic, assign) CGFloat layerCornerRadius; +- (BOOL)_locked_insetsLayoutMarginsFromSafeArea; + @end NS_ASSUME_NONNULL_END diff --git a/Source/Private/ASInternalHelpers.h b/Source/Private/ASInternalHelpers.h index fcd82da0a..d5c1e035b 100644 --- a/Source/Private/ASInternalHelpers.h +++ b/Source/Private/ASInternalHelpers.h @@ -100,6 +100,15 @@ ASDISPLAYNODE_INLINE void ASBoundsAndPositionForFrame(CGRect rect, CGPoint origi rect.origin.y + rect.size.height * anchorPoint.y); } +ASDISPLAYNODE_INLINE UIEdgeInsets ASConcatInsets(UIEdgeInsets insetsA, UIEdgeInsets insetsB) +{ + insetsA.top += insetsB.top; + insetsA.left += insetsB.left; + insetsA.bottom += insetsB.bottom; + insetsA.right += insetsB.right; + return insetsA; +} + @interface NSIndexPath (ASInverseComparison) - (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath; @end diff --git a/Source/Private/_ASPendingState.mm b/Source/Private/_ASPendingState.mm index d009771bc..a87707bb6 100644 --- a/Source/Private/_ASPendingState.mm +++ b/Source/Private/_ASPendingState.mm @@ -91,6 +91,9 @@ int setAccessibilityActivationPoint:1; int setAccessibilityPath:1; int setSemanticContentAttribute:1; + int setLayoutMargins:1; + int setPreservesSuperviewLayoutMargins:1; + int setInsetsLayoutMarginsFromSafeArea:1; } ASPendingStateFlags; @implementation _ASPendingState @@ -123,6 +126,9 @@ @implementation _ASPendingState CGFloat borderWidth; CGColorRef borderColor; BOOL asyncTransactionContainer; + UIEdgeInsets layoutMargins; + BOOL preservesSuperviewLayoutMargins; + BOOL insetsLayoutMarginsFromSafeArea; BOOL isAccessibilityElement; NSString *accessibilityLabel; NSAttributedString *accessibilityAttributedLabel; @@ -208,7 +214,9 @@ ASDISPLAYNODE_INLINE void ASPendingStateApplyMetricsToLayer(_ASPendingState *sta @synthesize borderColor=borderColor; @synthesize asyncdisplaykit_asyncTransactionContainer=asyncTransactionContainer; @synthesize semanticContentAttribute=semanticContentAttribute; - +@synthesize layoutMargins=layoutMargins; +@synthesize preservesSuperviewLayoutMargins=preservesSuperviewLayoutMargins; +@synthesize insetsLayoutMarginsFromSafeArea=insetsLayoutMarginsFromSafeArea; static CGColorRef blackColorRef = NULL; static UIColor *defaultTintColor = nil; @@ -263,6 +271,9 @@ - (instancetype)init shadowRadius = 3; borderWidth = 0; borderColor = blackColorRef; + layoutMargins = UIEdgeInsetsMake(8, 8, 8, 8); + preservesSuperviewLayoutMargins = NO; + insetsLayoutMarginsFromSafeArea = YES; isAccessibilityElement = NO; accessibilityLabel = nil; accessibilityAttributedLabel = nil; @@ -560,6 +571,24 @@ - (void)asyncdisplaykit_setAsyncTransactionContainer:(BOOL)flag _flags.setAsyncTransactionContainer = YES; } +- (void)setLayoutMargins:(UIEdgeInsets)margins +{ + layoutMargins = margins; + _flags.setLayoutMargins = YES; +} + +- (void)setPreservesSuperviewLayoutMargins:(BOOL)flag +{ + preservesSuperviewLayoutMargins = flag; + _flags.setPreservesSuperviewLayoutMargins = YES; +} + +- (void)setInsetsLayoutMarginsFromSafeArea:(BOOL)flag +{ + insetsLayoutMarginsFromSafeArea = flag; + _flags.setInsetsLayoutMarginsFromSafeArea = YES; +} + - (void)setSemanticContentAttribute:(UISemanticContentAttribute)attribute API_AVAILABLE(ios(9.0), tvos(9.0)) { semanticContentAttribute = attribute; _flags.setSemanticContentAttribute = YES; @@ -1036,6 +1065,18 @@ - (void)applyToView:(UIView *)view withSpecialPropertiesHandling:(BOOL)specialPr if (flags.setOpaque) ASDisplayNodeAssert(layer.opaque == opaque, @"Didn't set opaque as desired"); + if (flags.setLayoutMargins) + view.layoutMargins = layoutMargins; + + if (flags.setPreservesSuperviewLayoutMargins) + view.preservesSuperviewLayoutMargins = preservesSuperviewLayoutMargins; + + if (AS_AVAILABLE_IOS(11.0)) { + if (flags.setInsetsLayoutMarginsFromSafeArea) { + view.insetsLayoutMarginsFromSafeArea = insetsLayoutMarginsFromSafeArea; + } + } + if (flags.setSemanticContentAttribute) { view.semanticContentAttribute = semanticContentAttribute; } @@ -1199,6 +1240,11 @@ + (_ASPendingState *)pendingViewStateFromView:(UIView *)view pendingState.allowsEdgeAntialiasing = layer.allowsEdgeAntialiasing; pendingState.edgeAntialiasingMask = layer.edgeAntialiasingMask; pendingState.semanticContentAttribute = view.semanticContentAttribute; + pendingState.layoutMargins = view.layoutMargins; + pendingState.preservesSuperviewLayoutMargins = view.preservesSuperviewLayoutMargins; + if (AS_AVAILABLE_IOS(11)) { + pendingState.insetsLayoutMarginsFromSafeArea = view.insetsLayoutMarginsFromSafeArea; + } pendingState.isAccessibilityElement = view.isAccessibilityElement; pendingState.accessibilityLabel = view.accessibilityLabel; pendingState.accessibilityHint = view.accessibilityHint; @@ -1285,6 +1331,9 @@ - (BOOL)hasChanges || flags.setAsyncTransactionContainer || flags.setOpaque || flags.setSemanticContentAttribute + || flags.setLayoutMargins + || flags.setPreservesSuperviewLayoutMargins + || flags.setInsetsLayoutMarginsFromSafeArea || flags.setIsAccessibilityElement || flags.setAccessibilityLabel || flags.setAccessibilityAttributedLabel diff --git a/Tests/ASDisplayNodeTests.mm b/Tests/ASDisplayNodeTests.mm index 50b340890..ba956321d 100644 --- a/Tests/ASDisplayNodeTests.mm +++ b/Tests/ASDisplayNodeTests.mm @@ -39,6 +39,7 @@ #import #import #import +#import // Conveniences for making nodes named a certain way #define DeclareNodeNamed(n) ASDisplayNode *n = [[ASDisplayNode alloc] init]; n.debugName = @#n @@ -262,6 +263,12 @@ - (BOOL)resignFirstResponder { @end +@interface ASTestViewController: ASViewController +@end +@implementation ASTestViewController +- (BOOL)prefersStatusBarHidden { return YES; } +@end + @interface UIResponderNodeTestDisplayViewCallingSuper : _ASDisplayView @end @implementation UIResponderNodeTestDisplayViewCallingSuper @@ -477,6 +484,10 @@ - (void)checkValuesMatchDefaults:(ASDisplayNode *)node isLayerBacked:(BOOL)isLay XCTAssertEqual(NO, node.exclusiveTouch, @"default exclusiveTouch broken %@", hasLoadedView); XCTAssertEqual(YES, node.autoresizesSubviews, @"default autoresizesSubviews broken %@", hasLoadedView); XCTAssertEqual(UIViewAutoresizingNone, node.autoresizingMask, @"default autoresizingMask broken %@", hasLoadedView); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsMake(8, 8, 8, 8), node.layoutMargins), @"default layoutMargins broken %@", hasLoadedView); + XCTAssertEqual(NO, node.preservesSuperviewLayoutMargins, @"default preservesSuperviewLayoutMargins broken %@", hasLoadedView); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsZero, node.safeAreaInsets), @"default safeAreaInsets broken %@", hasLoadedView); + XCTAssertEqual(YES, node.insetsLayoutMarginsFromSafeArea, @"default insetsLayoutMarginsFromSafeArea broken %@", hasLoadedView); } else { XCTAssertEqual(NO, node.userInteractionEnabled, @"layer-backed nodes do not support userInteractionEnabled %@", hasLoadedView); XCTAssertEqual(NO, node.exclusiveTouch, @"layer-backed nodes do not support exclusiveTouch %@", hasLoadedView); @@ -584,6 +595,9 @@ - (void)checkValuesMatchSetValues:(ASDisplayNode *)node isLayerBacked:(BOOL)isLa if (!isLayerBacked) { XCTAssertEqual(UIViewAutoresizingFlexibleLeftMargin, node.autoresizingMask, @"autoresizingMask %@", hasLoadedView); XCTAssertEqual(NO, node.autoresizesSubviews, @"autoresizesSubviews broken %@", hasLoadedView); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(UIEdgeInsetsMake(3, 5, 8, 11), node.layoutMargins), @"layoutMargins broken %@", hasLoadedView); + XCTAssertEqual(YES, node.preservesSuperviewLayoutMargins, @"preservesSuperviewLayoutMargins broken %@", hasLoadedView); + XCTAssertEqual(NO, node.insetsLayoutMarginsFromSafeArea, @"insetsLayoutMarginsFromSafeArea broken %@", hasLoadedView); } } @@ -652,6 +666,9 @@ - (void)checkSimpleBridgePropertiesSetPropagate:(BOOL)isLayerBacked node.exclusiveTouch = YES; node.autoresizesSubviews = NO; node.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; + node.insetsLayoutMarginsFromSafeArea = NO; + node.layoutMargins = UIEdgeInsetsMake(3, 5, 8, 11); + node.preservesSuperviewLayoutMargins = YES; } }]; @@ -2495,6 +2512,50 @@ - (void)testThatItIsAllowedToRetrieveDebugDescriptionIncludingVCOffMainThread XCTAssert(hasVC); } +- (void)testThatSubnodeSafeAreaInsetsAreCalculatedCorrectly +{ + ASDisplayNode *rootNode = [[ASDisplayNode alloc] init]; + ASDisplayNode *subnode = [[ASDisplayNode alloc] init]; + + rootNode.automaticallyManagesSubnodes = YES; + rootNode.layoutSpecBlock = ^ASLayoutSpec * _Nonnull(__kindof ASDisplayNode * _Nonnull node, ASSizeRange constrainedSize) { + return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(1, 2, 3, 4) child:subnode]; + }; + + ASTestViewController *viewController = [[ASTestViewController alloc] initWithNode:rootNode]; + viewController.additionalSafeAreaInsets = UIEdgeInsetsMake(10, 10, 10, 10); + + // It looks like iOS 11 suppresses safeAreaInsets calculation for the views that are not on screen. + UIWindow *window = [[UIWindow alloc] init]; + window.rootViewController = viewController; + [window setHidden:NO]; + [window layoutIfNeeded]; + + UIEdgeInsets expectedRootNodeSafeArea = UIEdgeInsetsMake(10, 10, 10, 10); + UIEdgeInsets expectedSubnodeSafeArea = UIEdgeInsetsMake(9, 8, 7, 6); + + UIEdgeInsets windowSafeArea = UIEdgeInsetsZero; + if (AS_AVAILABLE_IOS(11.0)) { + windowSafeArea = window.safeAreaInsets; + } + + expectedRootNodeSafeArea = ASConcatInsets(expectedRootNodeSafeArea, windowSafeArea); + expectedSubnodeSafeArea = ASConcatInsets(expectedSubnodeSafeArea, windowSafeArea); + + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(expectedRootNodeSafeArea, rootNode.safeAreaInsets), + @"expected rootNode.safeAreaInsets to be %@ but got %@ (window.safeAreaInsets %@)", + NSStringFromUIEdgeInsets(expectedRootNodeSafeArea), + NSStringFromUIEdgeInsets(rootNode.safeAreaInsets), + NSStringFromUIEdgeInsets(windowSafeArea)); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(expectedSubnodeSafeArea, subnode.safeAreaInsets), + @"expected subnode.safeAreaInsets to be %@ but got %@ (window.safeAreaInsets %@)", + NSStringFromUIEdgeInsets(expectedSubnodeSafeArea), + NSStringFromUIEdgeInsets(subnode.safeAreaInsets), + NSStringFromUIEdgeInsets(windowSafeArea)); + + [window setHidden:YES]; +} + - (void)testScreenScale { XCTAssertEqual(ASScreenScale(), UIScreen.mainScreen.scale); From cf1c3f6d943cdac41886c52875ddbc19d3af989d Mon Sep 17 00:00:00 2001 From: dmaclach Date: Wed, 28 Mar 2018 00:17:13 -0700 Subject: [PATCH 119/133] Make NSIndexSet+ASHelpers.h reference local #trivial (#857) * Make NSIndexSet+ASHelpers.h reference local NSIndexSet+ASHelpers.h should be a user vs a system or framework include. * Update ASIntegerMap.mm --- Source/Details/ASIntegerMap.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Details/ASIntegerMap.mm b/Source/Details/ASIntegerMap.mm index 19f0bbe4a..e80ab7bfa 100644 --- a/Source/Details/ASIntegerMap.mm +++ b/Source/Details/ASIntegerMap.mm @@ -13,7 +13,7 @@ #import "ASIntegerMap.h" #import #import -#import +#import #import /** From 4bbbd725de84170d9afa58e4868ba7589d50c4ed Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 28 Mar 2018 11:29:50 -0700 Subject: [PATCH 120/133] Introduce ASRecursiveUnfairLock and tests (#858) * Introduce ASRecursiveUnfairLock and tests * Document it and put underscores to scare people away * Increment changelog * Integrate it with ASDN::Mutex behind experiment * Rename the experiment * Love these license headers oh so much * Move internal header because we have to * Address Jon's feedback --- AsyncDisplayKit.xcodeproj/project.pbxproj | 18 +- CHANGELOG.md | 1 + Schemas/configuration.json | 3 +- .../{Private => }/ASConfigurationInternal.h | 3 + .../{Private => }/ASConfigurationInternal.m | 3 +- Source/ASExperimentalFeatures.h | 1 + Source/AsyncDisplayKit.h | 1 + Source/Details/ASRecursiveUnfairLock.h | 59 +++++ Source/Details/ASRecursiveUnfairLock.m | 76 +++++++ Source/Details/ASThread.h | 203 +++++++----------- Tests/ASRecursiveUnfairLockTests.m | 188 ++++++++++++++++ 11 files changed, 426 insertions(+), 130 deletions(-) rename Source/{Private => }/ASConfigurationInternal.h (86%) rename Source/{Private => }/ASConfigurationInternal.m (97%) create mode 100644 Source/Details/ASRecursiveUnfairLock.h create mode 100644 Source/Details/ASRecursiveUnfairLock.m create mode 100644 Tests/ASRecursiveUnfairLockTests.m diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index bf592de6a..c9f5cba82 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -385,6 +385,9 @@ CCA282D01E9EBF6C0037E8B7 /* ASTipsWindow.h in Headers */ = {isa = PBXBuildFile; fileRef = CCA282CE1E9EBF6C0037E8B7 /* ASTipsWindow.h */; }; CCA282D11E9EBF6C0037E8B7 /* ASTipsWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.m */; }; CCA5F62E1EECC2A80060C137 /* ASAssert.m in Sources */ = {isa = PBXBuildFile; fileRef = CCA5F62D1EECC2A80060C137 /* ASAssert.m */; }; + CCAA0B7F206ADBF30057B336 /* ASRecursiveUnfairLock.h in Headers */ = {isa = PBXBuildFile; fileRef = CCAA0B7D206ADBF30057B336 /* ASRecursiveUnfairLock.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CCAA0B80206ADBF30057B336 /* ASRecursiveUnfairLock.m in Sources */ = {isa = PBXBuildFile; fileRef = CCAA0B7E206ADBF30057B336 /* ASRecursiveUnfairLock.m */; }; + CCAA0B82206ADECB0057B336 /* ASRecursiveUnfairLockTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCAA0B81206ADECB0057B336 /* ASRecursiveUnfairLockTests.m */; }; CCB1F95A1EFB60A5009C7475 /* ASLog.m in Sources */ = {isa = PBXBuildFile; fileRef = CCB1F9591EFB60A5009C7475 /* ASLog.m */; }; CCB1F95C1EFB6350009C7475 /* ASSignpost.h in Headers */ = {isa = PBXBuildFile; fileRef = CCB1F95B1EFB6316009C7475 /* ASSignpost.h */; }; CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */; }; @@ -417,7 +420,7 @@ CCED5E3E2020D36800395C40 /* ASNetworkImageLoadInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = CCED5E3C2020D36800395C40 /* ASNetworkImageLoadInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; CCED5E3F2020D36800395C40 /* ASNetworkImageLoadInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = CCED5E3D2020D36800395C40 /* ASNetworkImageLoadInfo.m */; }; CCED5E412020D49D00395C40 /* ASNetworkImageLoadInfo+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CCED5E402020D41600395C40 /* ASNetworkImageLoadInfo+Private.h */; settings = {ATTRIBUTES = (Private, ); }; }; - CCEDDDCA200C2AC300FFCD0A /* ASConfigurationInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = CCEDDDC8200C2AC300FFCD0A /* ASConfigurationInternal.h */; }; + CCEDDDCA200C2AC300FFCD0A /* ASConfigurationInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = CCEDDDC8200C2AC300FFCD0A /* ASConfigurationInternal.h */; settings = {ATTRIBUTES = (Public, ); }; }; CCEDDDCB200C2AC300FFCD0A /* ASConfigurationInternal.m in Sources */ = {isa = PBXBuildFile; fileRef = CCEDDDC9200C2AC300FFCD0A /* ASConfigurationInternal.m */; }; CCEDDDCD200C2CB900FFCD0A /* ASConfiguration.h in Headers */ = {isa = PBXBuildFile; fileRef = CCEDDDCC200C2CB900FFCD0A /* ASConfiguration.h */; settings = {ATTRIBUTES = (Public, ); }; }; CCEDDDCF200C42A200FFCD0A /* ASConfigurationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = CCEDDDCE200C42A200FFCD0A /* ASConfigurationDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -892,6 +895,9 @@ CCA282CE1E9EBF6C0037E8B7 /* ASTipsWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTipsWindow.h; sourceTree = ""; }; CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTipsWindow.m; sourceTree = ""; }; CCA5F62D1EECC2A80060C137 /* ASAssert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASAssert.m; sourceTree = ""; }; + CCAA0B7D206ADBF30057B336 /* ASRecursiveUnfairLock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASRecursiveUnfairLock.h; sourceTree = ""; }; + CCAA0B7E206ADBF30057B336 /* ASRecursiveUnfairLock.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASRecursiveUnfairLock.m; sourceTree = ""; }; + CCAA0B81206ADECB0057B336 /* ASRecursiveUnfairLockTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ASRecursiveUnfairLockTests.m; sourceTree = ""; }; CCB1F9591EFB60A5009C7475 /* ASLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLog.m; sourceTree = ""; }; CCB1F95B1EFB6316009C7475 /* ASSignpost.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASSignpost.h; sourceTree = ""; }; CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeSnapshotTests.m; sourceTree = ""; }; @@ -1115,6 +1121,8 @@ DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */, CCEDDDCC200C2CB900FFCD0A /* ASConfiguration.h */, CCEDDDD0200C488000FFCD0A /* ASConfiguration.m */, + CCEDDDC8200C2AC300FFCD0A /* ASConfigurationInternal.h */, + CCEDDDC9200C2AC300FFCD0A /* ASConfigurationInternal.m */, CCEDDDCE200C42A200FFCD0A /* ASConfigurationDelegate.h */, 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */, AC6456071B0A335000CF11B8 /* ASCellNode.mm */, @@ -1268,6 +1276,7 @@ CC7FD9E01BB5F750005CCB2B /* ASPhotosFrameworkImageRequestTests.m */, ACF6ED5A1B178DC700DA7C62 /* ASRatioLayoutSpecSnapshotTests.mm */, E52AC9BE1FEA915D00AA4040 /* ASRectMapTests.m */, + CCAA0B81206ADECB0057B336 /* ASRecursiveUnfairLockTests.m */, 7AB338681C55B97B0055FDE8 /* ASRelativeLayoutSpecSnapshotTests.mm */, 4E9127681F64157600499623 /* ASRunLoopQueueTests.m */, E586F96B1F9F9E2900ECE00E /* ASScrollNodeTests.m */, @@ -1367,6 +1376,8 @@ 055F1A3619ABD413004DAFF1 /* ASRangeController.h */, 055F1A3719ABD413004DAFF1 /* ASRangeController.mm */, 69F10C851C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h */, + CCAA0B7D206ADBF30057B336 /* ASRecursiveUnfairLock.h */, + CCAA0B7E206ADBF30057B336 /* ASRecursiveUnfairLock.m */, 81EE384D1C8E94F000456208 /* ASRunLoopQueue.h */, 81EE384E1C8E94F000456208 /* ASRunLoopQueue.mm */, 296A0A311A951715005ACEAA /* ASScrollDirection.h */, @@ -1414,8 +1425,6 @@ 058D0A01195D050800B7D73C /* Private */ = { isa = PBXGroup; children = ( - CCEDDDC8200C2AC300FFCD0A /* ASConfigurationInternal.h */, - CCEDDDC9200C2AC300FFCD0A /* ASConfigurationInternal.m */, CCE04B2A1E313EDA006AEBBB /* Collection Data Adapter */, E52F8AEE1EAE659600B5A912 /* Collection Layout */, 6947B0BB1E36B4E30007C478 /* Layout */, @@ -1807,6 +1816,7 @@ 693A1DCA1ECC944E00D0C9D2 /* IGListAdapter+AsyncDisplayKit.h in Headers */, E5E2D72E1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h in Headers */, E58E9E461E941D74004CFC59 /* ASCollectionLayoutDelegate.h in Headers */, + CCAA0B7F206ADBF30057B336 /* ASRecursiveUnfairLock.h in Headers */, E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */, E5B077FF1E69F4EB00C24B5B /* ASElementMap.h in Headers */, CCCCCCE31EC3EF060087FE10 /* NSParagraphStyle+ASText.h in Headers */, @@ -2278,6 +2288,7 @@ ACF6ED631B178DC700DA7C62 /* ASStackLayoutSpecSnapshotTests.mm in Sources */, E52AC9C01FEA916C00AA4040 /* ASRectMapTests.m in Sources */, CCE4F9BA1F0DBB5000062E4E /* ASLayoutTestNode.mm in Sources */, + CCAA0B82206ADECB0057B336 /* ASRecursiveUnfairLockTests.m in Sources */, 81E95C141D62639600336598 /* ASTextNodeSnapshotTests.m in Sources */, 3C9C128519E616EF00E942A0 /* ASTableViewTests.mm in Sources */, AEEC47E41C21D3D200EC1693 /* ASVideoNodeTests.m in Sources */, @@ -2357,6 +2368,7 @@ 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.m in Sources */, B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */, 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */, + CCAA0B80206ADBF30057B336 /* ASRecursiveUnfairLock.m in Sources */, B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */, CCB1F95A1EFB60A5009C7475 /* ASLog.m in Sources */, 767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.m in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 241a9c3ef..ab4597b64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ - [NoCopyRendering] Improved performance & fixed image memory not being tagged in Instruments. [Adlai Holler](https://github.com/Adlai-Holler) [#833](https://github.com/TextureGroup/Texture/pull/833/) - Use `NS_RETURNS_RETAINED` macro to make our methods a tiny bit faster. [Adlai Holler](https://github.com/Adlai-Holler) [#843](https://github.com/TextureGroup/Texture/pull/843/) - `ASDisplayNode, ASLayoutSpec, and ASLayoutElementStyle` now conform to `NSLocking`. They act as recursive locks. Useful locking macros have been added as `ASThread.h`. Subclasses / client code can lock these objects but should be careful as usual when dealing with locks. [Adlai Holler](https://github.com/Adlai-Holler) +- Introduces `ASRecursiveUnfairLock` as an experiment to improve locking performance. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Schemas/configuration.json b/Schemas/configuration.json index 366b29233..4cc58ecc7 100644 --- a/Schemas/configuration.json +++ b/Schemas/configuration.json @@ -15,7 +15,8 @@ "enum": [ "exp_graphics_contexts", "exp_text_node", - "exp_interface_state_coalesce" + "exp_interface_state_coalesce", + "exp_unfair_lock" ] } } diff --git a/Source/Private/ASConfigurationInternal.h b/Source/ASConfigurationInternal.h similarity index 86% rename from Source/Private/ASConfigurationInternal.h rename to Source/ASConfigurationInternal.h index f93af4bbc..2a7d7e4f5 100644 --- a/Source/Private/ASConfigurationInternal.h +++ b/Source/ASConfigurationInternal.h @@ -10,6 +10,9 @@ // http://www.apache.org/licenses/LICENSE-2.0 // +/// Note this has to be public because it's imported by public header ASThread.h =/ +/// It will be private again after exp_unfair_lock ends. + #import #import diff --git a/Source/Private/ASConfigurationInternal.m b/Source/ASConfigurationInternal.m similarity index 97% rename from Source/Private/ASConfigurationInternal.m rename to Source/ASConfigurationInternal.m index e9f0705ec..db2ab9d54 100644 --- a/Source/Private/ASConfigurationInternal.m +++ b/Source/ASConfigurationInternal.m @@ -79,14 +79,13 @@ - (BOOL)activateExperimentalFeature:(ASExperimentalFeatures)requested return (enabled != 0); } -#if DEBUG +// Define this even when !DEBUG, since we may run our tests in release mode. + (void)test_resetWithConfiguration:(ASConfiguration *)configuration { ASConfigurationManager *inst = ASGetSharedConfigMgr(); inst->_config = configuration ?: [self defaultConfiguration]; atomic_store(&inst->_activatedExperiments, 0); } -#endif @end diff --git a/Source/ASExperimentalFeatures.h b/Source/ASExperimentalFeatures.h index 9cb3d419e..0df466b6f 100644 --- a/Source/ASExperimentalFeatures.h +++ b/Source/ASExperimentalFeatures.h @@ -23,6 +23,7 @@ typedef NS_OPTIONS(NSUInteger, ASExperimentalFeatures) { ASExperimentalGraphicsContexts = 1 << 0, // exp_graphics_contexts ASExperimentalTextNode = 1 << 1, // exp_text_node ASExperimentalInterfaceStateCoalescing = 1 << 2, // exp_interface_state_coalesce + ASExperimentalUnfairLock = 1 << 3, // exp_unfair_lock ASExperimentalFeatureAll = 0xFFFFFFFF }; diff --git a/Source/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h index 111a8f310..19d49afe8 100644 --- a/Source/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -23,6 +23,7 @@ #import #import #import +#import #import #import diff --git a/Source/Details/ASRecursiveUnfairLock.h b/Source/Details/ASRecursiveUnfairLock.h new file mode 100644 index 000000000..fff42b59c --- /dev/null +++ b/Source/Details/ASRecursiveUnfairLock.h @@ -0,0 +1,59 @@ +// +// ASRecursiveUnfairLock.h +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import +#import +#import + +// Don't import C-only header if we're in a C++ file +#ifndef __cplusplus +#import +#endif + +// Note: We don't use ATOMIC_VAR_INIT here because C++ compilers don't like it, +// and it literally does absolutely nothing. +#define AS_RECURSIVE_UNFAIR_LOCK_INIT ((ASRecursiveUnfairLock){ OS_UNFAIR_LOCK_INIT, NULL, 0}) + +NS_ASSUME_NONNULL_BEGIN + +OS_UNFAIR_LOCK_AVAILABILITY +typedef struct { + os_unfair_lock _lock; + _Atomic(pthread_t) _thread; // Write-protected by lock + int _count; // Protected by lock +} ASRecursiveUnfairLock; + +CF_EXTERN_C_BEGIN + +/** + * Lock, blocking if needed. + */ +OS_UNFAIR_LOCK_AVAILABILITY +void ASRecursiveUnfairLockLock(ASRecursiveUnfairLock *l); + +/** + * Try to lock without blocking. Returns whether we took the lock. + */ +OS_UNFAIR_LOCK_AVAILABILITY +BOOL ASRecursiveUnfairLockTryLock(ASRecursiveUnfairLock *l); + +/** + * Unlock. Calling this on a thread that does not own + * the lock will result in an assertion failure, and undefined + * behavior if foundation assertions are disabled. + */ +OS_UNFAIR_LOCK_AVAILABILITY +void ASRecursiveUnfairLockUnlock(ASRecursiveUnfairLock *l); + +CF_EXTERN_C_END + +NS_ASSUME_NONNULL_END diff --git a/Source/Details/ASRecursiveUnfairLock.m b/Source/Details/ASRecursiveUnfairLock.m new file mode 100644 index 000000000..2842ba8b5 --- /dev/null +++ b/Source/Details/ASRecursiveUnfairLock.m @@ -0,0 +1,76 @@ +// +// ASRecursiveUnfairLock.m +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import "ASRecursiveUnfairLock.h" + +/** + * For our atomic _thread, we use acquire/release memory order so that we can have + * the minimum possible constraint on the hardware. The default, `memory_order_seq_cst` + * demands that there be a total order of all such modifications as seen by all threads. + * Acquire/release only requires that modifications to this specific atomic are + * synchronized across acquire/release pairs. + * http://en.cppreference.com/w/cpp/atomic/memory_order + * + * Note also that the unfair_lock involves a thread fence as well, so we don't need to + * take care of synchronizing other values. Just the thread value. + */ +#define rul_set_thread(l, t) atomic_store_explicit(&l->_thread, t, memory_order_release) +#define rul_get_thread(l) atomic_load_explicit(&l->_thread, memory_order_acquire) + +void ASRecursiveUnfairLockLock(ASRecursiveUnfairLock *l) +{ + // Just a cache for pthread_self so that we never call it twice. + pthread_t s = NULL; + + // Try to lock without blocking. If we fail, check what thread owns it. + // Note that the owning thread CAN CHANGE freely, but it can't become `self` + // because only we are `self`. And if it's already `self` then we already have + // the lock, because we reset it to NULL before we unlock. So (thread == self) is + // invariant. + + if (!os_unfair_lock_trylock(&l->_lock) && (rul_get_thread(l) != (s = pthread_self()))) { + // Owned by other thread. Possibly other threads are waiting too. Block. + os_unfair_lock_lock(&l->_lock); + } + // Now we've got the lock. Update the thread pointer and count. + rul_set_thread(l, s ?: pthread_self()); + l->_count++; +} + +BOOL ASRecursiveUnfairLockTryLock(ASRecursiveUnfairLock *l) +{ + // Same logic as `Lock` function, see comments there. + pthread_t s = NULL; + + if (!os_unfair_lock_trylock(&l->_lock) && (rul_get_thread(l) != (s = pthread_self()))) { + return NO; + } + rul_set_thread(l, s ?: pthread_self()); + l->_count++; + return YES; +} + +void ASRecursiveUnfairLockUnlock(ASRecursiveUnfairLock *l) +{ + // Ensure we have the lock. This check may miss some pathological cases, + // but it'll catch 99.999999% of this serious programmer error. + NSCAssert(rul_get_thread(l) == pthread_self(), @"Unlocking from a different thread than locked."); + + if (0 == --l->_count) { + // Note that we have to clear this before unlocking because, if another thread + // succeeds in locking above, but hasn't managed to update _thread, and we + // try to re-lock, and fail the -tryLock, and read _thread, then we'll mistakenly + // think that we still own the lock and proceed without blocking. + rul_set_thread(l, NULL); + os_unfair_lock_unlock(&l->_lock); + } +} diff --git a/Source/Details/ASThread.h b/Source/Details/ASThread.h index 6ee8a551b..198a8c810 100644 --- a/Source/Details/ASThread.h +++ b/Source/Details/ASThread.h @@ -15,14 +15,19 @@ // http://www.apache.org/licenses/LICENSE-2.0 // +#import + #import +#import #import #import #import #import +#import #import - +#import +#import ASDISPLAYNODE_INLINE BOOL ASDisplayNodeThreadIsMain() { @@ -110,7 +115,7 @@ ASDISPLAYNODE_INLINE void _ASUnlockScopeCleanup(id __strong *lockPtr) // This MUST always execute, even when assertions are disabled. Otherwise all lock operations become no-ops! // (To be explicit, do not turn this into an NSAssert, assert(), or any other kind of statement where the // evaluation of x_ can be compiled out.) -#define ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(x_) ({ \ +#define AS_POSIX_ASSERT_NOERR(x_) ({ \ __unused int res = (x_); \ ASDisplayNodeCAssert(res == 0, @"Expected %s to return 0, got %d instead. Error: %s", #x_, res, strerror(res)); \ }) @@ -229,25 +234,26 @@ namespace ASDN { Unlocker(Unlocker&) = delete; Unlocker &operator=(Unlocker&) = delete; }; - - template - class SharedUnlocker - { - std::shared_ptr _l; - public: - SharedUnlocker (std::shared_ptr const& l) ASDISPLAYNODE_NOTHROW : _l (l) { _l->unlock (); } - ~SharedUnlocker () { _l->lock (); } - SharedUnlocker(SharedUnlocker&) = delete; - SharedUnlocker &operator=(SharedUnlocker&) = delete; - }; + // Set once in Mutex constructor. Linker fails if this is a member variable. ?? + static BOOL gMutex_unfair; + +// Silence unguarded availability warnings in here, because +// perf is critical and we will check availability once +// and not again. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability" struct Mutex { /// Constructs a non-recursive mutex (the default). Mutex () : Mutex (false) {} ~Mutex () { - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_destroy (&_m)); + if (gMutex_unfair) { + // nop + } else { + AS_POSIX_ASSERT_NOERR(pthread_mutex_destroy (&_m)); + } #if CHECK_LOCKING_SAFETY _owner = 0; _count = 0; @@ -257,8 +263,16 @@ namespace ASDN { Mutex (const Mutex&) = delete; Mutex &operator=(const Mutex&) = delete; - void lock () { - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_lock (this->mutex())); + void lock() { + if (gMutex_unfair) { + if (_recursive) { + ASRecursiveUnfairLockLock(&_runfair); + } else { + os_unfair_lock_lock(&_unfair); + } + } else { + AS_POSIX_ASSERT_NOERR(pthread_mutex_lock(&_m)); + } #if CHECK_LOCKING_SAFETY mach_port_t thread_id = pthread_mach_thread_np(pthread_self()); if (thread_id != _owner) { @@ -287,7 +301,15 @@ namespace ASDN { _owner = 0; } #endif - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_unlock (this->mutex())); + if (gMutex_unfair) { + if (_recursive) { + ASRecursiveUnfairLockUnlock(&_runfair); + } else { + os_unfair_lock_unlock(&_unfair); + } + } else { + AS_POSIX_ASSERT_NOERR(pthread_mutex_unlock(&_m)); + } } pthread_mutex_t *mutex () { return &_m; } @@ -300,29 +322,57 @@ namespace ASDN { protected: explicit Mutex (bool recursive) { - if (!recursive) { - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_init (&_m, NULL)); + + // Check if we can use unfair lock and store in static var. + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + if (AS_AVAILABLE_IOS_TVOS(10, 10)) { + gMutex_unfair = ASActivateExperimentalFeature(ASExperimentalUnfairLock); + } + }); + + _recursive = recursive; + + if (gMutex_unfair) { + if (recursive) { + _runfair = AS_RECURSIVE_UNFAIR_LOCK_INIT; + } else { + _unfair = OS_UNFAIR_LOCK_INIT; + } } else { - pthread_mutexattr_t attr; - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutexattr_init (&attr)); - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE)); - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_init (&_m, &attr)); - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutexattr_destroy (&attr)); + if (!recursive) { + AS_POSIX_ASSERT_NOERR(pthread_mutex_init (&_m, NULL)); + } else { + // Fall back to recursive mutex. + static pthread_mutexattr_t attr; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + AS_POSIX_ASSERT_NOERR(pthread_mutexattr_init (&attr)); + AS_POSIX_ASSERT_NOERR(pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE)); + }); + AS_POSIX_ASSERT_NOERR(pthread_mutex_init(&_m, &attr)); + } } #if CHECK_LOCKING_SAFETY _owner = 0; _count = 0; #endif } - + private: - pthread_mutex_t _m; + BOOL _recursive; + union { + os_unfair_lock _unfair; + ASRecursiveUnfairLock _runfair; + pthread_mutex_t _m; + }; #if CHECK_LOCKING_SAFETY mach_port_t _owner; uint32_t _count; #endif }; - +#pragma clang diagnostic pop // ignored "-Wunguarded-availability" + /** Obj-C doesn't allow you to pass parameters to C++ ivar constructors. Provide a convenience to change the default from non-recursive to recursive. @@ -340,7 +390,6 @@ namespace ASDN { typedef Locker MutexLocker; typedef SharedLocker MutexSharedLocker; typedef Unlocker MutexUnlocker; - typedef SharedUnlocker MutexSharedUnlocker; /** If you are creating a static mutex, use StaticMutex. This avoids expensive constructor overhead at startup (or worse, ordering @@ -358,11 +407,11 @@ namespace ASDN { StaticMutex &operator=(const StaticMutex&) = delete; void lock () { - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_lock (this->mutex())); + AS_POSIX_ASSERT_NOERR(pthread_mutex_lock (this->mutex())); } void unlock () { - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_mutex_unlock (this->mutex())); + AS_POSIX_ASSERT_NOERR(pthread_mutex_unlock (this->mutex())); } pthread_mutex_t *mutex () { return &_m; } @@ -374,100 +423,6 @@ namespace ASDN { typedef Locker StaticMutexLocker; typedef Unlocker StaticMutexUnlocker; - struct Condition - { - Condition () { - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_cond_init(&_c, NULL)); - } - - ~Condition () { - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_cond_destroy(&_c)); - } - - // non-copyable. - Condition(const Condition&) = delete; - Condition &operator=(const Condition&) = delete; - - void signal() { - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_cond_signal(&_c)); - } - - void wait(Mutex &m) { - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_cond_wait(&_c, m.mutex())); - } - - pthread_cond_t *condition () { - return &_c; - } - - private: - pthread_cond_t _c; - }; - - struct ReadWriteLock - { - ReadWriteLock() { - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_rwlock_init(&_rwlock, NULL)); - } - - ~ReadWriteLock() { - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_rwlock_destroy(&_rwlock)); - } - - // non-copyable. - ReadWriteLock(const ReadWriteLock&) = delete; - ReadWriteLock &operator=(const ReadWriteLock&) = delete; - - void readlock() { - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_rwlock_rdlock(&_rwlock)); - } - - void writelock() { - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_rwlock_wrlock(&_rwlock)); - } - - void unlock() { - ASDISPLAYNODE_THREAD_ASSERT_ON_ERROR(pthread_rwlock_unlock(&_rwlock)); - } - - private: - pthread_rwlock_t _rwlock; - }; - - class ReadWriteLockReadLocker - { - ReadWriteLock &_lock; - public: - ReadWriteLockReadLocker(ReadWriteLock &lock) ASDISPLAYNODE_NOTHROW : _lock(lock) { - _lock.readlock(); - } - - ~ReadWriteLockReadLocker() { - _lock.unlock(); - } - - // non-copyable. - ReadWriteLockReadLocker(const ReadWriteLockReadLocker&) = delete; - ReadWriteLockReadLocker &operator=(const ReadWriteLockReadLocker&) = delete; - }; - - class ReadWriteLockWriteLocker - { - ReadWriteLock &_lock; - public: - ReadWriteLockWriteLocker(ReadWriteLock &lock) ASDISPLAYNODE_NOTHROW : _lock(lock) { - _lock.writelock(); - } - - ~ReadWriteLockWriteLocker() { - _lock.unlock(); - } - - // non-copyable. - ReadWriteLockWriteLocker(const ReadWriteLockWriteLocker&) = delete; - ReadWriteLockWriteLocker &operator=(const ReadWriteLockWriteLocker&) = delete; - }; - } // namespace ASDN #endif /* __cplusplus */ diff --git a/Tests/ASRecursiveUnfairLockTests.m b/Tests/ASRecursiveUnfairLockTests.m new file mode 100644 index 000000000..56c0245b4 --- /dev/null +++ b/Tests/ASRecursiveUnfairLockTests.m @@ -0,0 +1,188 @@ +// +// ASRecursiveUnfairLockTests.m +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import "ASTestCase.h" +#import +#import +#import + +@interface ASRecursiveUnfairLockTests : ASTestCase + +@end + +@implementation ASRecursiveUnfairLockTests { + ASRecursiveUnfairLock lock; +} + +- (void)setUp +{ + [super setUp]; + lock = AS_RECURSIVE_UNFAIR_LOCK_INIT; +} + +- (void)testTheAtomicIsLockFree +{ + XCTAssertTrue(atomic_is_lock_free(&lock._thread)); +} + +- (void)testRelockingFromSameThread +{ + ASRecursiveUnfairLockLock(&lock); + ASRecursiveUnfairLockLock(&lock); + ASRecursiveUnfairLockUnlock(&lock); + // Now try locking from another thread. + XCTestExpectation *e1 = [self expectationWithDescription:@"Other thread tried lock."]; + [NSThread detachNewThreadWithBlock:^{ + XCTAssertFalse(ASRecursiveUnfairLockTryLock(&self->lock)); + [e1 fulfill]; + }]; + [self waitForExpectationsWithTimeout:1 handler:nil]; + ASRecursiveUnfairLockUnlock(&lock); + + XCTestExpectation *e2 = [self expectationWithDescription:@"Other thread tried lock again"]; + [NSThread detachNewThreadWithBlock:^{ + XCTAssertTrue(ASRecursiveUnfairLockTryLock(&self->lock)); + ASRecursiveUnfairLockUnlock(&self->lock); + [e2 fulfill]; + }]; + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)testThatUnlockingWithoutHoldingMakesAssertion +{ +#ifdef NS_BLOCK_ASSERTIONS +#warning Assertions should be on for `testThatUnlockingWithoutHoldingMakesAssertion` + NSLog(@"Passing because assertions are off."); +#else + ASRecursiveUnfairLockLock(&lock); + XCTestExpectation *e1 = [self expectationWithDescription:@"Other thread tried lock."]; + [NSThread detachNewThreadWithBlock:^{ + XCTAssertThrows(ASRecursiveUnfairLockUnlock(&lock)); + [e1 fulfill]; + }]; + [self waitForExpectationsWithTimeout:10 handler:nil]; + ASRecursiveUnfairLockUnlock(&lock); +#endif +} + +#define CHAOS_TEST_BODY(contested, prefix, infix, postfix) \ +dispatch_group_t g = dispatch_group_create(); \ +for (int i = 0; i < (contested ? 16 : 2); i++) {\ +dispatch_group_enter(g);\ +[NSThread detachNewThreadWithBlock:^{\ + for (int i = 0; i < 20000; i++) {\ + prefix;\ + value += 150;\ + infix;\ + value -= 150;\ + postfix;\ + }\ + dispatch_group_leave(g);\ +}];\ +}\ +dispatch_group_wait(g, DISPATCH_TIME_FOREVER); + +#pragma mark - Correctness Tests + +- (void)testRecursiveUnfairLockContested +{ + __block int value = 0; + [self measureBlock:^{ + CHAOS_TEST_BODY(YES, ASRecursiveUnfairLockLock(&lock), {}, ASRecursiveUnfairLockUnlock(&lock)); + }]; + XCTAssertEqual(value, 0); +} + +- (void)testRecursiveUnfairLockUncontested +{ + __block int value = 0; + [self measureBlock:^{ + CHAOS_TEST_BODY(NO, ASRecursiveUnfairLockLock(&lock), {}, ASRecursiveUnfairLockUnlock(&lock)); + }]; + XCTAssertEqual(value, 0); +} + +#pragma mark - Lock performance tests + +#if RUN_LOCK_PERF_TESTS +- (void)testNoLockContested +{ + __block int value = 0; + [self measureBlock:^{ + CHAOS_TEST_BODY(YES, {}, {}, {}); + }]; + XCTAssertNotEqual(value, 0); +} + +- (void)testPlainUnfairLockContested +{ + __block int value = 0; + __block os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT; + [self measureBlock:^{ + CHAOS_TEST_BODY(YES, os_unfair_lock_lock(&unfairLock), {}, os_unfair_lock_unlock(&unfairLock)); + }]; + XCTAssertEqual(value, 0); +} + +- (void)testRecursiveMutexContested +{ + __block int value = 0; + pthread_mutexattr_t attr; + pthread_mutexattr_init (&attr); + pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE); + __block pthread_mutex_t m; + pthread_mutex_init (&m, &attr); + pthread_mutexattr_destroy (&attr); + + [self measureBlock:^{ + CHAOS_TEST_BODY(YES, pthread_mutex_lock(&m), {}, pthread_mutex_unlock(&m)); + }]; + pthread_mutex_destroy(&m); +} + +- (void)testNoLockUncontested +{ + __block int value = 0; + [self measureBlock:^{ + CHAOS_TEST_BODY(NO, {}, {}, {}); + }]; + XCTAssertNotEqual(value, 0); +} + +- (void)testPlainUnfairLockUncontested +{ + __block int value = 0; + __block os_unfair_lock unfairLock = OS_UNFAIR_LOCK_INIT; + [self measureBlock:^{ + CHAOS_TEST_BODY(NO, os_unfair_lock_lock(&unfairLock), {}, os_unfair_lock_unlock(&unfairLock)); + }]; + XCTAssertEqual(value, 0); +} + +- (void)testRecursiveMutexUncontested +{ + __block int value = 0; + pthread_mutexattr_t attr; + pthread_mutexattr_init (&attr); + pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE); + __block pthread_mutex_t m; + pthread_mutex_init (&m, &attr); + pthread_mutexattr_destroy (&attr); + + [self measureBlock:^{ + CHAOS_TEST_BODY(NO, pthread_mutex_lock(&m), {}, pthread_mutex_unlock(&m)); + }]; + pthread_mutex_destroy(&m); +} + +#endif // RUN_LOCK_PERF_TESTS +@end From df7d2a57373a754a34c3e1548e6fa8fa58a113ca Mon Sep 17 00:00:00 2001 From: Max Wang Date: Wed, 28 Mar 2018 18:29:05 -0700 Subject: [PATCH 121/133] Disable interface coalescing (#862) * fix SIMULATE_WEB_RESPONSE not imported #449 * Fix to make rangeMode update in right time * disable interface coalescing and fix tests * Revert to before coalescing for ease of reading * refactor, make min change * refactor * add comments * Add change log --- CHANGELOG.md | 1 + Source/ASConfigurationInternal.m | 4 ++-- Source/ASDisplayNode.mm | 14 +++++++++++++- Tests/ASRunLoopQueueTests.m | 4 ++++ 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab4597b64..5beec7c2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- Disable interface colescing and match to pre-colescing interface update behavior [Max Wang](https://github.com/wsdwsd0829) [#862](https://github.com/TextureGroup/Texture/pull/862) - [ASDisplayNode] Add safeAreaInsets, layoutMargins and related properties to ASDisplayNode, with full support for older OS versions [Yevgen Pogribnyi](https://github.com/ypogribnyi) [#685](https://github.com/TextureGroup/Texture/pull/685) - [ASPINRemoteImageDownloader] Allow cache to provide animated image. [Max Wang](https://github.com/wsdwsd0829) [#850](https://github.com/TextureGroup/Texture/pull/850) - [tvOS] Fixes errors when building against tvOS SDK [Alex Hill](https://github.com/alexhillc) [#728](https://github.com/TextureGroup/Texture/pull/728) diff --git a/Source/ASConfigurationInternal.m b/Source/ASConfigurationInternal.m index db2ab9d54..a11e24229 100644 --- a/Source/ASConfigurationInternal.m +++ b/Source/ASConfigurationInternal.m @@ -37,8 +37,8 @@ + (CFTypeRef)sharedInstance + (ASConfiguration *)defaultConfiguration NS_RETURNS_RETAINED { ASConfiguration *config = [[ASConfiguration alloc] init]; - // On by default for now, pending fix for https://github.com/TextureGroup/Texture/issues/853 - config.experimentalFeatures = ASExperimentalInterfaceStateCoalescing; + // TODO(wsdwsd0829): Fix #788 before enabling it. + // config.experimentalFeatures = ASExperimentalInterfaceStateCoalescing; return config; } diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index adddc67df..f638960ee 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -57,6 +57,10 @@ #else #define TIME_SCOPED(outVar) #endif +// This is trying to merge non-rangeManaged with rangeManaged, so both range-managed and standalone nodes wait before firing their exit-visibility handlers, as UIViewController transitions now do rehosting at both start & end of animation. +// Enable this will mitigate interface updating state when coalescing disabled. +// TODO(wsdwsd0829): Rework enabling code to ensure that interface state behavior is not altered when ASCATransactionQueue is disabled. +#define ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR 0 static ASDisplayNodeNonFatalErrorBlock _nonFatalErrorBlock = nil; NSInteger const ASDefaultDrawingPriority = ASDefaultTransactionPriority; @@ -3005,6 +3009,13 @@ - (void)didExitHierarchy // same runloop. Strategy: strong reference (might be the last!), wait one runloop, and confirm we are still outside the hierarchy (both layer-backed and view-backed). // TODO: This approach could be optimized by only performing the dispatch for root elements + recursively apply the interface state change. This would require a closer // integration with _ASDisplayLayer to ensure that the superlayer pointer has been cleared by this stage (to check if we are root or not), or a different delegate call. + +#if !ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR + if (![self supportsRangeManagedInterfaceState]) { + self.interfaceState = ASInterfaceStateNone; + return; + } +#endif if (ASInterfaceStateIncludesVisible(_pendingInterfaceState)) { void(^exitVisibleInterfaceState)(void) = ^{ // This block intentionally retains self. @@ -3013,11 +3024,12 @@ - (void)didExitHierarchy BOOL isVisible = ASInterfaceStateIncludesVisible(_pendingInterfaceState); ASInterfaceState newState = (_pendingInterfaceState & ~ASInterfaceStateVisible); __instanceLock__.unlock(); - if (!isStillInHierarchy && isVisible) { +#if ENABLE_NEW_EXIT_HIERARCHY_BEHAVIOR if (![self supportsRangeManagedInterfaceState]) { newState = ASInterfaceStateNone; } +#endif self.interfaceState = newState; } }; diff --git a/Tests/ASRunLoopQueueTests.m b/Tests/ASRunLoopQueueTests.m index b195cbc4d..7889be9be 100644 --- a/Tests/ASRunLoopQueueTests.m +++ b/Tests/ASRunLoopQueueTests.m @@ -188,6 +188,10 @@ - (void)testASCATransactionQueueDisable - (void)testASCATransactionQueueProcess { + ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil]; + config.experimentalFeatures = ASExperimentalInterfaceStateCoalescing; + [ASConfigurationManager test_resetWithConfiguration:config]; + ASCATransactionQueue *queue = [[ASCATransactionQueue alloc] init]; QueueObject *object = [[QueueObject alloc] init]; [queue enqueue:object]; From 41473844f40df6f8ff3b76d7c3535ed78595009e Mon Sep 17 00:00:00 2001 From: Max Wang Date: Thu, 29 Mar 2018 10:01:11 -0700 Subject: [PATCH 122/133] access view first before checking canBecome/Resign responder in becomeResponder methods (#829) * fix SIMULATE_WEB_RESPONSE not imported #449 * Fix to make rangeMode update in right time * Access view first before check canBecomeFirstResponder. --- Source/ASDisplayNode.mm | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index f638960ee..aa1497ac1 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -1009,12 +1009,12 @@ - (BOOL)__canBecomeFirstResponder - (BOOL)__becomeFirstResponder { + // Note: This implicitly loads the view if it hasn't been loaded yet. + [self view]; + if (![self canBecomeFirstResponder]) { return NO; } - - // Note: This implicitly loads the view if it hasn't been loaded yet. - [self view]; HANDLE_NODE_RESPONDER_METHOD(becomeFirstResponder); } @@ -1031,13 +1031,13 @@ - (BOOL)__canResignFirstResponder - (BOOL)__resignFirstResponder { + // Note: This implicitly loads the view if it hasn't been loaded yet. + [self view]; + if (![self canResignFirstResponder]) { return NO; } - // Note: This implicitly loads the view if it hasn't been loaded yet. - [self view]; - HANDLE_NODE_RESPONDER_METHOD(resignFirstResponder); } From 1e8c6f0e0f7f94936e4461e5cc731cb7c0a745d1 Mon Sep 17 00:00:00 2001 From: ricky Date: Sat, 31 Mar 2018 10:22:03 -0700 Subject: [PATCH 123/133] [Issue 838] Update ASCeilPixelValue and ASRoundPixelValue (#864) * [Issue 838] Update ASCeilPixelValue and ASRoundPixelValue Layouts can come back for 3x devices as a repeating decimal. Floats/doubles have a hard time representing these values and often the last digit or two will be garbage. This garbage can result in unexpected values from `ceil` or `round`. I try to fix this by subtracting `FLT_EPSILON` from the value before calling `ceil` or `round` https://github.com/TextureGroup/Texture/issues/838 * addressed comments on the pr --- CHANGELOG.md | 1 + Source/Private/ASInternalHelpers.m | 36 ++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5beec7c2e..32708c063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## master * Add your own contributions to the next release on the line below this with your name. +- User FLT_EPSILON in ASCeilPixelValue and ASFloorPixelValue to help with floating point precision errors when computing layouts for 3x devices. [Ricky Cancro](https://github.com/rcancro) [#838](https://github.com/TextureGroup/Texture/pull/864) - Disable interface colescing and match to pre-colescing interface update behavior [Max Wang](https://github.com/wsdwsd0829) [#862](https://github.com/TextureGroup/Texture/pull/862) - [ASDisplayNode] Add safeAreaInsets, layoutMargins and related properties to ASDisplayNode, with full support for older OS versions [Yevgen Pogribnyi](https://github.com/ypogribnyi) [#685](https://github.com/TextureGroup/Texture/pull/685) - [ASPINRemoteImageDownloader] Allow cache to provide animated image. [Max Wang](https://github.com/wsdwsd0829) [#850](https://github.com/TextureGroup/Texture/pull/850) diff --git a/Source/Private/ASInternalHelpers.m b/Source/Private/ASInternalHelpers.m index 478f8694b..3912a66c7 100644 --- a/Source/Private/ASInternalHelpers.m +++ b/Source/Private/ASInternalHelpers.m @@ -177,10 +177,23 @@ CGSize ASFloorSizeValues(CGSize s) return CGSizeMake(ASFloorPixelValue(s.width), ASFloorPixelValue(s.height)); } +// See ASCeilPixelValue for a more thoroguh explanation of (f + FLT_EPSILON), +// but here is some quick math: +// +// Imagine a layout that comes back with a height of 100.66666666663 +// for a 3x deice: +// 100.66666666663 * 3 = 301.99999999988995 +// floor(301.99999999988995) = 301 +// 301 / 3 = 100.333333333 +// +// If we add FLT_EPSILON to normalize the garbage at the end we get: +// po (100.66666666663 + FLT_EPSILON) * 3 = 302.00000035751782 +// floor(302.00000035751782) = 302 +// 302/3 = 100.66666666 CGFloat ASFloorPixelValue(CGFloat f) { CGFloat scale = ASScreenScale(); - return floor(f * scale) / scale; + return floor((f + FLT_EPSILON) * scale) / scale; } CGPoint ASCeilPointValues(CGPoint p) @@ -193,10 +206,29 @@ CGSize ASCeilSizeValues(CGSize s) return CGSizeMake(ASCeilPixelValue(s.width), ASCeilPixelValue(s.height)); } +// With 3x devices layouts will often to compute to pixel bounds but +// include garbage values beyond the precision of a float/double. +// This garbage can result in a pixel value being rounded up when it isn't +// necessary. +// +// For example, imagine a layout that comes back with a height of 100.666666666669 +// for a 3x device: +// 100.666666666669 * 3 = 302.00000000000699 +// ceil(302.00000000000699) = 303 +// 303/3 = 101 +// +// If we use FLT_EPSILON to get rid of the garbage at the end of the value, +// things work as expected: +// (100.666666666669 - FLT_EPSILON) * 3 = 301.99999964237912 +// ceil(301.99999964237912) = 302 +// 302/3 = 100.666666666 +// +// For even more conversation around this, see: +// https://github.com/TextureGroup/Texture/issues/838 CGFloat ASCeilPixelValue(CGFloat f) { CGFloat scale = ASScreenScale(); - return ceil(f * scale) / scale; + return ceil((f - FLT_EPSILON) * scale) / scale; } CGFloat ASRoundPixelValue(CGFloat f) From 4db9ea37d2a78cf2b79876af62d808bca28e8351 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Wed, 4 Apr 2018 08:01:12 -0700 Subject: [PATCH 124/133] [bugfix] Align timing of interface coalescing and range update. #trivial (#847) * fix SIMULATE_WEB_RESPONSE not imported #449 * Fix to make rangeMode update in right time * This is for cases when CollectionNode is created in cell of another collectionNode, the interfaceState(say didEnterVisible) is not called as expected. It's because interfaceCoalescing alter the time line and cause RangeController update before actual interface applied. --- Source/ASDisplayNodeExtras.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/ASDisplayNodeExtras.mm b/Source/ASDisplayNodeExtras.mm index 2839e1a6c..385343ae6 100644 --- a/Source/ASDisplayNodeExtras.mm +++ b/Source/ASDisplayNodeExtras.mm @@ -70,7 +70,7 @@ extern ASInterfaceState ASInterfaceStateForDisplayNode(ASDisplayNode *displayNod // Directly clear the visible bit if we are not in a window. This means that the interface state is, // if not already, about to be set to invisible as it is not possible for an element to be visible // while outside of a window. - ASInterfaceState interfaceState = displayNode.interfaceState; + ASInterfaceState interfaceState = displayNode.pendingInterfaceState; return (window == nil ? (interfaceState &= (~ASInterfaceStateVisible)) : interfaceState); } else { // For not range managed nodes we might be on our own to try to guess if we're visible. From e748d053d1be3c7bf78652ce4257fda52e86a1e2 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 4 Apr 2018 10:49:05 -0700 Subject: [PATCH 125/133] Remove Redundant Atomic Store from Recursive Unfair Lock in Recursive Case #trivial (#867) * Optimize recursive unfair lock to remove a redundant set * Simpler --- Source/Details/ASRecursiveUnfairLock.m | 31 +++++++++++++++++--------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/Source/Details/ASRecursiveUnfairLock.m b/Source/Details/ASRecursiveUnfairLock.m index 2842ba8b5..a07b751c4 100644 --- a/Source/Details/ASRecursiveUnfairLock.m +++ b/Source/Details/ASRecursiveUnfairLock.m @@ -28,33 +28,42 @@ void ASRecursiveUnfairLockLock(ASRecursiveUnfairLock *l) { - // Just a cache for pthread_self so that we never call it twice. - pthread_t s = NULL; - // Try to lock without blocking. If we fail, check what thread owns it. // Note that the owning thread CAN CHANGE freely, but it can't become `self` // because only we are `self`. And if it's already `self` then we already have // the lock, because we reset it to NULL before we unlock. So (thread == self) is // invariant. - if (!os_unfair_lock_trylock(&l->_lock) && (rul_get_thread(l) != (s = pthread_self()))) { - // Owned by other thread. Possibly other threads are waiting too. Block. + const pthread_t s = pthread_self(); + if (os_unfair_lock_trylock(&l->_lock)) { + // Owned by nobody. We now have the lock. Assign self. + rul_set_thread(l, s); + } else if (rul_get_thread(l) == s) { + // Owned by self (recursive lock). nop. + } else { + // Owned by other thread. Block and then set thread to self. os_unfair_lock_lock(&l->_lock); + rul_set_thread(l, s); } - // Now we've got the lock. Update the thread pointer and count. - rul_set_thread(l, s ?: pthread_self()); + l->_count++; } BOOL ASRecursiveUnfairLockTryLock(ASRecursiveUnfairLock *l) { - // Same logic as `Lock` function, see comments there. - pthread_t s = NULL; + // Same as Lock above. See comments there. - if (!os_unfair_lock_trylock(&l->_lock) && (rul_get_thread(l) != (s = pthread_self()))) { + const pthread_t s = pthread_self(); + if (os_unfair_lock_trylock(&l->_lock)) { + // Owned by nobody. We now have the lock. Assign self. + rul_set_thread(l, s); + } else if (rul_get_thread(l) == s) { + // Owned by self (recursive lock). nop. + } else { + // Owned by other thread. Fail. return NO; } - rul_set_thread(l, s ?: pthread_self()); + l->_count++; return YES; } From e90ba47a13b2d77e05da13d2d260aac2e86a6f19 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 4 Apr 2018 13:32:23 -0700 Subject: [PATCH 126/133] [NoCopyRendering] In non-VM case, use calloc to get a zerod buffer. (#869) --- Source/ASCGImageBuffer.h | 1 + Source/ASCGImageBuffer.m | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/ASCGImageBuffer.h b/Source/ASCGImageBuffer.h index 47d4b8d30..88f1fd9c3 100644 --- a/Source/ASCGImageBuffer.h +++ b/Source/ASCGImageBuffer.h @@ -19,6 +19,7 @@ NS_ASSUME_NONNULL_BEGIN AS_SUBCLASSING_RESTRICTED @interface ASCGImageBuffer : NSObject +/// Init a zero-filled buffer with the given length. - (instancetype)initWithLength:(NSUInteger)length; @property (readonly) void *mutableBytes NS_RETURNS_INNER_POINTER; diff --git a/Source/ASCGImageBuffer.m b/Source/ASCGImageBuffer.m index 215dbc696..8b236f65c 100644 --- a/Source/ASCGImageBuffer.m +++ b/Source/ASCGImageBuffer.m @@ -47,7 +47,7 @@ - (instancetype)initWithLength:(NSUInteger)length // Check the VM flag again because we may have failed above. if (!_isVM) { - _mutableBytes = malloc(length); + _mutableBytes = calloc(1, length); } } return self; From fbc22749aef3c72127f68a6c3fbd707e73b1784e Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 4 Apr 2018 13:33:47 -0700 Subject: [PATCH 127/133] Update Podspec (#866) * Update the podspec * Put that back * Put back the C++ standard library --- Texture.podspec | 8 -------- 1 file changed, 8 deletions(-) diff --git a/Texture.podspec b/Texture.podspec index a0d8b8422..7041e82f2 100644 --- a/Texture.podspec +++ b/Texture.podspec @@ -14,8 +14,6 @@ Pod::Spec.new do |spec| spec.ios.weak_frameworks = 'AssetsLibrary' spec.weak_frameworks = 'Photos','MapKit' - spec.requires_arc = true - spec.ios.deployment_target = '9.0' spec.tvos.deployment_target = '9.0' @@ -41,7 +39,6 @@ Pod::Spec.new do |spec| # See https://github.com/facebook/AsyncDisplayKit/issues/1153 'Source/TextKit/*.h', ] - core.xcconfig = { 'GCC_PRECOMPILE_PREFIX_HEADER' => 'YES' } end spec.subspec 'PINRemoteImage' do |pin| @@ -66,9 +63,4 @@ Pod::Spec.new do |spec| spec.social_media_url = 'https://twitter.com/TextureiOS' spec.library = 'c++' - spec.pod_target_xcconfig = { - 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++11', - 'CLANG_CXX_LIBRARY' => 'libc++' - } - end From 2a0c6f8a420da91617202aa7bca67314ef8c97e4 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Wed, 4 Apr 2018 13:34:05 -0700 Subject: [PATCH 128/133] Check in Xcode 9.3 "workspace checks" file (#868) --- .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 AsyncDisplayKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/AsyncDisplayKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/AsyncDisplayKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/AsyncDisplayKit.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + From 58045ecbc2fd47a9822dca86afbe84676cd06a30 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Fri, 6 Apr 2018 17:02:06 -0700 Subject: [PATCH 129/133] interface state coalescing coalescing sample project --- Source/ASConfigurationInternal.h | 1 + Source/ASRunLoopQueue.mm | 21 +- Source/Base/ASSignpost.h | 2 +- Source/Details/ASRangeController.mm | 7 +- .../InterfaceCoalescing/Default-568h@2x.png | Bin 0 -> 17520 bytes .../InterfaceCoalescing/Default-667h@2x.png | Bin 0 -> 18314 bytes .../InterfaceCoalescing/Default-736h@3x.png | Bin 0 -> 23380 bytes examples_extra/InterfaceCoalescing/Podfile | 5 + .../Sample.xcodeproj/project.pbxproj | 435 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/xcschemes/Sample.xcscheme | 100 ++++ .../contents.xcworkspacedata | 10 + .../InterfaceCoalescing/Sample/AppDelegate.h | 24 + .../InterfaceCoalescing/Sample/AppDelegate.m | 47 ++ .../InterfaceCoalescing/Sample/ICCellNode.h | 27 ++ .../InterfaceCoalescing/Sample/ICCellNode.m | 68 +++ .../Sample/ICCollectionNode.h | 13 + .../Sample/ICCollectionNode.m | 43 ++ .../Sample/ICDisplayNode.h | 13 + .../Sample/ICDisplayNode.m | 43 ++ .../InterfaceCoalescing/Sample/Info.plist | 36 ++ .../Sample/ViewController.h | 22 + .../Sample/ViewController.m | 109 +++++ .../InterfaceCoalescing/Sample/main.m | 26 ++ examples_extra/InterfaceCoalescing/best.png | Bin 0 -> 60439 bytes examples_extra/InterfaceCoalescing/medium.png | Bin 0 -> 1796 bytes examples_extra/InterfaceCoalescing/worst.png | Bin 0 -> 1147 bytes 27 files changed, 1054 insertions(+), 5 deletions(-) create mode 100644 examples_extra/InterfaceCoalescing/Default-568h@2x.png create mode 100644 examples_extra/InterfaceCoalescing/Default-667h@2x.png create mode 100644 examples_extra/InterfaceCoalescing/Default-736h@3x.png create mode 100644 examples_extra/InterfaceCoalescing/Podfile create mode 100644 examples_extra/InterfaceCoalescing/Sample.xcodeproj/project.pbxproj create mode 100644 examples_extra/InterfaceCoalescing/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 examples_extra/InterfaceCoalescing/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme create mode 100644 examples_extra/InterfaceCoalescing/Sample.xcworkspace/contents.xcworkspacedata create mode 100644 examples_extra/InterfaceCoalescing/Sample/AppDelegate.h create mode 100644 examples_extra/InterfaceCoalescing/Sample/AppDelegate.m create mode 100644 examples_extra/InterfaceCoalescing/Sample/ICCellNode.h create mode 100644 examples_extra/InterfaceCoalescing/Sample/ICCellNode.m create mode 100644 examples_extra/InterfaceCoalescing/Sample/ICCollectionNode.h create mode 100644 examples_extra/InterfaceCoalescing/Sample/ICCollectionNode.m create mode 100644 examples_extra/InterfaceCoalescing/Sample/ICDisplayNode.h create mode 100644 examples_extra/InterfaceCoalescing/Sample/ICDisplayNode.m create mode 100644 examples_extra/InterfaceCoalescing/Sample/Info.plist create mode 100644 examples_extra/InterfaceCoalescing/Sample/ViewController.h create mode 100644 examples_extra/InterfaceCoalescing/Sample/ViewController.m create mode 100644 examples_extra/InterfaceCoalescing/Sample/main.m create mode 100644 examples_extra/InterfaceCoalescing/best.png create mode 100644 examples_extra/InterfaceCoalescing/medium.png create mode 100644 examples_extra/InterfaceCoalescing/worst.png diff --git a/Source/ASConfigurationInternal.h b/Source/ASConfigurationInternal.h index 2a7d7e4f5..08cd0580d 100644 --- a/Source/ASConfigurationInternal.h +++ b/Source/ASConfigurationInternal.h @@ -30,6 +30,7 @@ BOOL ASActivateExperimentalFeature(ASExperimentalFeatures option); AS_SUBCLASSING_RESTRICTED @interface ASConfigurationManager : NSObject + /** * No API for now. * Just use ASActivateExperimentalFeature to access this efficiently. diff --git a/Source/ASRunLoopQueue.mm b/Source/ASRunLoopQueue.mm index d30a15a39..f03dbfb57 100644 --- a/Source/ASRunLoopQueue.mm +++ b/Source/ASRunLoopQueue.mm @@ -237,14 +237,17 @@ + (void)registerCATransactionObservers } preLayoutHandler = ^{ ASSignpostStartCustom(ASSignpostCATransactionLayout, 0, [CATransaction currentState]); + NSLog(@"@@@@ preLayout"); }; preCommitHandler = ^{ int state = [CATransaction currentState]; ASSignpostEndCustom(ASSignpostCATransactionLayout, 0, state, ASSignpostColorDefault); ASSignpostStartCustom(ASSignpostCATransactionCommit, 0, state); + NSLog(@"@@@@ preCommit"); }; postCommitHandler = ^{ ASSignpostEndCustom(ASSignpostCATransactionCommit, 0, [CATransaction currentState], ASSignpostColorDefault); + NSLog(@"@@@@ postCommit"); // Can't add new observers inside an observer. rdar://problem/31253952 dispatch_async(dispatch_get_main_queue(), ^{ [self registerCATransactionObservers]; @@ -546,11 +549,25 @@ - (instancetype)init // __unsafe_unretained allows us to avoid flagging the memory cycle detector. __unsafe_unretained __typeof__(self) weakSelf = self; void (^handlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { - [weakSelf processQueue]; + NSLog(@"!!! preCommit"); + while (true) { + for (UIWindow *window in [[UIApplication sharedApplication] windows]) { + if (!window.hidden) { + [window layoutIfNeeded]; + NSLog(@"^^^^ windowLayoutIfNeeded"); + } + } + if (_internalQueue.count > 0) { + [weakSelf processQueue]; + } else { + break; + } + } }; void (^postHandlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { ASDN::MutexLocker l(_internalQueueLock); _CATransactionCommitInProgress = NO; + NSLog(@"!!! postCommit"); }; _preTransactionObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, kASASCATransactionQueueOrder, handlerBlock); _postTransactionObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, kASASCATransactionQueuePostOrder, postHandlerBlock); @@ -613,7 +630,7 @@ - (void)processQueue // Mark the queue will end coalescing shortly until after CATransactionCommit. // This will give the queue a chance to apply any further interfaceState changes/enqueue // immediately within current runloop instead of pushing the work to next runloop cycle. - _CATransactionCommitInProgress = YES; +// _CATransactionCommitInProgress = YES; NSInteger internalQueueCount = _internalQueue.count; // Early-exit if the queue is empty. diff --git a/Source/Base/ASSignpost.h b/Source/Base/ASSignpost.h index c11a71878..0ad31ded9 100644 --- a/Source/Base/ASSignpost.h +++ b/Source/Base/ASSignpost.h @@ -48,7 +48,7 @@ static inline ASSignpostColor ASSignpostGetColor(ASSignpostName name, ASSignpost } } -#define AS_KDEBUG_ENABLE defined(PROFILE) && __has_include() +#define AS_KDEBUG_ENABLE 1 //defined(PROFILE) && __has_include() #if AS_KDEBUG_ENABLE diff --git a/Source/Details/ASRangeController.mm b/Source/Details/ASRangeController.mm index 3bb404428..f638d9a72 100644 --- a/Source/Details/ASRangeController.mm +++ b/Source/Details/ASRangeController.mm @@ -353,7 +353,7 @@ - (void)_updateVisibleNodeIndexPaths // instant we come onscreen. So, preload and display all of those things, but don't waste resources preloading yet. // We handle this as a separate case to minimize set operations for offscreen preloading, including containsObject:. - if ([allCurrentIndexPaths containsObject:indexPath]) { + if ([visibleIndexPaths containsObject:indexPath]) { //Propose changes // DO NOT set Visible: even though these elements are in the visible range / "viewport", // our overall container object is itself not visible yet. The moment it becomes visible, we will run the condition above @@ -367,6 +367,9 @@ - (void)_updateVisibleNodeIndexPaths interfaceState |= ASInterfaceStateDisplay; } } + else if ([displayIndexPaths containsObject:indexPath]) { //Propose changes + interfaceState |= ASInterfaceStatePreload; + } } ASCellNode *node = [map elementForItemAtIndexPath:indexPath].nodeIfAllocated; @@ -376,7 +379,7 @@ - (void)_updateVisibleNodeIndexPaths [newVisibleNodes addObject:node]; } // Skip the many method calls of the recursive operation if the top level cell node already has the right interfaceState. - if (node.interfaceState != interfaceState) { + if (node.pendingInterfaceState != interfaceState) { #if ASRangeControllerLoggingEnabled [modifiedIndexPaths addObject:indexPath]; #endif diff --git a/examples_extra/InterfaceCoalescing/Default-568h@2x.png b/examples_extra/InterfaceCoalescing/Default-568h@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ee80b93937cd9dd79502629b14fe09aa03cacc2 GIT binary patch literal 17520 zcmeI3&2QsG7{;dyhuxq(aR70$vO)rco)4~Hqb-mA2=CIb8^QK*gwP8wCVy+_i!WbB=&euO z!=w19^{!?6gA#W9HYtq<0qu=Ybz>Z0`-H?on{-{TR{Zmu?}~!!)QWeFmfQ*&q~~s* zhveXV_s~8+u`5n-qh6?vEov|zF&4&yz86{JS~2yt=yB346@|1*d{QfJCIbpbtv#XP zheR++hG@%*E|`^)Vkml9c~ekjMU!MrQZ!LfExBSThA{aQ>jipL4V{j)-@H8;jz+a& zFOCCCl18IZX{43>uq!E*N=1@YNmWJKLyXS67>`9Sx|NwseVQb)LpO+B-xCsF-1diY ztyoM3ntdkMH3(({dC`O&r6`SYASoqTS|)PrnI;&9{q)ovTOxfjAYL3%ow8IH^!(V5 zdj5(bXX%v#(>ZCiW@9fs-@#z%&{4c~N)b$uE>%W{X91D+N#qYhn{1uZOS!e|>SMPv zpPUO$NoM7_ld-!(mSi$nx)ib*s?uw<8X>{4A0GOCzn-nKy(vPW(GXs1VcYc*q_0;c z*nd9Rb1TxsF{#tVsEdj$D*B-+TUy1EO;I*2Sj^#R z=5cV0FXfW&oAYsOtK)|Q9M|0e?h+~Rx>af3nCm%PQdYz7`yo9oQrD`|vgVvBU1rvf z7sc4K$xgFQ8%nP0Sc)olh@)wuzTR$&x`VM;Hf>YXY_n{bs#dn!Y6`K{%F7q5o4!3v zw#vlXq1KM0n~q5oQE_zYZ)g>1Jsbid6i*sMS$nsnP**iK4W z-A;A`ajMdV*7<48loOe|IDwa=ocZVEtH&7ih{xJcnN`|rwMpc6;t>wXW|yvs$8Pk@ z@}dTMSEZ!x_uck_9fO)qQO@GMQ+b+k+QN;k1G;mdod%W(l9?2zMP^8s0o3jkq<92c7p$Z}i&2s`As z*nB{i;{rg~A;-n$1F{?!0KyJAE;b*K<+uP4cF1wD`G73P1%R+aj*HC)WH~MXgdK8R zY(5~%aRDIgkmF+W0a=a<0AYt57n={ra$EoiJ7nT2%-^yl9(}cTMBkzP^v2h3(D!cz zdwaiy(D|zfJ@^QrzyG1%zali05&G>uLe}R9z2tv(@5kE+U4MV4xp_GL`Sg5w7{{k8fuN`-gg~6EtdKy$@q3ealPsm_(n_S1wutt$JFzFN)xMF-1^Yl zKS&PRZ`w}KFH<+@u=1!M^4^5hZ;wLioUladup`fJl>Yeo+mhtDjncbTTWyEy?AY5p zkJ#S%_P%p|;?&&I?dEcQWOIW)OQbw>80kV~ynhxlWtYXlAadBoDZiAPi>^NL zy0gi-;FM-AJ$E+pE|H~~T$U|`e1_`$TJ80S(IklWgP_;USJ}=4p|rj(z1*gb=chz*y}FfH5CiynoZ z(1ULtmnQT|F2%kDAJ?(FLDZ*7)9ceCriA`cU70l&dQO*=y&m*}h@Tc~8g*q+b3v6Y zGkeRA6Y4u`tJUNUWzTbMv)ZYeIx}Tvs=93I-HKc_OiS*t8m(1Toz=z=+wG!!&bk#i zgLJEmtzB+S4XSl5!;n{9oyw*`b-6>UrmUK^il*vDw??bk{BY}ne9ro<$m3;>_6mK{ zv;U_`>IE_XGxBbybA%AHk*$y&Essi=Cl+F`4c zIsUhEU>dfjO$yTgGzYWw>l{=6h`CK=a#@px$7$NGR{I`p>s+{xJnqw$@4<_ua8kkN zOJ_ZOc(8fd2=@U+V2j1fk5@J z8HQPu7E)trK3jz+=d5(*t^B#1|0GbRzX|55>h#WYod>gPx=vT%g@XVf;t+9(`G73q z0zkwe;u7-#S;Pf^h(p9B<^!^b3jh&^h)c`|WDyqtA`TIkm=DMzE&xOvA}%o>kVRYo zh&V)CVm=^?xBw7wh`7XjKo)TUAmR{liTQvm;sQX#A>tDA0a?TafQUoHCFTRNhzkG_ zhloqe2V@Z!03r?%mzWR8A}#<#93n0;ACN^{0Ejq5Tw*>Ti?{#~afrCYd_Wd)0U+WK zaf$hWEaCz{#3AAm^8s1J1%QY{#3kkfvWN=+5r;xt%d@v^na^LX9rAZ*rRRS9n7@B3 zIh(s}Le5_zCFIw8gxH@D@_g{o-5>7o_jw0ft+oBp&%b@Yw8WM7 zrH5boo3EvZ_(1|l00|%gB!C2v01`j~NZ=X>eD`35{4^j-PY%DimD+7>Y`4C6{oeh* E06EB-U;qFB literal 0 HcmV?d00001 diff --git a/examples_extra/InterfaceCoalescing/Default-736h@3x.png b/examples_extra/InterfaceCoalescing/Default-736h@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8949cae16a4217842bd8ad813422c6b02e7cafa GIT binary patch literal 23380 zcmeI3&2QsG7{;dvp`~agBn}`rU}c2_5{^Co*u*CBswBI#5^1Zpi0)~3Y)@Ki6WiGC zChY|TCvFHXE5u>%NL;wV0fEGo^J@PC5E5KCaDuU&4|kFdg zdhMfNZ$I1by=i;VuulBQrS}<*{ybMEgw+Y z?`=z+D4~*BH)T)7hSad?*u+K?zba`e))iG(ur6cGRxKNw(&STfR@qT2@%#2p_u6DQ z7PV`KSr*%hG8&EQBfTCa2MV?4Y7lsEkRh;JT_T6Zzgu6CWjm;?#Ukp#wUkVU{u-UaE@^ zqby1fqcet_rOzCg%}K8}8++;b4u?yJPP41G8G;GYrOI^gIHt-DO{1g4qgQXUOS!b{ z>a(CfpPW-pdFIS>r{mxZS)M6n#Zo9|sKu_;?j)3CQL-0B1E*YN+f#&6rz5@GBVG{Z zNMC6weE<1m&#h>eWYl4c(U7q!V`EQKZH#RestsFJD<)-6&Z8IkLH~G(hhf@Aqv}!V z$$PNP%ca`4;^TXEKT3uqbAll`ph_Gbw3K;crRQu(*_~(*CG51Qqqmf0%@tL# z%OtV!#5ekuMW}3fo+cYjqbZZ7=E~GCvFmv%we)@gvDd507p%LH zca(3HiM7wHU9)NG4c*OMb=lB-OLlervW%Ms#tqVRUEP{mSL6%UTS>sm92r#lFw}aGvc-8^S+s2F7KLn=zH_>DnivE{L5fL|(tNwMYt#KUt6;MNm1~M^YZEUo zWsaBc2I{wzQ?2vUnkgr;U~vM^N4fN`$j=^QbVx(dhAOR!UT2%6Q9m1zgsvU1HSw1l zy|g^7;k{c*UiSyVzc33ax&2^sKn+c6P*;_G^K!n@JzsX48kQ}r_EkzOqv5;LIsT_} zU}&~ED@gy*9L(3RcSynm>O0ExvZf7>(zKng_C46vIdva-)Tgc7gQrX3w1O{|&Q|{L zV6(EzN&qR!9d0QLZSw_F_TSIT=isR5-_TU{QE>i$BCV!*>25)XkQ{H}i_^U`z-5-GJRH)BFa2HG_>+sQA=U>Gio()6`~F zT1ic$<#bgZor~I8wz3Cv_M1SN{U}%{tFv3r!#tQ@)5CP-ykHOxh&TjXVm@3JaB)Dy zA>b18;j(~>10oIqmzWQi1za2uaR|7?e7G#&;(&-lz$NCxWdRolL>vMxF&{1qxHur< z5O9h4a9O~`0TG9QOU#GM0xk}SI0Rf`K3o=XaX`c&;1cuUvVe;NA`StUm=Bi)TpSQ_ z2)M+2xGdn}fQUoDCFa9r0T%~E90D#eA1({HI3VH>aEbYFS-`~s5r=?F%!kVYE)Iw| z1YBZ1To!O~K*S;767%7*fQthn4gr^#50?d891w9R#I-tq&6bAj-P#d*iT2)B=M(k< zuH>!n^bk6E38D8sKf00e*l5C8%|00;m9AOHk_01yBIKmZ5;0U!VbfB+Bx0zd!= l00AHX1c1Q*gn;t`y7MJkdHVCOto({Mu5Na}c>U)4e*&NtopJyG literal 0 HcmV?d00001 diff --git a/examples_extra/InterfaceCoalescing/Podfile b/examples_extra/InterfaceCoalescing/Podfile new file mode 100644 index 000000000..71a7f2c4b --- /dev/null +++ b/examples_extra/InterfaceCoalescing/Podfile @@ -0,0 +1,5 @@ +source 'https://github.com/CocoaPods/Specs.git' +platform :ios, '9.0' +target 'Sample' do + pod 'Texture', :path => '../..' +end diff --git a/examples_extra/InterfaceCoalescing/Sample.xcodeproj/project.pbxproj b/examples_extra/InterfaceCoalescing/Sample.xcodeproj/project.pbxproj new file mode 100644 index 000000000..95e776acf --- /dev/null +++ b/examples_extra/InterfaceCoalescing/Sample.xcodeproj/project.pbxproj @@ -0,0 +1,435 @@ +// !$*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 */; }; + 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 */; }; + AD61969DBDF7FD632C7D07F8 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F8E019F6C75C267AC72A2F3 /* libPods-Sample.a */; }; + C088923B2076E4A600998174 /* ICCollectionNode.m in Sources */ = {isa = PBXBuildFile; fileRef = C088923A2076E4A600998174 /* ICCollectionNode.m */; }; + C088923E2076E61600998174 /* ICCellNode.m in Sources */ = {isa = PBXBuildFile; fileRef = C088923D2076E61600998174 /* ICCellNode.m */; }; + C08892412077EC0300998174 /* ICDisplayNode.m in Sources */ = {isa = PBXBuildFile; fileRef = C08892402077EC0300998174 /* ICDisplayNode.m */; }; +/* 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 = ""; }; + 1F2942882A3B5220B7506FFC /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; + 4E60AE604B72B745B8D6B008 /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; + 4F8E019F6C75C267AC72A2F3 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.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; }; + C08892392076E4A600998174 /* ICCollectionNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ICCollectionNode.h; sourceTree = ""; }; + C088923A2076E4A600998174 /* ICCollectionNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ICCollectionNode.m; sourceTree = ""; }; + C088923C2076E61600998174 /* ICCellNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ICCellNode.h; sourceTree = ""; }; + C088923D2076E61600998174 /* ICCellNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ICCellNode.m; sourceTree = ""; }; + C088923F2077EC0300998174 /* ICDisplayNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ICDisplayNode.h; sourceTree = ""; }; + C08892402077EC0300998174 /* ICDisplayNode.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ICDisplayNode.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 05E2127E19D4DB510098F589 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AD61969DBDF7FD632C7D07F8 /* libPods-Sample.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 05E2127819D4DB510098F589 = { + isa = PBXGroup; + children = ( + 05E2128319D4DB510098F589 /* Sample */, + 05E2128219D4DB510098F589 /* Products */, + 1A943BF0259746F18D6E423F /* Frameworks */, + 1AE410B73DA5C3BD087ACDD7 /* Pods */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 05E2128219D4DB510098F589 /* Products */ = { + isa = PBXGroup; + children = ( + 05E2128119D4DB510098F589 /* Sample.app */, + ); + name = Products; + sourceTree = ""; + }; + 05E2128319D4DB510098F589 /* Sample */ = { + isa = PBXGroup; + children = ( + 05E2128819D4DB510098F589 /* AppDelegate.h */, + 05E2128919D4DB510098F589 /* AppDelegate.m */, + C088923F2077EC0300998174 /* ICDisplayNode.h */, + C08892402077EC0300998174 /* ICDisplayNode.m */, + 05E2128B19D4DB510098F589 /* ViewController.h */, + 05E2128C19D4DB510098F589 /* ViewController.m */, + 05E2128419D4DB510098F589 /* Supporting Files */, + C08892392076E4A600998174 /* ICCollectionNode.h */, + C088923A2076E4A600998174 /* ICCollectionNode.m */, + C088923C2076E61600998174 /* ICCellNode.h */, + C088923D2076E61600998174 /* ICCellNode.m */, + ); + 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 = ( + 4F8E019F6C75C267AC72A2F3 /* libPods-Sample.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 1AE410B73DA5C3BD087ACDD7 /* Pods */ = { + isa = PBXGroup; + children = ( + 4E60AE604B72B745B8D6B008 /* Pods-Sample.debug.xcconfig */, + 1F2942882A3B5220B7506FFC /* Pods-Sample.release.xcconfig */, + ); + name = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 05E2128019D4DB510098F589 /* Sample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 05E212A419D4DB510098F589 /* Build configuration list for PBXNativeTarget "Sample" */; + buildPhases = ( + 0B36DF845776B41DB3DB1FC3 /* [CP] Check Pods Manifest.lock */, + E080B80F89C34A25B3488E26 /* Check Pods Manifest.lock */, + 05E2127D19D4DB510098F589 /* Sources */, + 05E2127E19D4DB510098F589 /* Frameworks */, + 05E2127F19D4DB510098F589 /* Resources */, + F012A6F39E0149F18F564F50 /* Copy Pods Resources */, + 682D6483AA9410CE0AF21363 /* Embed Pods Frameworks */, + 9E34612BC7C51DAA56B6AFA3 /* [CP] Embed Pods Frameworks */, + A51F01E9B9FF9C3FEAFC3182 /* [CP] 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 */ + 0B36DF845776B41DB3DB1FC3 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Sample-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 682D6483AA9410CE0AF21363 /* Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9E34612BC7C51DAA56B6AFA3 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + A51F01E9B9FF9C3FEAFC3182 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 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-Sample/Pods-Sample-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 05E2127D19D4DB510098F589 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C08892412077EC0300998174 /* ICDisplayNode.m in Sources */, + C088923B2076E4A600998174 /* ICCollectionNode.m in Sources */, + 05E2128D19D4DB510098F589 /* ViewController.m in Sources */, + 05E2128A19D4DB510098F589 /* AppDelegate.m in Sources */, + C088923E2076E61600998174 /* ICCellNode.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 = 4E60AE604B72B745B8D6B008 /* Pods-Sample.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "COCOAPODS=1", + "AS_FIXED_CONFIG_JSON=\"{ \\\"version\\\" : 1, \\\"experimental_features\\\": [ \\\"exp_interface_state_coalesce\\\" ] }\"", + ); + INFOPLIST_FILE = Sample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 05E212A619D4DB510098F589 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1F2942882A3B5220B7506FFC /* Pods-Sample.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + INFOPLIST_FILE = Sample/Info.plist; + 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_extra/InterfaceCoalescing/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/examples_extra/InterfaceCoalescing/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..a80c03824 --- /dev/null +++ b/examples_extra/InterfaceCoalescing/Sample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples_extra/InterfaceCoalescing/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme b/examples_extra/InterfaceCoalescing/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme new file mode 100644 index 000000000..4a653521b --- /dev/null +++ b/examples_extra/InterfaceCoalescing/Sample.xcodeproj/xcshareddata/xcschemes/Sample.xcscheme @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples_extra/InterfaceCoalescing/Sample.xcworkspace/contents.xcworkspacedata b/examples_extra/InterfaceCoalescing/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..7b5a2f305 --- /dev/null +++ b/examples_extra/InterfaceCoalescing/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples_extra/InterfaceCoalescing/Sample/AppDelegate.h b/examples_extra/InterfaceCoalescing/Sample/AppDelegate.h new file mode 100644 index 000000000..27e560aaf --- /dev/null +++ b/examples_extra/InterfaceCoalescing/Sample/AppDelegate.h @@ -0,0 +1,24 @@ +// +// AppDelegate.h +// Sample +// +// 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. +// +// 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_extra/InterfaceCoalescing/Sample/AppDelegate.m b/examples_extra/InterfaceCoalescing/Sample/AppDelegate.m new file mode 100644 index 000000000..057264242 --- /dev/null +++ b/examples_extra/InterfaceCoalescing/Sample/AppDelegate.m @@ -0,0 +1,47 @@ +// +// AppDelegate.m +// Sample +// +// 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. +// +// 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" +#import + +@interface ASConfigurationManager (Testing) ++ (void)test_resetWithConfiguration:(ASConfiguration *)configuration; +@end + + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil]; + config.experimentalFeatures |= ASExperimentalInterfaceStateCoalescing; + [ASConfigurationManager test_resetWithConfiguration:config]; + + self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; + self.window.backgroundColor = [UIColor whiteColor]; + ViewController *vc = [[ViewController alloc] init]; + UINavigationController *nvc = [[UINavigationController alloc] initWithRootViewController:vc]; + + self.window.rootViewController = nvc; + [self.window makeKeyAndVisible]; + + return YES; +} + +@end diff --git a/examples_extra/InterfaceCoalescing/Sample/ICCellNode.h b/examples_extra/InterfaceCoalescing/Sample/ICCellNode.h new file mode 100644 index 000000000..502f11f56 --- /dev/null +++ b/examples_extra/InterfaceCoalescing/Sample/ICCellNode.h @@ -0,0 +1,27 @@ +// +// ICCellNode.h +// Sample +// +// Created by Max Wang on 4/5/18. +// Copyright © 2018 Facebook. All rights reserved. +// + +#import + +@class ICCellNode; + +@protocol ICCellNodeDelegate +- (void)cellDidEnterVisibleState:(ICCellNode *)cellNode; +@end + +@interface ICCellNode : ASCellNode + +- (instancetype)initWithColor:(UIColor *)color colorName:(NSString *)colorName; + +@property (nonatomic, copy) NSString *colorName; +@property (nonatomic, assign) NSUInteger didEnterVisibleCount; +@property (nonatomic, assign) NSUInteger didExitVisibleCount; + +@property (nonatomic, weak) id delegate; + +@end diff --git a/examples_extra/InterfaceCoalescing/Sample/ICCellNode.m b/examples_extra/InterfaceCoalescing/Sample/ICCellNode.m new file mode 100644 index 000000000..3cab6d9b9 --- /dev/null +++ b/examples_extra/InterfaceCoalescing/Sample/ICCellNode.m @@ -0,0 +1,68 @@ +// +// ICCellNode.m +// Sample +// +// Created by Max Wang on 4/5/18. +// Copyright © 2018 Facebook. All rights reserved. +// + +#import "ICCellNode.h" + +@implementation ICCellNode { + UIColor *_color; + NSString *_colorName; +} + +- (instancetype)initWithColor:(UIColor *)color colorName:(NSString *)colorName { + if (self = [super init]) { + self.automaticallyManagesSubnodes = NO; + _color = color; + _colorName = colorName; + self.backgroundColor = _color; + + _didEnterVisibleCount = 0; + _didExitVisibleCount = 0; + } + return self; +} + +- (void)layout { + [super layout]; + NSLog(@"^^^^ Layout"); +} + +- (void)didEnterVisibleState { + [super didEnterVisibleState]; + self.didEnterVisibleCount += 1; + NSLog(@"^^^^ %@ didEnterVisibleState %@", _colorName, self); + + [self.delegate cellDidEnterVisibleState:self]; +} + +- (void)didExitVisibleState { + [super didExitVisibleState]; + self.didExitVisibleCount += 1; + NSLog(@"^^^^ %@ didExitVisibleState %@", _colorName, self); +} + +- (void)didEnterDisplayState { + [super didEnterDisplayState]; + NSLog(@"^^^^ %@ didEnterDisplayState %@", _colorName, self); +} + +- (void)didExitDisplayState { + [super didExitDisplayState]; + NSLog(@"^^^^ %@ didExitDisplayState %@", _colorName, self); +} + +- (void)didEnterPreloadState { + [super didEnterPreloadState]; + NSLog(@"^^^^ %@ didEnterPreloadState %@", _colorName, self); +} + +- (void)didExitPreloadState { + [super didExitPreloadState]; + NSLog(@"^^^^ %@ didExitPreloadState %@", _colorName, self); +} + +@end diff --git a/examples_extra/InterfaceCoalescing/Sample/ICCollectionNode.h b/examples_extra/InterfaceCoalescing/Sample/ICCollectionNode.h new file mode 100644 index 000000000..ee3290748 --- /dev/null +++ b/examples_extra/InterfaceCoalescing/Sample/ICCollectionNode.h @@ -0,0 +1,13 @@ +// +// ICCollectionNode.h +// Sample +// +// Created by Max Wang on 4/5/18. +// Copyright © 2018 Facebook. All rights reserved. +// + +#import + +@interface ICCollectionNode : ASCollectionNode + +@end diff --git a/examples_extra/InterfaceCoalescing/Sample/ICCollectionNode.m b/examples_extra/InterfaceCoalescing/Sample/ICCollectionNode.m new file mode 100644 index 000000000..fcdfff7dd --- /dev/null +++ b/examples_extra/InterfaceCoalescing/Sample/ICCollectionNode.m @@ -0,0 +1,43 @@ +// +// ICCollectionNode.m +// Sample +// +// Created by Max Wang on 4/5/18. +// Copyright © 2018 Facebook. All rights reserved. +// + +#import "ICCollectionNode.h" + +@implementation ICCollectionNode + +- (void)didEnterVisibleState { + [super didEnterVisibleState]; + NSLog(@"^^^^ didEnterVisibleState %@", self); +} + +- (void)didExitVisibleState { + [super didExitVisibleState]; + NSLog(@"^^^^ didExitVisibleState %@", self); +} + +- (void)didEnterDisplayState { + [super didEnterDisplayState]; + NSLog(@"^^^^ didEnterDisplayState %@", self); +} + +- (void)didExitDisplayState { + [super didExitDisplayState]; + NSLog(@"^^^^ didExitDisplayState %@", self); +} + +- (void)didEnterPreloadState { + [super didEnterPreloadState]; + NSLog(@"^^^^ didEnterPreloadState %@", self); +} + +- (void)didExitPreloadState { + [super didExitPreloadState]; + NSLog(@"^^^^ didExitPreloadState %@", self); +} + +@end diff --git a/examples_extra/InterfaceCoalescing/Sample/ICDisplayNode.h b/examples_extra/InterfaceCoalescing/Sample/ICDisplayNode.h new file mode 100644 index 000000000..40248b6de --- /dev/null +++ b/examples_extra/InterfaceCoalescing/Sample/ICDisplayNode.h @@ -0,0 +1,13 @@ +// +// ICDisplayNode.h +// Sample +// +// Created by Max Wang on 4/6/18. +// Copyright © 2018 Facebook. All rights reserved. +// + +#import + +@interface ICDisplayNode : ASDisplayNode + +@end diff --git a/examples_extra/InterfaceCoalescing/Sample/ICDisplayNode.m b/examples_extra/InterfaceCoalescing/Sample/ICDisplayNode.m new file mode 100644 index 000000000..9c29c5c86 --- /dev/null +++ b/examples_extra/InterfaceCoalescing/Sample/ICDisplayNode.m @@ -0,0 +1,43 @@ +// +// ICDisplayNode.m +// Sample +// +// Created by Max Wang on 4/6/18. +// Copyright © 2018 Facebook. All rights reserved. +// + +#import "ICDisplayNode.h" + +@implementation ICDisplayNode + +- (void)didEnterVisibleState { + [super didEnterVisibleState]; + NSLog(@"^^^^ didEnterVisibleState %@", self); +} + +- (void)didExitVisibleState { + [super didExitVisibleState]; + NSLog(@"^^^^ didExitVisibleState %@", self); +} + +- (void)didEnterDisplayState { + [super didEnterDisplayState]; + NSLog(@"^^^^ didEnterDisplayState %@", self); +} + +- (void)didExitDisplayState { + [super didExitDisplayState]; + NSLog(@"^^^^ didExitDisplayState %@", self); +} + +- (void)didEnterPreloadState { + [super didEnterPreloadState]; + NSLog(@"^^^^ didEnterPreloadState %@", self); +} + +- (void)didExitPreloadState { + [super didExitPreloadState]; + NSLog(@"^^^^ didExitPreloadState %@", self); +} + +@end diff --git a/examples_extra/InterfaceCoalescing/Sample/Info.plist b/examples_extra/InterfaceCoalescing/Sample/Info.plist new file mode 100644 index 000000000..35d842827 --- /dev/null +++ b/examples_extra/InterfaceCoalescing/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_extra/InterfaceCoalescing/Sample/ViewController.h b/examples_extra/InterfaceCoalescing/Sample/ViewController.h new file mode 100644 index 000000000..db689fe32 --- /dev/null +++ b/examples_extra/InterfaceCoalescing/Sample/ViewController.h @@ -0,0 +1,22 @@ +// +// ViewController.h +// Sample +// +// 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. +// +// 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 : ASViewController + +@end diff --git a/examples_extra/InterfaceCoalescing/Sample/ViewController.m b/examples_extra/InterfaceCoalescing/Sample/ViewController.m new file mode 100644 index 000000000..8ba8fb543 --- /dev/null +++ b/examples_extra/InterfaceCoalescing/Sample/ViewController.m @@ -0,0 +1,109 @@ +// +// ViewController.m +// Sample +// +// 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. +// +// 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 "ICCellNode.h" +#import "ICCollectionNode.h" +#import "ICDisplayNode.h" + +@interface ViewController () +@end + +@implementation ViewController { + NSArray *_colors; + NSArray *_colorNames; + + ICCollectionNode *_collectionNode; + UICollectionViewFlowLayout *_flowLayout; +} + +- (instancetype)init +{ + _flowLayout = [[UICollectionViewFlowLayout alloc] init]; + _collectionNode = [[ICCollectionNode alloc] initWithCollectionViewLayout:_flowLayout]; + + if (!(self = [super initWithNode:_collectionNode])) + return nil; + _flowLayout.minimumLineSpacing = 0; + self.navigationController.navigationBarHidden = NO; + _colors = @[[UIColor redColor], [UIColor greenColor], [UIColor blueColor]]; + _colorNames = @[@"Red", @"Green", @"Blue"]; + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + _collectionNode.delegate = self; + _collectionNode.dataSource = self; +} + +- (NSInteger)numberOfSectionsInCollectionNode:(ASCollectionNode *)collectionNode { + return 1; +} + +- (NSInteger)collectionNode:(ASCollectionNode *)collectionNode numberOfItemsInSection:(NSInteger)section { + return _colors.count; +} + +- (ASSizeRange)collectionNode:(ASCollectionNode *)collectionNode constrainedSizeForItemAtIndexPath:(NSIndexPath *)indexPath { + return ASSizeRangeMake([UIScreen mainScreen].bounds.size); +} + +- (ASCellNodeBlock)collectionNode:(ASCollectionNode *)collectionNode nodeBlockForItemAtIndexPath:(NSIndexPath *)indexPath { + return ^ASCellNode *{ + ICCellNode *cellNode = [[ICCellNode alloc] initWithColor: _colors[indexPath.row] colorName: _colorNames[indexPath.row]]; + cellNode.delegate = self; + return cellNode; + }; +} + +- (void)collectionNode:(ASCollectionNode *)collectionNode willDisplayItemWithNode:(ASCellNode *)node { + NSLog(@"^^^^ willDisplayItemWithNode %@", node); +} + +- (void)collectionNode:(ASCollectionNode *)collectionNode didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + ICDisplayNode *node = [[ICDisplayNode alloc] init]; + ViewController *vc = [[ViewController alloc] initWithNode:node]; + [self.navigationController pushViewController:vc animated:YES]; +} + +- (void)cellDidEnterVisibleState:(ICCellNode *)cellNode { + //_collectionNode.contentOffset = CGPointMake(0, 100); +} + +- (void)viewWillDisappear:(BOOL)animated { + [super viewWillDisappear:animated]; + NSLog(@"^^^^ viewWillDisappear %@", self); +} + +- (void)viewDidDisappear:(BOOL)animated { + [super viewDidDisappear:animated]; + NSLog(@"^^^^ viewDidDisappear %@", self); +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + NSLog(@"^^^^ viewWillAppear %@", self); +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + NSLog(@"^^^^ viewDidAppear %@", self); +} + + +@end diff --git a/examples_extra/InterfaceCoalescing/Sample/main.m b/examples_extra/InterfaceCoalescing/Sample/main.m new file mode 100644 index 000000000..756080fb2 --- /dev/null +++ b/examples_extra/InterfaceCoalescing/Sample/main.m @@ -0,0 +1,26 @@ +// +// main.m +// Sample +// +// 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. +// +// 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])); + } +} diff --git a/examples_extra/InterfaceCoalescing/best.png b/examples_extra/InterfaceCoalescing/best.png new file mode 100644 index 0000000000000000000000000000000000000000..d50d8103a690c32123ca25b79cfaecdc7267a482 GIT binary patch literal 60439 zcmeFYXIN9wvpyP%0wP7aB2@vEF4DVn5D<_WdT*g7l+Z;*L;*pHR0RR4kzPU*q<4@| zLICLydhgsF&iS2tKiqS_-Y|4ktc2*}_X?8+?HKdhE>W>0uTVPgIclrL0sLuCH$n>ujNVr?{Q!Av$I}b z0(FsQ|F20IX})AtaQCuf73LG>wGj|}#wse#CnzlVOiYl6^{IfM5Wj#Bzn~DW;8O_! zaS1_T*8l#;4&2Sl)?PwaQTe~`1$>fbcZ5PcB>4G#eSP_Sh4|dP9QXyr#l`srp7K9^ z$_re<>+SCbwesV2^M3Fj35s^!HeOC1P$zdcR-8mDYj+q_njM(we=Pyx@jqhSy#HHG zfWr9wtUUMy`2=uF`p<=$n*ZOILLmQhwKr7P?*E?e|Epl{SNIRod_6!@Q$o$n8*1fd zW2dGl%?^m*b8@nk5K(&iTv1+BTtq}kP*6|_P`HAkvY_ZQF-39Z=OPLM{~4?3ZUckZ zxk3Lk*7pA%EC0Wa#n}SH1GuxIotKl3ovpH$JB0Om%@R)k`&>l-S9$+E*7kp&i`f4< zmLHf5KW=CLA3Ob@uK@GGz5O4%1wQ zZsKak;^}I`m}VJr;wG|3Hz|kyj-rHy3Lz=G($%mI;T`!8A6~P!C6Rwn|7?#(6Wiay zM~W`*;zXvp!pl}6aaXnF$Gfk;h{vlvT|BE@O5tu6Z(aD>)q9e@Cw0Um{PZb1&JE?> zzqcpGy$Piu!o9Gz;o)8$N3-ExK)UyF|Nr-IKg8bQK7mv{uV4P_x7axb94h>0;D7xl z_rd1>2>l>SEiN3rgwJt@*5k6`eSh(joPz z(ocFSS9UEcO)sJ zBv(HKsf{_@TamSIe=KA$;dvPDy>+f}4KATChef{C$v{^1Ac&mGTs+~ULYU>Vda`la zs+WJ4E5SBb%kFm>A;x^N=HffupA4s0f~;QOqWY3)c|~%qU`wjoaGAc--d-7I#i+b|C|El5MD3MRTzA|v;fc+3^cTgmBM|7A*V*dkMWafG9Dq74HJuuA3xD9J-ho@-2uaNjisN9zFW|yo1mRaA)0xM z)thX}96uM^T$UWuQWKny`UmbVNC5sq4r&?QelN1`s2_nbkf<(pX0(0v++6SK}d97A)x zHLt;%F^QEf{>W)a^|r{L2;V;ub+t~Dj};{CoaO7xYObY$;)}dhw;+`ZRYjUT)g}%i zvm;`U`>D*{`ScFfKf~ISUyI^wNt9`rD5y3$&`M2scz22V-e#MklU9mP&)*gvMb^^SMlqCIn=-gBlI9N&nAE)(DizmnfbjY9cJ*vVx+F~M>9$F{qZKx%lN(Uy_o!~$7PvwC#qJ;4jn&Z689OHPgJSje*a8y zt3k}d%}n9meVxoPH-gr~7;^_3#OS+KMgxRldib5{`d>U~82Bc9o*}zCIJUwrj;71#S)FKax`ReNy|TNO|?B^wS)}c=_eul4Q&N0eeN| zt^dnoWH8XnTszx%f=z6s*sR{4402TaRI1^9wzSeL6>xxu(>bw&!UKaJ3z?sM3Rg)! zElWwCg!e8@X{(j7+?rdATxz?W{M%~uprC4O4dps{r~o)Um3`h?sOd&#&kYIO?#uZ( zG5zXsyK=4&k58Achdz-tx9dttbUolrOQlA8&C8lUCZukabT!;aQ#2ecUUNgv3_`Fw zuMOZuhdrb6{yv%aT1knor=ppFftSb+h&uZt^<{30OnCaF7fZESqkCx*I3Irv-?#>x?!DNDWFHRh@A~w0^{R6#(e`?q^$uK#LELKcH_tYXzPTk%F&R4cbcMOiDStybM?7u_;B_rw0{yK+uVYDw2B zX`_rUBD)p|t8T1L3MzPy6eWmkJKa0u=@t_&=ko{{iZVHocJio0n=vdK82#=$$vf77vBz|AWpJsVEsW>X zhVLaPZu#NX8#`I2@n%=j?=B+POGYR<#3&>BqjY>jJefJhZN3EU)B=>XsIN6)R?3Vk^!Z7c8Q zkJPMnO>0Ko(0U_T);TxT-^+IsIG00-#|9Cykg~3LFsDb$c!e!ftPB0jQtsX@);EBW zFc!m0)ffXfH$x63Yws$j)$2{#M43@n|E-r4VyCpl^Ds-{UMHGmBG+pkMo|+qe9R7>A8-8V==C#ZMwMCk5CdQo96wCi+O+i54aX z~P3*`uy#36QU42qg#Uj_g7U0BJlkwCZgfr0VAk~L5F7p94}J;0FxrTYiE zG$|duw6t&@UYhniZ}Q99&%sANQ^^*;9>N#i3eVE=bsIb_0e)O)2YaDKRV3}6F32_4 zkN@nJ~jm8%RJ)@4c@4Jti-3z|uUt9k)eJ+ZFr2t@wLof+JZSQo1H2bYG z{I#ey;?43j6B89ua?l#ndHW&flJa^`eIg#l5R#5W6LnxibW(m0c*8uxayf*in+c6E zR;0`3j=Gqdz6zcfgQB?;1>)VA*Dygi@wf15!mCzB>@gyYVucxixW__0cp`|-EllkOgcZ;{cbW6-x9{0 zx-^)j7cymPct-=^Y4P)^G(n^=y!vUKgVXOzx z^hEc{x8Cz?e3NBU6N@4fR5*(eFmO@XL(jBxH6=VW>8opvdi6r(F)K~c=HMQgk5kEa z*=-7DByW#qUfZ?aj$JhK+>8+pjO`r2gdE8T$ayW3!v^P>P&P1lmXGNz6A9zZV{0T| zM;8-rdpxWv<7b}mG~GBK;S%bfym|jiUhV2_ABVlhsE&z)cl@MayGK<&O-A zYC?QptEkXUoK|lPGuW^`ZzJTUZwqEAo$f!c;z@~;XEpPzcYeY{qKno?8WD-Yz7;gy6!Ze$oa#g+>8qA zrh#FeS&gqO8!{f5KDNwmPd7k*;omx>`+c->8~x5 z`8Pdv+BP*$P-cnE%aAEs;}Vf?d9jB*l{}(CZ}Dydpi1fE42NFalMKG~ZM6#}Zn;CF zOdpD_|^ZTAW zM~2hQ{yEZE);OoTxpIbAyQ(X`W3Zl4?wj;W8~r5wI;v92XGy0#Y9&I5m#N`mn2Ch1Fh_q8NYvgMIJU>kjS1vX#r z7VbH$exZ`+!NZAC2h=WHNMO){9)XS?PIAP5zk$=y zvnO>tO$}4`lU2A?2X&MrY@W>DvV01=z4rYR%DP0B@&J}?o9UX=r(-UqKng6$QWuy; z4;-VIUto=yfSv_!Ih{A|Y!e_ccOFjQu6!(~ z_eh(s^uWC>@*{WXxAHm#ZPY41EX=>cfAw_L6iI~JZmq#5w$cm|5&cII_9!rJVdzFV zRQWDs5oZ*HopB;&r@@xF6zN7;^HKU~f4<=pQ*Tva`72QZ-s?WygJgTc58le-6fba7 z+8k43K;c%g=wVY4keKjoiYr3?2IV_uMl&8m;iYo3ouSjs+d(c8#nb=VV@Ot^y+JzHMFX4ceB7x3{SW~R#>*dL*2 zK=Pe}I%tR{h|{QD8Jp&n4lZHf>&BVOu_?dsDiL=@j(br|c}Ph#FUrmMs^jsCK{XDE?{Dthpd8vPMqx(p-De&N z_sd%UFy${)^9ToWvrRlsBT794ziw?VPzBkf#MuB79|TaWQhp%X|MlWI^un5`+Ylc& zk(e;6i?ZiUXaN-MiKK%Oy?en+{N@g+I^&S(qLd6lY1P*%Nxi65ySWq`LQ}4yplh!e zePnZENQ}+ag>s*!fwsHqTk9IS?^S;sGM}i@EfEO2)(HFX_ItCCGrPBDQH)XiOW2Q3 zH61c2K!F)RLbuI{oqtNVT6(&3C{gzQA0ePX_TF4`@QvypkQWrX$pFm2rfA+#gG_4> zCP%LdLtGsO$+`b(FFkpX_+MAI&R3oeJttn zs7YG!{v97+uyTGtL`%zkpomFk{_#jBFLA|)?>AoaxKzEGulvhohW>p2Ea)NhqUq}) zE;$Vq*mpxVR!?=C$F#AX7(C>S)bIdN5`PM+vun7V5X&W+;LsE0|E1@ObF-F+J<&jw zgy2%GX~A>hBrca78YaD74}7`M3#a(aLLF@ARZJ{hA>$G{QUnJ$|GwEZ!%6C=q;iSG zi(f2%5lRFJm`~m;a@_W|!BPY}=ksOSq%Y#mkT<`^OrV!u!Utzp#BWI?Lg*Um@hsV; z%8ZW;&=aCfed?pUr0dK8=LKAv@$U}UWhYhr?6*5j7GWp}O6D8mSag1%-D?%Yfl<#F zp+u^lAMq(oOo-O0ri|=$mfFGm?zeRCn}7KE#k-jyz6;)eE&jN_%d5A95ODD4tz|yx z$QK7em$EHk{&mo*;sheli^NaTb+s*=?Prj)XBYM=nws`JcmQ_H`uJ9sSj=Coq8Av_ z#WlGS1!6&zG@FH}4gJfl465wR4Y>_<94Z^Z$X3ga4#wbDELNHK(`EJELhs{;TJFN5 ziofgyyi@wJUzuO$8g0FDn;hrW39$6j!>9Vb!19+9vD|jIAA$(8H`z$N2BN=H{4V)g zXQx8@P#_V986L+P>3kO7%@LVMNsBeWSeYkqS68ZtVyhF@c8*45xV4(5KWhizkhIKS z1&X-`CS9)PUjo0;_zskv-n_0eM?N~TFU&*+WbKEGi~Po&Ow9|9jDLL-KjjBbye)^` zZy*G*{+()&Z$i&rZ32f>pH!QQP%Q4cKPH%b_oi&khTu(eF^MRzJA2L`)@Kb>Ok{!OM5e@f{3~KpRJ{gUTP1D)hZf75Jrhzct8k%S6!+y;|ET-^m!xsXn@_+ znTLP`WVO;qhpFj*Vsz>-_5xE{{&b!}6}M*|)6Sz4J%5+Bo*a_eulm{R;L90d?{3__ zqzI7=PvTtlMLOEwA|S!-SIdJ+)ZYM)@ysBhdFX`eMrzL^d^ttGdQ@3jh^{rXCwejy zaT~V*JZVi0nTQd{B_DX^0a$iQ%eXfdFH{wKEe*~C2KGDc zU2cs@FAOvSP!xu#2oT981|1J(h{G+vlW$`QErK%BjRGs*>JUDsz|Mt6&Ypk8=za&I zdYYQ5_Z-&VP$}O5ZRZCY<{%3%CIm%HWYhE7dPf1D1zqxQy@$Oy8?l%5y#kQ3CRFFo z@(OdAugP-k0rUD^f&1|TP2zxtd#4DBvxfsJAO7b28JFekc*Lhp!ps4uX@!!p{y4^H z_MbegaZK#zb0Ege_v*fM$dTDa`A}w%_gr!Qk-7Ls>xW)4sc0gAB`OYgUK>NP!N^Qh zy#c;!jiJHUEj>IlZ4keW1V%DMx2>BFF2cB(t;SzfgUdWNdOu7&h6PVNX^n(@CZp5n@M7xZO%+R2N%PW(c z>q~D$xmx@c*5<{#Y@}C&=D#rIofFdj;TDzm-+7|?D}=hwlTARUeVFbX*aEMxxf*f` z0I1*Vj}?w2{VYRg2F36V=Xf);Yzsft#&<8e@SDw5ellUeNN%N>(Jrv zR2N{FI!b|)7kgB-=Az*$k;B55ncW)2(DL%BfNL0#gD}=@EIq7#7p&Z-y)*Pg1646( zDk`nyt?O#S32KtQcJqm$6UKUXQON;(oTN8~U?@s*ZRfOnOM7`WRO+3;IE*YmOV0(V zyd|lRp|&O+f39I)WS{G7@WDRES*w6a&#RQ&V{ui&4B4K^L`uce+4sT?B8DP zPN)h-7mqt$IETUF32giPUZq^5h7vl{o43i~C$0Lz$gZFI#>k(H8vpT&YX5ni)!4Laef6F;7NlYJq%0! zDllG59Ie%!TFWSiH5Syl{_800Gq8FJ%~pl46IB<9<+MGSuxXpqwc#lcucjFwl6|Pi z&1s@DzbCVGx2*rJ7RYDVEYDl^*(p$Sabx<6!hInJ1z?`%eJ??-4}t_NVGHnc6aJmF@_t_2IEn(*&BsR}=G zz^_&OJ-Z_f-CldmeW7=cWEbj>11lG$?~$`Lm{)#k7hi7lm-}>Kz}sFcybtarCGi}h zTR0pTl@V9}w5WaS&cCm`b@r8jnm=s7lL@ z%2;WC@#(7&tF|b4>&#j&J!V<1aqcm3KPc*+8GwYuVa=>&ry0!oum&50wBVZb!*2** zxmI)lcw@Ge#GO~(C!eb;JMLiu$3&1*T$>12%TVlT6NIV4w7OP>F`zPEMOa!ssWn}! zniZ2G!JM3Td+vVA80n|e-?u`1>TRnUrPHr1$3sPISz|M1lS`M5V=c~jGM$*7Ro@I= zC4c2eob}-H>*JJ+tTM$OS1qsXL3x8Y=?QnUbTF%o0gotL^q5zN%NwWd}!FRW4Mr#c35HT$Tcp%}#++Lh zv-);P38luE{L1urV^Izvt%wCM(ZcHEdLCjHMD_UNp5k!67|whXAUX+Ug|?bt{xwz} z@qIn*)4H8;W#C%5eQ263%k{2S{`(oAtw)P?YTP5WGy;IRB#cU{9F?4=>dE!U?ZV8lizatd}tWagaQW_ zu6|v)g2kyw+8cR+W39?L?wCm2tSoDlAZ(nFAfe_@PixYSHcLTEM={S4`@2`HoC>2j z|IliGaZ5(nMBSAIF!4nd7=<;YEjgN$H5}`6*q-u~`H)zJ9qI#EaK)olK8@il*NrdV znTGwDS`@vTrk$!?pzhTL5Rd0qV{MTV1*#GB!&#v38`q)T0m&V%PT=TlxB^o0P_;cI zS)JP_Yy)}csCJ}>8}#-X0u1m!`kZM3;yXYMtJX@Ge{YYE(Ysc(~Gc4jj`^qqTG%1rIM-5f! zUi|`+*}kok*3Xv8RpkZkUPMLK$%*n#a3%-UhCZ((R+CmZJ=E2Q)}pyZOnf)2M@?~a zBss86=L;M-sBaA4JD9J%J-xg~j%~A-t{+Mi&V;w3dzxCvpFId^-F-)E@@asmyxxYX(LHe{!w4 z?xdoX_JUP+$G=)~)o&!&$P#3Bii-qg>m!7<6(f6A9&My%Q|CJ7y|~Hz{g3!7sSTip zA)#lppQn_P-hB)N)t2YHPUZ2@4d%W9;!gh@BnA93?QCM0r++k!A(_F|Kw0k9H|pub z-dt&r>G&WdY02Cz0&e@D-ajjojqtd@K|6)tX&jocgBwJ%((&e@nhxx*O+ z0qR4{tPw55MX+=h({%K(X!KRmUj|-26bT2EQ_hjFHnT)0yXWyvxgO+`DczO_Ae7KF zC^I?;vuS1=TY?5$R;FLFC`8ye*c*)MO~_aC*#YVe;xs(J#R8C;TNSG_n)BmuKAA}q z*K||$6KX=_7#ON7}nS`7@{|D>>}&(*{9#bIo*JMOL%VYXo^Y$%}Q z==Ai}g@@$LYmu|{2ME*r2$4E8_X7h%H+Lb0StgXwL0vW6)LHhAGB!4FBi7CAeyAmC z9I<@QHD%GyG$kr}n0{~nlT&Z0qQI$dJYUevfjN)?SLX@&0f@Ri21z)bknlzNWkFj5 zvFuE%cz%D@lBSG>`Etfdsa+S*zfTScD4zpWAbszUVv0aLvgP>Y?dL%@1zsoFaNZ*t3D0iS#&A4wNhdwlE-p){3Ggd{d`}wIZ%93pL1%7@5PL* zwO4NXsC^pBq|<>g0NWx1Mmq)Fm`YS_bPizBrK}CFs#U2rxY^hYmw$b*E>jWO%|Q(2 z7X_0{HH{J-TM1dimY$`?FMW5DeBH_-vX*#I(uB{9Zr#KI(7-shIm2K7Z-%T*N zCiJ(azsAx7peGu`NwxZ~NzUDL4oV8H)zTm^X<6uG--yqyb*d)|qmVW!Twbdb z^jKDplR|Nv|DbC7Wkeniuz?0jy%^tp({z*WP5=#OvvCK4K^@wz$<$0%woEzSMb-D! z+D<56nD;I^thV<6UuKjV=JSs|w3uFl3c@TFwI~n?e+QkU8m$#q_!l{RfgB3J0l`%S zOVEuER0qv&>K~GyPxp5;>w$~N;AfW2%zHoC!5c3#S{Nyl-|$qsM>k69wME|M7XyS$m=dSVeq*@dh_g_h?QLX4*?Zdc@r=WPuiJsydQvw)f_Kd!D5b4hbX%l{_IiGW5zYbF%3$QHD`OSk(HL{t z8To)k#XrYTvWbK0roT>Nax4TYbgNMdqL#hJo9~6x~!Ne>A7M1+gbf%KX3JoLLJk^=0CgHjK>FiuofC+AeLd1jlbqTB=0YRC~WG4v}Pysb&<21_(>9b(_ktuP0KnW=VZ0 zXs)!4^@VjY5Z#srxmE->y+qnR#YJ?VZ8h`gno7(OQD*Glg@K*~Go%Tv8J^$IVa1?) zITiXna{^9P%^2F!z%o7%1ueCmLs6qhLX}tY%Nes=4hUb>K=g=SKt5~OXCSM3e;Yzz zFk~B_+5!r&#pd4lC&hVTj?pikalbA1poTCe`Z3CB-~I+?Yih9p-`wI7Nb2vt?S*CZ zQduG`b&iW_UPBICR6`n|<5=j8EA;`pcq7i0lh&w+s5Rb6phpnVz4JDkrv=0M)|&tF z$=0c=(B#JoWjFtT!=p6sv7E6~a;XFLi0a&t4uZbCBgHP!&Cj~N>Y05?$M z^psG-#&nP(0!5B50=%WL=FJSWGbRAZR32MTpi>8 zcE8`a&DB0s*jLxt=Y05RwZsfkKn$qt`Sh?z`g!8b2(xvs*1!!$^BFw%s+F}-XD0NE z0wrS`|>uhmBVRMUdM%- zChyH9z$)|+1H^t2FmM7>$*08uv99cR^_KEMQpzq7D4MWGu!=Y{&Ji|Uo!vv+-WN0q z)Kc0G_eKsn+1K*%cTHiV`XBM1KW+ZFl4fVJXpb5+R6iKa|0jfB*-$qtjcpL3J6l|d zK)Y1)Ouv+?>H0V#BQ6qAUx2<^EMrnI00|9c`#XJ+`du?Fs4$A*TZ@p9 z9%>q;@UyP=xhTT7^n!V!&Po3G{B!_?ZHKKf?_S;6orkT{-2099&mH^C4C|Ng^nzk8 z@QrzCI)?)5?D?5c{7IaM7BYLK9FBp+;o(R&sO6HP;qHW;}J=Eq(VvT$iv5KvTy_eLs(9HCdV?@_l7yr}pK z_P`}4ZI9&KAQk27qEj^|23MAYB;^A9A+%hs#MkI-Nx7;maI62ff<&v>;0sEy>u%5egUo~U)iOFS7WYJ#)ZJkiF%2kW)&%&JR)b; zl$?;ctm4$EJ4&}7;;;76nH^_D!L}wm;r8xlbpS^!7wbhX9342yiGQ-WEIGdaJ+`V& zA*m6;8A3{>s5X;kzfq4j+a$cd8vUeUXzl93eQ*(}eRu1+I${4F89>-ZZ5(x@f{>3> zDpTGI1a7k1npO^*y+Jw}bgI-iRIQ*jE@C`xkQls-CMKF4S>Rgf-Z(hO2MR+rl@eQK z+!8kkyX8+#525tvrb%nA2Y?Gve$4ne@}QEhN>f(L+7a9m=z3+=E8A9AY5uWe4A^VN2sfj}6#DfYBeY}|ulW0v){lD1C1hT2vEWHtx zbSt~HtuXo5un3K4lF^y!ASm0EZb(z?ur1zG<1gMV#*|Q%mMNZTjgX`ckDv$@p|b zOHm9|lJm#twzqKn#??Ir)Mz18Yg)1MdvrV@SuDMj;fU7?A4ow5H_ z!`I0NlCMH03{xO%X){d!8f8WkYixC*s6v~fdCE%SYA^ds&RaRTJO2v(kz)q|=M>us zU)9x!z4T1yFE(9Zjq*U3Zgg;8+vG2TJt(BpCnCTvUejUs^nm^@OEIav6H7`aKJlAP zpzgsB)IG|ijGK%sbM!(5z0mS}T;?HY^dtqyD#pc=*j&pGX;~XZoWzLGaEMrNUt>a% zxj?<&X5J}bl{a)%c|$-c&?Q(0jqc|IN-EuMLGzHcbXHKDHMEPzAK3lDDFjQA&qm4WDKq^!R-Lgzp_w5xBzSZ497R81kQH%68oDDM%FLU* zFk?M`X=PUW{77Hu14H3_-pV&Adi0fTC9NbLpk+lY^Dq#DC7ai1M>p!y!JC#QwSurr zqsIrD@w5)ho%u>6mi0MplW80A>gN+Y^n%9vtm!aTDD~wJXu+Y+_ud+8$#F zjuB(w@P}UZ4xTa1xDJVHK!!iF(AiH;>|o0M@<7R~7QA~`@rOGmW`~WZ4IkOhlkU05 z_xrk6uZD;SxB>ww^-3G3^qjr&S#@rqfZ(6t4!}cNj{eS%3HC*D)H%DUmVgK;hZ~dK z^*~QAPPhGQjhA^xP&PsH8IyuZb3llscb(5pZHnR5FFreS4nR47YTQ0W zRSB?-nOOS$K}AgIaL6|5S!W-#Za^LJw(fx9A3rb{>m9kH1c)i^pd&zFZE5~BZS{JT z?hN}#dfx)lh@AtIsLNknZHIYDZ-@W1e77>I>_$Z#v+++Ckgk923m}wxDHc%IDBhRJ z1SW#^KW#wwQ?_WA>%lTowca)T{B}JLzqOsUvn^zeD!`P{X_I`NDVZtMn6Bm&2_EN0 zJAYkQR}lmKUKj_Gi=Ip-WCT#xZb0<{V)7E5EHVQ~f##OgGiwvviu_L6Bp|!~e0K7$ zktX`_g)XW~aoK5Q|*yf6CVR;o~bCZa5Vda zpD&2mN77*pm0l5X_7;i0C4({SqikUT`VB)(EkrkW)^=%o3kd{qPA!rwb6XoXGuq9@~z1hQun9 z;*!y_Ts>ciM`_T{SI_cSQPoKkKsR^UToHnA@e$seH1BRm$s3l<;vshuFAs#6nnUeZ zLx0S@%0W0YKu@i|Q`v8&y|?TCp_T1KB3Fzs7*W8N-teODqQsX+b?}Bzr9f2m&Cpn= zdOm;)4jn|!0Awg9@7zq%dNO(`Gf?Qt@^`e5K?s(Ag%joLldJpFzS3wZhM;(QxVNdG}fQSsr?1PC<`>giwNR&#%6FFky-3r zdXdk;_R=H7EU$51u||GOGFWiNAmJh0&jLj3Pqegp=6yb~^Vy`3Lp3&>dql5@tYNFf zG~b-R^``FqS%iy0Ve4{*w`yZF*(t4Of*Fdw1QYZ|vQBk*V1WsbHE=!N#vwPb>G^IG za2g#Diq}A$zaH-Sn=dYzU%;hpa59qUQ}_4jEJXQU47SP3CY2TRl(ax#EdR6~15Iap z(I85nhSMQ+dUU4gy>;nE(ZdzV zty7`TyI8#op4FS6-|kk4d>J|Ceq)j{CWYV{6MWGsjSM&|@H{Xrt7Nvv{-TYnookeD1anh_c_O>u<57i~R>I6U+3E8Onv(cF{biyxSI{ z!AQ-RVHqG|YW7XQh{XR^(HS1gy?KDp8!Plq%~}cgupi)G8r`NOzFFvB`}2aAp#K62HV?U4m|{Bu@Z{dq!At1uYNxUE*!>vzO$r&cXiv}cS*9Umo)pzl5QWA~f>z6;&Hb5qKhY|ap({O)U z%I1K;3!(*qXzZ9K6>i3(%m3on-8;X#B#XEW*3w1BB!Qs)YRhPmRf|cw9{F&r#Ng@|f-&fhro1d) zv}w7$eNv_2Jw#OB7*jpV6NcicrS0UByKoRWF|N*& zl^>7QqsZzkz8CbMMks2}jbvTw06RM%0?kBcEa$WS#_$2$9Erk~reu7}z`W_0pL z&}?}Sq#(+h5MWcrC~@A-jwW*vUh(z~vk6E1RBJM%Z_H~C_6=Z?RpLtAo+AjA1fcx2 z@duc{rB^^sG5Ds8g4f5Wt;MBajCosf7)=E6p-<}N=AI~)M6W_Swo;Fgxm1as)@<`o zqJFUd1P^rn3!KZBy5W{|XQFPA7JOGKFfm@eDCTp_=H$0JbDW_o{(x8%%6y2bdZPP# zsY-ne(?HQZBEDFE44Q3{E-PlM;Z}H1w;-L;`OiDBc5saMEaJdB(eyPEX=) z6zwQEPa|O216-x*@%xodzYor5oV?ABidf~7+&j1_Kw+MtXZd6anA6L%edcC|ordl4 zPRB8z!#JYTE96rY$zli1#K-Pg9rS!1LJnS(V4$(T-Ay3ZeBb=%h$$VP`EYJ(GI4~q z=!3Sl)U{%R203aE;GW_V5w1#KV}R$h)NYP~^R3k4=wvGN;QjL;LD}~i=Vqg))je5X zfP0S)gF<(_hj{3DQ-w{+cF(p7^D{rUa-BJU?T!z^9OvHhjpD&E0KUC_}L4&o-9B91+ z&XM|$cU3)!EkqGVFqC|pN(Yf9;Tp3S#?G+=S{Fdj{Kh1Cu`Y5-54^u(4=8P&GE>jg zrfGAgGZe5*0sW!HES_;sy;oltXtV=t6@fD9k~uwc;Sct#;vGRKiLYEn<7!#c3gSsc zK{&AHaiNY9z#9+Mxqkz7G0Jzr#%u_8`ng+Fu2!D00J+hyQpVWpzo`D{-wmQrH4oQP z)&PorYu<~c@P7)5S1xV?fU)s^)zG*!YBJ>l^X<# z$71y=JNuSBf--~aC+*EaEaj{UT*raBvl-5nWKV%mfUSN?_n(VXY}vzfLE%Rk-q_IK znwBwa1UgMPy%8TIaMGV?7mp4)f=4mECb-)8$*y!#Wfx=EH1BmExHFfF#DQx#f|2F! zeMn=33bgGOx6%fZz-yzN86>c(YvbyLnpo*mky!xN1w z%)@4k3<7}4=ukduTQZ3EVAK0F1O94-n+VU+pccp02}KHo2#D}=_%19Z)x;PQUgw<9 z*9N*ov?$VZc&E)r`0#b$c5L?SZOyJNFZpX`&td|Au=XWuCEfyFur}qsZ^q9aO~4o@sPpwJ!213)h+UJzi=T}ip7JOr)?9u1FJ2fqaQ86 zZidb-Rt-n;#)5OSqFZ)=7TL*reiq}4HO@By;CfT^f$w7Uh}KiygUt;V5ex>>mMOm{ zDIhswKvCXPgh1Qg1(dAyU5sk<(DxgSZh`KV5@L67zVx@44aQdDpek#U&nAlfmqtwg z5YvSR%G}s~zLA8rJ_8~!KVD!U(4lzJ*w?>te^^@h+N$JJ;Rn4yY)1Z$)U&DJ*YY~j zyQh+oRujYu4a}Pw!>2`0m6 zaObF~4T?@!Y4=?a7bK*IW54I6q{KQZ=+ zt3f4g&lTZYei?Ri21J?V(+91VbJHipxKjV}Aeor@)nTI*H6`)Q%~wqXN|Vff=zmvx05! z&^mluleY%6hHdqyXob6jh#@6(oe&qP!2x`(^N7M8PhZ#V%3(&i53LW+E|e__v09oL z3w4~84F-Sz_yVZ^lil13801FZ30WU{B0}wD&d_HwZT$68fO3)TIWY^oub|TCpT}$hX)&Z{Nsm- zkWSI-QNh$#ZDmF~oGU9ZZHLPV+zCzZ)THHq!3HI^p=9~!!V-BT3xfe-Y)KME;e`H} z06Z%@X3ObELv1Ik_{qlw=;nOgUgO9S@P$ee&SHabf)`nfgKsEi*nFrgQ$SQ6SCsCN zcwd-E*Q8RNus2s7X@3Sd@?e$^NjxrN;9?Y<8Gs2@vX~mNe^8IctSrureAK~Obpk*y z)qWY6iJVm|<85%h_fjDb{y@7DEVCfOiLq=F?X!2@eV;(cJCMDj#k zS1n6Dem#)|vGT}Vj`hA+$ShaFnn1x8g@pT;6ZoA2fJbE-CF8BaCq&2cX!3?y z@aPB1${qop@_RAE`NZ#iu=GCYtR>zJyiP5PoF4))cBO*}nZxC0l_(@+! zc9y_#LOOb8ZG%P6<{7R7QJ5c7^7r#f=xow`pbulkAahy&(y6?!5PI1jhAG=$#J{M# z+^b&STl=nQ@R$s+m-iK9<7%8NOmVVK1A8Yo8>Z*5?fpAPt9t&Bpt2n8p2>YJWMVba z$b%tKB4S^;5OZIR8HhOf?pA%s6$q;)p{xgZ>SP1hhPMEBJ#|18mQ} z%-yFztrR)EQo0GOYT>&kOQP?UYX_|hvZBjB&P-tw9+;_PFSNMrbTi`M(x73&UiwR* zEB_s*G`TZ)4-psnEvoe|8=GH!zD6TFSc#1ry0t81`%u2C6sh|OPv4PAJetXUpnvib zQ(QW;JFTO19TucypAfkOV*IQQe@G9YpA=g*!~oBe@hx&;57s`>6{?)NogEuoQ3&;a z|ECA@(b#tcx+K$!mHscLt~(m8FIsB}X$V5Z=)FgY-g{@%=m|z|Gg@>KqW8`qdMCOe zdPW&7T67YOUZO{fcm39TYrW;KS>~QOXP;fZ{q23uolBO-{1|?6(b6)D{Dx{PcQS*3 z>F9AO!G&4=qUwHu&sc#B5+|mlX?B~@Jek=c7Y;0ey}330WLu~xgzTLD>YGpf^yArY zOQf$F89xa>bZL_!BdapyZFGtC9IJbX#$d{>@J<;mSsj}JnjfE1xNApHE!_<6+e=s* zwk>(0-T^s>EMZH@{0t{^1%Hv(+nE{B1?jsUQCIPHfy+!@aSjLRbQLD~ z*Hl3PJJCz-*6dmAtmsd~8NTtR)Mi*U?Xw5yn!OvYCwxwMKn3kI8=Fa2Hh0u?!(oMF zlYZ;p5`VV$-{*(zp4p94C7e-2XA=SEqnLdcvk|miCEkwpHP0U6v!=uGnj1#sL)cX# zzvFE(WOPm&@2|bvA|)iNf`5SYu>pOUfj-f^tCew{MwLW;ZbnioUqv2PO)vbE&LUV_ zoPW5-An$f(l?sE>Z8iOLd+_L|-nr_7R7*^?pc|YDgWqes%!lCtO&Xm`%3x5kViP5= z-#&)fi~}Dx{(E_V6HF(epEwk}Y&{}7Vfsz(6Xr1~LbgDy|I)DL*M^z6p^Z6%e-gjb z#G~^EN2sV~Y$oP!=N#Yrp8nnZZ7*Cv~TX+ z9cCyJXu=8+%U*|W=OVs`ji?FtP99o|?pn>`Fg4qmH+H><0^;*Czy1*Mn|KSRE`fSw zzQyXwq^!+Z`eKZm^FHgWn&UFFX=K*bKIv%%g{{C$wjMeUO9%`KUyY~IJRRtGKAlpN zZCU@OaG6rkYApPGb;y3-5Kl3^Y;ZpeossO412^CpdEm)BxF|!x`+$zx(k}MaPCW15 zwbhh@dsyGz&{Xdl>>Y!A?suGpPW$GQPxWl;BW@6RazWZI^BR{41!$ka#jIL=s9NTt zr|Bm@hfWYkPd)!UZs5jZ{;3>PN!Z^kX?lC0bb*g{WwxZfg=O`-nF?{8_@u*Hv(qQm ztC$qT`Ey7BDT%UWUPu8;<|B!$C+0?kS;9^F);V*y3XqwvOOe~`#2+M=Yy$OD2eUHK zdu(-vSOJE5TrSS3)VoArclCj0`~h6j3CHTTVzW25uQk`HsS-6g3%Y;N*Jb@;h}D?= z#>5v{oluR1Z9e_|IU(Qh)+VmtKsV1c5;1pt>sE*ew{|lc3;70V7k87W zK#6(pz!sb^a=(&>CH)vV z3LSa)e{lJtd4Y1mZRjUk(@Apq)SQCm78QOdtZzTr5Pj*r51JFTuPDuO9W5bpWlFtn zBP(otyZlCTv@Cx=*orr4Zy)K1G`Ool59T% zDe6RkGqoiUft3rSFSS~0$CsUYZ5Fl?NOLc-^-j@_)uJ*y+Y0*>IvIs|g`9>GnA0wo4-XrjFXbnGJ#(TlwLgK- z+1Fp&yKBzRq(!k}9W)K5$Nb=bOb>hi?Ab%}_$3_fNj}z1NtVe-jrXg~)bOmGxQnFT zHA@!(Z%f}n(5iCX4z-2u$E|6yWmqtLhE3;cP? zlvgep><-$Z+teAUnl1^@CC3kD34S6R(BOK{Deg8Mwpic8CXph)cbp0>uaZf{UdpfYNtH@7Yg$c=6v@3&_i7y z(pSUl;7SyCeM8ujOa$&>Sf##IbyUy`lc#(J==5JZ%g#M2eN%6zPwGJlDyGEqcsjA@ zxK^IOV3Ug_uJ zjRQ{t4WIGj#EtQ{8{Yoer3Ekw$6L}oZdiq&|`m@^x*iMRHh}`x`XnBGQ$PsXRcgpIdD!lnf5jr z`YSvuJ#X(Xx&H ztf@EC!?$v#nL&IjzbLZyn!T-idPh##C)?Dh9hjeCfAQQkv72bG;rBYYvl1S4LRm6p z6W4X5{lo%;68AMs&kI}q5L}H5tarY-=O{#Iw{iHjN6F1xAH!aXxe?o;W35fatRy+p z#sdFJ*`;+F(@kThgtgCnJ0(Bcmn;g1-Iyr3=EXc&tZQmV#iDYt3L%mf5gTpCzFyek zi2jL5z+GMaOjkQ;-0t$4Y~9>z@UNQ8-RRL3W?*a34Pzdp!|>#`!&xK$%_B9<>tpB3 zHZn);HyHIkI`v^XKd;DQ@u(*?llkI(ftyKGJ_WPj51tGGjieiGg-k`%ds)|w2BYkC z-D+BZwo%VB#CbBG#>v&Km-}3<6CqkMxj+HdTN#J*erp@Pn%^y#j`L!gaIw;-V|FH? zyT1Dlib4(^h@HA_R`1fN{{*6l`0hei)RicAqX;7e$ncQ{n=92d=dYTjeJuRdsm08@H|bJ=>f!Z3f~aTC1LacZhNROwV0j?%I{^jqyzmrOpWnbqw24o_1+Wx+6%6fC8pY{6oL@~_KoH8qqGdSr- zCzoHLF@D^{yI5`Kj_ioRTSYrgKopqPJD$y4k$ki!rsbp`Mf%YaXQ@X|c!j=g=frD@ z9p)`Bv6INJKT|)hCS@JUtFZ7a&P`voxQgBuKH~A8K7G4Ph#k1~_zY&kZ*4BuqYeOu zMVaZ8E3YjKFi69~GFM>bc85ei`QJZjr^o##BT%;Y-} zNQ~FN?-Vo{xN7OMrzmKb^b<>CoV$N%noIoKGw3v=^;TN*OzDoK>OsEHcU1(T0i52T zgzfFC^eJoO=SQwuw8)4D5op!ICw5!1m<(Csf;wL=@v&3- z1*tBrufcy)Y(rscVY!EjP~Hb z)3?Kx?$y{z)o_ZW8BN;s5Ag(+$tX4R^5K}x!_4LDP|7A|Y@vjI)4#_SN*`^Mq9Ju( z-g=;G=KdSjW+IU0X4NWd;on`!ied9tsr7IFHQ^oRj zDe=tupqjUs{vHg^I17L@tDlnPu0^P=j;dn9lefYBIyv9_Pi-bE%A_!EawJSo_QN1d zDxVNr+DpZ#!BVj)`Fr(RW=%Ub*I`DmZqwJMhY@bQA#X~9iLl*$)j5c9@RF03Tjq=_*Xd4YVE4HzM&E8`pt2SgNB$JDlrLU$g($~*`g*Mfl$wRNebcC`=PhC!xxJ`2hR6nr z1zrq4z8qq#YY6!luKQYiy3Z+ynhndQLzJSUr&`iH&o4 zTBJKGN)d7E8$zJ@HY2KvZl zJwB`xDH|9Gw+3aT-q}<*##KlhSGl1hjeO`PdQZhCvDe=Dv~8cz@bgV4-juB2rA+9gHJ$Js9M@xjC^4$JF z6VN1Q3VpBJ$4>|4msg@4B}oSo8i}|>+fAycIQ!4kPz6?BT_=iKpQ#f-`GNH2$A`L^ zhcM-D*9^8golRX`H*~_yY$H{IlqsEa&Xcj;jjbmCD)koK-TlY+SG!8@Izq|s^4*~|qy<}hYyk}yM7^UDTORa0p;iMW4VU5(G|oLgKp#d(w(+xc1t*aCg%AKt~#7L zM|xJLQ_jeBm+*z&Od6zY`e|78g_2VSWmUT*!%Sa&ZJURDgi9TO4-95nEkMu>O-Qx5t;54E36R$@i#Y}wfmvu&OFY(hJ^Nx^u9{jOu? zWTmxQx|%UW79%6TH~WIG)H=G-<}ns?b_tho*APB#3L4d8vGg?NUNw3v$a52H+$SsR zwG)5brR{9ap-x}CT@Ypny!*I$^URvaPmfX@UmD_5Gb1$`{hUwR6My`J>*zLGAhfQp zI6xSHN5YB-oopD%{a8Nb26`)HG<0NtJ4pq3ISF>nMBk9j%R=qO#c2cEdx4Tj#qOiu zgrB8GcYmtzY5((SqkI$OP(9v$79MdEi59wGxe(l^Na(d}APrx2<=u6!@#ZSkK!$n$ zDm|w;WVCzyyU*Iubge_#C2$wzBcM&kpr(1AxmW0dip>O%ZuRoa2r8*}?4a62_cC0C zAnzmmsjH6gve*DMOVYL;j4Sx(0tzS^a?pOMtf^&1RcAiWdM)nkkEl#_XTqCVY*==) zpyjjwXU7a&u9UjyxbHJG{>VF%xbOh%8RzkzLj2R}Z`peVq+fY=3#=D$13#q!a;a>x zY*KhFa2|>_0V><6*gDvxMAOJQ>M|$=L)v%h`AG@_K4MCA*{vP23;UA z8+Rs$=XWo{oKsA{W>0q|J5m$^X+c<;aY@vww$`2~Y0|Z*o`C>KVd+M9#C$)R+RH~( z1<@k6EFQ=8`8A*T;;9=!ugRVeZ^0wie!W)8GaayZd=E4>!fHWEV+|^x_mKs^=8H*` zXz$%C?5wLIoirrZ8SSE+ZbwV+#@JNFK+?rWB`P9|8PlzMIoraRD)Z0Az;Cb>YH)_HyJJrg!OE@oo2 zdfJUyoSU`meDy7dT-{m?Gxjy@BBa$(D(m4o3;T2|SU)VVS~IJgf+o_f$Hz23*3JF8 ziI|5n1=l0Oox8er&|7Dl%;`A3pP};*&OD^LZSESny?=Px;yoK(Cc0oN+yXFH0jajp zXraw?R`qN;;{`cgf%D0GKXJ%%@~Y%ZUsqE5yv`E)koLTsXxMt^x%V}D^Jt15#GR`J zd3xDAa&~U;&o`ISJm|@2)r2+P^&2oeX-W2tj)e|m`0B4Wk)o4#vd{~UQZDwz9q z*d|{j$E*CRB7SIf+$>FvjSB(8yhO_^8vd){sM`IxoUU!iH0sBuxk=1+vL2KC`O}HB zT`C(lEBgqGZ}ii7*RGEi871r>FUhlqARU8u7009Yi3LzVFwkAT4J|mYmy(Csj{Q@2 z%>jToYmtC)utG3DJsjmL_@zGLDYxYhX{H43W-zTD()G5ArY{&pNC~!sI|6aj<3GcX z&3<_mwH%Rqt6V5+llI749*=ROzNYQCy0|1j7>aOW4UH@mLvqL+tiQ`!Hh^RfCXDt| z2x>u_FVPeyHbSp(^Mx8>2J>p?xY>tN)TyJAy=F78# z=!e5!ugQ!>3-KKUxmIxh$cgohXBlnuEk2Mt>I=yD9ZClH-V(p;`lg->&3wQIYyG+| z{{;ooaZy|`lg?I?>e3=v%o@OXm)y=;FD7IXWM!cN+&t%NI1f&GgtZ(ad3xxU}gYfG!Y{ zalz6UjmGCwIQ4usG`3(X5MpQ-Yf`z;afAnkTPA0*<PJ92x!?P@n`KNh$$@FA;71e&pETcRqw2*z^ zCzesuvE9_QnZF=W@$rJzN{N*VO^c6%E(aH`w;s>5Q&c6f8ra;>M%>qIowb@*C4iW` z>Z*N}TM_1;U*d5Y7VW;I2ww%JQmNj0ba;`+4e>-hxmnS;JZtC8uRdyTHQ}l*CdOP& z2N#$xQ)i~$Ey`xmHjK`Q%UN4A6|ybXCN|z%6bXK`N{t1zrA}Uz*=p`6)amn!&So8F zs(8hzHvLGt<9T6sIvOfLewCc7ZVLs*u)ojsUm)hc4`~u25po&Y&I=fgozf(m3}|-v zq@P*1jzk8V+b!ogWsJ8k8n}c!?8*Q`LpOt94*%**&*SX5%}kx$qA?U&2+Ia`+Za&nJUB zco6j3o+}v9RPoBDkf!?EMU6{9$r3Y7 zbhyJrKGRz&5pXyM6O>OU_gbh+fAQ~v-mLIVJ&YV4k~+}tT3$3dV0A30C?8Y7OsH1R%h1Ld3_ zo83i}Vhq~cKdWLrTvzt=w_CNde5=1ntgFZrd6Y-3IOE#=0H(dXMFUR}rE;*_ zo8#Q+{ilZLF{VBMDEoKya-7&FW@2<8ikEwhiTGfwSh4Y}BMGT=( zT?l}8yib(mT9&_ll)9^LCT;!uYgq3ZVzv${&f-IL1f5|CiGnIF5z*thBNm$OIBaiY;5JRvZ>Mz)ji3R)uD|kDa0<&Z34JQ{2gELWAKx-}k8*N3eS~_?PHi_`q;JBMmZoi(Ro|HGf@xx#UC5MvfG6!;C2Y*#)CT;c(Du&4` z$4;>wU*M}(n8ki#!S8O;Awymou{swVxSrcd$Uzry#T}{5d(Bj6beQX4dn|Y*(u7P; zB8$Yl&WDEmvRT`7+GQ-s>;UXa_K!uw@Ni+QQ3&n(ebR4MD)h@H*sb;)l%%Q8A{mQaAn@Yg&9I;#(M; zd>mKN?sZ7mG5=pr{k&`*a|8SU!TYLd4`*?Jalto-@9!7Bu+=X{Y7dU89M}BQYZEA= zd;*nv#i`5PkBp6Rpy5y5#}@*IVoc4uNnKQ*PIQL zuOmRls14m_&qv)3#5|JHlzbTIHWlmKRmFN8uclzfDcFj0zf4@qIIiA*Vv=cj$u5LR z0W1g08su4EQ*Y}~4(2zE@%q+J*&O;ap#Cn*&pyWwjHEI~8eY4rWmPMLA|Qp9i7Zz8@JXL_@r#m`}b0Rtci#(BEmwcPv;A;v;pv&u3=~ zD1JZjGma8{T|R%!->emx)LP^w+j&l@CmUSJSo0_zb6AOg4cr@j@T_o8_ASTw{+EK` z&$3|9{glI&$m8ebEbZzHdJpuu0oGDo5R*5w1MBb7x{&l+j%lZD=18}GKU0!suaIIa zu*nC?A4TiVT&KI(EJ^g-%J$ix4jwrL2bYTx)GJ^Fw0)hARf#FN-c!Pr`I0B4cirZ1 za>ZoF$viwgg1V1nWJU#i<))#b&&xRf?DBmp6^&_UDFZKJD?${!tgW3w<+fO)`UZ&h z@i%RUW8G;$q~&~iJrOJ#&9e5A|81;_@V8szaK6dPrPi_d@0{68er=j*om||;qZRpC z7#YZDl;4uhK(xCyoQ44mKhWFI7?nok0VIuKe~SQ_VE8KGYgExW=?XrQ5|jc(-a?%7 zNElq?9fyJhY6j<0w6hg~^+%DX*y!2Yyfz+~4|A1+Rw94n7#fyyJ=dXzV}e0g3c^jB zS52fP9M1Kx5eux-p@pendi(*UtJ+J9&^5wX7L5xKL!?{S@w01*`*dU!%~Czs%yNKC zR)ntc)m1BcK>Vc>*{3j-NcjYIJ;FRjK2Y1pKL1z)p7|XYJ+#GNg$@yJMa)V6yqA~? z?j#j*+w)H|Q;pG*PWwD-Hf)GAs6i`Mi*I?P6c_0)P zplZdLm%t`O#JUodLxyR&P6SBIfw$HnY;JHdwPKYJAc0n}5YQt+W{0~0QSwP%S*iO@ zBv&I-`8?Pc&>Vj+tLqNtq9+$d^`QtEa3xKW8O#mKl& z2u5hmQ6zcPLx+3Z!4Gs^Dnc$op0W!F>1`-0^TOmcA!2yWSz)p~(ORFzRhJ!psA&Dx}KoN__wDxdv=)Y$s{`VQ0A4;wtg{*D& zN^!s@rE-=NWl^hsY=BM*EhiU_#EhMmaa9bZZOHiG5;9m1vo*gEUs80So!wwj4FPY> zB>)yh+8+xrs-Y?ug>2J`9y#lOPn@VVoSak6TE9Q67;pd%G{M%ZH*G;!)pQ1tboJ&x z1?9E%nOV%=)!rlSynGHS?X<%gT%p3K)y%|NUh4yM)ZKE5V+jtHk}yn^xiBVM9#058 zG(zw0L6(0dHfW$Wc+J!prUHau46TmzvJ*~e2%B4l;}55Ta#Yf#74YCdvbJdkD zwhu^egcX@})27$2&lZ>q%2#sIT+O5Utm>VYe;UbjVtiB)4j5UYc~#IzxPC7IMUqWC zwAen&y=RdrP$fg^UDlJ9__Yoz_sJ~gJSW$4#1;I#`uHdCL%eIvQVD0>i_gyELnx(- z{dlREBHd)=t9R;_l`|j9L1X2i5>-gni(O{M#)vwZsR^QpADs7(dDS`#$Cg9!8WaEo z@Wtq&$y9h38w~0pMgp8+8cN&4*n_X>!M)h38-+41aA^2gbBC6Qc`1t;dup_2sNY6m z176p540%a{EZS*Sw5)T|@<2~$i##B7RCO=-uuG%3Mz~J?Hg$^K4B9tE;L}1l?e+XV zl7TFCnt4&TKPBX$jgNApMERLyBT*$O-V^DXfG9?yEk+!+f7>~=s{lVKSni(qqX!e^ zZA9>&o6QTEuep<$5JnX4LLy~N1=xu}XkrG);x*7Be=qw9j z;d<&*qMMd-T~A0BRpcyL0;?A2OxM5+35Ib1_8P$gG&ANH=zD z0Mk_Y(`PJT{SQ&K-vXzWse^*bEjjU-wGQCg2faXfme!Vw^uX}xiHK1LD z;Mgs3K)X25;8H5Ok)IC@@Am_bMG;me=>SE&x&40O#%5!uE`N$gBbH2`3BV-1 z;;hCC&B3Rkoe`@6k{4OUWq=p=Lp8S4HB=)RsgV1WKSwTsZIzL0BUSuJjN?U=$~X;B zhyG4)aWapyMelvb6KwQ#I#FQ05G1t__1#lf!E%0z>1^uyp^RL{-`~H|cNRI~Gm^vb zTO(FY!dGX(0K5##Z)wPh(vi?*BQ}oOg~ux+H4=CH1`9;7X)P4o8|mWtE^uLzG%d*s zdP-b}Sfjvh#IhU^03do4dA3W%_Q-lbthAD+O9n(Jf`V3kQ^g=JHEAZD>HofjSp)}G z4VML*0CGJqwo)vD1?Z3E0oOAmuK_Fmo!df;94Fs;3d~!Nq=aHHKn_aG$6L74itO{=Yf2q4Xv^8O`AJNjSTh#A3Gfi5<6p1ZU zl9@SxCptm&aTFt8Xft|rAenAoQTjVqIOCF5xqw?!4B*}d3luTl>r_z8DE8U1;driA z`>NpA-mGHy-%0@*IXlskt*!0B;Nqy@Q79_8P?PxqV?wl>Q_FEs!0pw@Ll+wC7j7hv zJ9P?opVEh%0#k40S*k4hk<0D<){B@`V^7PZ?cjyz0&DPBZT-oecf$DHg?@p`|Sel>=OyBv?P{^$<6g8mgWl z@y#`4JYB8d7ATTF$bjL3^8l}Z9HE`~)^EB<)ksSNxb`#|=I)j73#Yr#B!Jxd68Q`z zcxZVF)R!O3yQ=8-nw>T8dU`Rm6le?cRWLL@BGg8WtCNI%QGkL%dmbgbEN>G43u%Hb zWu^NCcC1gWm+FoVvjMu+^9sp2v)X6JhNGSOkDr9t3%D3@!!Zeb@4BRmLA~Dp{01#q z=f>tLM0d3n{&ya3T=>C7ep<0aNW>3vcQ*k~-H(7=6()SRG_s5tYq^SJR$2HkLUdUl zBolg~GOLpq8eF9$Ht(~=kEI8nbF$GxE(U$^@)1abTf3?VT$+6q0DpO+X0nOZ7Ad># z!;diS8|F!~k@UNgN#@sL6v; zwO`HbWE4*#R)e&3VvlG6Lzp7=ij{djK|BGZHp*X29;vNsUR4=j#Rlv$rKF zQM2v-PKg0F5mMV;WyxiG7JZewB$pMEWcD;96Bg~Jg@c(=;JY$-9QCAuyQ*agaJoMx zjL$?LeD)|Yd3cV|tfTww=>e(_LXfczp5K_ImO ztON>B3O;NCy-Xswwgk!((li(mj18>RLbE%w44!2m@BkQoAQMOK00iSHFD zhB9akuxMaJ%=(w=4kPz3*97{0{wN?AMcIR89Z)!I!lxEAQ*;2jxc`TA9y|&CLtCm8 zy~CBo5GgBFV$nL24JI-Yl0pRAW!~E;FaUpKaicRKZ{+a@7yN9X!V_*B;uQvr3{>sK z1}BkV3C^~pr)0oqpqvME?+V#}6uN)iFHI!2wv^$HS7s`fjFXC&Ar%2X&Sdxj#DNv@ z4fKU$O;?Sa8CZG}>!#HtLov3DlY(HMoA(z?09-_W))fR6b@SNW-{vFXlm7KAf>;b4R0 zz4{!5z5VCn#qlK>3nM{!$%!Es_mvDKa=aH4z%6oy)jPboqe%2+=TdDv4jaZ4cZo1r zSkNmyu1+nxF3kBf{lx=7Rrk2Z?vm^xG}DRZz=ruxbozln6TUk2C^ty0*9a7#d1;Y( zoDLYb09twz9QYCLcSnXwz6-XjL}KO@gVq3jWjTMkc9C4>%z{af-|}uq-2uEulDw`z zI;a2)CF7LbDDwQ|W`SJAU64!aBQTDOkJE=;#I zDAW*O|DGea&4w=B9V>s_t(aAdk_pNIt^v?|_QRJ_#pKgxuKHP~uX}!C17DS*Pz1n} z!OoqLiF1=Z(uJw7ScT>r@glh%xrtlNyCN)U{ug>q8H0lp9j)>|u0O#(;{135;I5kp z*JSXbtHl$@3q;vpP7K~un7hGO!Z59iKiWPm6o5|?W_SdwXR`Y9CGl^TT$Wy)N4Kde z0(w}^6m*fYrL10+u6vWuQCDj6|AFU*(}PmogFz|{N5-!r<{oohBukh8F|o8&Jy=ji zILs0&3;mm&QCi;T!Z7p?Lk{{oZnbZswSc!frOU{aWJVKMpl+_S`wZz1T|76Eek1sk zfDytUIT_=OZpRwn@2y03uZ2`a1BTG@WoS(Q#nal$ z8o#%(s_LFeMxMIBOy2?O72^i|BLZPtd-)nbn_oEmSwSg984roCr#)IT47q`<190Z1 zW58`R%9lAGZvf!IJ#ve;4Ui|FfykM7JFAxsUpbb|xZRS$DEVP84J z$gmbE&umewD})0W(v=H{004=+K7$ZiGBWwNxta_%!3CV;-+Uki0Bro5Niw-B&VSjL z65_yr_=_a)m4(x5qNtjk6GqK(OuDjBgqS7CCY|Ck|D@`LR0g z-Uqpb3CwifNAvx620h)=J=)WdFyU9YA>lAN_Co@xhdUMkK;0S#kobKt50y&cirl>@ zNi{O8CShaS$JZR1Rg>wo&ExXuP5}Gh5K^I@x5rO00m$>C8@2-!KRrDGjI?5vG*%gK z#>#IY)H*2L%RL4-7;{2T<|a@Hyr`BGHzdN7GnVB(WDC`uuVO*iT3(3&E~F<<%mDUc z<@cWkwacVS*~^cwyVX;kMCxst;YSZV^~b^s&9J2RIk$P6+AfKKIgJFIjpAibw(LC$ zC2-1D3KY;|n^`3q7S$#kXA;V@2NB|ha*L9J80KAQBs>aF~#kh-GYy8JT!=kg#e$IaMz!{{k;Bu^zN*Er5y| z#$sb>2uF^H2WMO3RSTGs^jADqJk>s?kb_od6hy6xFyCBZ%%fXE;`*}nt6Lqp71x4- zbYd;g;+O+J0KTB0F1#4_kvGeW9>NSb{8y+Wzo};b&u$(B=}jS$K`>6|nb&%2K^Rfx z1js7DwZ*eu+zIkHcu8?EDUyKwN$(RgQ1q$@K=nCjOdBoc6HOnC9FfmSC;{o5{lXVY zWIeCfr_iZug?I9nuy?v|95zkhPfj1Pxn&?ev2gLDV0w0v3dQ?z)D5C+#Q5~}_3Q5F zVwKoF%-|wWX9c?uN!XvUd506m+}3maccMUP7*qtB&i?0E4lh{%dw<}6)VUk2G7T{X zfDM+aaKMZ{;38i`Os`4tkqBu-d+Pn!f%gR*?fW_(lOD9HtK%jTA-6Z2+LP)6lLIhu z;>Z!GH<5e*$g#rpASip_Na6Vj_POue2rvkBpP5x)es5U9O7tFlv5qm=^$_-Kt_9)V zvk;lrT_9^dygFGqs8BHP15>~vfDPu06x}iYNQ|~*V%@de)T`iH;FSUdp(qR+D=RJ| zbi@VvFR%dQxNHW%GRNeC*4}0dY*hbnx0M{!W;eDkBmV5qOH*t-B0lRB?yS+O!bUye=F4jjHuzE-gS4H;;a1feCNnp3H5_p8?QLv|0GjKb}6R2}~ zq)IbgB;Q4QtKWTPEH7%6F0xhG$&42|7T?5bD<4L(pB9)C0kbiUEh4BP4*u(k%t zLh)L#=?DFq=(b{6hBQdD8Hj;rEoFrEVQ|STwjr1kWWw;7dd0H9$j&#m!SSVmri3*d zJUq1Rn`TirTnsGrj9mxtBXx8Lpud7?tHFSg7a@RRM!3r($0bj+$KW&k2R*lX& zXzP3-8k*z+5B1P?Y>eP08=(vWeH@9%Ny-njK=IZ@Xk;?R=`O#esHl9%lYn`1##9%m zae8SBJRiU$AB)z3)E+*W(})PliWuZV7Q9YG8o}9<-Riyn3)4l`OS8@=oRP+rzO#Ge z%A+-HUyM9Rw5DprFbfl4@Juj}UgBi`(*ha#f$}(_r(^)(wrEXkTwS;*7CCb|yChna zPk@AVn#+#oB*@{#pD;3pqP6%m-VB#AABlX_xD;^Xc>;tA?*9Km)+^+8-#P6HOkk9? zAz`{wgjrV}#?MuuV=C}G#pY^@=AP7ar^_4$6Ik)0FI~}XV4Nj2-E;O6y26J7Q(S1wacCnHdD3U6a0* zUZOM;VxB8ajucOu8i|<^w6F04dV&UCULAdqrLPKot!1rhreD5f`SHubm<-@lT^IA+ zUCQ~#a>7^V>I0`9CJn+ALIwJj`&&G5$uP__5@z$*U%yjmCSQ}nbDWlk0-kd7DcUc3 ziV3iu&Ialj_TljZY-J0~<~B&r~V=M%AeV4!$sNq z2i_cT>5o7v^ShZ8B2^pQ_@cZ{VKv5k`ZCr#8SX&I*21OmV|zT!tw8R(c#9U`AIJVP0?kUS+v&nYiJ=?%** z#+Z1MmSPFC0=7Ktd4Esazga(q_T{{vhJfz-JlIorVaEGZ=SO@j)G}GPi8sD0{06@0 zHSvGW>G{^1|!1;ZP=^YH2n#@?Af%p5z|wakqK>&G;>w@oT-z%D{!s zOXPOM8G8sSh1yp2uVjH7eEg$T3~}a4p2sp8$^f7;altW8_$p9!b!R_pyD^E<0bF?B zUh$uG;B3S>irGhZy9a|IiX5u>l@OPi^&+PdLOaSRde7^DGz$G})*MI-l4`_95>H<( z0+xjOQnXvK04^h`ai8P(B|N91}L!9C$kPq~4p_TeP5Pzfx& z|En}w{rWG@=(u=f6Qk&MVCsPJbj_dK{Umj4phn9`yBO4@00&VZNv;%Z8{&o%c&1Nw zIr4>Uwsf-OS}1^W(R1qt?x7%bi(9!~+WomQbAND51BpHN`5HEcl2v8Kk%Iyc;VTOF z?9RrwwlfyPmkNGV$uuWa}Tmqi*2v*dqm5<_;anR!5694QRTJos7PA$Zw&yZVaYdLVZz zfhGIlA9Yr*MX`WsJ`?&)0@oc zoab3|toz`_Zg5-8MBGY;jQ^qUpHfivm$m=vFc?xbjFp;Yad=wEns$R%g?%)Xkw zkDS5r!o1d#UUnu*B%4I&_o>q_81^23;T61A!UtGDTfavKBNXDntRAk#`Jm;F1jb#%pW#A1m zJMLvMnz+OoBBd@_(>nQEpMmI+tXFn?AwL9`*3RH;_D@^nGv=+Hh24jR%1Mv4y9~2H zNd<=pdzX@$BP-9;n{63_nUxt<>`9<&hiWOQX}CFuJZtZj%wM2QqQs+2ovS*p5%lCx z*$Dt~@Oe8(`7oWY!k)CTxv5b_>+npZ6L1bk`z#6Ot`HCqLWT2vt=cmqy4(f-4KRif zYp(4EkGef^35cpOE6(~&jZ*pXJzt9`e@!>!ks&W|aScLl_mnIv8^gLJ4;{|pH>^&A z#1wvGcI^%~_Qz5dO@LgY)&T~(S*MySO@(QfYp&-$$b5mO9&mjf++=Z94?RzrP>__2 z@Ye3|$_d*&+>T#n6?y(;T9EO=^Fzx&O;V)?%Ibs)`)kL^2Sw}1#)eemoS6}~(hHz} zXePU2p`>-au7B@Dai9Z>EQI!iys3qS=+UO2A>;RIT zq&J;s=cbQzi>@QQnfuSy;fN=;bq^iMc&La^~-aN*k&aIKnbmd0b(RC4-@4|A%Qv7}5jcvb%NFe!t*>thk4A6#S4XL? zd$yCWuLy?A?RVa7*4Of@499a1eSi~k9W;(dN0d>^hrr$8d#;v|z>sNGfuTL?O& zE^d@t2Cl?FZ(w?VjatU}mPk&PGeMsLZT^yOEo%!@XnQTo*=v~E;wQ~iG}H)3zj+p z6W_e>bkbVOOLjEpCm4_8_#cw4!Xe7$>rx8BODNqE(xG&tA|Nf@Al=PUOA3N?=MqXR z4br(FAl)pzz|u%7wKU(u@B0IGp4pi@ch0?Y?>Q%~cgo7n(p($r*Ca+Zav_v>{3aYJ z{yxv)I$=w9Ia5W5kM6I0Rh9~X0_*fg02DeW-o5giGV27DY*~s*prJX3kzKiw&kSgr zG&Y>64$)eQg`AGCy|#Ea9>ZsqX7IAPk+76!c#u)N$fJ?qW1e}Gb~aOS`u~?R3y==^ z1009vu#TV%hiL}*buLN6_>E!Fq|$EL&h_D-ad;?~THoc7Pixl4shfPW2ruq_=4CKTy0E0@I!S6GfsL*SS~5CAm}Sz}_>< z?>pCy4|DuX`^id&Y?$4LT?LCaWDD%HX9-934^RGQ2kX)6vqhFy`-@L!tN=8+KL*TZ zO5@5D4=8V^r%B=t1xllV0_$VilMCI);%Gb^m^`SzCE8=&_#k`DZh=3)^@Q;;!L>#@ zR<2VNzE6=3yWjcT4p1bSeRja*zv=XfYb5GE4%i7kpvd>=B84_Vq>cxz8oVsgf1n)m zXr@vl0WO3S{VcDT+IerMj^%1BU@$8VcPrV+;czO^C1k+OcxypyigGk{Gq z<;D#j%3H>yq`C(0?m(N=zzds>a&UQaxV!6@z<8kOxIWDzrC*z;glz0L(51!d7U_&9 zj~mzL>lY)L)GfhywQ!QT<<%B#@=)mSAf1D^W^?V^nO>LzbQw*ij1xa4_yb8n{<(N) zA{##8qZ$z%QdCX6%v$+ogvilo8{L{F{ z9f5vL-aT^m*Vsg-cNB+jV?c9H_hob7GCNQg#Hs(yV`4iHY$WS}*!;x|0CRU6EyYOZ zRJ)vqVN=ls0OlGK15a&=)O!btzJ}5L;IrV{3`TdHQUoEGXT?@7fl@x0DP2as}3S@ZDnLVaN5=6Gl?^;u6WDT_#hdo;|Mrd8Z z_8!+ZkwDMk2=B>76Wv}?oA|C34k0{`{@~FrO0I^B%3_+|ia2V}vvfxKS00Iq@!WU{ zKAl&6EDDU`OAb6PnoC6&T|Eft4?Otp(uaIQY4|EE&i z+5!Yr9-I=a3;b$lH}O+*M2=8GZ~(oT+|Yx5R|`S&R)=cl6e*;oB~j3xoISq8NW&o> z2BgMCQs<^idB9w+98AB8Hexi(Aybx8<1}rQl(sa7C8Yg;iwhfPlW3FZh%TP}>j=dg zCM))H`?Vz7kq)!zZVqi%36jNt7T%3IgX52!0B|U-y~yH>q^hlDQg;eI;Z-7 zp8>jk+CnQ+ahTyQ$NzFkXJZmQ76`EVmlmKyFM0c4=xWs|ALfE2`Ks97nSHrcvVUwc zH4`dMS8CvqpOs;c%ra22RQb9$)MMf9BJW3a7%u1Q;UQ6HmXz~S?;(|*kfJJjedZ!sd!CM|dWsF2XQK1y6-V_YnVYC4o8^_E^ zp?~}Nwrtr$jJPKTk6#RJPGXiD+f!V}AZk;2h*fOYY&jgk`M;fHV?QPb%N06E$mYps z;+{-8A3`b%hhJR%ecv(w4zG!5b)2+(*^_Cq*H98nQyyg1_r&I8?d6?e@0_p#S?F&` zwTpqtXUkmQ4pRW9I#tw=RD82Yuc{E2obBkF&P&VcC9mMX zh!Lyno#Yky*+twd=AyOLLh^|Ee)(udccz=zoU{5P=%NO*(f}S{|7@8NQGA45+ggVJ zJGuhI0(C@i$U>HfG1h6oz&R}O&^wBG(g5QlD?%~UWk}iiLHWopB1hORmvF@y(Xb^e zOqx4>s4KeLz9wfsO|K;aQe8eCEzX)nzMt?_i`GxIc80aj%Qy?(5MmGACFD8nQ}Lqi zw;fSG{dZ*--h4Qgfj&0qDZu$*e0Rv0FaEi7t{M@oQ{F(d06czbX9mN9$ShTR=;Kps?$hI@wtz_OFpT@thGuY_+R^ABW9zT zpOs(7?t?W6nsaNLOrN!uu89`6imX6JbV{-GkjGZL{i6l`&98Z@%BgFdSzDi=q}U)o z77m5yUDmM4cerM8UC<4v$ZiE)4D$z^y(>)K4S~|D`XSdH-Z%5+THi>ovYet#xfD-# z=6o1n6CczDEp9LaH@t~^tTXmTXz8NX?rsR`V|rN}1W#MV-dc^=O6jR9Xe%;OWk6!Nn9DkQX`BXcI3lnx-pmTSORW%mZSLAwMLr>2L ze+zpsj%mlE9(#Ebqa7ZhYge1>s|$IixLeS_!J#C>nPE5fXl}K`OUf3JAwml3YbWPO z@&WHJDL!j@JjwWOhlwVJUHT$#k3{lS^wpEAm;ZkG>#LWO!xt|q0$`B5eNUw;l()Tb z0`pP0Tnzdz$^5vmfe zwOetaPl#y!6aP2*u~RrRmn_18$`NTTg?oGOjb@tGi(@cxZv00VO@pfUAJ}WgbRabg zygnmpHS|AKw4NN1Y=^ZMEzG`}drdfiBNA4;?<$HCxexI4alU0IPzF@|PBkJ+o zfD6_d_rK=7L>@?h%7(#Xs*d1UYn3{9%S9tYWwX+wfmCb6;(SM*uUx^!F=d=5{&c_{ znQ}WP<-5v8OB~4u8#pWKXdVj7Sh>lxdh8QFqC5X`0gy8+HqmYkd56`ktc3L??1($6IIwuhs1B=)o#xy*q4=6#Q!k!x{1zWgr^5ALFJEZLbA zjuoiXNkl#fBoAy~%a4B85^iP2NS{+=2Rj25!@rj+#KT}g9~z;rQ`g&e5dA;GeVbPn z(%dRr>$UE!Vn=wRPOFb801hzq+4Bj>wb)&L=+416Z_}#X8Yq&7yNDqqeW@Uj)!ivn zfE3^kXl>HGxjwf}LIRZr3T~PO7k`GC*hdO%>B6p!H&FF9<~_69!_w7h9_8z|`#}3J zoJmNqcU2&Vq>lgY_Zb=7rf+>T(c-%7adeDJNl2DPx`2u*G*^L3@*~04`hDUX-wJV1-jD>rsEqn< zM*$z{=m5HxW8iJemIlo+%Vnb74c}!W=9G}CZZBtp+9N~5z{hrnMyO37PmMSJTfMu# z2{amc-@wQIR3p2#LGOj1{Xg1J78bDAh2^Rq=1X@Z1PASyIYIS-pXN?*y$G zUw8G`y&AUP8;5c}lhp(<_Ip7>h+Xg47U!3jhrbyfTUs}5kN zpl`4K#%HY9J`z@--A^3KZ~h6}QzTobX#%~y*t6eTDG9VAAnJX$Z*A;Ul4#c=-cb5s z1!x@D&r8Z{u<~Tuu^DDf8_H{B@gzp_b0MW(N;|<-d5QS8!NSOnUCg$KjR$#ULV>%9 zGr2e$G}akOhE5IXgDP`|F(<;Zroo>%K+);Pj`ID6Nc_OmMYb^ss%l-<^7c9vf_N)G zO#n#W!gr?<<}n6Zo!h!ja#j7R3`c~Wl|C_hNz9zVcypX@7=m?APlXQk;UCj(|6x## z*bObYoJLuKL?7WHv!Pp@QK?cuo6U#DA}{$o-?!Vl*SlI;NA`qo9eBA@gA6%!X91EF z<^Iik)hld1z1A-O;q5jd+om*qSlTxyBt*;@wAlVdRG}Ya?Jtu-E&mmv&cr?=@hsTnHiGT+R0;!e~ zgY7>iY*HB~%8|#pI}Dl+cU=oMK5nu8PL?r|WAy=!D#Q#+RbkZ&>dTi2i3uM5LjuI%Bc~HWFk0D@a-pV}J&?j?`q2%RGP?grU z9WTKr5^sq>Arvaem>VxT`ENDQ@$=Bw--p%~xrJdn_w(*(MEKH&FHSs*&gwdK*I4W9 zsX*W)i(0v9PP;{J%J!7CD)?=a;2_rR-0c5tJZQAwXw??@6Y6oxx~26&TF%lm zKl@y`6NB`bj>2jziDQ8mE*)GK>M>V-g4npKW}y7 zmVzRK?Rv<=lEb)8;tg|hrc1xS{6l8p<*1&HJveb!DJA{MSqJzNL7Br$d#CnEW_sJ9 z!a3~?8AjkG6DL{pB<>|_l2R1)PCF+X`u=M0s`TE6kUmw-Wya(-9Qr-&Uu56r3c*z# z=HeGwD*Pwzfel7RD)i$oN1oRE2P-1Pe#>Z-!FWr%I5e4>;K< zkrj-X8E>4-o_h@ml*_(yNA?O(_;(r5m_XSWh5wW}(o-gzHWXcN80)Y)aMY7OoPCp! zCK@rzd^IOLG&i20l$@^hb3;$%3vc^pH4BvLBpg0i-+4tg;%(NE$QSS?saXWs6-$0;040p&+9RGi4MQ%3kzcNOl6udG;A&D0BQj54qOnS`pMgH{ zmZa5{+w#w`N8`#(FLZ{8Y))YLeC6jn^hz|2s-OVK5ZlPo(~HhFGEKcfu#2bHdQBCM z$&gTgLP?m>y;J3F?Q-tI$JwXD0ME}CtH3?H%?d@)HjSZX;!8VMgWTOZD@?Ke}f(~g~+Gn4-qe{x*wi(~Ml}(S1>qCqjYAPzfAF~2Pa|`0; zG9p&qGyHXf`&5oNx^h(>FVUsNEa6m~nTG!Wvc?e*_4F1S5WYHe$y*9{Dg388_=K?Z z!hddOn07~rdF#!T@WqfY%7HgfvyR)Jh5%cB%kTyWfKBiJl}WUnHC1S(CFDxIX$!aN zPmoN@{Oy#?D%QD3wqnbkUlbAsG*V*=**A%%Yz`dmC_K4ITW)muuyR$u2eSA0A>q$X z{c4p7D8+vPG=WjS9-h;T-;t`Gp+?51x5*fjQr>fLB;R(u!OrH z_SvH4P)6atUuEE*Yx-U3SUP;zF}mSob|D(Zbg_vz4V~g&o2%AS=Qz&fEl!2czkP8# z9!MxY^I(b2K;xMCYAY_IY3x;4Xs2Fu&mfSCo0Iy{>TXu!k6IG&@5YWFvYFP#9;;64 zG@xrd{`#of+oVTYs(QQ}CQz#V!4yf1l)V?rOSQ4b&IO+a*K%HjH#3!|JQ_cSmfiQ5i_E;Bw+2Sm|x)yF{z3K4r z9xKtbWO%c~gRb%k5@`RZws({p3mUK%%hYC8>hO*mQR=5Gt3F6NrUyD2eJlO_?QA=% z=W8U>n&=hpeOq)jGacX!nR&D3)RVH^^JOd=;$jTgatC29b3e~ipnp(RY^lPd4a1dT zE^nut=uFHOTL!04!u7Rf8A#Tp8cMC$c7A-c{zmJ6l0yL-QT#|R` zZMrYFG3+>MgJ#J;I={~Qtog<;c275r+o93VEzqTFX z?k>MpdGHp(5N9nRNfanS5&62@<34Ip_W8RMvZIpgW54^zT@Ju)vlvZ{>-hYz`{QM2 zTaSg;aX+TKX3f9j!m`t%H%Oq`=jtM8Zd<`_8zxAVl~$zy(Kz`NhQG4N>aWb}Yj1tf z4jt1eu;fMvBY}`?<`KQm{o0Tyqv?Xur6OHwo9&*D@bZ zl3bL$NzEZ{(NyZ)Dq<2o{&nvig{xYw>(? zwk!P!hqkX5y|q=Y*5+c~iIL%#QZ8NGyKLrI*~7loHM<)dWXhS^LTuu8(3vWf`*%Pu z=dYxM26h4~#3^W>H4I(4mVXnu%a-gkS^Q*3w z-KzxozRkmZE5h z>iNwBN6f94w_@W}=dH7xRmaiB19y#ujmYRJ$h_|;LI6;jeUpeNx$;=7Y87=5nFV|g-p=C}h9S>BqMML^CL^Dl z=Y9nyEe-`}ZY62mjb*5$EH!_$dw1OSn;f2dH?)!VX1-h-QM`dmGN9-iz@Icb1-$K9 zCibKo6}P{HW3&bPYa7q=K`|(g#)JqNjI?u?$Iu39jGKoxP}V~qa3ZEhk8Clr4bsxX zTE;J+haMdTy*-s44~=JF@WPjWW3k?rUV*Vj1tRypVWwFB5_Q1{AS z`IDPj>>a?oa}M*00@$1I4---vCF?YJN^9U<7_$-~^{sWIGB`GLxIC7>z6eEWs&TCF z=MfRG1NDVki+!6@^7ZVSf?K@>tgAgZG$@WR`}AbL(f;$uSRrcQd50T=B+L&g%kKHdf!>8 z{um7l)NR{aH}!^cM{y1%s_`aEBMX;xDUZZf|2<(ypIgW)Edd_C&I209>~^s5`J=D> zMT-r_DpRz#l7<@V+jS1cm(7&g*k(3|=Vmp^_O$@cnieSpx)2H>lXQ8=8TzfFDxrbYYsb<8{Se(U zKaVEOFE2IasaLWsoR*j4{;<?{uPtBXTjJd5) zk*a5NqCfA6-W)W6Xq)yJUCb<>ut#CQ#EA2k=52%W-xzwB-+VuysNZT&%}nq4{o#gl z_}~k|&Z@Q~jEU&29AaXJA&me2@xnAYu}!}if>1qMOLz*DEr;HSIlcH8yXb#)B&w0t zAuzh%y0tM5Zoa>(bU&r(Fmq{Tas1HaG;!Y&Bp|aN$vW`cj{9qEg>qWj(9{d@9wv88 z3^T_*x(GOZvWYqiQY0BY4!QV-E$FPmnT6YZT(-6_MM$O$s7@uUs;Jv1Y>Uu7z4;#x zzY0PgR?c@wG5NS8_NU|Mv`j|9nA`5gcxH{F8>Fj&=VFo3P~5ze;Fm0Ols4 zemg|-nVQuSpoxz53 zDHIV+Q{%kNX7Dq$FztM8m+ilCPww2U5~iEj}A$qccU%uZw$F$kM}n%3L=#=f8CBb+;P;1@3R@ z4ydH8lX!C5@l+rgHSl4ZJ%c)XOJqY3#bK#f=_;|uOa=A?x9gSeD zYR~@XhBI;n9BM1G?;qJ=-_7d1e2@mC&{=f#n}Tjvat>j0mck1v#|&6FFJ(WaYBzpJk5SmxZ_$a}V$se_R z7@&_V#^$(P7m{x~>leM)mgOp+($oUB)2*Q(KayL1XFq{Pj-c&l{_L^?RDL&t=G)_h zt<5W9SKJ;-G(b(JO9#)~T3y4t4;TP_(Hh0 zdHN$Efc_*=OyC>Y$s-Qh#CL1aVqf-3Cd-vgSj@fP!Fl4D-n>JKp8|^ie zu6*`{gL=;U>u#<7P^{JFPrP{D{a!!s>f2%VsC7<$K*LL?7{T7TFO4^1v}PS*dXRO~i9^zlC|Q3BOxpKy_#F7Q zX8u`m%Y{Km(1i8u06JRVFY(dVKI^vh(Aqy^F}!E4UmLwzz*|UV{(cZ(VP6*PWEErG zQA28T*OT0NiI#hx5GPu(bY+`w04YL)<$P(E#-_m(EMo~xGyl`Rsx!j0C-5}fECE2hsYlaF9&9vFRz#i;m zgd~MZj|h=I2{A4^m<@FvFVIh_3gG{rZVy#{_c>>i_-P7g!KCsCF(;G3lUMLeNcz5d z!Ls^95bYQ3oXeCr_l3~axz=HB-@qCkcR3$F$MSO_zn28S&V|wB6jMeXnOsdO&Y-R* zW7VRj@lQvmKN;R@<$D|YY@+{iIAVw63E7Czu=ePxcCv%8u(Mmg$~d zQ+lm@IHJn;aR>jqP?$r{1m>`&SZQ}&jp$_4?rgZaFh&+X==U=bqU9Q>+kk5W+}d|< zDOD+P{cHNmM>U64q`5ws(L4$|zMpy+`8jF2_0PVu?G8B#R}cc@I~`3I?fU}LZ%N++ z6^wRZlorUcZ!l3v_*0?~{>2~m$Tk0`-c|->`Qy`shpH}tTWAM*laH~@&N-458_eW8 zDEa(F{}nk$<>ZZu!+{H)jwt)ADlHk?7mT9Di`hhz9=!y^!JMX3$}|0=pooobc}|^i z)!L?+Xng1$8rrY4RFziu0+>p7VYIcq7hh-uujX%7SU_UChu}KUzPx1n*V~Hsh8qf|I%QLHP zrv!fQdi~FmdRaF;&CakGx%@SS4Gw-ev%MK*rQO>3rJo+HW3i39weo`({oP5VLIPUoxqF&O#lK1jxF5K|c32_~V5fAqCy*RZsD8Ds<*K73^&j z5Nc&a(x3(NM!5#9I8sF{*0RlTMc`R1Qg;jmZ2499E_2f7RQp!c0IJBBDd(c#1ukkQ z|1{mJUD_*y@Z_{wR2pO<6)pIowpBK0AwMk;Y)IM<>A-mIAJnC5w+HWW9{;BBZNj(y z3eaA<4%#ds?0bT|dG3iOkY8=BjdZER|78&M;CjqyuFt{Ku;Rx`e$voLvm>Tl4~ z#Z9PHV)bj8^T9oeui#N|D|lSgyEPVrLFb_K-~ul0KExallz&)xuTvJ}yrYkX_7LgX z0J~_cmHOapQZho(M`(I@E+=_#5fxtgObqnm(Fp%O$?NEJ1B;nI{5;}1N8X&2vtOs( zEzUN%Fb$vw_u!Dz|JdJ#FH;}+4XA=@hOH-!(E!EUXjYOlZvtJ1r}!$JL8zgKZCeGg|9!F7$gI^f4QR#Nr`M*U zx|q{d&8W(-pc)dd%w}?9&F0k4H}ggZ8A;0tkRHFyg^6%LB3Xnv~0_cqzPdo{pp8y)*Y(jL)(a0tmp;nm0sSPpkfA*;Dj3c z`Uda)U|3}Zj`so=Z3dt{Y&=|c=AYB1%kvFB9`)$T&`_Jd=h-~Wedjv~4SVJPgg?p3 zv<1s>_AVRu!O`FT1sCfFEq!ilXd+$X$@mdkM=9N0Zu5z7;cCovflKyOmI9DK4k#vB z6bWpo9SlPBAJS|>S>{(<^;4Z!hJXQHgX=A6$THf-?Np2g`nmn9Cv9W6e>3?7MK~PH>7nBQ~#Gnyno7hckQ9HVj z3N4yX-^oq~U%|es6SfAt9J`+Guw{ANo6pE>%N(OWN2%E1APADaPBC)Il-xe)?j=gG zfX8H)?Zqd^q)0Z|1 zu~RB|*;p+o1J4U85aCH78WD4D*<_xuc%-Y18XU#S7x;7MqS64deU$30H2F-TA2{4J z(ebbGqL;nBOZUKccdAA5`)OY}YUwu>#XPrfpf7~cuv&w(cFAst*??_X@eFXm*uVzs z2ttO&o!!6go`^zsIe}5KZ>$?T*1Ya&;@;g^DWNTuPO~kyf6_c47cV`AlTx4Q@Q>r6 zp&wN7Eo)kXIrrPnA4Vn;l?%fHlny13fqeS>FCI@ATD-B)u-r{^&>Xg0=RwNk%bit^ zd>6gWhksP&LF50r+ z!wbGiB`JlF**n)JJ5_T`@;68bqG2T+y}LV-sS96t7uM#O){)vn)ZIvE0Madu-G2yd zG&lZ_8nD##Y$Mw=s*PEviQdK`j>! z>Eo}z2G8Qh|C(?n@AN&VCfy_(x&0*l|7~s)X+Hu|)={rqc^Bd~=XE>f z?^BiRls8}XaQD&J^CjA9b#67BUZ^B!{vv#^06s=u#24nee#mk+x@|O+@-ZAAuyyP} z9ZQ3dw$f5l-E^$6VZe8cO#F*$g|{y|ZnIbBW1^!yoa!|sLtWEiY?oUhfDZ|C%?E$d zb*Xi`>+t~L)Oo#&!u0=53U%1dV$yZS{U)`Vhl>ZVhVIofvLb}ecBjt$Die2b(4^h> zg*53VLY~#MS_rGe+2E{A%)DA$**HqoEbwIaV1FFO-YDogNgFfL7B+Q@G4i}$BvtY1Dp z9b+j*_HJ56dkE3iz}$643Rw|vP^3M08Ro87$Zn7EC~(ims&6x|FYSd8(%cbSJ=&m+ zPT8?loirR3^JrMH+s?%JtStFy?Ppr#*7SP-Ao6s0{=ytC8#%@~YV%C9dkw(5;F{Cn zQlUL<`?LO*V9tJ1BAZ#1nwn>=lV&H+&A>_oC7Q_Wn-Wf;KV7)2h}P~Azg2jYN+Rp8 z8mJWfHFGEh{G@i-O-|c zVPH;|qM)RUMvv^hS*NnyXTQkJGR88k8O!Skuv}srp9IxK?%<)h?9uVbOFJ*@xt^~3 zEWAZ?nwpnbO7jdA9VBp$ZMhMm*3u0c^E7@flc;L?-8(&VF?Dph3Tri& zwP`>9e2gjk{K<9ha-{3utg3h5yp4a)Tg{}N1F`0>2QCEwC<#?PXoRvW(@ew-@Go!j z<8%|!BznRpx?>cv zVtzaicyQw1=?~BynVhS`xG@pF*!=>!{yBO@M=&5TtfhB|zj~TmHOZt+faua^l%p=9 zQP6bpnK|Jwb|_vyQ@^tACIGIQTU}5*K~N9Ltamc0@x6#V7>a!XqobIOg)}UMTDdPw zvJ7x5Zc$>bT1d*Y+C^tJuIgFWUllnz^I1G|@q?-FR5iHWDg5)Zrw=RzfaPEC`1D={ z%E?HYR|mzbW3)QgZldmBo#9C+itLGevWIW)+zao$b$+R7Mj-DyTsN$#=cHf5Z<`hL6#;MZ)?Bvv zxTDlW9FA9==YnheG{Q>zdYG)vWUS^ZyO?wScHQ?UIFv!vuUXYb6sER3QfPbhx^GM> zgzIFN(n%|eQG$nNK+sF{nXYo`ihi*#u7?)BZi^lMy)2}3;G8EpVE%>e!GhV159(ei&h*tp7VpR)5oTo+`rfp zm9>HX`GM^&PTkFw(KDFbeO1xXt83IyDZbNqss+ijV_dV(h)MfUR+l3(q}G{DH!OX7 zg8|B^v;a`YYPXRWA6W3CGZk06vBa8r`5!)jmi>Ne^yI?6qsCurtFiP0LUtH4C-(eb zYvFYpr%&9Vv`fnN%{}S4)%s-DP6gN~kK2wDJb^Nn_$J1NE-_ z{%2SH!0E)`SLoko!M|#uDr(NcUl}E?4PMeFSLdmz3usoXDa{g}3p+Rl&W=wKUOHkt z4i(BIw&nzzGq*^_X>dY3flLp+4cCq; ze3JN!hd{;Ko8nl%%1OMo>-!JMKIoT_CWs2@khh`4vYL5VptuTh#O%WxGG~N6)#rJ? zK!fLTPY{nX!3Rftb#h~AoZEl)_1}rVasJzj z_rU%^`4p_dEQImTVq0ARo65q3is4`!-NgwDKIKr zg!(%x8=QoMp3Nquii1m6Zf|$56h(*ES^ybUEwW*Bi=#ZmxpFLrU;EGU(Ap8yhSs95 zMMe8$39_o8Y4C^W_5OQ66CB8y#4MW7{tM4KU`~^Sd-i;JNuPFsZrrKBxz}FTWWo9e zOg!TL1kGZ#L5}~OAE=z%E0xTuEaPCDL@?$Mdx8+D4$GmEfD@n3zW~J7f#EWMNN-)7 zilUKN=>#-(wo0D~Bpe;TEYE^7nTm#vk_*;XbO`NCkRxa;){fYvG)ojQ( z6W^y$7SkXvd55X0y+ISMJ>+)_pv1oK0{yqH4U1Bhwc2tB-KuQ@x839bbVhVA%fx zf)ZD8laEcT`7*o_F%(x(YXLc}EEulYrvL3>H-F|MqaA-=-6#HvjeBzKdXhOu@B25` za59U-T#aRQASD)Uz1N>iAqt#qBB9q6nmW6c)?mXY-p3(1`qhE^4(r^8GopWTXn7Q8 z0x474F|*5$`LU67)fZzw++)+y?hZY-`uXZ9u)S_@_2R~azZRea8@iWu0~%3G^+q5# zn!$v(=}@WV*3sL)$CW6IeZ!5FU9k)MR5y^?%KDS>zd1hArAu0u-2^~bNJM~@f#KruEIX@90%=@;JKI9_e_M^`gN%}qLH z-{$Cwz6?2L<7^(5J8zi0K`9YTg6*Fcnv~jBw7310LHJWnD9eCk&9cL=yO(8)Olbhd zL1DcT?=<8pIiSY`u@j~T9{}@m+?W}G@b?P6N1j`wWWybHj|{ps$pNGV zvB_6;@6R~5O`9^1nlM}Q3Vkkb&yG??{IH4!CqLMv!yTsA$8WC%?>dA-I8rErY+x=~ zG>az@3(#;50R8=RkYN(DXt=sVq|uXQv9$cytz@~gidOJ6Ng=iv;-*O~C%GnI`rW<& zq%+POD}_s`xNby+xptF`9_W_2=mYApL-C}U^<6w#qIo)BkZ*B0KtD=TKD z@hvXnoZz1)ux?Ojpv#ev;FM)cXszCpkc7{#notI3a>;-~#9OGZH^-(d)`P1P5QriW zo2y*Qt<=NZvL&_qRsLH$6EC?LI=4-ov;PyY_tyw+&Xgm2ozg_HA8-4&IfcF?h9j>!sqV}|VJb#u(CopkTMAqNoD&)wo8!J- zyd$dpvLr?M-%7ufy|C}RygrXqH_fe4O1@-CFh(-vgcFQtp)0Y! zt^$%i*IxWVwJ!6lH)^p{3QxquOy5i&$+*fk!=pV{Jf9K*XTpTl1+yf>~5ylwt?atrjwh(mo>Un zp7#+zx|hXNcbwSe@gDpOje7^6(o!xPCAoT@>>n^3_q@7FO+u|dEn@gr2ly<4iZ@oa z4E)?{7O?@)HH6YR$Nt5dX(o{aG?O;Xq3j0j{s8bUV}W5ea*fXVAH7|GExCN_(b@5k z*66i>;9!;8QJc%fYB4U|pOH!Rb8qtt_rKJwi1>W$>G8?a>xB$K^)8Ko!^&(Eq=!IC zkV##dOF;AMEfI4u!}&h_ zjDb#3BRcZ_M`X>SxXcZ-TqHz2LZ*j_o)-*Q9V)@)VRl_B8A;~xni73IR*rb>zzf$n zAs_qV6HRj1<9X5CT4os#L?H;=0US?LwArDDOvo*r=}EN?3#j}oFm{*W`pJL?T295<_CS7v_u4-q&nfzyCw=?4PqkkX~pij>omsF zN}sj55I#NT)H@b-5#CQ!^pJY$B6(F~UAvWQm(^>jbP#pU*VF+CoUWRK_)i@kb{5KJ z4!x4!VwcRSk$(w-({qK*XvZRoL4Y*Yt{}fEY1O zgZP7@_f$Q=G5;I`Tlx;y5zmyYO?kP~7>6!tT+rWiQ|5BA)7zq)07-UCW43aYVE*kt zQ8rWQS3X$STw`X{h{-?tujvWJ%UjD)mF;&p2Hbbs6W7;X9)ByXJ|EmW z2Uc`IihDftpNIX5b0x3P>>M@auf*{_HVLzn2lO0;vA&t6?Pa1w7yT;bk(0eJV#494M@?YWUoy_XvN;DbkS9D91m zdHuq?SrVdEC}2WJu6)@mjlP`8T?OHsD};kFW&rMQ^+X+UNk87*NT0%~5ssQ0jlsxC zU$}}oz6f0E`_A4-x9C3Ns`)2fm#6}i=62+RN|bPf{;IA4c716N3A;YhT5&ioS|pud zgpZ$fht=aR{=4BS_<2O9ZHIPgw!Q){|#OCna^+UYl5mY1}XCCmi_lU@?n8X8Z z8{UXy<(4ccuuPJ&ei8@!pZ!-_uLKcS`N_Nc$?5nqz$@;8Pl`*|bXRjYsKVdc8f^_}Bk&v-H&HI)!QcV0O^KX+S1-&LO;VR`Bn zl(|I2MD|}6MSr4L-XDtTG*yp9>zr1t1^#>CLBEO4L?^$e37gOTO-kUs`#B$)4axSt zbM-y?v-`a`4Il$>SlQhJB$N*O<5Ntgge}YT4cwfq;2E>^x9*z6S`^h^^fc*v#jIo@ z{j`mGYwfj&`kazK>ayvIZ8P&byiMG~auJr0Tb-0&m|s@`q!Rd$cvuNMUfBKc1)%|7 z2|P@z1kh?SK5t%+TB^yc)c16%vtuzhmgB&~${_SNWX6-&JeAsk@2$yycdYjoJ|FM{ z7FWu%=(JH~?ZaoT<;SOO2)m+xcl;_okmTRB!dP{zWgzXyWE}4vfBpsvz>VUdH95g& zUa1PTTc&m7^juyskHKb3$oCt{tZ0S|T6skQRFF-f*)3pm_TIXlFaOJJiSgX`NE;pN z@fiQV1=1xckjY~HKl$ACR{p^RiU8XZ~o4FfEz+>}wVF62r zzEh=f=T<<|9vCR7-LGbO>t7zES8jObSFmDBHLe(Ll1TAm`r|8E4uOI-4 zVT|wL4X+GLoPf84yG^g_|E=qUq>@mm;`@^|5ZolFMt~|UmOi$Ne zq|lbZMJ8=9 z*=9S;f-ARuNWRQ(`W)t#_1Z0RZ}CU4g-lq&HtxJCrED8uX79s}R+ihd9>;k3&#^MF zp>NQ!zPGg1od?O)51;rs+q8eD<6(dx`zWaX_Z9BCvbAAGzHBLwnwAKTuXN-SOruqn z`xrMZ+2-&LR|W?dX7xldbj%;A@O0qO*)%ZcFq}nyK&eN}HtvNACI4B(rbr5YP6y5R zIVJT7tQ8%9*@B*TY9GpDaJ-4#4`}?f%>!)?!0G=ou}5}yDamSjw_()k*x1o$?iOSY z6HN2oFYxjxJn9j0+QeqV8fl=&l8yCw7-rS#1WCrsdQC>}_qZqORiZo)l82S%hBkm&p+w ziee+{j~FW!ur;HLL=zX!+C$;m^SLKcxHcq@rk#}cy|{7~qN3Wk%-!qx@}oJOQi9b@ zVDi>Tsi7du+RfNFPhXaDsqW1NQwp(@eT@MFH*&~P$30rpB!=dE#B=6QY~3~U;gRDY&;;YPFPgUV@;X9P)jcK3o?OWjTUVvS{DShAw{JW?55Qk$^8 z22pqhDywVtdWDfA(gB2B-{PKBo^$X)ND;M7T;@F}n6(uGpc@U5;ZC z@!Ow$%80v{f%HDL`wrOGxJ>t0?A@badjdHqiGsADXkZuPL}ds zGIcmr7b@iQi@WMbcUxpkg@c56e5wNfOO%(jwPq{floVg&MbbNuAvwZ#6IA`}z-Fxq z*-r}gn${FVP%T!>;rtg_Lr{k(!?f6v^=P-xnXZcJ=%O< z{!r)=EU7hp^c9D_bKPp*B`!8;J*MEq&!hr{tXqX$H(q@-{;XtcIu)IAEkxmKl)4q0 zCoMij%tUA5(I%(%fOaIHq%a3*H}u!Ng>UU)cJXqW@Yw?L4_Eb^w41CM{xMH(w{*Z< zc8WaYpDpa7Vik*ODIaFmZcaj=g$UU2M-Im2*Ujvco=HbDVm2Ml&_EaGr?zW`9CcTB zDGt3tX}&#x;Rp5BP39vE^K6@b%o{{Y^QY;MhD5JD3Ui&9c{EO0ygzW_1j4jsC4 zXq*1qIteyq!erPoeAIC63VDHcuh=(r6fwbplH*M#zzDKu)KfEv-Co3aR@Q>g}t%i=pL3EGC z$Tb8zE)dvVIXKWB=%uauVIr|W37dS1@1?}ek)>RZ$@J!iNTWwoopr=-VpI|tKC#XZ z4za>ZAz3Y7Tv*NP!AH~xXf&qgf>x+#XnKT$Z=~B4?f<#U9|zbk*Nz*#Om)tsSsc>fieS2P|HcI7C<2F~>WYMPD#uT2dVdRRt8A3zO$ zC!aEOZlOi0IJ%fLq!=sfN8uVq9x-iZEoGmiC6IxFzy{Gd995fNDA3)B-jrQVNH5J! z+y=dj#p~WN1-OqZvOPNhx~%6N3U9Q+BB0Q)HQ%3O!8m&~L;wgsD0yRVECcQ021G$3 zAybwu`jhj*Rl!%qK=DD;6unJLNYbnse>N5uPlE6=DBk$Vqy??L1m%0TMBK&)YYn}s zPl$s|wXO_Oz{=B>PU`&?6R{oXDW#~p*_zn(Q&HTI&v#T7Iw6FYr-dN2`ALNmc1Ngi zIbc%U#C^*^dc2#oGlMjo9;&m_FO83P_KH-MmXxw=OvBui0+$K55Z!Y0`I=u<6;$eY z_7_q>!=6E>;T$C6^~Zqzhs--V>y7buzTh#y3oPT@#K`CPYh=gTgP5hf-h;||3S>2& z{FrPNQ)T+)0$Z1j2s8#po89e-(xGZ;aSWH0Q@$5DdCsZ!|Mp$?40#6m(T`APz!7w zm!9$2p409IDVu#4zwdABW%Z>~K;E{d*@=pK0Vw;a_K%zli%*$fWkpkhxkFU*x=1?< zZoTz>&yf{##*6LnGto2<1OAQ6#u`D-psXM%?-0QZPYk8yEcZ(~Z@ETh|B>Xo%X|Kx zZGf{8)^3K5VUEUfj-<@a1wQWgXW{eynsM{xnoK{_Yvf3g`78IYo(ZrYKB z^`(@E_i!TAj}ngYckh`>ZP(IjN)XdBwh)j3?6RR}Ptm#m5*SfpbYfrgl%MJnUzsk+ zVAg?ou(b72-igK6bT6@Qcp64LApD_Ew4&tFtJ5oN%dHodmKE$^dn$b?;e-n-Ro_ZU zz8$U73*KJp=|MNt`Wf^|=<0H7x9`Lq>CA(-b1ft@=%NIz#4$|!*ls3O-`+jZp3 zHXwg)q}wtJ`F)11{#wm^ka@9=wqEC^MA(`zO1R&XQhUbG9tuU*wB-l;e;hbd3;IoX zW41wytnD0}nS-W$OVutE8h(;M)7q*Kkd!=~L<$OvtfJfjoS1vP(HDMK2&;e+Y?N{< zoI-A53V6$m#h7{##Hp$Lt=(GIc{#!8lL}@8_lu@eOkZPhjvDsD0Ycpz^_#0E%$hJc(ycq%VXmCx9lD#$jh^8S4zZS~P2CA2E|I>llDy zu%53=1jsyxA!6HVnPJ(;-;lWa9r_?`QB!Z^$7RAl$I19GM zXtH^bS7Y;cVeC$?j*)u~8cdr0LYCI3{l9s7fbUy1qh*VA`K9DU}{_jl4;EZ{_9663f<@+>ZL-9^!^jVn56h-M8@D z8hag$t~yF-Bfk@wG0pBL2O|O{OwZX&+(k9t)Da}ntpwV}|o=;5Oo&g`Ls1lB7f_Zw{^P2+%0@+Tp zEtQOfKZAfNe(k--RKg)bWfl5DLxf@{#H}XN{878G%CrlJHdX*7g(A}tyYrp(!^_=) ztyNo-GV}{*ZqX)4?q~Py`u+zS$xlFH82!)rjX!5$Yty%WzrJAd<7;*c+)DA@n2>Jd zAd?m3S79KK43LrVr1pw=BMKdMl?q06NFPGxzMbKbZ)rzueceK{d%1m5!>vRjE-QG$6 z$I4VV`pV++r+tGr4l^)VAx;AOK>b`zV6q80_|Jh+AYwdLzCFqs8Tz3-eX=Vhpg~LX z&;sXSksl zu3u?{jYo(sChbAB7Uu&=g6UYgB-&vYrPt5*$hm3Vidfb1@^R-3JPS9LCdr1b!64E2(VZRAkjL2EhbWEDs z@tE!_I(>8$d7^+mj}{#bvGl>*a+64IeIM8?_x<>}QOwkv?PFtt*PkhwIJ0` z9W?>lyfc@#Rvrqf4-*+jNeJHt8Qw7iU0?Ta_EU1d?+oHq#^hs;CSiFAbA+pI1=Qmi zk8b7B9(Q~-Vvo&on@7%hKmniCU4Qw-qZk#$7cM;TRJNF3N3z1vXtI zG*wK~F0R>3+nIGrc$!@^&<-?Bo1|?8{Ly<*Kkkjn|h5bNms89+6*r- zZ~U-SdpY#^J4iHuj;L=$yy2>489i*l{%EYB?OpFXkpr|mOCJ?=!_J-TX`=}lxOd@C zhyK*UxP0t|JQC3|OSGf{c@w*vcv4c#ae<+3quX)cmhz#``-T|Rbv4-WQLvFT&)Xkm z%{7>dRw~^}X-IW^^})!JjVJ*3N6-(WQ+Ok($4!or0}bl^nQCLwC>>C&B?$>BM|W$K zmYthyD(XGP&ib&Y*g8!ymL8BrW6SaS0(D}$;HrzBnaiFJ8?;a#zuY9_II#qZHff*} zq&Y5ki8)eU;wp}fZr3Cn#52DsBv&gJU-Qu8TIc7)FYEB02YqPjFRYe{06e%Bsoo+b z%|VyRlu$(LY(0jnhb?KG7gg>-5#%NT>av?Nxp?+SdC>8o&qKH~U8^&el%t#jfcW zj(XcxSvaQwWc^^43a|s++Tsgp?_?+_?trc9nI*@d?bH^gnGXA32oF|_cer7ld868( zlYg73RK^=75B^RSIjp@qApgcgPwAGzXkS5`Ca46N*0ye~o^Kw4ZU=3@8R)MXWUg#StbP;SHHAQ2x& z48yqrGG*O5&7i*1w>uFIiY)$RJRM&cvWjj{;B=?*c1+4EhHp?Z z{iKXZQ;!6h>c*J1F0~V#t=y)S8DK-{OZ#_cdVROM&`8!1O0U)fZy*7;+7m>^N_yM zmhT%mzd!=ZPeGyR(nQ|td1&%3wbRm0n<R5%)d)7>lPI&!LP%Ybv5+oHNE-UJ{4lc<@=58|+sw+W(x`+(1t6Csi&rnEN zJP&ePGd0EHH}jJ$^tW?L#x>qgyqg#!KiDBN)sFt&4ptL4WjC06qRw;=ocuLY{EICF zl82=km@2EewDyWQ(^g-2SBqRL-h9(=QGW*+)BaGF)3V?fWfW@~Q+l9iS5hU3X!m3D z6Knj`tT+0q^PbZ)3%47pCd68xJ*Xioz!JiL!MyrIS*m{!1*;#PksN#`J<)=A)OyQW5zG9PpW&q>v(Z* zY4NJdRxAs13i11z)eEMXuS|iW8hZx3L^^<9RBKe_;4h4S7#yw#<5RXSFiPI6)w}46RAh zoPLv{K)9tw1GVpv(C&qm$3)qqOcR=$k7{+Fss&0PB18`m&%|$J{6d|-WWxE7J#$+z zU&ijRu;8M{9L^dsI9y|uRnm7bTT14wllxnJFcqJa_As2~L}_mJBa#+zj@aDu7{@~m z79yEghEFQswJ)^nz#Z2UiruE^7ufQ|z4+B~q>X2JJr(6cffs{c7(7N@9)tIl`ZS1P z%N8P{H|R_=7})33Gg3*eu|phH{FS2nF7_z{JuZLq28cgl1(~KLQ!PtB{Ha`7p=mpb zRwh@B5zx>*r(rjKcKp>($!RiJ2|}g_pi6f_Zhuy9;OyG=uXXNAU%Do~Nwy~?=YLmA zO3Y!pkVy;^DsZtf8K`AT8riGVdaaC2SUb&Kew1}PF$eevgeoGL@yFt4>J!C?&RZFt z?>SBZibLkGrtPSclq(r%)a_+jTlPuGRJyyQX2VriHmt?V=Gr9< z`k5P7wZb0TxxiHq@YOMr6QKl@hg$3ZM&2|W%X;PNQ8HkzFL8Id?y}Sb!(rusiFMse z7wPK@Z3AW0C)7T}eJ>=P3X2p&@PUVm^~KzgkvEi| z6Myz_J#355R{5&*?dr@#!B;hxK`sQec|sv0WmL$U_H#8@A}^|(z`j_$Vl9_6v(a_S zn()@@A$1=lJ{ConJ_{-`eE68nuiuUj5@Nr-MVMZa_aTN3s5mtts8H>7nNE()BW3ev zOY89zZs~8wyo8bYl z1IB*{;Ls3^{}8}#0F1vg{7dlf&-1>|dwjDSrILB> zZa!`}9L`-7&X2@aKga7j68j#HWWK}}Z&V;hqmUHTtT4hjjtWVJ0g+CT3P-{U)rw_S zI24C-3ed>ps9d~|r9^ZDg#$yd=nNPehYOu!F({O2FbX8YsTw^S>^)Nl0vZ(?jG>81 zVgnCWYrClBojcgaM%z7Un<)qYA*H)9Otu3mbgzmxb*e$3zf#4?)w| z;0IB1u@vATMi`(GXu(PnLBSp3nk-ZWGwtYk!s!a#%wB%)5|$m>1Yghs-jWPBEF zlC3bn#7NkLq#Ko3KT-mQ$yn{a9LNF0)Uf!7GF=DjQ4yaFVkZQZM#U0P1oRN9kVB^n z=ny2JGHGNAk3r*dICLgcz~BsXMS2se&@16#uI3Y$`9&^^XM`0fVw53>cDQ>|HG(20 zHDUmGJYbesq15Odc95gCLsDU*W;v`98WA1vzBnw+X9Um%WG8m?v;XPz@DVmXj_t>}#f*=W59_gUH)3;r^gy^T zc3sROK1XJ`ZHtago22k$Q}VUB#bTc`GYFF_<(#{MzWpBf4B*l9m!b+VYz(1Bx_W$N z*{qxN6Pi}-o@O~Uy4fG+`CX8? zZOkf~UO)QmTtgbco+ht?j(NPzJ+rnTA>+`WM!)RGukLXhcI+HW^10$aY0R|hEf(iW zdcmOw@m@1Cm%9v3=3iczp*!XI7!lh>A1M5*6KYkFNC`GICk6}EeS1>k+#_wSbDmeLC5-g@fMR1~9S?%ZNE znIU*vT(z{ncuhi|Ru-x(n=f4*4fc$gmP|`%d6szCG~NHvb^8c?>cln|Cu1f)-J_?u zctXO)y)XR&6^G4Z_u9y(Mb3M6^{)wx725A=EW0H=MaMm7|M+rUQ|^=2!m{RWm)Yav zCLH{lxD=j_)-2W>Zyt-WATtEu#Ex@03G< z2OE=~1aO<%qe?vS@qI1_FE4%Jx>?=xs^!AQh41vm?;1whlWP(#%DKW;&ZLuW&&#u~ zhThzNKdvS(YM{K(p2f&)8jKlnETC!RsB){fBy5wUn`FMzyM34M&Dg1@+Aq%_)dk8@ z3z`P~W41iI*w-8E+HX*NaO#1^Qyo+@PtyqX&eSn9CU{y`F<5cl+@V#IKS>EB9#T?_(8-;$P3ydc|ev z21+s%ypqc5%CB;e#O?c|&#JP=K))0U)!)0X`^`%maVzjdX%>-@9GjGoYoHp*W&_X^J;|=TYDV;C6PeFKg3PS_!qio(iQ*! literal 0 HcmV?d00001 diff --git a/examples_extra/InterfaceCoalescing/worst.png b/examples_extra/InterfaceCoalescing/worst.png new file mode 100644 index 0000000000000000000000000000000000000000..78727fa98fafb5e2aeafd07bf0cd3905fad8cdf9 GIT binary patch literal 1147 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1SD^YpWXnZBuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFe_(0UTq__OB&@Hb09I0xZL0)vRD^GUf^&XRs)DJWscy1?p^1XIrJkXw zp^1f|j)IYap|QS!vA&_PuAz~Yfu)t9sR9%z0c|TvNwW%aaf8|g1^l#~=$>Fbx5 zm+O@q>*W`v>l<2HTIw4Z=^Gj80#)c1SLT%@R_NvxE5l51Ni9w;$}A|!%+FH*nV6WA zUs__T1av9H3%LbwWAlok!2}F2{ffi_eM3D1ke6TzeSPsO&CP|YE-nd5MYtEM!Nnn! z1*!T$sm1xFMajU3OH&3}Rbb^@l$uzQUlfv`p92fUfQz*Q}aq-dQ%X3-Eis!rG?xApv^9+MVV!(DQ-pixe8!ETV-N#ip zfO+Dbr;B4q#jTzR{{GDl0!Qn2b90BNuB~6BHqWQ(|%=-%ynrsiauvFqju<;q0O)l8ds=54gman~cy1H1!ERHl8h zc&j3Otl-Vpuv0IJ$|4TWI{DJ>sa2rYzOOr@>bA#JZ9RGS$(Aj*erKIXZuzrf=O+)5 zl}FE8bIudHv-DRI`)Wl+;ffE-Ejf4ltrX&QDtPAcTqFIWypeO6Q_m041O|t!96>|p R|K~x)fv2mV%Q~loCIA+oh!Fq) literal 0 HcmV?d00001 From ed851cd9f22255fad62b7ad812774b14150095f3 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Mon, 9 Apr 2018 17:07:29 -0700 Subject: [PATCH 130/133] Fix hide/exit ordering, investigate thrashing --- Source/ASCollectionView.mm | 9 +++++- Source/ASDisplayNode.mm | 1 + Source/ASRunLoopQueue.mm | 29 ++++++++++--------- Source/Base/ASSignpost.h | 2 +- Source/Details/ASRangeController.mm | 6 ++-- .../contents.xcworkspacedata | 10 +++++++ .../Sample.xcodeproj/project.pbxproj | 5 ++++ .../InterfaceCoalescing/Sample/ICCellNode.m | 9 ++++-- .../Sample/ICDisplayNode.m | 8 +++++ .../InterfaceCoalescing/Sample/Info.plist | 2 +- .../Sample/ViewController.m | 1 - 11 files changed, 60 insertions(+), 22 deletions(-) create mode 100644 examples/ASViewController/Sample.xcworkspace/contents.xcworkspacedata diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index 72d8b78d4..ab8d33509 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -2273,13 +2273,20 @@ - (void)didMoveToWindow { BOOL visible = (self.window != nil); ASDisplayNode *node = self.collectionNode; + BOOL rangeCoontrollerUpdated = NO; if (!visible && node.inHierarchy) { + // Exit CellNodes first before Collection to match UIKit behaviors + if (![node supportsRangeManagedInterfaceState]) { + rangeCoontrollerUpdated = YES; + [_rangeController setNeedsUpdate]; + [_rangeController updateIfNeeded]; + } [node __exitHierarchy]; } // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their // their update in the layout pass - if (![node supportsRangeManagedInterfaceState]) { + if (![node supportsRangeManagedInterfaceState] && !rangeCoontrollerUpdated) { [_rangeController setNeedsUpdate]; [_rangeController updateIfNeeded]; } diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index aa1497ac1..0f221f395 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -3104,6 +3104,7 @@ - (void)setInterfaceState:(ASInterfaceState)newState } else { ASDN::MutexLocker l(__instanceLock__); if (_pendingInterfaceState != newState) { + NSLog(@"pendingInterfaceState: %lu, interfaceState: %lu node: %@", newState, self.interfaceState, self); _pendingInterfaceState = newState; [[ASCATransactionQueue sharedQueue] enqueue:self]; } diff --git a/Source/ASRunLoopQueue.mm b/Source/ASRunLoopQueue.mm index f03dbfb57..a44e42962 100644 --- a/Source/ASRunLoopQueue.mm +++ b/Source/ASRunLoopQueue.mm @@ -550,19 +550,19 @@ - (instancetype)init __unsafe_unretained __typeof__(self) weakSelf = self; void (^handlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { NSLog(@"!!! preCommit"); - while (true) { - for (UIWindow *window in [[UIApplication sharedApplication] windows]) { - if (!window.hidden) { - [window layoutIfNeeded]; - NSLog(@"^^^^ windowLayoutIfNeeded"); - } - } - if (_internalQueue.count > 0) { - [weakSelf processQueue]; - } else { - break; - } - } + [weakSelf processQueue]; +// while (true) { +// UIWindow *window = [[UIApplication sharedApplication] keyWindow]; +// if (!window.hidden) { +// NSLog(@"^^^^ windowLayoutIfNeeded"); +// [window layoutIfNeeded]; +// } +// if (_internalQueue.count > 0) { +// [weakSelf processQueue]; +// } else { +// break; +// } +// } }; void (^postHandlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { ASDN::MutexLocker l(_internalQueueLock); @@ -630,7 +630,7 @@ - (void)processQueue // Mark the queue will end coalescing shortly until after CATransactionCommit. // This will give the queue a chance to apply any further interfaceState changes/enqueue // immediately within current runloop instead of pushing the work to next runloop cycle. -// _CATransactionCommitInProgress = YES; + _CATransactionCommitInProgress = YES; NSInteger internalQueueCount = _internalQueue.count; // Early-exit if the queue is empty. @@ -706,6 +706,7 @@ - (void)enqueue:(id)object } if (!foundObject) { + NSLog(@"^^^^ Adding object to queue: %@", object); [_internalQueue addPointer:(__bridge void *)object]; CFRunLoopSourceSignal(_runLoopSource); diff --git a/Source/Base/ASSignpost.h b/Source/Base/ASSignpost.h index 0ad31ded9..c11a71878 100644 --- a/Source/Base/ASSignpost.h +++ b/Source/Base/ASSignpost.h @@ -48,7 +48,7 @@ static inline ASSignpostColor ASSignpostGetColor(ASSignpostName name, ASSignpost } } -#define AS_KDEBUG_ENABLE 1 //defined(PROFILE) && __has_include() +#define AS_KDEBUG_ENABLE defined(PROFILE) && __has_include() #if AS_KDEBUG_ENABLE diff --git a/Source/Details/ASRangeController.mm b/Source/Details/ASRangeController.mm index f638d9a72..10a3dee79 100644 --- a/Source/Details/ASRangeController.mm +++ b/Source/Details/ASRangeController.mm @@ -366,10 +366,12 @@ - (void)_updateVisibleNodeIndexPaths // In that case we'll just set it back to MeasureLayout. Only set Display | Preload if in allCurrentIndexPaths. interfaceState |= ASInterfaceStateDisplay; } - } - else if ([displayIndexPaths containsObject:indexPath]) { //Propose changes + } else if ([displayIndexPaths containsObject:indexPath]) { //Propose changes interfaceState |= ASInterfaceStatePreload; } + else { + interfaceState = ASInterfaceStateNone; + } } ASCellNode *node = [map elementForItemAtIndexPath:indexPath].nodeIfAllocated; diff --git a/examples/ASViewController/Sample.xcworkspace/contents.xcworkspacedata b/examples/ASViewController/Sample.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..7b5a2f305 --- /dev/null +++ b/examples/ASViewController/Sample.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/examples_extra/InterfaceCoalescing/Sample.xcodeproj/project.pbxproj b/examples_extra/InterfaceCoalescing/Sample.xcodeproj/project.pbxproj index 95e776acf..1c60b6ad7 100644 --- a/examples_extra/InterfaceCoalescing/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/InterfaceCoalescing/Sample.xcodeproj/project.pbxproj @@ -158,6 +158,7 @@ TargetAttributes = { 05E2128019D4DB510098F589 = { CreatedOnToolsVersion = 6.0.1; + DevelopmentTeam = F4M3Z8FSX9; }; }; }; @@ -386,6 +387,7 @@ baseConfigurationReference = 4E60AE604B72B745B8D6B008 /* Pods-Sample.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = F4M3Z8FSX9; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "COCOAPODS=1", @@ -393,6 +395,7 @@ ); INFOPLIST_FILE = Sample/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Samplex; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -402,8 +405,10 @@ baseConfigurationReference = 1F2942882A3B5220B7506FFC /* Pods-Sample.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = F4M3Z8FSX9; INFOPLIST_FILE = Sample/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.facebook.AsyncDisplayKit.Samplex; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; diff --git a/examples_extra/InterfaceCoalescing/Sample/ICCellNode.m b/examples_extra/InterfaceCoalescing/Sample/ICCellNode.m index 3cab6d9b9..fe5512073 100644 --- a/examples_extra/InterfaceCoalescing/Sample/ICCellNode.m +++ b/examples_extra/InterfaceCoalescing/Sample/ICCellNode.m @@ -15,13 +15,14 @@ @implementation ICCellNode { - (instancetype)initWithColor:(UIColor *)color colorName:(NSString *)colorName { if (self = [super init]) { - self.automaticallyManagesSubnodes = NO; +// self.automaticallyManagesSubnodes = NO; _color = color; _colorName = colorName; - self.backgroundColor = _color; _didEnterVisibleCount = 0; _didExitVisibleCount = 0; + + self.backgroundColor = _color; } return self; } @@ -31,6 +32,10 @@ - (void)layout { NSLog(@"^^^^ Layout"); } +- (void)didLoad { + [super didLoad]; +} + - (void)didEnterVisibleState { [super didEnterVisibleState]; self.didEnterVisibleCount += 1; diff --git a/examples_extra/InterfaceCoalescing/Sample/ICDisplayNode.m b/examples_extra/InterfaceCoalescing/Sample/ICDisplayNode.m index 9c29c5c86..bc1d9ac3a 100644 --- a/examples_extra/InterfaceCoalescing/Sample/ICDisplayNode.m +++ b/examples_extra/InterfaceCoalescing/Sample/ICDisplayNode.m @@ -10,6 +10,14 @@ @implementation ICDisplayNode +- (instancetype)init +{ + self = [super init]; + if (self) { + self.backgroundColor = [UIColor whiteColor]; + } + return self; +} - (void)didEnterVisibleState { [super didEnterVisibleState]; NSLog(@"^^^^ didEnterVisibleState %@", self); diff --git a/examples_extra/InterfaceCoalescing/Sample/Info.plist b/examples_extra/InterfaceCoalescing/Sample/Info.plist index 35d842827..fb4115c84 100644 --- a/examples_extra/InterfaceCoalescing/Sample/Info.plist +++ b/examples_extra/InterfaceCoalescing/Sample/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/examples_extra/InterfaceCoalescing/Sample/ViewController.m b/examples_extra/InterfaceCoalescing/Sample/ViewController.m index 8ba8fb543..2cf126297 100644 --- a/examples_extra/InterfaceCoalescing/Sample/ViewController.m +++ b/examples_extra/InterfaceCoalescing/Sample/ViewController.m @@ -105,5 +105,4 @@ - (void)viewDidAppear:(BOOL)animated { NSLog(@"^^^^ viewDidAppear %@", self); } - @end From 9aa8900ccc2c8376ad01f06d712f7d730648fe39 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Mon, 9 Apr 2018 17:56:32 -0700 Subject: [PATCH 131/133] check range mode changes to minimum on collection invisible --- Source/ASRunLoopQueue.mm | 26 +++++++++---------- Source/Details/ASRangeController.mm | 9 +++++-- .../Sample.xcodeproj/project.pbxproj | 4 +-- .../InterfaceCoalescing/Sample/AppDelegate.m | 3 +++ 4 files changed, 25 insertions(+), 17 deletions(-) diff --git a/Source/ASRunLoopQueue.mm b/Source/ASRunLoopQueue.mm index a44e42962..977d23dab 100644 --- a/Source/ASRunLoopQueue.mm +++ b/Source/ASRunLoopQueue.mm @@ -550,19 +550,19 @@ - (instancetype)init __unsafe_unretained __typeof__(self) weakSelf = self; void (^handlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { NSLog(@"!!! preCommit"); - [weakSelf processQueue]; -// while (true) { -// UIWindow *window = [[UIApplication sharedApplication] keyWindow]; -// if (!window.hidden) { -// NSLog(@"^^^^ windowLayoutIfNeeded"); -// [window layoutIfNeeded]; -// } -// if (_internalQueue.count > 0) { -// [weakSelf processQueue]; -// } else { -// break; -// } -// } +// [weakSelf processQueue]; + while (true) { + UIWindow *window = [[UIApplication sharedApplication] keyWindow]; + if (!window.hidden) { + NSLog(@"^^^^ windowLayoutIfNeeded"); + [window layoutIfNeeded]; + } + if (_internalQueue.count > 0) { + [weakSelf processQueue]; + } else { + break; + } + } }; void (^postHandlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { ASDN::MutexLocker l(_internalQueueLock); diff --git a/Source/Details/ASRangeController.mm b/Source/Details/ASRangeController.mm index 10a3dee79..8e67b3cf0 100644 --- a/Source/Details/ASRangeController.mm +++ b/Source/Details/ASRangeController.mm @@ -368,10 +368,15 @@ - (void)_updateVisibleNodeIndexPaths } } else if ([displayIndexPaths containsObject:indexPath]) { //Propose changes interfaceState |= ASInterfaceStatePreload; - } - else { + } else { interfaceState = ASInterfaceStateNone; } + +// else { +// interfaceState |= ASInterfaceStatePreload; +// } + // we may not want this because preserve presload state can avoid loading data with thrash + } ASCellNode *node = [map elementForItemAtIndexPath:indexPath].nodeIfAllocated; diff --git a/examples_extra/InterfaceCoalescing/Sample.xcodeproj/project.pbxproj b/examples_extra/InterfaceCoalescing/Sample.xcodeproj/project.pbxproj index 1c60b6ad7..26859039b 100644 --- a/examples_extra/InterfaceCoalescing/Sample.xcodeproj/project.pbxproj +++ b/examples_extra/InterfaceCoalescing/Sample.xcodeproj/project.pbxproj @@ -340,7 +340,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -375,7 +375,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/examples_extra/InterfaceCoalescing/Sample/AppDelegate.m b/examples_extra/InterfaceCoalescing/Sample/AppDelegate.m index 057264242..c844e2aa0 100644 --- a/examples_extra/InterfaceCoalescing/Sample/AppDelegate.m +++ b/examples_extra/InterfaceCoalescing/Sample/AppDelegate.m @@ -19,6 +19,7 @@ #import "ViewController.h" #import +#import @interface ASConfigurationManager (Testing) + (void)test_resetWithConfiguration:(ASConfiguration *)configuration; @@ -29,6 +30,8 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [ASDisplayNode setShouldShowRangeDebugOverlay:YES]; + ASConfiguration *config = [[ASConfiguration alloc] initWithDictionary:nil]; config.experimentalFeatures |= ASExperimentalInterfaceStateCoalescing; [ASConfigurationManager test_resetWithConfiguration:config]; From 2a721b8fd8618c724dc2afddb4660d58e00530e6 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Mon, 9 Apr 2018 22:42:41 -0700 Subject: [PATCH 132/133] add didEnterHierarchy for PagerNode, fix rang debug bar, refactor --- Source/ASCollectionView.mm | 17 +++++++++-------- Source/ASDisplayNode+Subclasses.h | 6 ++++++ Source/ASDisplayNode.mm | 10 ++++++++++ Source/ASPagerNode.m | 4 ++-- Source/ASTableView.mm | 14 +++++++++++--- Source/Debug/AsyncDisplayKit+Debug.m | 15 +++++++++++---- Source/Details/ASRangeController.h | 5 +++++ Source/Details/ASRangeController.mm | 10 +++++++--- 8 files changed, 61 insertions(+), 20 deletions(-) diff --git a/Source/ASCollectionView.mm b/Source/ASCollectionView.mm index ab8d33509..44a6ca6e0 100644 --- a/Source/ASCollectionView.mm +++ b/Source/ASCollectionView.mm @@ -2273,22 +2273,23 @@ - (void)didMoveToWindow { BOOL visible = (self.window != nil); ASDisplayNode *node = self.collectionNode; - BOOL rangeCoontrollerUpdated = NO; + BOOL rangeControllerUpdated = NO; + if (!visible && node.inHierarchy) { - // Exit CellNodes first before Collection to match UIKit behaviors if (![node supportsRangeManagedInterfaceState]) { - rangeCoontrollerUpdated = YES; - [_rangeController setNeedsUpdate]; - [_rangeController updateIfNeeded]; + rangeControllerUpdated = YES; + // Exit CellNodes first before Collection to match UIKit behaviors (tear down bottom up). + // Although we have not yet cleared the interfaceState's Visible bit (this happens in __exitHierarchy), + // the ASRangeController will get the correct value from -interfaceStateForRangeController:. + [_rangeController updateRanges]; } [node __exitHierarchy]; } // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their // their update in the layout pass - if (![node supportsRangeManagedInterfaceState] && !rangeCoontrollerUpdated) { - [_rangeController setNeedsUpdate]; - [_rangeController updateIfNeeded]; + if (![node supportsRangeManagedInterfaceState] && !rangeControllerUpdated) { + [_rangeController updateRanges]; } // When we aren't visible, we will only fetch up to the visible area. Now that we are visible, diff --git a/Source/ASDisplayNode+Subclasses.h b/Source/ASDisplayNode+Subclasses.h index 3b7b7d0d5..bd8a44190 100644 --- a/Source/ASDisplayNode+Subclasses.h +++ b/Source/ASDisplayNode+Subclasses.h @@ -357,6 +357,12 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)willEnterHierarchy ASDISPLAYNODE_REQUIRES_SUPER; +/** + * Called just after the view is added to a window. + * Note: this may be called multiple times during view controller transitions. To overcome this: use didEnterVisibleState or it's equavalents. + */ +- (void)didEnterHierarchy ASDISPLAYNODE_REQUIRES_SUPER; + /** * Called after the view is removed from the window. */ diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 0f221f395..45337d987 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -2876,6 +2876,8 @@ - (void)__enterHierarchy } __instanceLock__.unlock(); + + [self didEnterHierarchy]; } - (void)__exitHierarchy @@ -2996,6 +2998,14 @@ - (void)willEnterHierarchy } } +- (void)didEnterHierarchy { + ASDisplayNodeAssertMainThread(); + ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"You should never call -willEnterHierarchy directly. Appearance is automatically managed by ASDisplayNode"); + ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); + ASDisplayNodeAssert(_flags.isInHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); + ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__); +} + - (void)didExitHierarchy { ASDisplayNodeAssertMainThread(); diff --git a/Source/ASPagerNode.m b/Source/ASPagerNode.m index 719a45687..72c34df9e 100644 --- a/Source/ASPagerNode.m +++ b/Source/ASPagerNode.m @@ -221,9 +221,9 @@ - (void)proxyTargetHasDeallocated:(ASDelegateProxy *)proxy [self setDelegate:nil]; } -- (void)didEnterVisibleState +- (void)didEnterHierarchy { - [super didEnterVisibleState]; + [super didEnterHierarchy]; // Check that our view controller does not automatically set our content insets // It would be better to have a -didEnterHierarchy hook to put this in, but diff --git a/Source/ASTableView.mm b/Source/ASTableView.mm index 58b3d033d..a9396bd64 100644 --- a/Source/ASTableView.mm +++ b/Source/ASTableView.mm @@ -1906,16 +1906,24 @@ - (void)willMoveToWindow:(UIWindow *)newWindow - (void)didMoveToWindow { BOOL visible = (self.window != nil); + BOOL rangeControllerUpdated = NO; + ASDisplayNode *node = self.tableNode; if (!visible && node.inHierarchy) { + if (![node supportsRangeManagedInterfaceState]) { + rangeControllerUpdated = YES; + // Exit CellNodes first before Collection to match UIKit behaviors (tear down bottom up). + // Although we have not yet cleared the interfaceState's Visible bit (this happens in __exitHierarchy), + // the ASRangeController will get the correct value from -interfaceStateForRangeController:. + [_rangeController updateRanges]; + } [node __exitHierarchy]; } // Updating the visible node index paths only for not range managed nodes. Range managed nodes will get their // their update in the layout pass - if (![node supportsRangeManagedInterfaceState]) { - [_rangeController setNeedsUpdate]; - [_rangeController updateIfNeeded]; + if (![node supportsRangeManagedInterfaceState] && !rangeControllerUpdated) { + [_rangeController updateRanges]; } // When we aren't visible, we will only fetch up to the visible area. Now that we are visible, diff --git a/Source/Debug/AsyncDisplayKit+Debug.m b/Source/Debug/AsyncDisplayKit+Debug.m index add247b14..b6d0d0d4a 100644 --- a/Source/Debug/AsyncDisplayKit+Debug.m +++ b/Source/Debug/AsyncDisplayKit+Debug.m @@ -389,6 +389,8 @@ - (void)layoutToFitAllBarsExcept:(NSInteger)barsToClip if (totalHeight > 0) { totalHeight -= (BAR_THICKNESS * barsToClip); + } else if (totalHeight == 0) { + } if (barsToClip == 0) { @@ -453,6 +455,7 @@ - (void)updateRangeController:(ASRangeController *)controller _ASRangeDebugBarView *viewToUpdate = [self barViewForRangeController:controller]; CGRect boundsRect = self.bounds; + CGRect visibleRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, ASRangeTuningParametersZero, scrollableDirections, scrollDirection); CGRect displayRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, displayTuningParameters, scrollableDirections, scrollDirection); CGRect preloadRect = CGRectExpandToRangeWithScrollableDirections(boundsRect, preloadTuningParameters, scrollableDirections, scrollDirection); @@ -473,7 +476,9 @@ - (void)updateRangeController:(ASRangeController *)controller } if (ASScrollDirectionContainsVerticalDirection(scrollDirection)) { - + if (self.bounds.size.height == 0) { + return; + } if (displayRect.size.height >= preloadRect.size.height) { displayRangeLargerThanPreload = YES; } else { @@ -481,9 +486,9 @@ - (void)updateRangeController:(ASRangeController *)controller } if (displayRangeLargerThanPreload) { - visibleRatio = visibleRect.size.height / displayRect.size.height; + visibleRatio = displayRect.size.height != 0 ? visibleRect.size.height / displayRect.size.height : 1; displayRatio = 1.0; - preloadRatio = preloadRect.size.height / displayRect.size.height; + preloadRatio = displayRect.size.height != 0 ? preloadRect.size.height / displayRect.size.height : 1; } else { visibleRatio = visibleRect.size.height / preloadRect.size.height; displayRatio = displayRect.size.height / preloadRect.size.height; @@ -491,7 +496,9 @@ - (void)updateRangeController:(ASRangeController *)controller } } else { - + if (self.bounds.size.width == 0) { + return; + } if (displayRect.size.width >= preloadRect.size.width) { displayRangeLargerThanPreload = YES; } else { diff --git a/Source/Details/ASRangeController.h b/Source/Details/ASRangeController.h index ca47ef862..912027632 100644 --- a/Source/Details/ASRangeController.h +++ b/Source/Details/ASRangeController.h @@ -64,6 +64,11 @@ AS_SUBCLASSING_RESTRICTED */ - (void)updateIfNeeded; +/** + * Update the ranges immediately. + */ +- (void)updateRanges; + /** * Add the sized node for `indexPath` as a subview of `contentView`. * diff --git a/Source/Details/ASRangeController.mm b/Source/Details/ASRangeController.mm index 8e67b3cf0..898da690a 100644 --- a/Source/Details/ASRangeController.mm +++ b/Source/Details/ASRangeController.mm @@ -154,12 +154,16 @@ - (void)setNeedsUpdate - (void)updateIfNeeded { if (_needsRangeUpdate) { - _needsRangeUpdate = NO; - - [self _updateVisibleNodeIndexPaths]; + [self updateRanges]; } } +- (void)updateRanges +{ + _needsRangeUpdate = NO; + [self _updateVisibleNodeIndexPaths]; +} + - (void)updateCurrentRangeWithMode:(ASLayoutRangeMode)rangeMode { _preserveCurrentRangeMode = YES; From 87af616784d580a7443d310562db6e83a898ec86 Mon Sep 17 00:00:00 2001 From: Max Wang Date: Wed, 11 Apr 2018 22:06:19 -0700 Subject: [PATCH 133/133] hack window --- Source/ASRunLoopQueue.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/ASRunLoopQueue.mm b/Source/ASRunLoopQueue.mm index 977d23dab..99524bec0 100644 --- a/Source/ASRunLoopQueue.mm +++ b/Source/ASRunLoopQueue.mm @@ -26,6 +26,7 @@ #import #import #import +#import #define ASRunLoopQueueLoggingEnabled 0 #define ASRunLoopQueueVerboseLoggingEnabled 0 @@ -552,7 +553,7 @@ - (instancetype)init NSLog(@"!!! preCommit"); // [weakSelf processQueue]; while (true) { - UIWindow *window = [[UIApplication sharedApplication] keyWindow]; + UIWindow *window = [[NSClassFromString(@"UIApplication") sharedApplication] keyWindow]; if (!window.hidden) { NSLog(@"^^^^ windowLayoutIfNeeded"); [window layoutIfNeeded];