iOS Masonry学习和探究

前言

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容