WIP Standardized property macros

This commit is contained in:
Adlai Holler
2019-05-19 11:18:23 -07:00
parent 0a3a37e8ca
commit 1e1e1d4dc2
6 changed files with 179 additions and 206 deletions
@@ -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 = "<group>"; };
CC2F65EC1E5FFB1600DA57C9 /* ASMutableElementMap.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMutableElementMap.h; sourceTree = "<group>"; };
CC2F65ED1E5FFB1600DA57C9 /* ASMutableElementMap.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMutableElementMap.mm; sourceTree = "<group>"; };
CC31881B2291B0A20065A093 /* ASPropertyMacros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASPropertyMacros.h; sourceTree = "<group>"; };
CC35CEC120DD7F600006448D /* ASCollections.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASCollections.h; sourceTree = "<group>"; };
CC35CEC220DD7F600006448D /* ASCollections.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollections.mm; sourceTree = "<group>"; };
CC35CEC520DD87280006448D /* ASCollectionsTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASCollectionsTests.mm; sourceTree = "<group>"; };
@@ -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 */,
+1
View File
@@ -1489,6 +1489,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
[self _recursivelyTriggerDisplayAndBlock:synchronously];
}
- (void)setShouldBypassEnsureDisplay:(BOOL)shouldBypassEnsureDisplay
{
MutexLocker l(__instanceLock__);
+1 -1
View File
@@ -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.
+15 -26
View File
@@ -15,10 +15,13 @@
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASDisplayNodeInternal.h>
#import <AsyncDisplayKit/ASEqualityHelpers.h>
#import <AsyncDisplayKit/ASGraphicsContext.h>
#import <AsyncDisplayKit/ASInsetLayoutSpec.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/ASLayout.h>
#import <AsyncDisplayKit/ASPropertyMacros.h>
#import <AsyncDisplayKit/ASThread.h>
@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<id<MKAnnotation>> *)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
+98
View File
@@ -0,0 +1,98 @@
//
// ASPropertyMacros.h
// AsyncDisplayKit
//
// Created by Adlai Holler on 5/19/19.
// Copyright © 2019 Pinterest. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <objc/message.h>
#import <AsyncDisplayKit/ASThread.h>
#include <functional>
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 <typename T>
NS_INLINE std::function<void(id, const T&)> 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 <typename T>
NS_INLINE T ObjCGetter(Mutex &mutex, std::function<T()> 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 <typename T>
NS_INLINE bool ObjCSetter(unowned id self,
Mutex &mutex,
std::function<T()> getter,
std::function<void(T)> setter,
const T &value,
std::function<bool(const T& a, const T& b)> is_equal,
std::function<T(const T&val)> copy,
std::function<void(const T&val)> release,
std::function<void(unowned id self, const T &old_value)> side_effect) {
mutex.lock();
const T old_value = getter();
const bool bail = is_equal ? is_equal(old_value, value) : std::equal_to<T>()(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<type>(mutex, [&self]{ return expr; }); \
}
#define AS_SETTER(upperName, lock, type, expr, isEqual, copy, release, sideEffect) \
- (void)set##upperName:(type)newValue { \
AS::ObjCSetter<type>(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<type>(@selector(method)))
#define AS_BASIC_SETTER(upperName, lock, type, expr) \
AS_SETTER(upperName, lock, type, expr, std::equal_to<type>(), nullptr, nullptr, nullptr)
+60 -179
View File
@@ -24,12 +24,15 @@
#import <AsyncDisplayKit/ASEqualityHelpers.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/ASPropertyMacros.h>
#import <AsyncDisplayKit/CoreGraphics+ASConvenience.h>
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
#import <AsyncDisplayKit/ASTextLayout.h>
#import <AsyncDisplayKit/ASThread.h>
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<ASTextLinePositionModifier>)modifier
{
ASLockedSelfCompareAssignObjects(_textContainer.linePositionModifier, modifier);
}
- (id<ASTextLinePositionModifier>)textContainerLinePositionModifier
{
ASLockScopeSelf();
return _textContainer.linePositionModifier;
}
AS_GETTER(textContainerLinePositionModifier, __instanceLock__, id<ASTextLinePositionModifier>,
_textContainer.linePositionModifier);
AS_SETTER_PLUS_METHOD(TextContainerLinePositionModifier, __instanceLock__, id<ASTextLinePositionModifier>, _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<NSNumber *> *)scaleFactors
{
AS_TEXT_ALERT_UNIMPLEMENTED_FEATURE();
ASLockScopeSelf();
if (ASCompareAssignCopy(_pointSizeScaleFactors, scaleFactors)) {
[self setNeedsLayout];
}
}
- (NSArray<NSNumber *> *)pointSizeScaleFactors
{
return ASLockedSelf(_pointSizeScaleFactors);
}
AS_GETTER(pointSizeScaleFactors, __instanceLock__, NSArray<NSNumber *> *, _pointSizeScaleFactors);
AS_SETTER_PLUS_METHOD(PointSizeScaleFactors, __instanceLock__, NSArray<NSNumber *> *, _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
{