🚀 一、Masonry 是什么?为什么还要学?
Masonry 是 iOS 最经典的链式 Auto Layout 库,用来替代苹果的 NSLayoutConstraint 写法。
例如 Apple 原生写法:
NSLayoutConstraint *c = [NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:view2
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:10];
而 Masonry:
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(view2.mas_bottom).offset(10);
}];
可读性强,代码量少,性能差别极小,因此至今仍大量项目使用。
📌 二、Masonry 语法快速入门(10 分钟掌握)
1. 创建约束
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.top.equalTo(self.view).offset(20);
make.width.height.mas_equalTo(100);
}];
2. 常用的 MASViewAttribute
| 属性 | 含义 |
|---|---|
| mas_left | 左 |
| mas_right | 右 |
| mas_top | 上 |
| mas_bottom | 下 |
| mas_width | 宽 |
| mas_height | 高 |
| mas_center | 中心 |
| mas_centerX | 水平中心 |
| mas_centerY | 垂直中心 |
📌 三、Masonry 的四种主要写法
1)makeConstraints:只添加不更新
[view mas_makeConstraints:^(MASConstraintMaker *make) { ... }];
2)updateConstraints:只更新已有约束
避免重复添加导致冲突(IMPORTANT!)
[view mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(200);
}];
3)remakeConstraints:先移除所有旧约束再重新添加
适合 UI 状态切换。
[view mas_remakeConstraints:^(MASConstraintMaker *make) { ... }];
4)mas_distributeViewsAlongAxis 水平/垂直均分
[views mas_distributeViewsAlongAxis:MASAxisTypeHorizontal
withFixedSpacing:10
leadSpacing:15
tailSpacing:15];
[views mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(20);
make.height.mas_equalTo(30);
}];
📌 四、常见布局场景(极实用)
1. 居中 + 固定宽高
[view mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.view);
make.size.mas_equalTo(CGSizeMake(80, 80));
}];
2. 两个 view 左右排列
[leftView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.view).offset(20);
make.right.equalTo(rightView.mas_left).offset(-10);
make.centerY.equalTo(self.view);
make.height.mas_equalTo(40);
}];
[rightView mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.view).offset(-20);
make.centerY.equalTo(self.view);
make.width.equalTo(leftView); // 等宽
make.height.mas_equalTo(40);
}];
3. scrollView + container 适配内容高度
(非常容易写错)
[self.scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.view);
}];
[self.container mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(self.scrollView);
make.width.equalTo(self.scrollView); // 关键
}];
📌 五、Masonry 的原理(大白话)
1. MASViewConstraint / MASLayoutConstraint 是什么?
Masonry 自己实现了一个“包装层”:
MASViewAttribute
只是 view + attribute(例如 view.left)MASViewConstraint
包含一个或多个 MASLayoutConstraint,是你写的约束逻辑MASLayoutConstraint
继承自 NSLayoutConstraint,真正被 Auto Layout 使用
你在 Memory Graph 看到一堆 MASLayoutConstraint/MASViewAttribute 不是泄漏,这是正常内存暂存(苹果控件内部也有一堆)。
📌 六、Masonry 内存问题(很多项目踩过)
⚠️ 核心点:Masonry 本身几乎不会泄漏,泄漏几乎都是你 block retain self。
例如:
[self.view mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view).offset(self.someValue); // retain self!!!
}];
虽然普通 block 结束后释放,但 Masonry 会(短暂)持有 maker → maker 持有 constraint → constraint 会持有 items(view) → view 持有 vc
导致 vc 一直释放不了。
🔥 Masonry block 防 retain self 的写法(必须背)
__weak typeof(self) weakSelf = self;
[self.view mas_makeConstraints:^(MASConstraintMaker *make) {
__strong typeof(weakSelf) self = weakSelf;
make.top.equalTo(self.view).offset(self.someValue);
}];
但更好的方式是:
✔ 使用 mas_offset 而不是 offset(self.xxx)
make.top.mas_equalTo(self.someValue);
这不会 retain self。
务必降低 block 中 self 的出现频率。
📌 七、Masonry 常见崩溃(高频)
1. 冲突警告:Unable to simultaneously satisfy constraints
原因:
- 加了重复约束(特别是 height / width)
- mas_makeConstraints 写了两次
- 动态更新没用 update而用 make
解决:
- 用
mas_updateConstraints - 切换 UI 时用
mas_remakeConstraints - 打印约束
po [view constraintsAffectingLayoutForAxis:...]
2. 视图未添加到父视图就设置约束 → crash
[self.view addSubview:subView];
[subView mas_makeConstraints:...]; // 必须在 addSubview 之后
3. 约束循环依赖,导致布局卡死
例:
make.left.equalTo(label.mas_right);
make.right.equalTo(label.mas_left);
📌 八、Masonry 性能问题
对比原生 NSLayoutConstraint,性能几乎相同。
影响性能的是:
- 动态频繁 remakeConstraints(避免)
- scrollView cell 中重复创建约束(最好缓存约束)
- 在 layoutSubviews 中重新创建约束(禁用!)
📌 九、如何在 Xcode Memory Graph 判断 Masonry 是否泄漏?
你之前问过“为何看到一堆 MASLayoutConstraint/MASViewConstraint 没有看到 view 和 vc?”
解释:
- MASLayoutConstraint/MASViewConstraint 在 Masonry 内部会短暂存活,用于 layout pass
- 不是泄漏,因为没有形成 strong reference 循环
判断方法(最可靠):
✔ 看 VC 是否有强引用路径
- 打开 Memory Graph
- 搜索你的 VC 类
- 查看 retain cycle 或 reference chain
如果 VC 被:
- view
- view → layer
- timer
- block
等持有 → 才是泄漏
MASLayoutConstraint 本身不会持有 VC(除非你在 block 中用了 self)
📌 十、完整 Masonry 示例(配动画/切换布局)
@interface DemoVC ()
@property (nonatomic, strong) UIView *box;
@property (nonatomic, assign) BOOL expanded;
@end
@implementation DemoVC
- (void)viewDidLoad {
[super viewDidLoad];
self.box = [UIView new];
self.box.backgroundColor = UIColor.redColor;
[self.view addSubview:self.box];
[self setupInitialLayout];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggle)];
[self.view addGestureRecognizer:tap];
}
- (void)setupInitialLayout {
[self.box mas_makeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.view);
make.width.height.mas_equalTo(100);
}];
}
- (void)toggle {
self.expanded = !self.expanded;
[self.box mas_remakeConstraints:^(MASConstraintMaker *make) {
make.center.equalTo(self.view);
make.width.height.mas_equalTo(self.expanded ? 200 : 100);
}];
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded];
}];
}
@end
📌 十一、你可能最关心的问题(因为你最近在查内存)
❗ Masonry 是否会导致内存泄漏?
不会
真正泄漏的是:
- 你在 block 中 retain self
- 你重复创建 view 却不释放
- 你把约束持有到 property 中又忘记释放
❗ Masonry 可以 100% 通过 Memory Graph 找到泄漏吗?
- 可以找到由 block 引起的持有
- 可以找到视图未释放问题
- MASLayoutConstraint 不应作为泄漏判断依据