diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 4cb42268..b2002689 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -341,6 +341,7 @@ CC224E962066CA6D00BBA57F /* configuration.json in Resources */ = {isa = PBXBuildFile; fileRef = CC224E952066CA6D00BBA57F /* configuration.json */; }; CC2F65EE1E5FFB1600DA57C9 /* ASMutableElementMap.h in Headers */ = {isa = PBXBuildFile; fileRef = CC2F65EC1E5FFB1600DA57C9 /* ASMutableElementMap.h */; }; CC2F65EF1E5FFB1600DA57C9 /* ASMutableElementMap.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC2F65ED1E5FFB1600DA57C9 /* ASMutableElementMap.mm */; }; + CC31881C2291B8380065A093 /* ASPropertyMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = CC31881B2291B0A20065A093 /* ASPropertyMacros.h */; }; CC35CEC320DD7F600006448D /* ASCollections.h in Headers */ = {isa = PBXBuildFile; fileRef = CC35CEC120DD7F600006448D /* ASCollections.h */; settings = {ATTRIBUTES = (Public, ); }; }; CC35CEC420DD7F600006448D /* ASCollections.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC35CEC220DD7F600006448D /* ASCollections.mm */; }; CC35CEC620DD87280006448D /* ASCollectionsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CC35CEC520DD87280006448D /* ASCollectionsTests.mm */; }; @@ -868,6 +869,7 @@ 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.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMutableElementMap.mm; sourceTree = ""; }; + CC31881B2291B0A20065A093 /* ASPropertyMacros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASPropertyMacros.h; sourceTree = ""; }; CC35CEC120DD7F600006448D /* ASCollections.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASCollections.h; sourceTree = ""; }; CC35CEC220DD7F600006448D /* ASCollections.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollections.mm; sourceTree = ""; }; CC35CEC520DD87280006448D /* ASCollectionsTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionsTests.mm; sourceTree = ""; }; @@ -1246,6 +1248,7 @@ E54E00711F1D3828000B30D7 /* ASPagerNode+Beta.h */, A2763D771CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h */, A2763D781CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.mm */, + CC31881B2291B0A20065A093 /* ASPropertyMacros.h */, CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */, D785F6601A74327E00291744 /* ASScrollNode.h */, D785F6611A74327E00291744 /* ASScrollNode.mm */, @@ -1986,6 +1989,7 @@ 68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */, CCCCCCE11EC3EF060087FE10 /* ASTextUtilities.h in Headers */, B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */, + CC31881C2291B8380065A093 /* ASPropertyMacros.h in Headers */, CCDC9B4D200991D10063C1F8 /* ASGraphicsContext.h in Headers */, E5C347B11ECB3D9200EC4BE4 /* ASBatchFetchingDelegate.h in Headers */, CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */, diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index dede5300..22e3fcad 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -1489,6 +1489,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock) [self _recursivelyTriggerDisplayAndBlock:synchronously]; } + - (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay { MutexLocker l(__instanceLock__); diff --git a/Source/ASMapNode.h b/Source/ASMapNode.h index 9ddc2b6f..b789f3fe 100644 --- a/Source/ASMapNode.h +++ b/Source/ASMapNode.h @@ -77,7 +77,7 @@ typedef NS_OPTIONS(NSUInteger, ASMapNodeShowAnnotationsOptions) * @abstract This property specifies how to show the annotations. * @default Default value is ASMapNodeShowAnnotationsIgnored */ -@property ASMapNodeShowAnnotationsOptions showAnnotationsOptions; +@property (nonatomic) ASMapNodeShowAnnotationsOptions showAnnotationsOptions; /** * @abstract The block which should return annotation image for static map based on provided annotation. diff --git a/Source/ASMapNode.mm b/Source/ASMapNode.mm index 554f51a0..cda4a7de 100644 --- a/Source/ASMapNode.mm +++ b/Source/ASMapNode.mm @@ -15,10 +15,13 @@ #import #import +#import +#import #import #import #import #import +#import #import @interface ASMapNode() @@ -26,6 +29,7 @@ MKMapSnapshotter *_snapshotter; BOOL _snapshotAfterLayout; NSArray *_annotations; + ASMapNodeShowAnnotationsOptions _showAnnotationsOptions; } @end @@ -35,7 +39,6 @@ @synthesize mapDelegate = _mapDelegate; @synthesize options = _options; @synthesize liveMap = _liveMap; -@synthesize showAnnotationsOptions = _showAnnotationsOptions; #pragma mark - Lifecycle - (instancetype)init @@ -320,30 +323,19 @@ _mapView = nil; } -- (NSArray *)annotations -{ - ASLockScopeSelf(); - return _annotations; -} - -- (void)setAnnotations:(NSArray *)annotations -{ - annotations = [annotations copy] ? : @[]; - - ASLockScopeSelf(); - _annotations = annotations; +void DidSetAnnotations(ASMapNode *self, NSArray *oldValue) { ASMapNodeShowAnnotationsOptions showAnnotationsOptions = self.showAnnotationsOptions; - if (self.isLiveMap) { - [_mapView removeAnnotations:_mapView.annotations]; - [_mapView addAnnotations:annotations]; + if (self->_liveMap) { + [self->_mapView removeAnnotations:self->_mapView.annotations]; + [self->_mapView addAnnotations:self->_annotations]; if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { BOOL const animated = showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsAnimated; - [_mapView showAnnotations:_mapView.annotations animated:animated]; + [self->_mapView showAnnotations:self->_mapView.annotations animated:animated]; } } else { if (showAnnotationsOptions & ASMapNodeShowAnnotationsOptionsZoomed) { - self.region = [self regionToFitAnnotations:annotations]; + self.region = [self regionToFitAnnotations:self->_annotations]; } else { [self takeSnapshot]; @@ -351,6 +343,9 @@ } } +AS_GETTER(annotations, __instanceLock__, NSArray *, _annotations); +AS_SETTER(Annotations, __instanceLock__, NSArray *, _annotations, ASObjectIsEqual, AS::ObjCCopy, nullptr, DidSetAnnotations); + - (MKCoordinateRegion)regionToFitAnnotations:(NSArray> *)annotations { if([annotations count] == 0) @@ -374,14 +369,8 @@ return region; } --(ASMapNodeShowAnnotationsOptions)showAnnotationsOptions { - return ASLockedSelf(_showAnnotationsOptions); -} - --(void)setShowAnnotationsOptions:(ASMapNodeShowAnnotationsOptions)showAnnotationsOptions { - ASLockScopeSelf(); - _showAnnotationsOptions = showAnnotationsOptions; -} +AS_GETTER(showAnnotationsOptions, __instanceLock__, ASMapNodeShowAnnotationsOptions, _showAnnotationsOptions) +AS_BASIC_SETTER(ShowAnnotationsOptions, __instanceLock__, ASMapNodeShowAnnotationsOptions, _showAnnotationsOptions); #pragma mark - Layout - (void)setSnapshotSizeWithReloadIfNeeded:(CGSize)snapshotSize diff --git a/Source/ASPropertyMacros.h b/Source/ASPropertyMacros.h new file mode 100644 index 00000000..6a94dd96 --- /dev/null +++ b/Source/ASPropertyMacros.h @@ -0,0 +1,98 @@ +// +// ASPropertyMacros.h +// AsyncDisplayKit +// +// Created by Adlai Holler on 5/19/19. +// Copyright © 2019 Pinterest. All rights reserved. +// + +#import +#import + +#import + +#include + +namespace AS { + /** + * Convenience function to wrap a msg-send in a C++ function. An example use + * is to invoke `setNeedsDisplay`. The method must take no argument + * and return no value. + */ + template + NS_INLINE std::function ObjCCall(SEL selector) { + return [selector](id self, __unused const T& old_value) { + auto castMsgSend = (void(*)(id object, SEL s))objc_msgSend; + castMsgSend(self, selector); + }; + } + + NS_INLINE id ObjCCopy(unowned id object) { + return [object copyWithZone:nullptr]; + } + + template + NS_INLINE T ObjCGetter(Mutex &mutex, std::function get) { + mutex.lock(); + T result = get(); + mutex.unlock(); + return result; + } + + /** + * The primitive function for locked setters. + * @param self The object the setter is being called on. + * @param mutex The mutex to lock for the ivar. + * @param getter The + * @param value The new property value. + * @param is_equal Optional custom comparison. + * @param copy Optional custom copy. + * @param release Optional custom release for old value. + * @param side_effect Optional function to call before unlocking. + */ + template + NS_INLINE bool ObjCSetter(unowned id self, + Mutex &mutex, + std::function getter, + std::function setter, + const T &value, + std::function is_equal, + std::function copy, + std::function release, + std::function side_effect) { + mutex.lock(); + const T old_value = getter(); + const bool bail = is_equal ? is_equal(old_value, value) : std::equal_to()(old_value, value); + if (bail) { + mutex.unlock(); + return false; + } + + setter(copy ? copy(value) : value); + if (side_effect) { + side_effect(self, old_value); + } + if (release) { + release(old_value); + } + mutex.unlock(); + return true; + } +} + +#define AS_GETTER(lowerName, mutex, type, expr) \ +- (type)lowerName { \ + return AS::ObjCGetter(mutex, [&self]{ return expr; }); \ +} + +#define AS_SETTER(upperName, lock, type, expr, isEqual, copy, release, sideEffect) \ +- (void)set##upperName:(type)newValue { \ + AS::ObjCSetter(self, lock, [&self]{ return expr; }, [&self](type newValue){ expr = newValue; }, newValue, isEqual, copy, release, sideEffect); \ +} + +#define AS_SETTER_PLUS_METHOD(upperName, lock, type, expr, isEqual, copy, release, method) \ + AS_SETTER(upperName, lock, type, expr, isEqual, copy, release, AS::ObjCCall(@selector(method))) + +#define AS_BASIC_SETTER(upperName, lock, type, expr) \ + AS_SETTER(upperName, lock, type, expr, std::equal_to(), nullptr, nullptr, nullptr) + diff --git a/Source/ASTextNode2.mm b/Source/ASTextNode2.mm index a8f7f248..3db558a2 100644 --- a/Source/ASTextNode2.mm +++ b/Source/ASTextNode2.mm @@ -24,12 +24,15 @@ #import #import +#import #import #import #import #import +using namespace AS; + @interface ASTextCacheValue : NSObject { @package AS::Mutex _m; @@ -315,30 +318,13 @@ static NSArray *DefaultLinkAttributeNames() { #pragma mark - Layout and Sizing -- (void)setTextContainerInset:(UIEdgeInsets)textContainerInset -{ - ASLockScopeSelf(); - if (ASCompareAssignCustom(_textContainer.insets, textContainerInset, UIEdgeInsetsEqualToEdgeInsets)) { - [self setNeedsLayout]; - } -} +AS_GETTER(textContainerInset, __instanceLock__, UIEdgeInsets, _textContainer.insets); +AS_SETTER_PLUS_METHOD(TextContainerInset, __instanceLock__, UIEdgeInsets, _textContainer.insets, + UIEdgeInsetsEqualToEdgeInsets, nullptr, nullptr, setNeedsLayout); -- (UIEdgeInsets)textContainerInset -{ - // textContainer is invariant and has an atomic accessor. - return _textContainer.insets; -} - -- (void)setTextContainerLinePositionModifier:(id)modifier -{ - ASLockedSelfCompareAssignObjects(_textContainer.linePositionModifier, modifier); -} - -- (id)textContainerLinePositionModifier -{ - ASLockScopeSelf(); - return _textContainer.linePositionModifier; -} +AS_GETTER(textContainerLinePositionModifier, __instanceLock__, id, + _textContainer.linePositionModifier); +AS_SETTER_PLUS_METHOD(TextContainerLinePositionModifier, __instanceLock__, id, _textContainer.linePositionModifier, ASObjectIsEqual, ObjCCopy, nullptr, setNeedsLayout); - (CGSize)calculateSizeThatFits:(CGSize)constrainedSize { @@ -745,26 +731,10 @@ static NSArray *DefaultLinkAttributeNames() { #pragma mark - Highlighting -- (ASTextNodeHighlightStyle)highlightStyle -{ - ASLockScopeSelf(); - - return _highlightStyle; -} +AS_GETTER(highlightStyle, __instanceLock__, ASTextNodeHighlightStyle, _highlightStyle); +AS_BASIC_SETTER(HighlightStyle, __instanceLock__, ASTextNodeHighlightStyle, _highlightStyle); -- (void)setHighlightStyle:(ASTextNodeHighlightStyle)highlightStyle -{ - ASLockScopeSelf(); - - _highlightStyle = highlightStyle; -} - -- (NSRange)highlightRange -{ - ASLockScopeSelf(); - - return _highlightRange; -} +AS_GETTER(highlightRange, __instanceLock__, NSRange, _highlightRange); - (void)setHighlightRange:(NSRange)highlightRange { @@ -838,18 +808,11 @@ static NSArray *DefaultLinkAttributeNames() { #pragma mark - Placeholders -- (UIColor *)placeholderColor -{ - return ASLockedSelf(_placeholderColor); -} - -- (void)setPlaceholderColor:(UIColor *)placeholderColor -{ - ASLockScopeSelf(); - if (ASCompareAssignCopy(_placeholderColor, placeholderColor)) { - self.placeholderEnabled = CGColorGetAlpha(placeholderColor.CGColor) > 0; - } +void DidSetPlaceholderColor(ASTextNode2 *self, UIColor *oldValue) { + self.placeholderEnabled = (CGColorGetAlpha(self->_placeholderColor.CGColor) > 0); } +AS_GETTER(placeholderColor, __instanceLock__, UIColor *, _placeholderColor); +AS_SETTER(PlaceholderColor, __instanceLock__, UIColor *, _placeholderColor, ASObjectIsEqual, AS::ObjCCopy, nullptr, DidSetPlaceholderColor); - (UIImage *)placeholderImage { @@ -1020,59 +983,21 @@ static NSArray *DefaultLinkAttributeNames() { * Shadowed text is pretty rare, and we are a framework that targets serious developers. * We should probably ignore these properties and tell developers to set the shadow into their attributed text instead. */ -- (CGColorRef)shadowColor -{ - return ASLockedSelf(_shadowColor); -} +AS_GETTER(shadowColor, __instanceLock__, CGColorRef, _shadowColor); +AS_SETTER_PLUS_METHOD(ShadowColor, __instanceLock__, CGColorRef, _shadowColor, CGColorEqualToColor, + CGColorRetain, CGColorRelease, setNeedsDisplay); -- (void)setShadowColor:(CGColorRef)shadowColor -{ - ASLockScopeSelf(); - if (_shadowColor != shadowColor && CGColorEqualToColor(shadowColor, _shadowColor) == NO) { - CGColorRelease(_shadowColor); - _shadowColor = CGColorRetain(shadowColor); - [self setNeedsDisplay]; - } -} +AS_GETTER(shadowOffset, __instanceLock__, CGSize, _shadowOffset); +AS_SETTER_PLUS_METHOD(ShadowOffset, __instanceLock__, CGSize, _shadowOffset, CGSizeEqualToSize, + nullptr, nullptr, setNeedsDisplay); -- (CGSize)shadowOffset -{ - return ASLockedSelf(_shadowOffset); -} +AS_GETTER(shadowOpacity, __instanceLock__, CGFloat, _shadowOpacity); +AS_SETTER_PLUS_METHOD(ShadowOpacity, __instanceLock__, CGFloat, _shadowOpacity, nullptr, + nullptr, nullptr, setNeedsDisplay); -- (void)setShadowOffset:(CGSize)shadowOffset -{ - ASLockScopeSelf(); - if (ASCompareAssignCustom(_shadowOffset, shadowOffset, CGSizeEqualToSize)) { - [self setNeedsDisplay]; - } -} - -- (CGFloat)shadowOpacity -{ - return ASLockedSelf(_shadowOpacity); -} - -- (void)setShadowOpacity:(CGFloat)shadowOpacity -{ - ASLockScopeSelf(); - if (ASCompareAssign(_shadowOpacity, shadowOpacity)) { - [self setNeedsDisplay]; - } -} - -- (CGFloat)shadowRadius -{ - return ASLockedSelf(_shadowRadius); -} - -- (void)setShadowRadius:(CGFloat)shadowRadius -{ - ASLockScopeSelf(); - if (ASCompareAssign(_shadowRadius, shadowRadius)) { - [self setNeedsDisplay]; - } -} +AS_GETTER(shadowRadius, __instanceLock__, CGFloat, _shadowRadius); +AS_SETTER_PLUS_METHOD(ShadowRadius, __instanceLock__, CGFloat, _shadowRadius, nullptr, nullptr, + nullptr, setNeedsDisplay); - (UIEdgeInsets)shadowPadding { @@ -1080,19 +1005,8 @@ static NSArray *DefaultLinkAttributeNames() { return UIEdgeInsetsZero; } -- (void)setPointSizeScaleFactors:(NSArray *)scaleFactors -{ - AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE(); - ASLockScopeSelf(); - if (ASCompareAssignCopy(_pointSizeScaleFactors, scaleFactors)) { - [self setNeedsLayout]; - } -} - -- (NSArray *)pointSizeScaleFactors -{ - return ASLockedSelf(_pointSizeScaleFactors); -} +AS_GETTER(pointSizeScaleFactors, __instanceLock__, NSArray *, _pointSizeScaleFactors); +AS_SETTER_PLUS_METHOD(PointSizeScaleFactors, __instanceLock__, NSArray *, _pointSizeScaleFactors, ASObjectIsEqual, ObjCCopy, nullptr, setNeedsLayout); #pragma mark - Truncation Message @@ -1114,61 +1028,39 @@ static NSAttributedString *DefaultTruncationAttributedString() } } -- (NSAttributedString *)truncationAttributedText -{ - return ASLockedSelf(_truncationAttributedText); -} +AS_GETTER(truncationAttributedText, __instanceLock__, NSAttributedString *, _truncationAttributedText); +AS_SETTER_PLUS_METHOD(TruncationAttributedText, __instanceLock__, NSAttributedString *, _truncationAttributedText, + ASObjectIsEqual, AS::ObjCCopy, nullptr, _invalidateTruncationText); -- (void)setTruncationAttributedText:(NSAttributedString *)truncationAttributedText +AS_GETTER(additionalTruncationMessage, __instanceLock__, NSAttributedString *, _additionalTruncationMessage); +AS_SETTER_PLUS_METHOD(AdditionalTruncationMessage, __instanceLock__, NSAttributedString *, + _additionalTruncationMessage, ASObjectIsEqual, AS::ObjCCopy, nullptr, _invalidateTruncationText); + +NS_INLINE void DidSetTruncationMode(ASTextNode2 *self, NSLineBreakMode oldValue) { - ASLockScopeSelf(); - if (ASCompareAssignCopy(_truncationAttributedText, truncationAttributedText)) { - [self _invalidateTruncationText]; + ASTextTruncationType truncationType; + switch (self->_truncationMode) { + case NSLineBreakByTruncatingHead: + truncationType = ASTextTruncationTypeStart; + break; + case NSLineBreakByTruncatingTail: + truncationType = ASTextTruncationTypeEnd; + break; + case NSLineBreakByTruncatingMiddle: + truncationType = ASTextTruncationTypeMiddle; + break; + default: + truncationType = ASTextTruncationTypeNone; } + + self->_textContainer.truncationType = truncationType; + + [self setNeedsDisplay]; } -- (NSAttributedString *)additionalTruncationMessage -{ - return ASLockedSelf(_additionalTruncationMessage); -} - -- (void)setAdditionalTruncationMessage:(NSAttributedString *)additionalTruncationMessage -{ - ASLockScopeSelf(); - if (ASCompareAssignCopy(_additionalTruncationMessage, additionalTruncationMessage)) { - [self _invalidateTruncationText]; - } -} - -- (NSLineBreakMode)truncationMode -{ - return ASLockedSelf(_truncationMode); -} - -- (void)setTruncationMode:(NSLineBreakMode)truncationMode -{ - ASLockScopeSelf(); - if (ASCompareAssign(_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]; - } -} +AS_GETTER(truncationMode, __instanceLock__, NSLineBreakMode, _truncationMode); +AS_SETTER(TruncationMode, __instanceLock__, NSLineBreakMode, _truncationMode, nullptr, nullptr, nullptr, + DidSetTruncationMode); - (BOOL)isTruncated { @@ -1187,19 +1079,8 @@ static NSAttributedString *DefaultTruncationAttributedString() return ASTextNodeCompatibleLayoutWithContainerAndText(container, _attributedText); } -- (NSUInteger)maximumNumberOfLines -{ - // _textContainer is invariant and this is just atomic access. - return _textContainer.maximumNumberOfRows; -} - -- (void)setMaximumNumberOfLines:(NSUInteger)maximumNumberOfLines -{ - ASLockScopeSelf(); - if (ASCompareAssign(_textContainer.maximumNumberOfRows, maximumNumberOfLines)) { - [self setNeedsDisplay]; - } -} +AS_GETTER(maximumNumberOfLines, __instanceLock__, NSUInteger, _textContainer.maximumNumberOfRows); +AS_SETTER_PLUS_METHOD(MaximumNumberOfLines, __instanceLock__, NSUInteger, _textContainer.maximumNumberOfRows, nullptr, nullptr, nullptr, setNeedsDisplay); - (NSUInteger)lineCount {