iOS 开发每天分享优质文章UI

iOS Masonry学习和探究

2019-08-05  本文已影响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返回值必须还是一个对象(根据业务需求不同,可以返回的是这个对象实例本身,也可以是这个类的另一个实例,更可以是另一个类的实例对象。)

上一篇下一篇

猜你喜欢

热点阅读