Merge pull request #1735 from levi/betterFlatten3

[ASLayout] Clean up flattening process of ASLayout
This commit is contained in:
appleguy 2016-06-11 23:15:16 -07:00 committed by GitHub
commit c211c76c64
8 changed files with 152 additions and 114 deletions

View File

@ -62,7 +62,9 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS
@implementation ASDisplayNode @implementation ASDisplayNode
// these dynamic properties all defined in ASLayoutOptionsPrivate.m // these dynamic properties all defined in ASLayoutOptionsPrivate.m
@dynamic spacingAfter, spacingBefore, flexGrow, flexShrink, flexBasis, alignSelf, ascender, descender, sizeRange, layoutPosition; @dynamic spacingAfter, spacingBefore, flexGrow, flexShrink, flexBasis,
alignSelf, ascender, descender, sizeRange, layoutPosition, layoutableType;
@synthesize name = _name; @synthesize name = _name;
@synthesize preferredFrameSize = _preferredFrameSize; @synthesize preferredFrameSize = _preferredFrameSize;
@synthesize isFinalLayoutable = _isFinalLayoutable; @synthesize isFinalLayoutable = _isFinalLayoutable;
@ -659,6 +661,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
return _layout == nil || _layout.isDirty; return _layout == nil || _layout.isDirty;
} }
- (ASLayoutableType)layoutableType
{
return ASLayoutableTypeDisplayNode;
}
#pragma mark - Layout Transition #pragma mark - Layout Transition
- (void)transitionLayoutWithAnimation:(BOOL)animated - (void)transitionLayoutWithAnimation:(BOOL)animated
@ -1916,13 +1923,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
ASLayoutableValidateLayout(layout); ASLayoutableValidateLayout(layout);
#endif #endif
} }
return [layout flattenedLayoutUsingPredicateBlock:^BOOL(ASLayout *evaluatedLayout) { return [layout filteredNodeLayoutTree];
if (self.usesImplicitHierarchyManagement) {
return ASObjectIsEqual(layout, evaluatedLayout) == NO && [evaluatedLayout.layoutableObject isKindOfClass:[ASDisplayNode class]];
} else {
return [_subnodes containsObject:evaluatedLayout.layoutableObject];
}
}];
} else { } else {
// If neither -layoutSpecThatFits: nor -calculateSizeThatFits: is overridden by subclassses, preferredFrameSize should be used, // If neither -layoutSpecThatFits: nor -calculateSizeThatFits: is overridden by subclassses, preferredFrameSize should be used,
// assume that the default implementation of -calculateSizeThatFits: returns it. // assume that the default implementation of -calculateSizeThatFits: returns it.
@ -2415,7 +2416,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
- (void)__layoutSublayouts - (void)__layoutSublayouts
{ {
for (ASLayout *subnodeLayout in _layout.immediateSublayouts) { for (ASLayout *subnodeLayout in _layout.sublayouts) {
((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [subnodeLayout frame]; ((ASDisplayNode *)subnodeLayout.layoutableObject).frame = [subnodeLayout frame];
} }
} }

View File

@ -31,6 +31,11 @@ extern BOOL CGPointIsNull(CGPoint point);
*/ */
@property (nonatomic, weak, readonly) id<ASLayoutable> layoutableObject; @property (nonatomic, weak, readonly) id<ASLayoutable> layoutableObject;
/**
* The type of ASLayoutable that created this layout
*/
@property (nonatomic, readonly) ASLayoutableType type;
/** /**
* Size of the current layout * Size of the current layout
*/ */
@ -53,38 +58,39 @@ extern BOOL CGPointIsNull(CGPoint point);
*/ */
@property (nonatomic, readonly) NSArray<ASLayout *> *sublayouts; @property (nonatomic, readonly) NSArray<ASLayout *> *sublayouts;
/**
* A list of sublayouts that were not already flattened.
*/
@property (nonatomic, readonly) NSArray<ASLayout *> *immediateSublayouts;
/** /**
* Mark the layout dirty for future regeneration. * Mark the layout dirty for future regeneration.
*/ */
@property (nonatomic, getter=isDirty) BOOL dirty; @property (nonatomic, getter=isDirty) BOOL dirty;
/** /**
* A boolean describing if the current layout has been flattened. * @abstract Returns a valid frame for the current layout computed with the size and position.
* @discussion Clamps the layout's origin or position to 0 if any of the calculated values are infinite.
*/ */
@property (nonatomic, readonly, getter=isFlattened) BOOL flattened; @property (nonatomic, readonly) CGRect frame;
/** /**
* Initializer. * Designated initializer
*/
- (instancetype)initWithLayoutableObject:(id<ASLayoutable>)layoutableObject
constrainedSizeRange:(ASSizeRange)sizeRange
size:(CGSize)size
position:(CGPoint)position
sublayouts:(NSArray *)sublayouts NS_DESIGNATED_INITIALIZER;
/**
* Convenience class initializer for layout construction.
* *
* @param layoutableObject The backing ASLayoutable object. * @param layoutableObject The backing ASLayoutable object.
* * @param size The size of this layout.
* @param size The size of this layout. * @param position The position of this layout within its parent (if available).
* * @param sublayouts Sublayouts belong to the new layout.
* @param position The position of this layout within its parent (if available).
*
* @param sublayouts Sublayouts belong to the new layout.
*/ */
+ (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject + (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject
constrainedSizeRange:(ASSizeRange)sizeRange constrainedSizeRange:(ASSizeRange)sizeRange
size:(CGSize)size size:(CGSize)size
position:(CGPoint)position position:(CGPoint)position
sublayouts:(nullable NSArray<ASLayout *> *)sublayouts sublayouts:(nullable NSArray<ASLayout *> *)sublayouts;
flattened:(BOOL)flattened;
/** /**
* Convenience initializer that has CGPointNull position. * Convenience initializer that has CGPointNull position.
@ -93,9 +99,7 @@ extern BOOL CGPointIsNull(CGPoint point);
* or for creating a sublayout of which the position is yet to be determined. * or for creating a sublayout of which the position is yet to be determined.
* *
* @param layoutableObject The backing ASLayoutable object. * @param layoutableObject The backing ASLayoutable object.
*
* @param size The size of this layout. * @param size The size of this layout.
*
* @param sublayouts Sublayouts belong to the new layout. * @param sublayouts Sublayouts belong to the new layout.
*/ */
+ (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject + (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject
@ -109,7 +113,6 @@ extern BOOL CGPointIsNull(CGPoint point);
* or a sublayout of which the position is yet to be determined. * or a sublayout of which the position is yet to be determined.
* *
* @param layoutableObject The backing ASLayoutable object. * @param layoutableObject The backing ASLayoutable object.
*
* @param size The size of this layout. * @param size The size of this layout.
*/ */
+ (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject + (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject
@ -120,9 +123,7 @@ extern BOOL CGPointIsNull(CGPoint point);
* Convenience initializer that is flattened and has CGPointNull position. * Convenience initializer that is flattened and has CGPointNull position.
* *
* @param layoutableObject The backing ASLayoutable object. * @param layoutableObject The backing ASLayoutable object.
*
* @param size The size of this layout. * @param size The size of this layout.
*
* @param sublayouts Sublayouts belong to the new layout. * @param sublayouts Sublayouts belong to the new layout.
*/ */
+ (instancetype)flattenedLayoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject + (instancetype)flattenedLayoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject
@ -131,22 +132,16 @@ extern BOOL CGPointIsNull(CGPoint point);
sublayouts:(nullable NSArray<ASLayout *> *)sublayouts; sublayouts:(nullable NSArray<ASLayout *> *)sublayouts;
/** /**
* @abstract Evaluates a given predicate block against each object in the receiving layout tree * Convenience initializer that creates a layout based on the values of the given layout, with a new position
* and returns a new, 1-level deep layout containing the objects for which the predicate block returns true. * @param layout The layout to use to create the new layout
* * @param position The position of the new layout
* @param predicateBlock The block is applied to a layout to be evaluated.
* The block takes 1 argument: evaluatedLayout - the layout to be evaluated.
* The block returns YES if evaluatedLayout evaluates to true, otherwise NO.
*
* @return A new, 1-level deep layout containing the layouts for which the predicate block returns true.
*/ */
- (ASLayout *)flattenedLayoutUsingPredicateBlock:(BOOL (^)(ASLayout *evaluatedLayout))predicateBlock; + (instancetype)layoutWithLayout:(ASLayout *)layout position:(CGPoint)position;
/** /**
* @abstract Returns a valid frame for the current layout computed with the size and position. * Traverses the existing layout tree and generates a new tree that represents only ASDisplayNode layouts
* @discussion Clamps the layout's origin or position to 0 if any of the calculated values are infinite.
*/ */
- (CGRect)frame; - (ASLayout *)filteredNodeLayoutTree;
@end @end

View File

@ -9,10 +9,12 @@
*/ */
#import "ASLayout.h" #import "ASLayout.h"
#import "ASAssert.h" #import "ASAssert.h"
#import "ASLayoutSpecUtilities.h"
#import "ASInternalHelpers.h"
#import "ASDimension.h" #import "ASDimension.h"
#import "ASInternalHelpers.h"
#import "ASLayoutSpecUtilities.h"
#import <queue> #import <queue>
CGPoint const CGPointNull = {NAN, NAN}; CGPoint const CGPointNull = {NAN, NAN};
@ -22,25 +24,35 @@ extern BOOL CGPointIsNull(CGPoint point)
return isnan(point.x) && isnan(point.y); return isnan(point.x) && isnan(point.y);
} }
@interface ASLayout ()
/**
* A boolean describing if the current layout has been flattened.
*/
@property (nonatomic, getter=isFlattened) BOOL flattened;
@end
@implementation ASLayout @implementation ASLayout
+ (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject @dynamic frame, type;
constrainedSizeRange:(ASSizeRange)sizeRange
size:(CGSize)size - (instancetype)initWithLayoutableObject:(id<ASLayoutable>)layoutableObject
position:(CGPoint)position constrainedSizeRange:(ASSizeRange)sizeRange
sublayouts:(NSArray *)sublayouts size:(CGSize)size
flattened:(BOOL)flattened position:(CGPoint)position
sublayouts:(NSArray *)sublayouts
{ {
ASDisplayNodeAssert(layoutableObject, @"layoutableObject is required."); self = [super init];
if (self) {
NSParameterAssert(layoutableObject);
#if DEBUG #if DEBUG
for (ASLayout *sublayout in sublayouts) { for (ASLayout *sublayout in sublayouts) {
ASDisplayNodeAssert(!CGPointIsNull(sublayout.position), @"Invalid position is not allowed in sublayout."); ASDisplayNodeAssert(CGPointIsNull(sublayout.position) == NO, @"Invalid position is not allowed in sublayout.");
} }
#endif #endif
ASLayout *l = [super new]; _layoutableObject = layoutableObject;
if (l) {
l->_layoutableObject = layoutableObject;
if (!isValidForLayout(size.width) || !isValidForLayout(size.height)) { if (!isValidForLayout(size.width) || !isValidForLayout(size.height)) {
ASDisplayNodeAssert(NO, @"layoutSize is invalid and unsafe to provide to Core Animation! Production will force to 0, 0. Size = %@, node = %@", NSStringFromCGSize(size), layoutableObject); ASDisplayNodeAssert(NO, @"layoutSize is invalid and unsafe to provide to Core Animation! Production will force to 0, 0. Size = %@, node = %@", NSStringFromCGSize(size), layoutableObject);
@ -48,27 +60,40 @@ extern BOOL CGPointIsNull(CGPoint point)
} else { } else {
size = CGSizeMake(ASCeilPixelValue(size.width), ASCeilPixelValue(size.height)); size = CGSizeMake(ASCeilPixelValue(size.width), ASCeilPixelValue(size.height));
} }
l->_constrainedSizeRange = sizeRange; _constrainedSizeRange = sizeRange;
l->_size = size; _size = size;
l->_dirty = NO; _dirty = NO;
if (CGPointIsNull(position) == NO) { if (CGPointIsNull(position) == NO) {
l->_position = CGPointMake(ASCeilPixelValue(position.x), ASCeilPixelValue(position.y)); _position = CGPointMake(ASCeilPixelValue(position.x), ASCeilPixelValue(position.y));
} else { } else {
l->_position = position; _position = position;
} }
l->_sublayouts = [sublayouts copy]; _sublayouts = sublayouts != nil ? [sublayouts copy] : @[];
l->_flattened = flattened; _flattened = NO;
NSMutableArray<ASLayout *> *result = [NSMutableArray array];
for (ASLayout *sublayout in l->_sublayouts) {
if (!sublayout.isFlattened) {
[result addObject:sublayout];
}
}
l->_immediateSublayouts = result;
} }
return l; return self;
}
- (instancetype)init
{
ASDisplayNodeAssert(NO, @"Use the designated initializer");
return [self init];
}
#pragma mark - Class Constructors
+ (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject
constrainedSizeRange:(ASSizeRange)sizeRange
size:(CGSize)size
position:(CGPoint)position
sublayouts:(NSArray *)sublayouts
{
return [[self alloc] initWithLayoutableObject:layoutableObject
constrainedSizeRange:sizeRange
size:size
position:position
sublayouts:sublayouts];
} }
+ (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject + (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject
@ -80,8 +105,7 @@ extern BOOL CGPointIsNull(CGPoint point)
constrainedSizeRange:sizeRange constrainedSizeRange:sizeRange
size:size size:size
position:CGPointNull position:CGPointNull
sublayouts:sublayouts sublayouts:sublayouts];
flattened:NO];
} }
+ (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject + (instancetype)layoutWithLayoutableObject:(id<ASLayoutable>)layoutableObject
@ -103,53 +127,61 @@ extern BOOL CGPointIsNull(CGPoint point)
constrainedSizeRange:sizeRange constrainedSizeRange:sizeRange
size:size size:size
position:CGPointNull position:CGPointNull
sublayouts:sublayouts sublayouts:sublayouts];
flattened:YES];
} }
- (ASLayout *)flattenedLayoutUsingPredicateBlock:(BOOL (^)(ASLayout *))predicateBlock + (instancetype)layoutWithLayout:(ASLayout *)layout position:(CGPoint)position
{
return [self layoutWithLayoutableObject:layout.layoutableObject
constrainedSizeRange:layout.constrainedSizeRange
size:layout.size
position:position
sublayouts:layout.sublayouts];
}
#pragma mark - Layout Flattening
- (ASLayout *)filteredNodeLayoutTree
{ {
NSMutableArray *flattenedSublayouts = [NSMutableArray array]; NSMutableArray *flattenedSublayouts = [NSMutableArray array];
struct Context { struct Context {
ASLayout *layout; ASLayout *layout;
CGPoint absolutePosition; CGPoint absolutePosition;
BOOL visited;
BOOL flattened;
}; };
// Queue used to keep track of sublayouts while traversing this layout in a BFS fashion. // Queue used to keep track of sublayouts while traversing this layout in a BFS fashion.
std::queue<Context> queue; std::queue<Context> queue;
queue.push({self, CGPointMake(0, 0), NO, NO}); queue.push({self, CGPointMake(0, 0)});
while (!queue.empty()) { while (!queue.empty()) {
Context &context = queue.front(); Context context = queue.front();
if (context.visited) { queue.pop();
queue.pop();
} else { if (self != context.layout && context.layout.type == ASLayoutableTypeDisplayNode) {
context.visited = YES; ASLayout *layout = [ASLayout layoutWithLayout:context.layout position:context.absolutePosition];
layout.flattened = YES;
if (predicateBlock(context.layout)) { [flattenedSublayouts addObject:layout];
[flattenedSublayouts addObject:[ASLayout layoutWithLayoutableObject:context.layout.layoutableObject }
constrainedSizeRange:context.layout.constrainedSizeRange
size:context.layout.size for (ASLayout *sublayout in context.layout.sublayouts) {
position:context.absolutePosition if (sublayout.isFlattened == NO) {
sublayouts:nil queue.push({sublayout, context.absolutePosition + sublayout.position});
flattened:context.flattened]];
}
for (ASLayout *sublayout in context.layout.sublayouts) {
// Mark layout trees that have already been flattened for future identification of immediate sublayouts
BOOL flattened = context.flattened ? : context.layout.flattened;
queue.push({sublayout, context.absolutePosition + sublayout.position, NO, flattened});
} }
} }
} }
return [ASLayout flattenedLayoutWithLayoutableObject:_layoutableObject return [ASLayout layoutWithLayoutableObject:_layoutableObject
constrainedSizeRange:_constrainedSizeRange constrainedSizeRange:_constrainedSizeRange
size:_size size:_size
sublayouts:flattenedSublayouts]; sublayouts:flattenedSublayouts];
}
#pragma mark - Accessors
- (ASLayoutableType)type
{
return _layoutableObject.layoutableType;
} }
- (CGRect)frame - (CGRect)frame

View File

@ -34,7 +34,8 @@
@implementation ASLayoutSpec @implementation ASLayoutSpec
// these dynamic properties all defined in ASLayoutOptionsPrivate.m // these dynamic properties all defined in ASLayoutOptionsPrivate.m
@dynamic spacingAfter, spacingBefore, flexGrow, flexShrink, flexBasis, alignSelf, ascender, descender, sizeRange, layoutPosition; @dynamic spacingAfter, spacingBefore, flexGrow, flexShrink, flexBasis,
alignSelf, ascender, descender, sizeRange, layoutPosition, layoutableType;
@synthesize isFinalLayoutable = _isFinalLayoutable; @synthesize isFinalLayoutable = _isFinalLayoutable;
- (instancetype)init - (instancetype)init
@ -48,6 +49,11 @@
return self; return self;
} }
- (ASLayoutableType)layoutableType
{
return ASLayoutableTypeLayoutSpec;
}
#pragma mark - Layout #pragma mark - Layout
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize

View File

@ -21,6 +21,11 @@
@class ASLayout; @class ASLayout;
@class ASLayoutSpec; @class ASLayoutSpec;
typedef NS_ENUM(NSUInteger, ASLayoutableType) {
ASLayoutableTypeLayoutSpec,
ASLayoutableTypeDisplayNode
};
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
/** /**
@ -41,6 +46,8 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
@protocol ASLayoutable <ASEnvironment, ASStackLayoutable, ASStaticLayoutable, ASLayoutablePrivate, ASLayoutableExtensibility> @protocol ASLayoutable <ASEnvironment, ASStackLayoutable, ASStaticLayoutable, ASLayoutablePrivate, ASLayoutableExtensibility>
@property (nonatomic, readonly) ASLayoutableType layoutableType;
/** /**
* @abstract Calculate a layout based on given size range. * @abstract Calculate a layout based on given size range.
* *
@ -50,7 +57,6 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
- (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize; - (ASLayout *)measureWithSizeRange:(ASSizeRange)constrainedSize;
#pragma mark - Layout options from the Layoutable Protocols #pragma mark - Layout options from the Layoutable Protocols
#pragma mark - ASStackLayoutable #pragma mark - ASStackLayoutable

View File

@ -67,7 +67,7 @@
} }
if (_previousLayout) { if (_previousLayout) {
NSIndexSet *insertions, *deletions; NSIndexSet *insertions, *deletions;
[_previousLayout.immediateSublayouts asdk_diffWithArray:_pendingLayout.immediateSublayouts [_previousLayout.sublayouts asdk_diffWithArray:_pendingLayout.sublayouts
insertions:&insertions insertions:&insertions
deletions:&deletions deletions:&deletions
compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) { compareBlock:^BOOL(ASLayout *lhs, ASLayout *rhs) {
@ -80,7 +80,7 @@
&_removedSubnodes, &_removedSubnodes,
&_removedSubnodePositions); &_removedSubnodePositions);
} else { } else {
NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [_pendingLayout.immediateSublayouts count])]; NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [_pendingLayout.sublayouts count])];
findNodesInLayoutAtIndexes(_pendingLayout, indexes, &_insertedSubnodes, &_insertedSubnodePositions); findNodesInLayoutAtIndexes(_pendingLayout, indexes, &_insertedSubnodes, &_insertedSubnodePositions);
_removedSubnodes = nil; _removedSubnodes = nil;
} }
@ -160,7 +160,7 @@ static inline void findNodesInLayoutAtIndexesWithFilteredNodes(ASLayout *layout,
std::vector<NSUInteger> positions = std::vector<NSUInteger>(); std::vector<NSUInteger> positions = std::vector<NSUInteger>();
NSUInteger idx = [indexes firstIndex]; NSUInteger idx = [indexes firstIndex];
while (idx != NSNotFound) { while (idx != NSNotFound) {
ASDisplayNode *node = (ASDisplayNode *)layout.immediateSublayouts[idx].layoutableObject; ASDisplayNode *node = (ASDisplayNode *)layout.sublayouts[idx].layoutableObject;
ASDisplayNodeCAssert(node, @"A flattened layout must consist exclusively of node sublayouts"); ASDisplayNodeCAssert(node, @"A flattened layout must consist exclusively of node sublayouts");
// Ignore the odd case in which a non-node sublayout is accessed and the type cast fails // Ignore the odd case in which a non-node sublayout is accessed and the type cast fails
if (node != nil) { if (node != nil) {

View File

@ -71,7 +71,7 @@ NSString * const ASTransitionContextToLayoutKey = @"org.asyncdisplaykit.ASTransi
- (NSArray<ASDisplayNode *> *)subnodesForKey:(NSString *)key - (NSArray<ASDisplayNode *> *)subnodesForKey:(NSString *)key
{ {
NSMutableArray<ASDisplayNode *> *subnodes = [NSMutableArray array]; NSMutableArray<ASDisplayNode *> *subnodes = [NSMutableArray array];
for (ASLayout *sublayout in [self layoutForKey:key].immediateSublayouts) { for (ASLayout *sublayout in [self layoutForKey:key].sublayouts) {
[subnodes addObject:(ASDisplayNode *)sublayout.layoutableObject]; [subnodes addObject:(ASDisplayNode *)sublayout.layoutableObject];
} }
return subnodes; return subnodes;

View File

@ -65,9 +65,7 @@
constrainedSizeRange:sizeRange constrainedSizeRange:sizeRange
size:layout.size size:layout.size
sublayouts:@[layout]]; sublayouts:@[layout]];
_layoutUnderTest = [layout flattenedLayoutUsingPredicateBlock:^BOOL(ASLayout *evaluatedLayout) { _layoutUnderTest = [layout filteredNodeLayoutTree];
return [self.subnodes containsObject:(ASDisplayNode *)evaluatedLayout.layoutableObject];
}];
self.frame = CGRectMake(0, 0, _layoutUnderTest.size.width, _layoutUnderTest.size.height); self.frame = CGRectMake(0, 0, _layoutUnderTest.size.width, _layoutUnderTest.size.height);
[self measure:_layoutUnderTest.size]; [self measure:_layoutUnderTest.size];
} }