iOS Masonry学习和探究

作者: 炒河粉儿 | 来源:发表于2019-08-05 19:35 被阅读0次

    前言

    开发中对UI进行布局,有很多种,常用的包括frame,Autolayout,storyboard,Masonry等。代码布局添加约束依赖使用masonry框架是一个很不错的选择。此篇文章就是对masonry框架内部实现进行一个探究,从而学习他的编程思想。

    Masonry的核心思想

    Masonry框架其实是对NSLayoutConstraint的一个封装,使用了函数式编程和链式编程的思想,使描述语法更加简洁明了,并具备了很高的可读性。

    Masonry的核心使用方法

    一个简单的masonry的使用

        _redView = [[UIView alloc]init];
        _redView.backgroundColor = [UIColor redColor];
        [self.view addSubview:_redView];
        
        [_redView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.top.equalTo(self.view).offset(10);
            make.right.bottom.equalTo(self.view).offset(-10);
        }];
    

    使用masonry进行布局的时候,都会调用这个方法。

    - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
        self.translatesAutoresizingMaskIntoConstraints = NO;
        MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
        block(constraintMaker);
        return [constraintMaker install];
    }
    

    这个方法的返回值为一个数组,参数是一个block,void(^)(MASConstraintMaker *)。它的作用是给控件设置布局。

    1. 创建一个约束制造者MASConstraintMaker
    2. 调用block(maker),调用外部block中描述控件约束代码,将约束全部保存到约束制造者。
    3. constraintMaker调用install,返回值为一个数组。[constraint install]内部实现的就是遍历约束制造者中的约束,然后给控件添加约束。

    点击block代码块中make.left,进入到内部,一层层查找,会跳转到下面的方法。

    - (MASConstraint *)left {
        return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
    }
    - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
        return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
    }
    
    - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
        //self.view->redView
        //1.相对于哪个view,确定是给哪个view添加约束。
        MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
        //控件约束类,把当前的这个redview传入到这个类中。
        MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
        //判断传过来的是哪个类型,从left点击进来可以查看此方法调用的时候参数(MASConstraint *)constraint传入的为nil,因此这里不会进入。
        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;
        }
        //  传入的为nil,会进入下面的判断。签署代理,将新增的约束保存到数组当中。
        if (!constraint) {
            newConstraint.delegate = self;
            [self.constraints addObject:newConstraint];
        }
        //返回值为约束类MASViewConstraint
        return newConstraint;
    }
    
        _redView = [[UIView alloc]init];
        _redView.backgroundColor = [UIColor redColor];
        [self.view addSubview:_redView];
        
        [_redView mas_makeConstraints:^(MASConstraintMaker *make) {
        
            //make.left -> 返回为MASViewConstraint类,此时点击进入top,调用top的是MASViewConstraint类,一层一层点击进去查看
        
            make.left.top.equalTo(self.view).offset(10);
            make.right.bottom.equalTo(self.view).offset(-10);
        }];
    
    //此时top会进入到这个方法 签署的代理对象
    - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
        NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");
    
        return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
    }
    
    //又进入了这个方法
    - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
        //self.view->redView
        //1.相对于哪个view,确定是给哪个view添加约束。
        MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
        //控件约束类,把当前的这个redview传入到这个类中。
        MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
        //判断传过来的是哪个类型,从top进入的时候,传入的(MASConstraint *)constraint为self,当时的self就是left返回的类型,就是MASViewConstraint类型,因此会进入到下面的代码,将top的约束保存到maker的数组中,
        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];
            //返回值为MASCompositeConstraint类型
            return compositeConstraint;
        }
        //  传入的不为nil,不会进入下面的判断。
        if (!constraint) {
            newConstraint.delegate = self;
            [self.constraints addObject:newConstraint];
        }
        //返回值为约束类MASViewConstraint
        return newConstraint;
    }
    
    //再次进行约束的时候 此时返回的self为MASCompositeConstraint类,点击进入方法。
    - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    //此时的self为MASCompositeConstraint类 再次点击进入
        [self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
        return self;
    }
    
    - (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
        id<MASConstraintDelegate> strongDelegate = self.delegate;
        //strongDelegate调用这个方法,strongDelegate = self.delegate,此时的strongDelegate为最初签署的代理MASConstraintMaker类
        MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
        newConstraint.delegate = self;
        [self.childConstraints addObject:newConstraint];
        //返回值为MASConstraint类
        return newConstraint;
    }
    

    层层设置则同理,第一次调用left时,当时的self为MASConstraintMaker类型,会[self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute]这样的调用方式,和MASConstraintMaker类签署代理,并且将约束保存到数组当中,返回类型为MASViewConstraint,再次调用left,right,top等等操作时,这时候的self为MASViewConstraint类型,进入到其他的方法,会[self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];这样的调用方式,此时constraint传入了之前返回的MASViewConstraint类型的对象,内部会走不同的判断方法,将新的约束加入到之前保存约束的数组当中,同时返回类型为MASCompositeConstraint的返回值,再次添加约束,此时self为MASCompositeConstraint类,会走[self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];,在跳转到- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute,内部调用MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];方法,这时候的strongDelegate是MASConstraintMaker类,则会再次进入添加约束的方法中,返回值的类型仍然为MASCompositeConstraint类。则实现了依次添加约束的效果。

        _redView = [[UIView alloc]init];
        _redView.backgroundColor = [UIColor redColor];
        [self.view addSubview:_redView];
        
        [_redView mas_makeConstraints:^(MASConstraintMaker *make) {
        
            //equalTo方法返回值为一个block equalTo()就是调用了block
            //这里体现了链式编程的思想 返回值为block
            make.left.top.equalTo(self.view).offset(10);
            make.right.bottom.equalTo(self.view).offset(-10);
        }];
    
    - (MASConstraint * (^)(id))equalTo {
        return ^id(id attribute) {
            //这里的返回值又是一个block
            //一层层点进去最终会进入下面的代码
            return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
        };
    }
    
    //这里的返回值仍然是一个block //一级级跳转,其实最终实现的就是将约束的值添加进去。
    - (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;
                return self;
            }
        };
    }
    

    通过下面这行代码就将要添加的约束,以及约束的数值都添加了进去。

    make.left.top.equalTo(self.view).offset(10);
    

    此时

    - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
        self.translatesAutoresizingMaskIntoConstraints = NO;
        MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
        //此时block代码块就执行完毕了
        block(constraintMaker);
        //然后执行这一步代码,把约束添加到视图上。
        return [constraintMaker install];
    }
    

    外部的调用,参数是一个block,利用了函数式编程的思想,block代码块中的点语法连续的书写,利用了返回值为block,block的返回值又是对象,对象继续使用点语法调用方法的思想,就是链式编程的核心思想。实现链式编程的关键就是声明一个block的属性,而这个block返回值必须还是一个对象(根据业务需求不同,可以返回的是这个对象实例本身,也可以是这个类的另一个实例,更可以是另一个类的实例对象。)

    相关文章

      网友评论

        本文标题:iOS Masonry学习和探究

        本文链接:https://www.haomeiwen.com/subject/yqjvdctx.html