iOS Developer

Masonry讲解与实战中内存优化

2017-08-28  本文已影响620人  走着走着就会敲代码了

一个朋友说他原本用frame做代码布局,打算试试约束布局,好吧!懒得一一说就默默的写篇文章。。。Masonry相对于原生来说,在代码添加约束上非常强大、实用、简便(想试试原生约束可以看我之前写的一篇帖子代码约束NSLayoutConstraint)。本篇文章就简单说一下Masonry的使用以及也是重点Masonry内存优化(内存优化为后期更新),需要先将Masonry下载并导入项目中Masonry下载地址。废话就不说了,开始来点干货。

Masonry调用方法

新增约束,不管原本是否已经存在约束都新增约束

- (NSArray *)mas_makeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;

更新约束。

- (NSArray *)mas_updateConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;

清除之前的约束,只保留最新约束。不管原本是否已经存在约束,都先进行清除约束,再新增约束。

- (NSArray *)mas_remakeConstraints:(void(NS_NOESCAPE ^)(MASConstraintMaker *make))block;

对这个理解很重要,因为如果你只是再使用新增mas_makeConstraints约束那你后面可能需要进行内存优化了,而且是效率显著的优化,后文会讲解这块(此内容为2017-12-13更新)

masonry使用

举个栗子,用栗子来说明:

 [_logoView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.left.mas_equalTo(0);
        make.size.mas_equalTo(CGSizeMake(MAINSIZE.width, SCR_W * 278));
 }];

block里面对需要进行约束的位置设置代码,例如make.top.left.mas_equalTo(0);它是设置logoView距离(如果没有设置视图默认为父视图)它的父视图的top、left距离为0,也就是居左、顶部,因为top、left他们距离父视图的距离相同就可以一次性的设置过去,当然上面的写法可以写成:

make.top.mas_equalTo(0);
make.left.mas_equalTo(0);

也可以写成

make.top.equalTo(self.view.mas_top);//self.view为父视图
make.left.equalTo(self.view.mas_left);

上面的写法也可以写成

make.top.equalTo(self.view.mas_top).offset(0);//self.view为父视图
make.left.equalTo(self.view.mas_left).offset(0);offset(0)//距离self.view的左边向右偏移0个单位

还可以写成用边距来设置,例如:

make.top.equalTo(self.view.mas_topMargin);
make.left.equalTo(self.view.mas_leftMargin);

这些设置都是可以的,主要是看你个人习惯。
equalTo后面带的可以是视图或者值(需要的像素),如果带的是值需要将值包装一下如:make.top.equalTo(@(0));
mas_equalTo后面带的是数值(可以是动态的数值),也只能是值。
如果你需要设置的约束是动态的,比如你确定宽度,高度不确定,那你只需要设置宽度的约束,让高度自适应就可以。反之,你确定高度不确定宽度,也是一样的道理。
注意如果你需要设置的约束为动态距离,即根据不同的屏幕间距(或边距)不同,那你设置的值不能写死,需要为动态值,跟frame设置动态距离是一样的。可以考虑用比例值,附带个宏:#define SCR_W(x) [UIScreen mainScreen].bounds.size.width/375*x。

Masonry属性

MASConstraintMaker(己身视图)
@property (nonatomic, strong, readonly) MASConstraint *left;//左边
@property (nonatomic, strong, readonly) MASConstraint *top;//上方
@property (nonatomic, strong, readonly) MASConstraint *right;//右边
@property (nonatomic, strong, readonly) MASConstraint *bottom;//下方
@property (nonatomic, strong, readonly) MASConstraint *leading;//头部
@property (nonatomic, strong, readonly) MASConstraint *trailing;//尾部
@property (nonatomic, strong, readonly) MASConstraint *width;//宽度
@property (nonatomic, strong, readonly) MASConstraint *height;//高度
@property (nonatomic, strong, readonly) MASConstraint *centerX;//横向居中,即中心点X轴坐标
@property (nonatomic, strong, readonly) MASConstraint *centerY;//纵向居中,即中心点Y轴坐标
@property (nonatomic, strong, readonly) MASConstraint *baseline;//文本基线

在iOS 8之后可以设置它的间距、边距约束

@property (nonatomic, strong, readonly) MASConstraint *leftMargin;//左边边距
@property (nonatomic, strong, readonly) MASConstraint *rightMargin;//右边边距
@property (nonatomic, strong, readonly) MASConstraint *topMargin;//上方边距
@property (nonatomic, strong, readonly) MASConstraint *bottomMargin;//下方边距
@property (nonatomic, strong, readonly) MASConstraint *leadingMargin;//头部边距
@property (nonatomic, strong, readonly) MASConstraint *trailingMargin;//尾部边距
@property (nonatomic, strong, readonly) MASConstraint *centerXWithinMargins;//X轴中心点边距
@property (nonatomic, strong, readonly) MASConstraint *centerYWithinMargins;//Y轴中心点边距

上面好像没说说道size属性,这个也是一样的既然有了width、hight,那当然也有size。

MASViewAttribute(对比视图)

与本身视图是一一对应的,只是需要添加上mas_,例如:mas_bottom

@property (nonatomic, strong, readonly) MASViewAttribute *mas_left;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_top;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_right;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottom;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leading;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_trailing;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_width;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_height;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerX;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerY;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_baseline;
@property (nonatomic, strong, readonly) MASViewAttribute *(^mas_attribute)(NSLayoutAttribute attr);

iOS 8之后

@property (nonatomic, strong, readonly) MASViewAttribute *mas_leftMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_rightMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_topMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_bottomMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_leadingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_trailingMargin;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerXWithinMargins;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerYWithinMargins;

本身视图属性、跟对比视图属性在苹果原生的约束布局上其实是一样的,只是masonry做了区别。

等分布局

       // 水平
       [@[self.imageButton,
          self.linkButton,
          self.fontButton,
          self.sizeButton,
          self.alignmentButton,
          self.colorButton,
          self.keyboardButton] mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedSpacing:pading leadSpacing:pading tailSpacing:pading];
       // 高度
       [@[self.imageButton,
          self.linkButton,
          self.fontButton,
          self.sizeButton,
          self.alignmentButton,
          self.colorButton,
          self.keyboardButton] mas_makeConstraints:^(MASConstraintMaker *make) {
             make.bottom.equalTo(self).offset(-kEditorToolBarButtonBottomSpace);
             make.height.equalTo(@(kEditorToolBarButtonWidth));
         }];

均等布局mas_distributeViewsAlongAxis设置完水平布局或者垂直布局后,需要设置每一个控件自身的布局属性。

tableView动态布局UITableViewAutomaticDimension

iOS 8 之后可以使用的动态约束UITableViewAutomaticDimension,使用方法:

    _messageTableView.rowHeight = UITableViewAutomaticDimension;

    [_messageTableView setEstimatedRowHeight:44];

但是在自定义Cell的时候,可能会存在一个问题那就是约束未被初始化导致第一次加载tableView的时候高度不准确,在拖动或者滚动后动态高度才是正确的,看下面效果图:

未初始化约束效果图
造成这个的原因是:将约束写在- (void)layoutSubviews方法中,我怀疑苹果那边的UITableViewAutomaticDimension与layoutSubviews有点冲突,所以我们在给cell赋值的时候需要layoutIfNeeded调用约束:
        [_titleLabel setText:@"这是系统消息"];
        
        [_contentLabel setText:@"摸摸摸爱妃玛科技发福了卡积分老卡机啊福利肯德基奥拉夫控件的啊发送的发送啊发送的发送发送的发送方公司的反感"];
                
        [_timeLabel setText:@"2017-09-08 10:10"];
        
        [self layoutIfNeeded];//约束如果是写在layoutSubviews别漏了这句代码,调用约束。

更新约束

很多时候还是需要根据数据来对布局进行处理,从而需要对约束进行更新。
原本的约束代码:

 [_fullView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(_couponLabel.mas_bottom).mas_offset(SCR_W * 10);
            make.left.equalTo(_iconView.mas_right).offset(SCR_W * 10);
            make.size.mas_equalTo(CGSizeMake(SCR_W * 25, SCR_W * 15));
        }];

在需要更新约束的地方,调用如下代码:

 //更新控件头部的位置
        [_fullView mas_updateConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(_dashedView.mas_bottom).mas_offset(SCR_W * 10);
        }];
        //立即更新
        [self layoutIfNeeded];

Masonry使用优化

缘由

使用Masonry已经有一段时间了,但是发现项目中tableView做了复用但是每次滚动,内存都会一直增加,对就是增加根本停不下来。开始怀疑人生了,毕竟复用就那些注意点,但是检查了十几遍的代码还是没发现问题。直到重复滚了好好几十遍的tableView后确认了一个问题:在更新cell上面的某个控件的时候会造成一个问题就是卡顿、内存增加。有了目标开始针对性查找,最后确认了一个事——
官方文档对updateConstraints的解释:
Custom views that set up constraints themselves should do so by overriding this method.When your custom view notes that a change has been made to the view that invalidates one of its constraints, it should immediately remove that constraint, and then call setNeedsUpdateConstraints to note that constraints need to be updated. Before layout is performed, your implementation of updateConstraints will be invoked, allowing you to verify that all necessary constraints for your content are in place at a time when your custom view’s properties are not changing.
重点是说在对没用的或失效的约束应该立即删除。而Masonry的mas_makeConstraints方法是添加约束。每添加一层,内存增长一次,你不删除,它就一直在。

猜想:mas_makeConstraints在复用的时候是无效的(达不到复用的效果),这么说吧复用的时候滚动tableView每一个cell都会重新走,但是cell上的控件约束会重走也就是会重新添加约束。经过尝试就是这么操蛋,使用mas_makeConstraints它不管原本是否已经有约束都会新增约束,复用的列表也是同理。

解决

在需要重复使用mas_makeConstraints的地方替换成mas_remakeConstraints,因为mas_remakeConstraints会先移除原本的约束,再重新添加。

也考虑要是否可以使用mas_updateConstraints,因为第一次设置约束时mas_updateConstraints代替不了mas_makeConstraints。

强调

mas_makeConstraints只会新增约束,不管原本是否已经有约束。
mas_remakeConstraints会先移除约束,再新增约束。
PS:不是每个地方都必须使用mas_remakeConstraints,能再确保不会重复添加约束的情况下使用mas_makeConstraints相对来说会更好。

再尝试滚动tableView后发现复用的效果出来了,看来平时没注意还是给自己挖了个坑,但是现在填也不算晚。希望再次更新强调了下后人就可以少了个坑。

技巧

1、子视图相对于父视图居中,可以考虑设置它的X、Y轴约束
2、子视图填满父视图,可以考虑设置子视图的四个边距为0
3、动态高度,可以只设置该视图的宽,高度由约束自动生成(动态高度,然后需要设置最大高度,好像是没法设置的)
就简单说这几点吧,具体的还是靠实践。多撸代码,也就熟能生巧了。
4、动画或者改变位置,可以将需要更改的某条约束定义为全局,然后根据需求进行更改。例如: _leftConstraint

[_titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
       _leftConstraint = make.left.mas_equalTo(SCR_W(15));
        make.top.equalTo(@15);
        make.size.mas_equalTo(CGSizeMake(SCR_W(345), SCR_W(15)));
    }];

总的介绍就这些吧(后续会更新),具体的还是需要看你自己多去尝试、实践才能孰能生巧。代码上可以简便一些,通用的布局,熟了可能一句约束就OK不熟的话可能需要拆分写好几句,才能达到相应的效果。

上一篇 下一篇

猜你喜欢

热点阅读