目录
-
一、Autolayout的使用
对比了一下Autolayout和Masonry,突出Masonry代码的简洁性。 -
二、链式编程
从一个demo深入浅出的介绍了下链式编程思想(内容节选自iOS音视频) -
三、Masonry源码解读
3.1、View+MASAdditions
3.2、MASConstraint
----- MASCompositeConstraint
3.3、MASViewConstraint
3.4、MASViewAttribute
3.5、MASConstraintMaker
3.6、NSArray+MASAdditions
3.7、ViewController+MASAdditions
一、Autolayout的使用:
/**
使用NSLayouConstraont为控件添加约束
@param view1 需要添加约束的控件
@param attr1 添加约束的方向
@param relation 约束值关系(>、<、=)
@param view2 被参照控件
@param attr2 被参照控件的被参照方向
@param multiplier 间距倍数关系
@param c 传入最终差值
@return NSLayoutConstraint
*/
+(instancetype)constraintWithItem:(id)view1
attribute:(NSLayoutAttribute)attr1
relatedBy:(NSLayoutRelation)relation
toItem:(nullable id)view2
attribute:(NSLayoutAttribute)attr2
multiplier:(CGFloat)multiplier
constant:(CGFloat)c
{
/**
* NSLayoutAttribute枚举
*
* NSLayoutAttributeLeft = 1,
* NSLayoutAttributeTop,
* NSLayoutAttributeBottom,
* NSLayoutAttributeLeading,
* NSLayoutAttributeTrailing,
*
* ......
*
* NSLayoutAttributeNotAnAttribute
*/
/**
* NSLayoutRelation枚举
*
* NSLayoutRelationLessThanOrEqual, 小于等于
* NSLayoutRelationEqual, 等于
* NSLayoutRelationGreaterThanOrEqual, 大于等于
*/
}
Autolayout的简单使用
1.1、如图所示,创建一个redview,距superView上、下、左、右各100pt.
demo-1.png 创建对象.png NSLayoutConstraint.png
1.2、Masonry对比
masonry.png二、链式编程
- 将多个操作(多行代码)通过()链接在一起成为一句代码,使代码的可读性好。
- 特点:方法的返回值是block,block必须有返回值(本身对象),block参数(需要操作的值)。
- 代表:masonry
关于链式编程,我在简书上发现了深入浅出-iOS函数式编程的实现 && 响应式编程概念这篇文章,讲的真如标题一样,深入浅出,非常便于刚开始接触链式编程思想的人了解。在这里将这个哥们的文章摘除一小段说一下。
链式编程经典语句
make.top.mas_equalTo(self.view).with.mas_equalTo(100);
如何书写链式编程
新建一个Person类,并为其增加两个方法
@interface Person : NSObject
- (void)run;
- (void)study;
@end
@implementation Person
- (void)run
{
NSLog(@"run");
}
- (void)study
{
NSLog(@"study");
}
@end
实例化并调用相关的方法
Person *person = [[Person alloc] init];
[person run];
[person study];
在这里我们实现了一个非常简单的对象方法调用,这是我们最常用的方法,而我们的最终目标是要写成这样,调用完成之后直接添加点语法继续调用。
person.runBlock().studyBlock().runBlock();
可以先将最终的目标进行分解,例如
[[person run] study];
分析:
显然,如果想要实现[person run]调用一个方法,那么run就需要一个返回一个对象,让这个对象去调用study。这样分析后,就简单了,就是增加一个返回值。
@interface Person : NSObject
- (Person *)run;
- (Person *)study;
@end
@implementation Person
- (Person *)run
{
NSLog(@"run");
return [[Person alloc] init];
}
- (Person *)study
{
NSLog(@"study");
return[[Person alloc] init];
}
@end
实现最终目标:
person.runBlock().studyBlock().runBlock();
在OC中,`()`block是以()的形式去执行的,猜想如果返回一个block的话,那么我就可以用()来实现runBlock()这种效果了吧!
再结合我们的分解步骤,runBlock()代表执行了一个block,如果这个block的返回值的是一个对象的话,那么调用另外一个方法;这样就可以一直链接下去吧!实现了我们想要的目标!
@interface Person : NSObject
- (Person* (^)(void))runBlock;
- (Person* (^)(void))studyBlock;
@end
@implementation Person
- (Person* (^)(void))runBlock
{
Person *(^block)(void) = ^() {
NSLog(@"run");
return self;
};
return block;
}
- (Person* (^)(void))studyBlock
{
Person *(^block)(void) = ^() {
NSLog(@"study");
return self;
};
return block;
}
@end
外部调用
Person *person = [[Person alloc] init];
person.runBlock().studyBlock();
masonry类比
make.top.mas_equalTo(self.view).with.mas_equalTo(100);
------------------------------------------------------------
- (MASConstraint * (^)(id))mas_equalTo {
return ^id(id attribute) {
return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
};
}
在这里,借用iOS音视频这个哥们的一个小demo,整理了一下链式编程的一个概念
- 如果想再去调用别的方法,那么就需要返回一个对象;
- 如果想用()去执行,那么需要返回一个block;
- 如果想让返回的block再调用对象的方法,那么这个block就需要返回一个对象(即返回值为一个对象的block)
三、Masonry源码解读
Masonry目录.pngMasonry是iOS在控件布局中经常使用的一个轻量级框架,在日常开发中,你可能不知道Autolayout,但是你一定知道Masonry,它是对Autolayout进行了二次封装,用链式编程的思想简化了NSLayoutConstraint的使用方式,使代码变得更为简洁。
首先就从入口函数开始讲起
函数入口.png
3.1、View+MASAdditions
首先为view创建了一个分类,方便控件直接调用
// MASViewAttribute主要是将view与约束封装在了一起,在下面回说到
@property (nonatomic, strong, readonly) MASViewAttribute *mas_left; //
@property (nonatomic, strong, readonly) MASViewAttribute *mas_top;
......
@property (nonatomic, strong, readonly) MASViewAttribute *mas_centerY;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_baseline;
......
// 创建约束
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *make))block;
// 更新约束
- (NSArray *)mas_updateConstraints:(void(^)(MASConstraintMaker *make))block;
// 删除原来并创建最新的约束
- (NSArray *)mas_remakeConstraints:(void(^)(MASConstraintMaker *make))block;
// 寻找两个视图的最近的公共父视图(类比两个数字的最小公倍数)
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view;
- 方法解读 mas_makeConstraints
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block
{
// 取消自动添加约束
self.translatesAutoresizingMaskIntoConstraints = NO;
// 创建MASConstraintMaker工厂类
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
// 通过block回调添加的约束,为constraintMaker的属性赋值
block(constraintMaker);
// 添加约束并返回约束数组(Array<MASConstraint>)
return [constraintMaker install];
}
创建、更新、删除并更新约束的区别
由源代码可以看出
/**
* mas_updateConstraints
*
* constraintMaker.updateExisting = YES;
*/
/**
* mas_remakeConstraints
*
* constraintMaker.removeExisting = YES;
*/
- (NSArray *)install
{
// 如果removeExisting属性为YES
if (self.removeExisting)
{
// 遍历所有的约束,全部移除
NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
for (MASConstraint *constraint in installedConstraints)
{
[constraint uninstall];
}
}
/**
* self.constraints
*
* 存放所有的约束
* NSArray<NSLayoutConstraint *> *constraints
*/
NSArray *constraints = self.constraints.copy;
// 约束安装
for (MASConstraint *constraint in constraints)
{
constraint.updateExisting = self.updateExisting;
[constraint install];
}
// 清空constraints(将约束安装完成后,这个临时存放约束的容器就没用了)
[self.constraints removeAllObjects];
return constraints;
}
在以上方法里面需要解读的就是[constraint install],那我们就进入到MASConstraint这个类型来一探究竟。
3.2、MASConstraint、MASCompositeConstraint、MASViewConstraint
- (void)install { MASMethodNotImplemented(); }
// 卧槽,这是什么鬼,MASMethodNotImplemented定义了一个宏,大概意思就是说方法未实现,点进去看是这样:
#define MASMethodNotImplemented() \
@throw [NSException exceptionWithName:NSInternalInconsistencyException \
reason:[NSString stringWithFormat:@"You must override %@ in a subclass.", NSStringFromSelector(_cmd)] \
userInfo:nil]
抛出NSException的提示“一定要在子类中实现这个消息方法”,奥!!明白了,需要子类进行重写,那我们来看一下都有谁继承了MASConstraint类:
/**
* A group of MASConstraint objects
*
* 一组“MASConstraint”集合(存储一系列约束)
*/
@interface MASCompositeConstraint : MASConstraint
/**
* A single constraint.
* Contains the attributes neccessary for creating a NSLayoutConstraint and adding it to the appropriate view
*
* 该类是对NSLayoutConstraint的进一步封装
*/
@interface MASViewConstraint : MASConstraint <NSCopying>
从注释中我们可以了解到,“ MASCompositeConstraint”只是用来存储的容器,而“ MASViewConstraint”则是对“ NSLayoutConstraint”的进一步封装,所以,直接进入MASViewConstraint中查看被重写的“install”
3.3、MASViewConstraint (它的父类是MASViewConstraint)
- (void)install
{
//1、检测该约束是否已经安装
if (self.hasBeenInstalled) {
return;
}
//2、从MASViewAttribute中分离view和约束
MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;
//3、如果没有secondViewAttribute,默认secondItem为其父类,secondAttribute等于firstLayoutAttribute
if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
secondLayoutItem = self.firstViewAttribute.view.superview;
secondLayoutAttribute = firstLayoutAttribute;
}
//4、创建autolayout的约束
MASLayoutConstraint *layoutConstraint
= [MASLayoutConstraint constraintWithItem:firstLayoutItem
attribute:firstLayoutAttribute
relatedBy:self.layoutRelation
toItem:secondLayoutItem
attribute:secondLayoutAttribute
multiplier:self.layoutMultiplier
constant:self.layoutConstant];
//5、将priority和key赋值
layoutConstraint.priority = self.layoutPriority;
layoutConstraint.mas_key = self.mas_key;
//6、找到要添加约束的installView
if (self.secondViewAttribute.view) {
MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
NSAssert(closestCommonSuperview,
@"couldn't find a common superview for %@ and %@",
self.firstViewAttribute.view, self.secondViewAttribute.view);
self.installedView = closestCommonSuperview;
} else if (self.firstViewAttribute.isSizeAttribute) {
self.installedView = self.firstViewAttribute.view;
} else {
self.installedView = self.firstViewAttribute.view.superview;
}
// 7、添加或更新约束
MASLayoutConstraint *existingConstraint = nil;
if (self.updateExisting) {
existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
}
if (existingConstraint) {
// just update the constant
existingConstraint.constant = layoutConstraint.constant;
self.layoutConstraint = existingConstraint;
} else {
[self.installedView addConstraint:layoutConstraint];
self.layoutConstraint = layoutConstraint;
[firstLayoutItem.mas_installedConstraints addObject:self];
}
}
该类是对NSLayoutConstriant的进一步封装,作用:初始化NSLayoutConstriant并将该对象添加在相应的视图上。
- (id)copyWithZone:(NSZone __unused *)zone {
MASViewConstraint *constraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:self.firstViewAttribute];
constraint.layoutConstant = self.layoutConstant;
constraint.layoutRelation = self.layoutRelation;
constraint.layoutPriority = self.layoutPriority;
constraint.layoutMultiplier = self.layoutMultiplier;
constraint.delegate = self.delegate;
return constraint;
}
在这里我们需要对以上代码进行解读:
1、install这个核心方法处于MASViewConstraint这个类中,在上面我们说过,该类是对NSLayoutConstraint的进一步封装,NSLayoutConstriant在初始化时需要NSLayoutAttribute和所约束的View,MASViewAttribute的类主要是将view和attributet融合在了一起。
2、secondViewAttribute来源
secondViewAttribute的值来自 “make.top.mas_equalTo(self.view)” 中的 “(self.view)”,这一点我们可以在以下方法中找到答案
3.4、MASViewAttribute 类解读
这个类相对来说比较简单,从类名可以看出这个类是对NSLayoutAttribute的封装(每一个Attribute都有一个View与之对应)
MASViewAttribute = UIView + NSLayoutAttribute + item,
view表示所约束的对象,而item就是该对象上可以被约束的部分
@interface MASViewAttribute : NSObject
@property (nonatomic, weak, readonly) MAS_VIEW *view;
@property (nonatomic, weak, readonly) id item;
@property (nonatomic, assign, readonly) NSLayoutAttribute layoutAttribute;
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute;
- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute;
- (BOOL)isSizeAttribute;
@end
- (id)initWithView:(MAS_VIEW *)view item:(id)item layoutAttribute:(NSLayoutAttribute)layoutAttribute {
self = [super init];
if (!self) return nil;
_view = view;
_item = item;
_layoutAttribute = layoutAttribute;
return self;
}
3.5、MASConstraintMaker(工厂类)
负责创建MASConstraint类型的对象,用户可以通过该Block回调过来的MASConstraintMaker对象给View指定添加的约束以及约束的值。该工厂中的constraints属性数组就记录了该工厂创建的所有MASConstraint对象。
在上面masonry的使用中,我们提到过这个类中的“install”方法,下面就一些其他属性做一下介绍。
@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;
......
除了上面这些方向性的约束,还有一些如size、edges、center都可以帮助我们更为方便简洁的使用masonry.
例如:
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.mas_equalTo(UIEdgeInsetsMake(10, 10, 10, 10));
}];
mas_makeContrains:
(1)、创建一个约束制造者
(2)、调用block(maker),把所有控件的约束全部保存到约束制造者里面
(3)、[constrainMaker install],遍历约束制造者的所有约束给控件添加约束
3.6、NSArray+MASAdditions
这个数组的分类主要提供了UI成组适配,这里就使用做一下介绍
// 组内方向
typedef NS_ENUM(NSUInteger, MASAxisType) {
MASAxisTypeHorizontal,
MASAxisTypeVertical
};
// 下面这三个方法与View+MASAdditions用法及效果相同,这里不做过多阐述
- (NSArray *)mas_makeConstraints:(void (^)(MASConstraintMaker *make))block;
- (NSArray *)mas_updateConstraints:(void (^)(MASConstraintMaker *make))block;
- (NSArray *)mas_remakeConstraints:(void (^)(MASConstraintMaker *make))block;
/**
* 组间距固定,宽度距自适应
*
* @param axisType 方向
* @param fixedSpacing 组间距
* @param leadSpacing 第一个view与父类开始的(左/上)距离
* @param tailSpacing 最后一个view与父类结束(右/下)的距离
*/
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType
withFixedSpacing:(CGFloat)fixedSpacing
leadSpacing:(CGFloat)leadSpacing
tailSpacing:(CGFloat)tailSpacing;
/**
* 宽度固定,组间距自适应
*
* @param axisType 方向
* @param fixedItemLength 单个控件宽度
* @param leadSpacing 第一个view与父类开始的(左/上)距离
* @param tailSpacing 最后一个view与父类结束(右/下)的距离
*/
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType
withFixedItemLength:(CGFloat)fixedItemLength
leadSpacing:(CGFloat)leadSpacing
tailSpacing:(CGFloat)tailSpacing;
例:对一组view进行约束添加
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
[self.view addSubview:redView];
UIView *blueView = [[UIView alloc] init];
blueView.backgroundColor = [UIColor blueColor];
[self.view addSubview:blueView];
UIView *grayView = [[UIView alloc] init];
grayView.backgroundColor = [UIColor grayColor];
[self.view addSubview:grayView];
宽度固定,间距自适应
[@[redView, blueView, grayView] mas_distributeViewsAlongAxis:MASAxisTypeHorizontal
withFixedItemLength:10
leadSpacing:30
tailSpacing:30];
[@[redView, blueView, grayView] mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.view.mas_top).with.offset(100);
make.height.mas_equalTo(100);
}];
间距固定,宽度自适应
[@[redView, blueView, grayView] mas_distributeViewsAlongAxis:MASAxisTypeHorizontal
withFixedSpacing:10
leadSpacing:30
tailSpacing:30];
[@[redView, blueView, grayView] mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.mas_equalTo(self.view.mas_top).with.offset(100);
make.height.mas_equalTo(100);
}];
效果图如下:
NSArray+MASAdditions.png
3.7、ViewController+MASAdditions
提供了ViewController的LayoutGuide相关属性,自动根据bar高度设置的引导属性值。
UINavigationBar | UITabbar |
---|---|
mas_topLayoutGuide | mas_bottomLayoutGuide |
mas_topLayoutGuideTop | mas_bottomLayoutGuideTop |
mas_topLayoutGuideBottom | mas_bottomLayoutGuideBottom |
例:
存在navigationBar 时,mas_topLayoutGuideBottom 相当于 增加了44。
不存在navigationBar 时,mas_topLayoutGuideBottom 相对于 0 。
简单使用:
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.mas_equalTo(self.view);
make.top.mas_equalTo(self.mas_topLayoutGuide);
make.height.mas_equalTo(100);
}];
END
第一次就源码解读分析写一篇博客,由于篇幅较长,对文章整体的把控可能较差,后续还会打磨一下,希望各位不吝赐教
参考文章:
Masonry 框架的使用
追求Masonry
深入浅出-iOS函数式编程的实现 && 响应式编程概念
Masonry - 自动布局
网友评论
https://github.com/zhenglibao/FlexLib