iOS Masonry 源码阅读

<code>Masonry</code>源码阅读

<strong>阅读源码是一种美妙的体验</strong>

这么强大的布局库,就不做解释了。因为系统的自动布局写起来很麻烦,所以 Masonry 成了当前流行的使用代码布局的方式(当然是在OC中)

具体使用如下:

[self.headView addSubview:self.headLabel];
[self.headLabel mas_makeConstraints:^(MASConstraintMaker *make) {
    make.bottom.mas_equalTo(-2);
    make.left.mas_equalTo(13);
    make.height.mas_equalTo(15);
}];
    

<code>Masonry</code> 也是支持链式调用的。

view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.equalTo(superview).with.insets(padding);
}];

不做解释了。

<strong>阅读源码是重点</strong>

<code>Masonry</code> 在 手机 开发 和 Mac 开发都能用,所以宏定义了

#if TARGET_OS_IPHONE || TARGET_OS_TV

    #import <UIKit/UIKit.h>
    #define MAS_VIEW UIView
    #define MAS_VIEW_CONTROLLER UIViewController
    #define MASEdgeInsets UIEdgeInsets

    typedef UILayoutPriority MASLayoutPriority;
    static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;
    static const MASLayoutPriority MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
    static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 500;
    static const MASLayoutPriority MASLayoutPriorityDefaultLow = UILayoutPriorityDefaultLow;
    static const MASLayoutPriority MASLayoutPriorityFittingSizeLevel = UILayoutPriorityFittingSizeLevel;

#elif TARGET_OS_MAC

    #import <AppKit/AppKit.h>
    #define MAS_VIEW NSView
    #define MASEdgeInsets NSEdgeInsets

    typedef NSLayoutPriority MASLayoutPriority;
    static const MASLayoutPriority MASLayoutPriorityRequired = NSLayoutPriorityRequired;
    static const MASLayoutPriority MASLayoutPriorityDefaultHigh = NSLayoutPriorityDefaultHigh;
    static const MASLayoutPriority MASLayoutPriorityDragThatCanResizeWindow = NSLayoutPriorityDragThatCanResizeWindow;
    static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 501;
    static const MASLayoutPriority MASLayoutPriorityWindowSizeStayPut = NSLayoutPriorityWindowSizeStayPut;
    static const MASLayoutPriority MASLayoutPriorityDragThatCannotResizeWindow = NSLayoutPriorityDragThatCannotResizeWindow;
    static const MASLayoutPriority MASLayoutPriorityDefaultLow = NSLayoutPriorityDefaultLow;
    static const MASLayoutPriority MASLayoutPriorityFittingSizeCompression = NSLayoutPriorityFittingSizeCompression;

#endif

根据平台的不同,重新定义了 MAS_VIEW 和 MASEdgeInsets

在布局之前,内部代码会自动加上<code>self.translatesAutoresizingMaskIntoConstraints = NO;</code>,所以不用我们外部加。

跟一个布局流程,看看内部怎样做的。

内部定义了一个 <code> MAS_VIEW </code> 的 分类,这样子我们在外部,只要是<code>UIView</code>或者 <code>UIView</code>的子类,然后引入头文件,就可以调用方法了。

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
/// 设置自动布局。
    self.translatesAutoresizingMaskIntoConstraints = NO;
    /// 生成 MASConstraintMaker
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    /// 执行外部的block
    block(constraintMaker);
    /// 安装布局信息
    return [constraintMaker install];
}

<code> mas_makeConstraints </code> 添加布局信息

  • 生成<code> MASConstraintMaker</code>
  • <code>block(constraintMaker)</code>把上一步生成的对象作为参数。然后调用外面的<code>block</code>,重点是设置 上一步生成对象的属性
  • 让布局信息生效

<strong>接下来,我们的注意力只要转移到<code> MASConstraintMaker</code> 就行了</strong>

生成 <code>MASConstraintMaker</code>对象的过程比较简单

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

  • 用 view 初始化一个对象 这里 view 的引用是 <code>weak</code>的。
  • 创建一个 <code> self.constraints </code>数组,字面意思理解,是存放所有约束的。我们大胆猜测,跟链式调用,是有关系的。

<strong><code>install</code></strong>的过程也简单,

- (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;
}

  • 判断要不要删除之前已经存在的约束 <code>removeExisting</code>,如果要删除,则调用 <code> MASConstraint 的 uninstall </code>方法卸载掉约束 <code> removeExisting </code>只有在 <code>mas_remakeConstraints</code>的时候才为 <code>YES</code>
  • 遍历 <code>self.constraints</code> 然后调用 <code> MASConstraint 的 install </code>方法,让约束生效
  • 清空 <code> self.constraints </code>数组,因为保存的约束都已经生效了。

<strong>接下来只要分析<code> MASConstraint </code> 思路就清楚了</strong>

<code>MASConstraint</code>是MAS库,封装的,关于约束的一个类,外面能够进行链式调用的设置约束,也是得益于<code>MASConstraint</code>。定义了很多常用的约束操作。比如:

- (MASConstraint *)left;
- (MASConstraint *)top;
- (MASConstraint *)right;
- (MASConstraint *)bottom;
- (MASConstraint *)leading;
- (MASConstraint *)trailing;
- (MASConstraint *)width;
- (MASConstraint *)height;
- (MASConstraint *)centerX;
- (MASConstraint *)centerY;
- (MASConstraint *)baseline;

~~~~~ 还有N多个操作。

上面的函数都返回自身对象,所以才能够进行链式调用。

我们上面的例子中

block 执行了

make.bottom.mas_equalTo(-2);
make.left.mas_equalTo(13);
make.height.mas_equalTo(15);

因为<code> bottom left height </code>属性都是lazy load 的,所以只要调用了,就会添加一个约束给 view

- (MASConstraint *)bottom {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeBottom];
}

最终会调用 ps(我们标记一下这个函数 为 <strong>S</strong>)

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    /// 生成 MASViewAttribute MASViewAttribute 是用来描述一个view 和 约束的 关系
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    /// 生成 MASViewConstraint MASViewConstraint 是 MAS描述约束的。 
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    /// 我们先记下S这里的作用,以后看看作用,
    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;
}

到这一步,可以看到<code>self.constraints</code>中存的是<code>MASViewConstraint</code> 对象,并且把代理指向了<code> maker </code>

设置约束的值,一般使用已经定义好的宏

#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__)

#endif

如果不想使用 <code>mas</code>开头的宏,可以在全局定义 <code> MAS_SHORTHAND_GLOBALS </code> 比如:

#define MAS_SHORTHAND_GLOBALS

就可以了

所以,类似

栗子 <strong>A</strong>

UIButton *button = [UIButton new];
    [self.view addSubview:button];
    [button mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.mas_offset(10);
        make.top.mas_equalTo(10);
        make.size.mas_equalTo(CGSizeMake(20, 20));
    }];

中设置 <code>left.mas_offset(10);</code>左边距为10 最后就是调用,<code>MASConstraint</code>的

- (MASConstraint * (^)(NSValue *value))valueOffset {
    return ^id(NSValue *offset) {
        NSAssert([offset isKindOfClass:NSValue.class], @"expected an NSValue offset, got: %@", offset);
        [self setLayoutConstantWithValue:offset];
        return self;
    };
}

函数,函数同样是返回一个 <code>block</code>,并且 <code>block</code>中返回<code>self</code>方便链式调用.

- (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);
    }
}

<code> setLayoutConstantWithValue </code>函数中,会判断设置的值得类型,然后进行不同的设置,根据约束的不同 比如:<code>NSLayoutAttributeWidth NSLayoutAttributeLeft</code> 等等。。不做多余解释,

跟 <strong>栗子 A</strong> 有同样作用的链式调用,可以这样子写。

<strong> 栗子 B</strong>

[button mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.mas_offset(10).top.mas_equalTo(10);
        make.size.mas_equalTo(CGSizeMake(20, 20));
}];
    

不同之处在于

make.left.mas_offset(10).top.mas_equalTo(10);

第一个 <code>.left</code> 是 <code> MASConstraintMaker </code>中的属性,调用完成后会返回 自己。 第二个 <code>top</code> 是 <code>MASViewConstraint</code> 自己的。下面分析一下,这个怎么玩的。

你看,调用 .top 的时候,<code>MASViewConstraint</code> 没有做什么事情,直接调用 <code>delegate</code> 的 <code>[constraint:addConstraintWithLayoutAttribute:];</code>方法

#pragma mark - attribute chaining

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");

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

峰回路转,调用到我们刚才标记的 <strong>S</strong> 那里了,这回,第一个参数有值了。我们可以揭开神秘的面纱了。

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;
    }
  • 首先生成<code> MASCompositeConstraint </code> 对象。
  • 然后把 对象的代理设置为 <code>self</code>
  • 然后替换 传进来的第一个参数在<code> constraints</code> 中的值。
  • 最后返回 对象,因为<code> MASCompositeConstraint</code> 继承自 <code> MASConstraint </code> 所以后面的链式调用也是可以继续的。

替换函数如下

- (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];
}

之所以要替换,是因为 防止在最后 遍历 <code> self.constraints </code>设置约束的时候重复 因为 <code>MASCompositeConstraint</code> 也是继承自 <code>MASConstraint</code> 和 <code>MASViewConstraint</code> 一样 实现了很多之后要用到的方法。比如 <code>install</code> 等等。

<code>MASConstraint</code> 定义了约束 操作的函数,但是对约束的操作都放在子类中进行

  • <code>MASViewConstraint</code>一个单独的约束
  • <code>MASComposisteConstraint</code>一组约束

<code>MASConstraint</code>中需要子类重写的方法,都抛出了异常

#define MASMethodNotImplemented() \
    @throw [NSException exceptionWithName:NSInternalInconsistencyException \
                                   reason:[NSString stringWithFormat:@"You must override %@ in a subclass.", NSStringFromSelector(_cmd)] \
                                 userInfo:nil]

<code>MASViewConstraint</code> 单独的约束,重写了 <code>MASConstraint</code> 标记的需要重写的方法。在父类链式调用的基础上,增加了。

@property (nonatomic, strong, readonly) MASViewAttribute *firstViewAttribute;
@property (nonatomic, strong, readonly) MASViewAttribute *secondViewAttribute;

用来描述跟约束相关的信息。

<code> MASCompositeConstraint </code> 代表一组约束。内部存放了一个私有变量

@property (nonatomic, strong) NSMutableArray *childConstraints;

用来存放单个的约束的信息,一般都是存放 <code>MASViewConstraint</code>.

mas_makeConstraints 
mas_updateConstraints 
mas_remakeConstraints

函数的区别:

<code>updateConstraints</code> 函数里里会设置<code>updateExisting</code>为 <code>YES</code>
<code>remakeConstraints</code> 函数里会设置<code>removeExisting = YES;</code>

如果 <code>updateExisting</code>为<code> YES </code>的时候再一个约束<code>install</code>的时候会先寻找当前<code>view</code> 有没有相同的约束,如果有,就直接更新约束的 <code>constant</code>值。代码如下:

MASLayoutConstraint *existingConstraint = nil;
    if (self.updateExisting) {
        existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
    }
    if (existingConstraint) {
        // just update the constant
        existingConstraint.constant = layoutConstraint.constant;
        self.layoutConstraint = existingConstraint;
    }
    

如果 <code>removeExisting = YES;</code> 在 <code>install</code>之前,会删除<code>view</code>的所有约束

if (self.removeExisting) {
        NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
        for (MASConstraint *constraint in installedConstraints) {
            [constraint uninstall];
        }
    }

<strong> Masonry </strong>这个经常使用库我们就摸到了冰山一角了。以后有空一行一行的扣一下细节

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

推荐阅读更多精彩内容