The implementation of navigation is very simple, with only 2800 lines of code and 10 navigation files. Here is the navigation diagram for the core categories and the core flow analysis:

Comparison between NSLayoutConstraint and navigation

NSLayoutConstraint adds the AutoLayout constraint

First, add AutoLayout’s native method to a view:

  • Create an NSLayoutConstraint object using NSLayoutAttribute;
  • Then [view addConstraint:]
UIView *superView = self.view;
UIView *view1 = [UIView new];
[superView addSubView:view1];

// Disable automatic constraints
view1.translatesAutoresizingMaskIntoConstraints = NO;

// Create a left constraint for view1 relative to superView
NSLayoutConstraint *leftConstraint
= [NSLayoutConstraint constraintWithItem:view1
                               attribute:NSLayoutAttributeLeft
                               relatedBy:NSLayoutRelationEqual
                                  toItem:superView
                               attribute:NSLayoutAttributeLeft
                              multiplier:1.0
                                constant:10];
// omit 30 lines
NSLayoutConstraint *rightConstraint;
NSLayoutConstraint *topConstraint;
NSLayoutConstraint *bottomConstraint;
    
[view1 addConstraint:leftConstraint];
[view1 addConstraint:rightConstraint];
/ / to omit...
Copy the code

Add the AutoLayout constraint using navigation

The constraint added above, using the following navigation, simplifies about 35 lines of code:

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
     make.edges.equalTo(superView).offset(10);
}];
Copy the code

Navigation adds constraint flow parsing

Inside the mas_makeConstraints logic expands as follows:

    [view1 mas_makeConstraints:^(...) {
        view1.translatesAutoresizingMaskIntoConstraints = NO;
        MASConstraintMaker *maker = [[MASConstraintMaker alloc] initWithView:view1];
 
        // call block() :
        maker.left.equalTo(0);
        maker.width.and.top.equalTo().offset(2);
 
        [maker install];
    }]
Copy the code

As you can see from the above example, in the navigation call to add the constraint [view1 mas_makeConstraints:], I would like to post the following:

  1. Disable automatic translatesAutoresizingMaskIntoConstraints constraints;
  2. Initialize a ConstraintMaker class and add top, bottom, left, right/width constraints to Maker (note that this step only records the constraints into Maker’s Constraint Array).
  3. At Maker Install, add constraints from the Constraint Array to the view one by one.

1: Generate constraints (and record)

1.1 make. Left

    //
    MASConstraintMaker *make = [MASConstraintMaker new];
    // make. Left just get method (MASConstraintMaker MASConstraint* left attribute)
    MASConstraint *mas = make.left;
Copy the code
  • You can see that make.left calls the. Left attribute;
  • But because we overwrite the get method for the left attribute, make. Left calls [make addConstraint:] to add a constraint of type NSLayoutAttributeLeft.
  • The get method of the left attribute returns a MASConstraint object, so that subsequent chained calls can be made;
// MASConstraintMaker.m

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

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    
    // Build MASViewAttribute with view and NSLayoutAttribute
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    // Build MASViewConstraint with MASViewAttribute
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    
    / /... omit
    
    if(! constraint) {// Function??
        newConstraint.delegate = self;
        // Add MASViewConstraint to make.constraints(Array) and wait for install to add the constraint to the view
        [self.constraints addObject:newConstraint];
    }
    // Return's MASViewConstraint can continue the chain call
    return newConstraint;
}
Copy the code

1.2 make. Left. The and

MASConstraint *mas = make.left; You can see that make.left returns a MASConstraint * type.

Make. Left. And. With and and with are called as get methods, which simply return MASConstraint self, so you can continue the chain call

1.3 make. Left. And width

MASConstraint *mas = make.left; You can see that make.left returns a MASConstraint * type. So instead of calling maker’s method, the chain call to.width calls MASConstraint’s method:

// MASConstraint.m

- (MASConstraint *)width {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth];
}
Copy the code

MASConstraint is an Abstract Abstract class: it provides only an interface, with an empty implementation inside and requires subclass handling. So addConstraintWithLayoutAttribute: call in width will call into subclasses MASViewConstraint:

// MASViewConstraint.m

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
Copy the code

No constraints are generated or added to the MASViewConstraint; the delegate is called to handle it. This delegate just happens to be the maker object, so it’s back to the same logic as make.left in the previous step (note the omission of code in the previous step) :

// MASConstraintMaker.m

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    
    // make. Left, omit...
    // MASViewAttribute *viewAttribute = ;
    // MASViewConstraint *newConstraint = ;

    // when make.left, the constraint passed by this method is nil, so this code is omitted when parsing make.left
    // make.left.width to.width, the constraint passed here is the newConstraint object generated by make.left
    if ([constraint isKindOfClass:MASViewConstraint.class]) {
        // Note: the constraint here is left constraint; NewConstraint namely width constraint
        NSArray *children = @[constraint, newConstraint];
        // MASCompositeConstraint 是 MASConstraint 的子类,constraint group
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        
        // Replace constraint:
        // Replace the left constraint constraint recorded in maker.constraintArray with constraint Group
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        return compositeConstraint;
    }
    / /... omit
    // If constraint is true, return will not be repeated to array
}

// array replaces elements
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
    NSUInteger index = [self.constraints indexOfObject:constraint];
    [self.constraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
Copy the code

1.4 make. Left. And. Width. EqualTo ()

Used to make the left as the get method returns the MASViewConstraint object, you can continue to chain call: make. Left. EqualTo (superView), is called MASViewConstraint. EqualTo ();

The equalTo input parameter is an attribute of type ID. The attribute type can be MASViewAttribute, UIView, or NSValue. The equalTo method internally determines the type if.

EqualTo is a macro definition, unfolds the invocation chain is: qualTo (x) – > mas_equalTo (x) – > MASViewConstraint. EqualToWithRelation (x, NSLayoutRelationEqual)

Note:

  • Here will NSLayoutRelation parameter is introduced to call to MASViewConstraint. EqualToWithRelation ();
  • EqualTo with Relation holds equalTo’s incomingId type attributeNSLayoutRelationGo to the MASViewConstraint object and return the MASViewConstraint object to continue the chain call;
// MASViewConstraint.m

- (MASConstraint * (^)(id))mas_equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

- (MASConstraint * (^)(id.NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        / /... To simplify the
        
        // Saves the attribute and NSLayoutRelation types passed in by equalTo
        self.layoutRelation = relation;
        self.secondViewAttribute = attribute;
        // Continue to return MASConstraint, the chain call
        return self;
    };
}
Copy the code

1.5 make. Left. EqualTo (). Offset ()

Offset: make.left. EqualTo (superView).offset(2);

Note:

  • offsetequalToDifferent, not likeequalToEqualTo (XXX); equalTo(XXX);
  • offsetThe method is a method with no input arguments, but the return value of the method is a block with input arguments and return values, so it can be called with.offset(3);
  • Also, since the return value of the block is still the MASConstraint object, the chain call can continue;

The simplified logic is as follows:

    // make. Left is the get method
    MASConstraint* mas = make.left;
    MASConstraint*(^block)(CGFloat f) = mas.offset;
    // Since the return value of.offset is block, we can call:
    mas.offset(2);
    // mas.offset(2) equals:
    block(2);
    

    // The return value of block
    MASConstraint *mas2 = mas.offset(2);
Copy the code

1.6 make. XXX

  • othermake.center make.insets.and.sizeIt’s the same usage;
  • make.left.priority(MASLayoutPriorityDefaultLow)Is equal to themake.left.priorityLow;

2: adds constraints

2.1 [make install]

Add constraint MASConstraintMaker *make, make install finally executes constraint:

// MASConstraintMaker.m

- (NSArray *)install {
    
    // the logic for mas_remakeConstraints: remove all the constraints that have been added and then add them again
    if (self.removeExisting) {
        NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
        for (MASConstraint *constraint ininstalledConstraints) { [constraint uninstall]; }}NSArray *constraints = self.constraints.copy;
    // Iterate over each MASConstraint added in make and install
    for (MASConstraint *constraint in constraints) {
        // The logic for mas_updateConstraints: if the tag needs to be updated, update the constraints later in MASViewConstraint Install
        constraint.updateExisting = self.updateExisting;
        
        // The final install constraint is still in the MASViewConstraint
        [constraint install];
    }
    [self.constraints removeAllObjects];
    return constraints;
}
Copy the code

2.2 [constraint install]

// MASViewConstraint.m

- (void)install {
    / /... Ellipsis: Logic that avoids repeated additions
    
    // Retrieve two MASViewAttribute objects from the MASViewConstraint record
    MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
    NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
    MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
    NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;

    / /... omit
    
    // create an NSLayoutAttribute
    MASLayoutConstraint *layoutConstraint
        = [MASLayoutConstraint constraintWithItem:firstLayoutItem
                                        attribute:firstLayoutAttribute
                                        relatedBy:self.layoutRelation
                                           toItem:secondLayoutItem
                                        attribute:secondLayoutAttribute
                                       multiplier:self.layoutMultiplier
                                         constant:self.layoutConstant];

    // ...
    
    // After creating the constraint object, look for the constraint to add to the View:
    if (self.secondViewAttribute.view) { // If two views are relative constraints, get the two common parent views.
        MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
        self.installedView = closestCommonSuperview;
    } else if (self.firstViewAttribute.isSizeAttribute) { // If Width or Height is added, it is added to the current view
        self.installedView = self.firstViewAttribute.view;
    } else { // If neither a relative view nor a constraint of type Size is specified, the constraint object is added to the parent view of the current view
        self.installedView = self.firstViewAttribute.view.superview;
    }

    // Whether the constraint has been added
    MASLayoutConstraint *existingConstraint = nil;
    
    // the logic for mas_updateConstraints
    if (self.updateExisting) {
        existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
    }
    if (existingConstraint) { // If yes, reassign directly
        existingConstraint.constant = layoutConstraint.constant;
        // ...
    } else {
        // If no, add
        / / 2, [view addConstraint: NSLayoutAttribute]; !!!!!!!!!
        [self.installedView addConstraint:layoutConstraint];
        // ...}}Copy the code

Code technique

Flexible use of block

Block writing

    @property (nonatomic, strong) UIView *(^myBlock)(NSLayoutAttribute attr);
    
    self.myBlock = ^UIView *(NSLayoutAttribute attr) {
        return greenView;
    };

        // when blcok is on the right, ^ is first
    dispatch_block_t t = ^void(void) {}; UIView *(^block)(NSLayoutAttribute attr) = self.myBlock; self.myBlock = block;Copy the code

For more block syntax, see How Do I Declare A block in Objective-C?

Block acts as an input parameter to simplify external calls

When a method needs to pass in a ConfigModel, use a block as an input parameter: let the outside world not care about the ConfigModel initialization method, but only the configuration item

/ / use:
[view mas_makeConstraints:^(MASConstraintMaker *make) {
     make.edges.equalTo(lastView).offset(2);
}];

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {

    // New a maker, then call block to pass out
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    // After external configuration of maker, maker install
    block(constraintMaker);
    
    return [constraintMaker install];
}
Copy the code

Application case: SDK initialization

[SHWAccountSDK setupConfig:^(SHWAccountConfig *config) {
    config.appKey = @ "1";
    config.secret = @ "2";
}];
Copy the code

The block argument is called as a result callback

[SHWAccountSDK getSMSCode:phoneNum success:^(BOOL isSuccess) {
    // getSMSCode creates a Request object inside;
} failure:^(NSString *errCode, NSString *errMsg) {

}];

MyRequest *request;
[request startWithSuccess:^(BOOL isSuccess) {
    NSLog(@"getSMSCodeAsyncWithPhoneNum success");
} failure:^(NSString *errCode, NSString *errMsg) {
    NSLog(@"getSMSCodeAsyncWithPhoneNum fail");
}];
Copy the code

Block as return value, chain call

If the return value of a function is a block, we can write a chain call elegantly:

- (MASConstraint * (^)(CGFloat))offset {
    return ^id(CGFloat offset){
        self.offset = offset;
        return self;
    };
}

/ / use:
[blueView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(superview.mas_bottom).offset(padding);
}];
Copy the code

Principle:

    MASConstraint *mas = [MASConstraint new];
    MASConstraint*(^block)(CGFloat f) = mas.offset;
    
    // The following is equal
    block(2);
    mas.offset(2);
    // Since the return value of mas.offset is a block (CGFloat), append (2) to it
    // Since the block return value is still mas, we can continue the chain call
Copy the code

Contrast:

// Return value is not block, only self class
- (MASConstraint *)setOffset:(CGFloat)offset {
    self.offset = offset;
    return self;
}
/ / use:
MASConstraint *mas = [MASConstraint new];
// Can only be called this way
[[mas setOffset:3] setOffset:1];
Copy the code

MASBoxValue: mas_equalTo、equalTo

Mas_equalTo () can pass in many types of input arguments: ·

    [view1 makeConstraints:^(MASConstraintMaker *make) {
        make.left.mas_equalTo(view2);
        make.left.mas_equalTo(3);
        make.left.mas_equalTo(@(2));

        make.left.mas_equalTo(view2.mas_left);
        make.size.mas_equalTo(CGSizeMake(10.10));

        make.edges.equalTo(UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f));
        make.height.equalTo(@[redView, blueView])
    }];
Copy the code

Its definition is as follows:


#define MASBoxValue(value)      _MASBoxValue(@encode(__typeof__((value))), (value))
#define mas_equalTo(...)        equalTo(MASBoxValue((__VA_ARGS__)))

- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return. ; }; }// Even though the id type is passed in, the parse still supports arguments and and converts values like float, double, and int to the same object NSNumber as equalTo:
static inline id _MASBoxValue(const char *type, ...) {
    va_list v;
    va_start(v, type);
    id obj = nil;
    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(int)) = =0) {
        int actual = (int)va_arg(v, int);
        obj = [NSNumber numberWithInt:actual];
    } / /... omit
    
    va_end(v);
    return obj;
}
Copy the code

This incidentally explains the difference between mas_equalTo and equalTo: there is no difference, mas_equalTo still calls equalTo, except that the call is preceded by a boxValue conversion type

reading

  • IOS navigation framework source parsing
  • Read SnapKit and navigation automatic layout framework source code