Masonry source code analysis

Masonry source code analysis

    The core of Masonry is still to use the native NSLayoutConstraint class to add constraints, which makes it more convenient for developers to add constraint layouts through unified encapsulation and chained functional programming.

1. The core View+MASAdditions category

    This class is the core class used in Masonry to add, update and reset constraints. Which provides our most commonly used layout functions. First of all, it can be seen from the category naming that the classes extended by this category are set by macros:

@interface MAS_VIEW (MASAdditions)

The MAS_VIEW macro achieves the function of platform shielding. On iOS, it is UIView, and on MacOS, it is actually NSView.

    A number of layout properties are defined in the MASAdditions category, such as top, bottom, left, right margins, width height, and more. These attributes are abstracted as the MASViewAttribute object, which will be described in detail later.

//左
@property (nonatomic, strong, readonly) MASViewAttribute *mas_left;
//上
@property (nonatomic, strong, readonly) MASViewAttribute *mas_top;
//右
@property (nonatomic, strong, readonly) MASViewAttribute *mas_right;
//下
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottom;
//前
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leading;
//后
@property (nonatomic, strong, readonly) MASViewAttribute *mas_trailing;
//宽
@property (nonatomic, strong, readonly) MASViewAttribute *mas_width;
//高
@property (nonatomic, strong, readonly) MASViewAttribute *mas_height;
//水平中心
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerX;
//垂直中心
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerY;
//基线
@property (nonatomic, strong, readonly) MASViewAttribute *mas_baseline;
//这个是一个链式编程的通用转换方法,使用这个属性将系统的NSLayoutAttribute转换成抽象的MASViewAttribute对象
@property (nonatomic, strong, readonly) MASViewAttribute *(^mas_attribute)(NSLayoutAttribute attr);

//基线相关
@property (nonatomic, strong, readonly) MASViewAttribute *mas_firstBaseline;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_lastBaseline;

//安全区 相关
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuide API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideTop API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideBottom API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideLeft API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideRight API_AVAILABLE(ios(11.0),tvos(11.0));

//关联的key值
@property (nonatomic, strong) id mas_key;

Here are the 3 most commonly used layout methods:

//创建约束
- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
//更新约束
- (NSArray *)mas_updateConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;
//重新创建约束
- (NSArray *)mas_remakeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;

The specific implementations of these three functions are basically the same, and the core processes are: turn off the view Autoresizing feature -> create a constraint generator -> configure the constraint generator -> call back the developer's constraint settings -> load constraints. The difference between these three functions is only in the part of configuring the constraint generator. The updateExisting parameter is configured as YES, which means that the existing constraints are to be updated, and the removeExisting is configured as YES, which means that the constraints are to be recreated. The constraint generator is abstracted as a MASConstraintMaker object, let's look at this class in detail.

2. MASConstraintMaker Constraint Generator

    The MASConstraint class is mainly used to construct constraint objects. Although it is similar to the MASAdditions extension, it also defines the constraint attribute object, but all its Get methods have been reimplemented. When we call the constraint attribute through the Get method, the following core functions will be executed:

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    //创建属性
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    //创建约束对象
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    if ([constraint isKindOfClass:MASViewConstraint.class]) {
        //进行复合
        //replace with composite constraint
        NSArray *children = @[constraint, newConstraint];
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        return compositeConstraint;
    }
    if (!constraint) {
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }
    //将约束对象返回
    return newConstraint;
}

The design of the above function can cleverly implement composite constraints. For example, a constraint such as make.width.height.equalTo(@100) is actually composited into the MASCompositeConstraint object starting from width. After the property of the constraint is created, its value needs to be set. Let's look at the MASViewConstraint object.

3. MASConstraint constraint object

    The MASViewConstraint class inherits from the MASConstraint class, and the MASConstraint class has a subclass called the MASCompositeConstraint class. The basic constraint value setting methods are defined in MASConstraint, all of which are in the form of block callbacks, so chain programming can be performed:

//位置
- (MASConstraint * (^)(MASEdgeInsets insets))insets;
//尺寸偏移
- (MASConstraint * (^)(CGSize offset))sizeOffset;
//中心位置偏移
- (MASConstraint * (^)(CGPoint offset))centerOffset;
//比例 *
- (MASConstraint * (^)(CGFloat multiplier))multipliedBy;
//比例 /
- (MASConstraint * (^)(CGFloat divider))dividedBy;
//优先级
- (MASConstraint * (^)(MASLayoutPriority priority))priority;
//直接设置为低优先级
- (MASConstraint * (^)(void))priorityLow;
//直接设置为中优先级
- (MASConstraint * (^)(void))priorityMedium;
//直接设置为高优先级
- (MASConstraint * (^)(void))priorityHigh;
//设置绝对等于
- (MASConstraint * (^)(id attr))equalTo;
//大于等于
- (MASConstraint * (^)(id attr))greaterThanOrEqualTo;
//小于等于
- (MASConstraint * (^)(id attr))lessThanOrEqualTo;

Read the Get method of this property, you will find that they all return the current object itself, which is also prepared for chain programming. There are two more interesting properties in MASConstraint:

- (MASConstraint *)with;
- (MASConstraint *)and;

These two properties have no actual function and no effect. Their implementation is to return the current object directly to enhance the readability of the code.

    The install and uninstall functions in the MASConstraint class are the core constraint adding methods, which will perform the conversion, addition or deletion of the system's native constraint objects. The core install function is parsed as follows:

- (void)install {
    //如果已经被加载 直接返回
    if (self.hasBeenInstalled) {
        return;
    }
    //如果系统layout对象已经创建 直接添加之后 返回
    if ([self supportsActiveProperty] && self.layoutConstraint) {
        self.layoutConstraint.active = YES;
        [self.firstViewAttribute.view.mas_installedConstraints addObject:self];
        return;
    }
    //获取布局的视图与属性
    MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
    NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
    MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
    NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;
    //如果不是尺寸布局并且 相对视图不存在 默认对父视图进行相对布局
    if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
        secondLayoutItem = self.firstViewAttribute.view.superview;
        secondLayoutAttribute = firstLayoutAttribute;
    }
    //创建布局对象
    MASLayoutConstraint *layoutConstraint
        = [MASLayoutConstraint constraintWithItem:firstLayoutItem
                                        attribute:firstLayoutAttribute
                                        relatedBy:self.layoutRelation
                                           toItem:secondLayoutItem
                                        attribute:secondLayoutAttribute
                                       multiplier:self.layoutMultiplier
                                         constant:self.layoutConstant];
    //设置key和优先级
    layoutConstraint.priority = self.layoutPriority;
    layoutConstraint.mas_key = self.mas_key;
    //设置约束对象对用于的视图
    if (self.secondViewAttribute.view) {
        //获取共同的父视图
        MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
        NSAssert(closestCommonSuperview,
                 @"couldn't find a common superview for %@ and %@",
                 self.firstViewAttribute.view, self.secondViewAttribute.view);
        self.installedView = closestCommonSuperview;
    } else if (self.firstViewAttribute.isSizeAttribute) {
        self.installedView = self.firstViewAttribute.view;
    } else {
        self.installedView = self.firstViewAttribute.view.superview;
    }


    MASLayoutConstraint *existingConstraint = nil;
    //更新约束的操作
    if (self.updateExisting) {
        existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
    } 
    if (existingConstraint) {
        // just update the constant
        existingConstraint.constant = layoutConstraint.constant;
        self.layoutConstraint = existingConstraint;
    } else {
        //添加约束
        [self.installedView addConstraint:layoutConstraint];
        self.layoutConstraint = layoutConstraint;
        [firstLayoutItem.mas_installedConstraints addObject:self];
    }
}

Four, a little trick

    A function in Masonry worth learning, its role is to wrap any type of value with a layer of object, the function is as follows:

//值包装宏
#define MASBoxValue(value) _MASBoxValue(@encode(__typeof__((value))), (value))

static inline id _MASBoxValue(const char *type, ...) {
    va_list v;
    va_start(v, type);
    id obj = nil;
    //进行类型判断 结构体包装成NSValue 基本类型包装成NSNumber
    if (strcmp(type, @encode(id)) == 0) {
        id actual = va_arg(v, id);
        obj = actual;
    } else if (strcmp(type, @encode(CGPoint)) == 0) {
        CGPoint actual = (CGPoint)va_arg(v, CGPoint);
        obj = [NSValue value:&actual withObjCType:type];
    } else if (strcmp(type, @encode(CGSize)) == 0) {
        CGSize actual = (CGSize)va_arg(v, CGSize);
        obj = [NSValue value:&actual withObjCType:type];
    } else if (strcmp(type, @encode(MASEdgeInsets)) == 0) {
        MASEdgeInsets actual = (MASEdgeInsets)va_arg(v, MASEdgeInsets);
        obj = [NSValue value:&actual withObjCType:type];
    } else if (strcmp(type, @encode(double)) == 0) {
        double actual = (double)va_arg(v, double);
        obj = [NSNumber numberWithDouble:actual];
    } else if (strcmp(type, @encode(float)) == 0) {
        float actual = (float)va_arg(v, double);
        obj = [NSNumber numberWithFloat:actual];
    } else if (strcmp(type, @encode(int)) == 0) {
        int actual = (int)va_arg(v, int);
        obj = [NSNumber numberWithInt:actual];
    } else if (strcmp(type, @encode(long)) == 0) {
        long actual = (long)va_arg(v, long);
        obj = [NSNumber numberWithLong:actual];
    } else if (strcmp(type, @encode(long long)) == 0) {
        long long actual = (long long)va_arg(v, long long);
        obj = [NSNumber numberWithLongLong:actual];
    } else if (strcmp(type, @encode(short)) == 0) {
        short actual = (short)va_arg(v, int);
        obj = [NSNumber numberWithShort:actual];
    } else if (strcmp(type, @encode(char)) == 0) {
        char actual = (char)va_arg(v, int);
        obj = [NSNumber numberWithChar:actual];
    } else if (strcmp(type, @encode(bool)) == 0) {
        bool actual = (bool)va_arg(v, int);
        obj = [NSNumber numberWithBool:actual];
    } else if (strcmp(type, @encode(unsigned char)) == 0) {
        unsigned char actual = (unsigned char)va_arg(v, unsigned int);
        obj = [NSNumber numberWithUnsignedChar:actual];
    } else if (strcmp(type, @encode(unsigned int)) == 0) {
        unsigned int actual = (unsigned int)va_arg(v, unsigned int);
        obj = [NSNumber numberWithUnsignedInt:actual];
    } else if (strcmp(type, @encode(unsigned long)) == 0) {
        unsigned long actual = (unsigned long)va_arg(v, unsigned long);
        obj = [NSNumber numberWithUnsignedLong:actual];
    } else if (strcmp(type, @encode(unsigned long long)) == 0) {
        unsigned long long actual = (unsigned long long)va_arg(v, unsigned long long);
        obj = [NSNumber numberWithUnsignedLongLong:actual];
    } else if (strcmp(type, @encode(unsigned short)) == 0) {
        unsigned short actual = (unsigned short)va_arg(v, unsigned int);
        obj = [NSNumber numberWithUnsignedShort:actual];
    }
    va_end(v);
    return obj;
}

Where @encode() is a compile-time feature that converts the incoming type to a standard OC type string.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325161648&siteId=291194637