Portal: Chain programming small Demo

This article is an explanation and notes of the source code for the navigation framework. Before learning about navigation, I would like to ask you why this framework is designed: traditional code layout using system API. Then, I would like to explore the navigation call stack according to the syntax of the links I would like to retrieve. Finally, learn and think about the design patterns and chained programming ideas used by the framework.

1. Previous shortcomings: pure code layout of system API

  • System to automatic layout (AutoLayout) API
+(instancetype)constraintWithItem:(id)view1
                       attribute:(NSLayoutAttribute)attr1
                       relatedBy:(NSLayoutRelation)relation
                          toItem:(nullable id)view2
                       attribute:(NSLayoutAttribute)attr2
                      multiplier:(CGFloat)multiplier
                        constant:(CGFloat)c;
Copy the code
  • Traditional code uses system apis for layout
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. self.view.backgroundColor = [UIColor yellowColor]; UIView *subView = [[UIView alloc] init]; subView.backgroundColor = [UIColor redColor]; // Before setting the constraint, add the subView [self.view addSubview:subView]; // Use autoLayout constraint to disallow AutoresizingMask conversion to constraint [subView]setTranslatesAutoresizingMaskIntoConstraints:NO]; / / set the subView relative to the VIEW on the lower left right 40 pixels NSLayoutConstraint * constraintTop = [NSLayoutConstraint constraintWithItem: subView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop Multiplier: 1.0 constant: 40]; NSLayoutConstraint *constraintLeft = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeLeft RelatedBy: NSLayoutRelationEqual toItem: self. The view attribute: NSLayoutAttributeLeft multiplier: 1.0 constant: 40]; // Since the origin of the iOS coordinate system is in the upper left corner, The right margin using negative NSLayoutConstraint * constraintBottom = [NSLayoutConstraint constraintWithItem: subView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom Multiplier: constant: 1.0-40]; NSLayoutConstraint *constraintRight = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeRight RelatedBy: NSLayoutRelationEqual toItem: self. The view attribute: NSLayoutAttributeRight multiplier: 1.0 constant: - 40]; / / will be added to an array of four constraints of NSArray * array = [NSArray arrayWithObjects: constraintTop, constraintLeft, constraintBottom, constraintRight, nil]; // Set constraints to Contraints [self.view addConstraints:array]; }Copy the code

As you can see, the system’s traditional code layout is a bit cumbersome. To simplify the traditional layout code, AutoLayout is packaged by a third-party framework widely used for navigation, and SnapKit for Swift. This article is for the interpretation and learning notes of the source code for navigation. Before that, here is the navigation source code structure:

2. I’ll link you to the next page

2.1 mas_makeConstraints: External call

  • Call example
#import "Masonry.h"
Copy the code
[self.containerView addSubview:self.bannerView]; [self.bannerView mas_makeConstraints:^(MASConstraintMaker *make) { make.leading.equalTo(self.containerView.mas_leading);  make.top.equalTo(self.containerView.mas_top); make.trailing.equalTo(self.containerView.mas_trailing); Make. Height. EqualTo (@ (kViewWidth (131.0)));}];Copy the code

2.2 mas_makeConstraints: Implementation principle, through the imported header file analysis

  • Masonry.h
#import <Foundation/Foundation.h>/ /! Project version numberforMasonry. FOUNDATION_EXPORT double MasonryVersionNumber; / /! Project version stringfor Masonry.
FOUNDATION_EXPORT const unsigned char MasonryVersionString[];

#import "MASUtilities.h"
#import "View+MASAdditions.h"
#import "View+MASShorthandAdditions.h"
#import "ViewController+MASAdditions.h"
#import "NSArray+MASAdditions.h"
#import "NSArray+MASShorthandAdditions.h"
#import "MASConstraint.h"
#import "MASCompositeConstraint.h"
#import "MASViewAttribute.h"
#import "MASViewConstraint.h"
#import "MASConstraintMaker.h"
#import "MASLayoutConstraint.h"
#import "NSLayoutConstraint+MASDebugAdditions.h"
Copy the code

The View+MASAdditions class adds the mas_makeConstraints method to UIView

  • View+MASAdditions.m
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block { self.translatesAutoresizingMaskIntoConstraints =  NO; MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self]; block(constraintMaker);return [constraintMaker install];
}
Copy the code
  • MASConstraintMaker.m
@interface MASConstraintMaker () <MASConstraintDelegate>

@property (nonatomic, weak) MAS_VIEW *view;
@property (nonatomic, strong) NSMutableArray *constraints;

@end
Copy the code
- (id)initWithView:(MAS_VIEW *)view {
    self = [super init];
    if(! self)return nil;
    
    self.view = view;
    self.constraints = NSMutableArray.new;
    
    return self;
}
Copy the code

2.3 .topThrough:MASConstraintMakerClass source code analysis

Examine the first (and only) constraint property set: for example

make.top.equalTo(self.containerView.mas_top);
Copy the code
2.3.1 Analysis of MASConstraintMaker
  • MASConstraintMaker.m
- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}

- (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;
}
Copy the code

The newConstraint returned by this method is an example of the MASViewConstraint class, which in turn is a subclass of MASConstraint. The return type is fine to write as MASConstraint.

If (! The code in constraint). As you can see, you finally set the newConstraint object proxy to self (MASConstraintMaker) and add it to the self.constraints array you prepared at the beginning, returning.

Set the MASConstraintDelegate delegate of the MASViewConstraint newConstraint object to self (MASConstraintMaker) so that multiple constraint properties can be set simultaneously! Chain syntax.

  • MASConstraint+Private.h
@protocol MASConstraintDelegate <NSObject>

/**
 *	Notifies the delegate when the constraint needs to be replaced with another constraint. For example
 *  A MASViewConstraint may turn into a MASCompositeConstraint when an array is passed to one of the equality blocks
 */
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint;

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute;

@end
Copy the code
2.3.2 Continuing analysis of MASConstraintMaker

The masConstraintMaker. m code on Page 2.3.1 initializes the MASViewAttribute object and saves the View, item, and NSLayoutAttribute attributes.

  • MASViewAttribute.m
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute {
    self = [self initWithView:view item:view layoutAttribute:layoutAttribute];
    return self;
}

- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute {
    self = [super init];
    if(! self)return nil;
    
    _view = view;
    _item = item;
    _layoutAttribute = layoutAttribute;
    
    return self;
}
Copy the code

The MASViewConstraint object is then initialized, internally configuring some default parameters and saving the first constraint parameter, MASViewAttribute, as above.

  • MASViewConstraint.m
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute {
    self = [super init];
    if(! self)return nil;
    
    _firstViewAttribute = firstViewAttribute;
    self.layoutPriority = MASLayoutPriorityRequired;
    self.layoutMultiplier = 1;
    
    return self;
}
Copy the code

2.4 .equalTo: through the base classMASConstraintAnd its subclassesMASViewConstraintAnalysis of the

After the first constraint property is set, when you go to.equalto, the previous return is already a MASViewConstraint(inherited from MASConstraint) object, so you call the getter method for the Block property declared and implemented in the base class MASConstraint.

  • MASConstraint.m
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}
Copy the code

The base class MASConstraint only declares and does not implement the equalToWithRelation abstract method. Top, the newConstraint returned by this method is an instance of its subclass MASViewConstraint, so call the equalToWithRelation method implemented by subclass MASViewConstraint:

  • MASViewConstraint.m
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        if([attribute isKindOfClass:NSArray.class]) { NSAssert(! self.hasLayoutRelation, @"Redefinition of constraint relation");
            NSMutableArray *children = NSMutableArray.new;
            for (id attr in attribute) {
                MASViewConstraint *viewConstraint = [self copy];
                viewConstraint.layoutRelation = relation;
                viewConstraint.secondViewAttribute = attr;
                [children addObject:viewConstraint];
            }
            MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
            compositeConstraint.delegate = self.delegate;
            [self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
            return compositeConstraint;
        } else{ NSAssert(! self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
            self.layoutRelation = relation;
            self.secondViewAttribute = attribute;
            returnself; }}; }Copy the code

More code, temporarily can see else {inside the code.

(1) self.layoutRelation = relation;

The first one is that self.layoutRelation holds the constraint and overwrites the set method to use self.haslayoutrelation as a BOOL to indicate that the constraint is there.

  • MASViewConstraint.m
- (void)setLayoutRelation:(NSLayoutRelation)layoutRelation {
    _layoutRelation = layoutRelation;
    self.hasLayoutRelation = YES;
}
Copy the code
(2) self.secondViewAttribute = attribute;

I then overwrite the set method of self.secondViewattribute again, which will do different things for different situations.

- (void)setSecondViewAttribute:(id)secondViewAttribute {
    if ([secondViewAttribute isKindOfClass:NSValue.class]) {
        [self setLayoutConstantWithValue:secondViewAttribute];
    } else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
        _secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
    } else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {
        MASViewAttribute *attr = secondViewAttribute;
        if (attr.layoutAttribute == NSLayoutAttributeNotAnAttribute) {
            _secondViewAttribute = [[MASViewAttribute alloc] initWithView:attr.view item:attr.item layoutAttribute:self.firstViewAttribute.layoutAttribute];;
        } else{ _secondViewAttribute = secondViewAttribute; }}else {
        NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute); }}Copy the code

Where, the first case corresponds to:

Make. Height. EqualTo (@ 20.0 f)Copy the code

When an NSValue is passed in, the constraint offset, centerOffset, sizeOffset, or insets are directly set. The call stack is as follows:

//MASViewConstraint.m
if ([secondViewAttribute isKindOfClass:NSValue.class]) {
    [self setLayoutConstantWithValue:secondViewAttribute];
}
//MASConstraint.m
- (void)setLayoutConstantWithValue:(NSValue *)value {
    if ([value isKindOfClass:NSNumber.class]) {
        self.offset = [(NSNumber *)value doubleValue];
    } else if (strcmp(value.objCType, @encode(CGPoint)) == 0) {
        CGPoint point;
        [value getValue:&point];
        self.centerOffset = point;
    } else if (strcmp(value.objCType, @encode(CGSize)) == 0) {
        CGSize size;
        [value getValue:&size];
        self.sizeOffset = size;
    } else if (strcmp(value.objCType, @encode(MASEdgeInsets)) == 0) {
        MASEdgeInsets insets;
        [value getValue:&insets];
        self.insets = insets;
    } else {
        NSAssert(NO, @"attempting to set layout constant with unsupported value: %@", value);
    }
}
//MASViewConstraint.m
- (void)setOffset:(CGFloat)offset {
    self.layoutConstant = offset;
}
//MASViewConstraint.m
- (void)setLayoutConstant:(CGFloat)layoutConstant {
    _layoutConstant = layoutConstant;
#if TARGET_OS_MAC && ! (TARGET_OS_IPHONE || TARGET_OS_TV)
    if (self.useAnimator) {
        [self.layoutConstraint.animator setConstant:layoutConstant];
    } else {
        self.layoutConstraint.constant = layoutConstant;
    }
#else
    self.layoutConstraint.constant = layoutConstant;
#endif
}
Copy the code

The second case is usually passed directly to a view:

make.top.equalTo(self)
Copy the code

At this point, a MASViewAttribute with the same layoutAttribute as firstViewArribute is initialized, and the code above aligns the view to the top of the view.

In the third case, a view’s MASViewAttribute is passed:

make.top.equalTo(view.mas_bottom);
Copy the code

When you write it this way, it’s usually because the direction of the constraint is different. This line aligns the top of the view with the bottom of the view.

2.5 .height.widthI would like to link it to the next page

  • Call example
make.height.width.equalTo(@20);
Copy the code

. Among them, the first constraint set height properties, is called MASConstraintMaker. M in height, addConstraintWithLayoutAttribute, And – (MASConstraint *)constraint:(MASConstraint *)constraint LayoutAttribute addConstraintWithLayoutAttribute: (NSLayoutAttribute).

  • MASConstraintMaker.m
- (MASConstraint *)height {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight];
}

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}

- (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;
}
Copy the code

The method call stack returns a MASViewConstraint(parent is MASConstraint) object.

So, when we set the second constraint property with.width, we call the.width from the base class masconstraint.m, And then call the subclass MASViewConstraint addConstraintWithLayoutAttribute method of implementation. The call stack is:

  • MASConstraint.m
- (MASConstraint *)width {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth];
}

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute {
    MASMethodNotImplemented();
}
Copy the code
  • MASViewConstraint.m
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { NSAssert(! self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");

    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
Copy the code

What is self.delegate? As described in Section 2.3.1, Masconstraintmaker. m set the MASConstraintDelegate delegate for MASViewConstraint newConstraint object to self. The purpose of this is to be able to set multiple constraint properties at the same time, known as chained syntax. So, the second set constraints with the first set constraint attribute eventually did call the method (are MASConstraintMaker. M implemented addConstraintWithLayoutAttribute).

  • MASConstraintMaker.m
- (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;
}
Copy the code

When the second constraint property is set and executed, we can also see that the constraint is not nil, but a MASViewConstraint object, so the method call stack does not return a MASViewConstraint object, It’s the MASCompositeConstraint object, so let’s look at this class.

2.6 Set of constraints:MASCompositeConstraint

MASCompositeConstraint is a collection of constraints with a private array that holds multiple MASViewAttribute objects.

make.height.width.equalTo(@20)
Copy the code

When we set the second constraint property and go to.width, we end up going:

  • MASConstraintMaker.m
- (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];
        returncompositeConstraint; }... }Copy the code

Wight MASViewConstraint object into the array, create the MASCompositeConstraint object, and also set the delegate. Finally, the MASViewConstraint object added in sell.constraints is replaced with the MASCompositeConstraint object.

#pragma mark - MASConstraintDelegate- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint { NSUInteger index = [self.childConstraints indexOfObject:constraint]; NSAssert(index ! = NSNotFound, @"Could not find constraint %@", constraint);
    [self.childConstraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
Copy the code

In addition, we can click on the MASCompositeConstraint initialization method to see that all MASViewConstraint objects in the array are also set as a delegate through the for loop.

- (id)initWithChildren:(NSArray *)children {
    self = [super init];
    if(! self)return nil;

    _childConstraints = [children mutableCopy];
    for (MASConstraint *constraint in _childConstraints) {
        constraint.delegate = self;
    }

    return self;
}
Copy the code

The purpose of this is also to be able to continue the chain call, for example we set the third constraint property.left

make.height.width.left.equalTo(@20);
Copy the code

The call stack is as follows:

  • MASConstraint.m
- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
Copy the code
  • MASCompositeConstraint.m
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    [self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
    return self;
}

- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    id<MASConstraintDelegate> strongDelegate = self.delegate;
    MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
    newConstraint.delegate = self;
    [self.childConstraints addObject:newConstraint];
    return newConstraint;
}
Copy the code

As you can see, the MASConstraintMaker factory class is again called as a delegate:

  • MASConstraintMaker.m
- (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;
}
Copy the code

At this point, notice that neither if body goes in, unlike the first or second constraint setting. So, this time just initialize the MASViewConstraint object and return it directly, and then go back to the private array sell.childConstraints added to MASCompositeConstraint in the previous method to return the spare.

With regard to.equalto (@20) after the third constraint setting, since.left returns the MASCompositeConstraint object, there is a bit of a change at this stage, the call stack is as follows:

  • MASConstraint.m
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}
Copy the code
  • MASCompositeConstraint.m
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attr, NSLayoutRelation relation) {
        for (MASConstraint *constraint in self.childConstraints.copy) {
            constraint.equalToWithRelation(attr, relation);
        }
        return self;
    };
}
Copy the code

As you can see, this loops through the previously prepared private array self.childConstraints and calls the equalToWithRelation method of MASViewConstraint. M, just like we did above.

2.7 Adding constraints to a view

The mas_makeConstraints method ends by calling the [constraintMaker Install] method to add all the constraints stored in the self.constraints array.

  • MASConstraintMaker.m
 - (NSArray *)install {
    if (self.removeExisting) {
        NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
        for (MASConstraint *constraint in installedConstraints) {
            [constraint uninstall];
        }
    }
    NSArray *constraints = self.constraints.copy;
    for (MASConstraint *constraint in constraints) {
        constraint.updateExisting = self.updateExisting;
        [constraint install];
    }
    [self.constraints removeAllObjects];
    return constraints;
}
Copy the code

If you need to rebuild constraints, calling the mas_remakeConstraints: method removes all constraints from the view and then emptying them all through a for loop with a call to uninstall:

(2). If the constraints do not need to be rebuilt, the constraints prepared in the self.constraints array are fetched and added to the view by calling Install through the for loop.

For install, is an abstract method of the base class MASConstraint, the body of which is implemented by MASViewConstraint or MASCompositeConstraint. The body of the Install method for MASCompositeConstraint is also a call to install implemented by the MASViewConstraint class.

  • MASConstraint.m
- (void)install { MASMethodNotImplemented(); }
Copy the code
  • MASCompositeConstraint.m
- (void)install {
    for (MASConstraint *constraint inself.childConstraints) { constraint.updateExisting = self.updateExisting; [constraint install]; }}Copy the code
  • MASViewConstraint.m

Here is more code, not separate parsing, directly divided into 7 steps to write the source code in the comments, as follows:

- (void)install {// [1] Returns the constraint if it exists and is active.if (self.hasBeenInstalled) {
        return; } // [2] If self.layoutConstraint responds to the isActive method and is not null, this constraint is activated and added to the mas_installedConstraints array, and returns.if ([self supportsActiveProperty] && self.layoutConstraint) {
        self.layoutConstraint.active = YES;
        [self.firstViewAttribute.view.mas_installedConstraints addObject:self];
        return; } // [3] Get the attributes that will be used to initialize the NSLayoutConstraint subclass MASLayoutConstraint. MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item; NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute; MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item; NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute; // alignment attributes must have a secondViewAttribute // therefore we assume that is refering to superview // eg Make.left. EqualTo (@10) // [4] Check if the constraint being added is of type size and self.secondViewattribute is nil, (eg make.left.equalto (@10)) automatically adds the constraint to the superView of the constraint's first argument view.if(! self.firstViewAttribute.isSizeAttribute && ! self.secondViewAttribute) { secondLayoutItem = self.firstViewAttribute.view.superview; secondLayoutAttribute = firstLayoutAttribute; } // [5] The NSLayoutConstraint subclass, MASLayoutConstraint, is then initialized. MASLayoutConstraint *layoutConstraint = [MASLayoutConstraint constraintWithItem:firstLayoutItem attribute:firstLayoutAttribute relatedBy:self.layoutRelation toItem:secondLayoutItem attribute:secondLayoutAttribute multiplier:self.layoutMultiplier constant:self.layoutConstant]; layoutConstraint.priority = self.layoutPriority; layoutConstraint.mas_key = self.mas_key; // [6] This code checks whether there is a view that constrains the second argument, if there is, it looks for the public Superview that constrains the first and second argument views, which is equivalent to finding the least common multiple of two numbers. If the first parameter does not meet the first condition, the constraint will determine whether the first parameter is of type size, if yes, directly fetch its view; If the constraint is not satisfied, it will directly take the first parameter view superview of the constraint.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; } // [7] If the current constraint needs to be upgraded, the original constraint is obtained and replaced with the new constraint, so that there is no need to install the constraint again for the view. If there is no updatable constraint in the original view, then the constraint is added to the installedView found in the previous step. 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]; }}Copy the code

The mas_closestCommonSuperview method in step [6] looks for the common superView of the firstLayoutItem and secondLayoutItem views, which is equivalent to finding the least common multiple of two numbers.

  • View+MASAdditions.m
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {
    MAS_VIEW *closestCommonSuperview = nil;

    MAS_VIEW *secondViewSuperview = view;
    while(! closestCommonSuperview && secondViewSuperview) { MAS_VIEW *firstViewSuperview = self;while(! closestCommonSuperview && firstViewSuperview) {if (secondViewSuperview == firstViewSuperview) {
                closestCommonSuperview = secondViewSuperview;
            }
            firstViewSuperview = firstViewSuperview.superview;
        }
        secondViewSuperview = secondViewSuperview.superview;
    }
    return closestCommonSuperview;
}
Copy the code

4. I’ll retrieve the next thing I’ll retrieve.

3.1 make. Edges. EqualTo (view)

  • example
make.edges.equalTo(view)
Copy the code

Let’s look at this again, the call stack looks like this:

  • MASConstraintMaker.m
- (MASConstraint *)edges {
    return [self addConstraintWithAttributes:MASAttributeTop | MASAttributeLeft | MASAttributeRight | MASAttributeBottom];
}
- (MASConstraint *)addConstraintWithAttributes:(MASAttribute)attrs {
    __unused MASAttribute anyAttribute = (MASAttributeLeft | MASAttributeRight | MASAttributeTop | MASAttributeBottom | MASAttributeLeading
                                          | MASAttributeTrailing | MASAttributeWidth | MASAttributeHeight | MASAttributeCenterX
                                          | MASAttributeCenterY | 
                                          
                    ......
                        
    NSMutableArray *attributes = [NSMutableArray array];
    
    if (attrs & MASAttributeLeft) [attributes addObject:self.view.mas_left];
    if (attrs & MASAttributeRight) [attributes addObject:self.view.mas_right];
    if(attrs & MASAttributeTop) [attributes addObject:self.view.mas_top]; . NSMutableArray *children = [NSMutableArray arrayWithCapacity:attributes.count];for (MASViewAttribute *a in attributes) {
        [children addObject:[[MASViewConstraint alloc] initWithFirstViewAttribute:a]];
    }
    
    MASCompositeConstraint *constraint = [[MASCompositeConstraint alloc] initWithChildren:children];
    constraint.delegate = self;
    [self.constraints addObject:constraint];
    return constraint;
}
Copy the code

Too much code is omitted, as you can see that this code simply returns a MASCompositeConstraint object with multiple constraints, and the rest of the operations are the same.

EqualTo (UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f));

The example in 3.1 above could also be written like this:

Make. Edges. EqualTo (UIEdgeInsetsMake (0.0 f, f 0.0, 0.0 f, 0.0 f));Copy the code

Note that equalTo here is a macro defined in masconstraint.h:

  • MASConstraint.h
#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
#define mas_greaterThanOrEqualTo(...) greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_lessThanOrEqualTo(...) lessThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_offset(...) valueOffset(MASBoxValue((__VA_ARGS__)))
#ifdef MAS_SHORTHAND_GLOBALS
#define equalTo(...) mas_equalTo(__VA_ARGS__)
#define greaterThanOrEqualTo(...) mas_greaterThanOrEqualTo(__VA_ARGS__)
#define lessThanOrEqualTo(...) mas_lessThanOrEqualTo(__VA_ARGS__)
#define offset(...) mas_offset(__VA_ARGS__)
Copy the code

Substituting the above macro definition, the previous code is equivalent to:

Make. Edges. EqualTo (MASBoxValue (UIEdgeInsetsMake (0.0 f to 0.0 f to 0.0 f to 0.0 f)));Copy the code

MASBoxValue is a macro that wraps some basic data structures in C and Objective-C like double CGPoint CGSize in NSValue.

It also supports direct calls to size, center, etc.

make.center.equalTo(CGPointMake(0, 50));
make.size.equalTo(CGSizeMake(200, 100));
Copy the code

3.3 make. Height. EqualTo (@ [redView, blueView])

make.height.equalTo(@[redView, blueView])
Copy the code

When you get to.equalto, you end up calling the equalToWithRelation method in MASViewConstraint. M

  • MASConstraint.m
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}
Copy the code
  • MASViewConstraint.m
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        if([attribute isKindOfClass:NSArray.class]) { NSAssert(! self.hasLayoutRelation, @"Redefinition of constraint relation");
            NSMutableArray *children = NSMutableArray.new;
            for (id attr in attribute) {
                MASViewConstraint *viewConstraint = [self copy];
                viewConstraint.layoutRelation = relation;
                viewConstraint.secondViewAttribute = attr;
                [children addObject:viewConstraint];
            }
            MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
            compositeConstraint.delegate = self.delegate;
            [self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
            return compositeConstraint;
        } else{... }}; }Copy the code

The MASViewConstraint implements the NSCopying protocol. Calling [self copy] creates the MASViewConstraint object

- (id)copyWithZone:(NSZone __unused *)zone {
    MASViewConstraint *constraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:self.firstViewAttribute];
    constraint.layoutConstant = self.layoutConstant;
    constraint.layoutRelation = self.layoutRelation;
    constraint.layoutPriority = self.layoutPriority;
    constraint.layoutMultiplier = self.layoutMultiplier;
    constraint.delegate = self.delegate;
    return constraint;
}
Copy the code

It then performs different operations based on the Value type in the passed array.

- (void)setSecondViewAttribute:(id)secondViewAttribute {
    if ([secondViewAttribute isKindOfClass:NSValue.class]) {
        [self setLayoutConstantWithValue:secondViewAttribute];
    } else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
        _secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
    } else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {
        _secondViewAttribute = secondViewAttribute;
    } else {
        NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute); }}Copy the code

Finally, the MASCompositeConstraint object is generated and the MASConstraintMaker method is called as a delegate to replace the constraints in the self.constraints array:

- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint { NSUInteger index = [self.constraints indexOfObject:constraint]; NSAssert(index ! = NSNotFound, @"Could not find constraint %@", constraint);
    [self.constraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
Copy the code

4. Learning inspiration from the source code of the framework

4.1 Simplified design pattern: Factory class & Factory method

The MASConstraintMaker class is a factory class that creates objects of type MASConstraint (dependent on the MASConstraint interface, not the implementation). In UIView’s View+MASAdditions category are some of the methods in the MASConstraintMaker class that we’re calling. The argument to the Block in the mas_makeConstraints method is the MASConstraintMaker object when we add a constraint to the subView using the navigation above. The user can specify the constraint to add to the View and the value of the constraint through the MASConstraintMaker object called back from the Block. The Constraints property array in this factory records all MASConstraint objects created by the factory.

MASConstraintMaker is a constraint factory class because the MASConstraintMaker assignment creates the NSLayoutConstraint object, Since the NSLayoutConstraint class is further encapsulated as MASViewConstraint, MASConstraintMaker is the object responsible for creating the MASViewConstraint. Call the Install method of the MASViewConstraint object to add the constraint to the appropriate view.

With that said, to summarize, if you call Maker. top, maker.left, etc., each of these methods will call the factory method below to create the corresponding MASViewConstraint object, recorded in the factory object’s constraint array. This is done by specifying the current factory object MASConstraintMaker as the proxy for the MASViewConstraint object. So one MASViewConstraint object can call the factory method through the proxy to create a new MASViewConstraint object, where the proxy mode is used.

Role analysis

  • Client: UIView, acted by categorizing View+MASAdditions

  • Factory: MASConstraintMaker

  • Abstract product: MASConstraint

  • Specific products: MASCompositeConstraint

4.2 Real design mode: combination mode

To put it another way, I would like to say that IT will not be a simple factory mode, but a Composite design mode, which can be translated into Chinese.

2 classicPortfolio modelParticipants in:
Client
  • Manipulate composite objects through the Component interface.
Component
  • Declare interfaces for objects in a composition.
  • Where appropriate, implement the default behavior of interfaces common to all classes
  • Declare an interface for accessing and managing Component children.
  • Define an interface in a recursive structure to access a parent part and implement it as appropriate.
Leaf
  • Represents a leaf node object in a composition that has no child nodes.
  • Defines the behavior of primitive objects in a composition.
Composite
  • Defines the behavior of those parts that have child parts.
  • Implement actions related to child parts in the Composite interface.
4.2.2 fromPortfolio modelFrom the point of view ofMasonryRole analysis in the framework:

UIView, call navigation by sorting View+MASAdditions

Client
  • MASConstraintMaker
Component
  • MASConstraint
Leaf
  • MASViewConstraint
Composite
  • MASCompositeConstraint

4.3 Programming idea: chain programming

Objective-c is a dynamic language that uses a dynamic messaging mechanism where an object or class invokes a method. The point syntax in OC can only apply to class properties through setter and getter methods, not to methods. Chained syntax can only be implemented through getter methods like block properties.

Chained programming idea: The core idea is to take a block as the return value of a method, and return the type of the caller itself, and return the method in the form of setter, so that continuous calls can be realized, that is, chained programming.

[Example] Simple use of chain programming ideas to achieve a simple calculator function:

4.3.1 Create a new class named CaculateMaker for calculation.

4.3.2 Declare a method add in CaculateMaker. H:
  • CaculateMaker.h
//  CaculateMaker.h
//  ChainBlockTestApp

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface CaculateMaker : NSObject

@property (nonatomic, assign) CGFloat result;

- (CaculateMaker *(^)(CGFloat num))add;

@end
Copy the code
4.3.3 Implement this method in CaculateMaker. M file:
  • CaculateMaker.m
//  CaculateMaker.m
//  ChainBlockTestApp


#import "CaculateMaker.h"@implementation CaculateMaker - (CaculateMaker *(^)(CGFloat num))add; {return ^CaculateMaker *(CGFloat num){
        _result += num;
        return self;
    };
}

@end
Copy the code
4.3.4 Import CaculateMaker. H file into viewController and call add method to complete chain syntax:
  • ViewController.m
CaculateMaker *maker = [[CaculateMaker alloc] init];
maker.add(20).add(30);
Copy the code

As you can see from the navigation layout above, it writes a category for UIView, extends the mas_makeConstraints method, and passes the MASConstraintMaker object as a block argument, UIView layout is completed in the implementation of block, which reflects the idea of functional programming.

4.3.5 In the same way, we can add NSObject+Caculate:
  • NSObject+Caculate.h
//  NSObject+Caculate.h
//  ChainBlockTestApp

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "CaculateMaker.h"

@interface NSObject (Caculate)

- (CGFloat)caculate:(void (^)(CaculateMaker *make))block;

@end
Copy the code
  • NSObject+Caculate.m
//  NSObject+Caculate.m
//  ChainBlockTestApp

#import "NSObject+Caculate.h"@implementation NSObject (Caculate) - (CGFloat)caculate:(void (^)(CaculateMaker *make))block; { CaculateMaker *make = [[CaculateMaker alloc] init]; block(make);return make.result;
}

@end
Copy the code
4.3.6 Last call in viewController, it is easy to implement the chain syntax:
  • ViewController.m
CGFloat result = [NSObject caculate:^(CaculateMaker *maker) {
    maker.add(10).add(20).add(30);
}];
NSLog(@"Result :%.2f",result);
Copy the code

5. Refer to reading

  • Masonry parsing

    • http://qiufeng.me/masonry
    • https://www.cnblogs.com/ludashi/p/5591572.html
  • The factory pattern

    • https://www.jianshu.com/p/7b89b7f587f9
    • https://www.jianshu.com/p/847af218b1f0
  • Portfolio model

    • http://www.runoob.com/design-pattern/composite-pattern.html
    • http://www.cnblogs.com/gaochundong/p/design_pattern_composite.html
    • http://www.cnblogs.com/peida/archive/2008/09/09/1284686.html
  • Chain programming

    • https://www.jianshu.com/p/cb9252f5105b
    • https://www.jianshu.com/p/ac8bdd3430e7