iOS框架·Masonry源碼深度解析及學習啓示:設計模式與鏈式編程思想

傳送門:鏈式編程小Demohtml

這篇文章是 Masonry 框架源碼的解析和筆記。學習Masonry以前,先了解這個框架設計的初衷---傳統的利用系統API進行純代碼佈局的不足。而後,根據Masonry常見的幾個鏈式語法中,順藤摸瓜地瞭解Masonry的調用棧。最後,學習並思考這個框架用到的設計模式鏈式編程思想。git

1. 以前的不足:系統API純代碼佈局

  • 系統給的自動佈局(AutoLayout)的API
+(instancetype)constraintWithItem:(id)view1
                       attribute:(NSLayoutAttribute)attr1
                       relatedBy:(NSLayoutRelation)relation
                          toItem:(nullable id)view2
                       attribute:(NSLayoutAttribute)attr2
                      multiplier:(CGFloat)multiplier
                        constant:(CGFloat)c;
複製代碼
  • 傳統代碼中使用系統API進行佈局
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.view.backgroundColor = [UIColor yellowColor];
    
    
    UIView *subView = [[UIView alloc] init];
    subView.backgroundColor = [UIColor redColor];
    // 在設置約束前,先將子視圖添加進來
    [self.view addSubview:subView];
    
    // 使用autoLayout約束,禁止將AutoresizingMask轉換爲約束
    [subView setTranslatesAutoresizingMaskIntoConstraints:NO];
    
    // 設置subView相對於VIEW的上左下右各40像素
    NSLayoutConstraint *constraintTop = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:40];
    NSLayoutConstraint *constraintLeft = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:40];
    // 因爲iOS座標系的原點在左上角,因此設置下,右邊距使用負值
    NSLayoutConstraint *constraintBottom = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-40];
    NSLayoutConstraint *constraintRight = [NSLayoutConstraint constraintWithItem:subView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:-40];
    
    // 將四條約束加進數組中
    NSArray *array = [NSArray arrayWithObjects:constraintTop, constraintLeft, constraintBottom, constraintRight, nil];
    // 把約束條件設置到父視圖的Contraints中
    [self.view addConstraints:array];
}
複製代碼

可見,系統傳統的代碼佈局有點繁瑣。爲了簡化上述傳統佈局代碼,被普遍應用的第三方框架 MasonryAutoLayout 進行了封裝,Swift版則是 SnapKit。這篇文章就是針對 Masonry 源代碼的解析與學習筆記。在這以前,以下圖所示,是 Masonry 源代碼的結構圖:github

Masonry 源碼結構圖

2. 順藤摸瓜:Masonry鏈式語法的調用棧解析

2.1 mas_makeConstraints:外部調用

  • 調用例子
#import "Masonry.h"
複製代碼
[self.containerView addSubview:self.bannerView];
[self.bannerView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.leading.equalTo(self.containerView.mas_leading);
    make.top.equalTo(self.containerView.mas_top);
    make.trailing.equalTo(self.containerView.mas_trailing);
    make.height.equalTo(@(kViewWidth(131.0)));
}];
複製代碼

2.2 mas_makeConstraints:實現原理,經過導入的頭文件分析

  • Masonry.h
#import <Foundation/Foundation.h>

//! Project version number for Masonry.
FOUNDATION_EXPORT double MasonryVersionNumber;

//! Project version string for Masonry.
FOUNDATION_EXPORT const unsigned char MasonryVersionString[];

#import "MASUtilities.h"
#import "View+MASAdditions.h"
#import "View+MASShorthandAdditions.h"
#import "ViewController+MASAdditions.h"
#import "NSArray+MASAdditions.h"
#import "NSArray+MASShorthandAdditions.h"
#import "MASConstraint.h"
#import "MASCompositeConstraint.h"
#import "MASViewAttribute.h"
#import "MASViewConstraint.h"
#import "MASConstraintMaker.h"
#import "MASLayoutConstraint.h"
#import "NSLayoutConstraint+MASDebugAdditions.h"
複製代碼

其中View+MASAdditions分類爲UIView添加了mas_makeConstraints方法編程

  • View+MASAdditions.m
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}
複製代碼
  • MASConstraintMaker.m
@interface MASConstraintMaker () <MASConstraintDelegate>

@property (nonatomic, weak) MAS_VIEW *view;
@property (nonatomic, strong) NSMutableArray *constraints;

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

2.3 .top:經過MASConstraintMaker類源碼分析

先分析設置 第一個約束屬性 的狀況(且惟一一個):例如設計模式

make.top.equalTo(self.containerView.mas_top);
複製代碼
2.3.1 MASConstraintMaker的分析
  • MASConstraintMaker.m
- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    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;
}
複製代碼

該方法返回的newConstraint是一個MASViewConstraint類的示例,而MASViewConstraint類又是MASConstraint的子類,返回類型寫成MASConstraint沒毛病。數組

代碼較多,暫時能夠只先看if (!constraint)裏面的代碼。可見,最後設置 newConstraint對象代理爲self (即 MASConstraintMaker),並添加到一開始準備好的 self.constraints 數組中,返回。bash

其中,設置 MASViewConstraintnewConstraint 對象的 MASConstraintDelegate 代理爲self (即 MASConstraintMaker),其做用就是爲了可以同時設置多個約束屬性!即鏈式語法。數據結構

  • MASConstraint+Private.h
@protocol MASConstraintDelegate <NSObject>

/**
 *	Notifies the delegate when the constraint needs to be replaced with another constraint. For example
 *  A MASViewConstraint may turn into a MASCompositeConstraint when an array is passed to one of the equality blocks
 */
- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint;

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute;

@end
複製代碼
2.3.2 MASConstraintMaker的繼續分析

第2.3.1節的MASConstraintMaker.m代碼中,先是初始化了 MASViewAttribute 對象並保存了 view、item以及 NSLayoutAttribute 三個屬性。框架

  • MASViewAttribute.m
- (id)initWithView:(MAS_VIEW *)view layoutAttribute:(NSLayoutAttribute)layoutAttribute {
    self = [self initWithView:view item:view layoutAttribute:layoutAttribute];
    return self;
}

- (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;
}
複製代碼

而後又初始化了 MASViewConstraint 對象,內部配置了些默認參數並保存瞭如上的第一個約束參數 MASViewAttributeless

  • MASViewConstraint.m
- (id)initWithFirstViewAttribute:(MASViewAttribute *)firstViewAttribute {
    self = [super init];
    if (!self) return nil;
    
    _firstViewAttribute = firstViewAttribute;
    self.layoutPriority = MASLayoutPriorityRequired;
    self.layoutMultiplier = 1;
    
    return self;
}
複製代碼

2.4 .equalTo :經過基類MASConstraint及其子類MASViewConstraint分析

第一個約束屬性 設置完後,走到.equalTo時,前面返回已是一個 MASViewConstraint(繼承自MASConstraint) 對象了,於是調用的是在基類MASConstraint中聲明並實現的block屬性getter方法。

  • MASConstraint.m
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}
複製代碼

其中,基類 MASConstraint 僅僅聲明,並無實現equalToWithRelation抽象方法。可是,如2.3節中的鏈式語法.top,該方法返回的newConstraint實際是其子類--MASViewConstraint類的實例,故而可調用子類MASViewConstraint實現的equalToWithRelation方法:

  • MASViewConstraint.m
- (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;
        }
    };
}
複製代碼

代碼較多,暫時可先看else {裏面的代碼。

(1) self.layoutRelation = relation;

首先是 self.layoutRelation 保存了約束關係且重寫了 set 方法,在裏面用 self.hasLayoutRelation 這個 BOOL 標識已經有約束關係。

  • MASViewConstraint.m
- (void)setLayoutRelation:(NSLayoutRelation)layoutRelation {
    _layoutRelation = layoutRelation;
    self.hasLayoutRelation = YES;
}
複製代碼
(2) self.secondViewAttribute = attribute;

而後一樣是重寫了 self.secondViewAttributeset 方法,這裏會根據不一樣的狀況作不一樣的操做。

- (void)setSecondViewAttribute:(id)secondViewAttribute {
    if ([secondViewAttribute isKindOfClass:NSValue.class]) {
        [self setLayoutConstantWithValue:secondViewAttribute];
    } else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
        _secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
    } else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {
        MASViewAttribute *attr = secondViewAttribute;
        if (attr.layoutAttribute == NSLayoutAttributeNotAnAttribute) {
            _secondViewAttribute = [[MASViewAttribute alloc] initWithView:attr.view item:attr.item layoutAttribute:self.firstViewAttribute.layoutAttribute];;
        } else {
            _secondViewAttribute = secondViewAttribute;
        }
    } else {
        NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);
    }
}
複製代碼

其中,第1種狀況對應的是:

make.height.equalTo(@20.0f)
複製代碼

傳入 NSValue 的時, 會直接設置 constraintoffset, centerOffset, sizeOffset, 或者 insets。調用棧以下:

//MASViewConstraint.m
if ([secondViewAttribute isKindOfClass:NSValue.class]) {
    [self setLayoutConstantWithValue:secondViewAttribute];
}
//MASConstraint.m
- (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);
    }
}
//MASViewConstraint.m
- (void)setOffset:(CGFloat)offset {
    self.layoutConstant = offset;
}
//MASViewConstraint.m
- (void)setLayoutConstant:(CGFloat)layoutConstant {
    _layoutConstant = layoutConstant;
#if TARGET_OS_MAC && !(TARGET_OS_IPHONE || TARGET_OS_TV)
    if (self.useAnimator) {
        [self.layoutConstraint.animator setConstant:layoutConstant];
    } else {
        self.layoutConstraint.constant = layoutConstant;
    }
#else
    self.layoutConstraint.constant = layoutConstant;
#endif
}
複製代碼

第2種狀況,通常是直接傳入一個視圖:

make.top.equalTo(self)
複製代碼

這時, 就會初始化一個 layoutAttribute 屬性與 firstViewArribute 相同的 MASViewAttribute, 上面的代碼就會使視圖與 view 頂部對齊。

第3種狀況,會傳入一個視圖的 MASViewAttribute:

make.top.equalTo(view.mas_bottom);
複製代碼

使用這種寫法時, 通常是由於約束的方向不一樣. 這行代碼會使視圖的頂部與 view 的底部對齊。

2.5 .height.width:Masonry的鏈式語法特性

  • 調用例子
make.height.width.equalTo(@20);
複製代碼

其中,.height 設置第一個約束屬性時,調用的是 MASConstraintMaker.m 中的 .heightaddConstraintWithLayoutAttribute,以及- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute

  • MASConstraintMaker.m
- (MASConstraint *)height {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeHeight];
}

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    return [self constraint:nil addConstraintWithLayoutAttribute:layoutAttribute];
}

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    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;
}
複製代碼

該方法調用棧返回的是一個MASViewConstraint(父類是 MASConstraint) 對象。

所以,經過 .width 設置第二個約束屬性的時候,調用的先是基類 MASConstraint.m 中的.width,而後調用由子類MASViewConstraint實現的addConstraintWithLayoutAttribute方法。這時候的調用棧爲:

  • MASConstraint.m
- (MASConstraint *)width {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeWidth];
}

- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute __unused)layoutAttribute {
    MASMethodNotImplemented();
}
複製代碼
  • MASViewConstraint.m
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation");

    return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
}
複製代碼

這其中,self.delegate 是什麼呢?如2.3.1節所述,MASConstraintMaker.m 中設置了 MASViewConstraintnewConstraint 對象的 MASConstraintDelegate 代理爲「self」 (即 MASConstraintMaker),其做用就是爲了可以同時設置多個約束屬性,即鏈式語法。因此,第二個設置約束屬性跟第一個設置約束屬性最終 調用的方法同樣(都是MASConstraintMaker.m中實現的addConstraintWithLayoutAttribute)。

  • MASConstraintMaker.m
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    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;
}
複製代碼

當設置 第二次約束屬性 並執行完以後,咱們還能夠發現 constraint 不爲 nil,而是一個 MASViewConstraint 對象 ,因此該方法調用棧返回的不是 MASViewConstraint 對象,而是 MASCompositeConstraint 這個對象了,下面咱們來看看這個類。

2.6 約束的集合: MASCompositeConstraint

MASCompositeConstraint 是約束的集合,它裏面有個私有的數組用來存放多個 MASViewAttribute 對象。

make.height.width.equalTo(@20)
複製代碼

當設置 第二個約束屬性,走到 .width 時,最終走的是:

  • MASConstraintMaker.m
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    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判讀裏面,將 .height .wight 兩條約束 MASViewConstraint對象塞到數組裏,建立 MASCompositeConstraint 對象,而且一樣設置了 delegate,最後還把 self.constraints 裏面事先添加好的約束 MASViewConstraint 對象替換成了 MASCompositeConstraint 對象。

#pragma mark - MASConstraintDelegate

- (void)constraint:(MASConstraint *)constraint shouldBeReplacedWithConstraint:(MASConstraint *)replacementConstraint {
    NSUInteger index = [self.childConstraints indexOfObject:constraint];
    NSAssert(index != NSNotFound, @"Could not find constraint %@", constraint);
    [self.childConstraints replaceObjectAtIndex:index withObject:replacementConstraint];
}
複製代碼

另外,咱們能夠點擊 MASCompositeConstraint 初始化方法裏看看,它內部會經過 for 循環,把數組裏面的全部 MASViewConstraint 對象一樣設置了 delegate

- (id)initWithChildren:(NSArray *)children {
    self = [super init];
    if (!self) return nil;

    _childConstraints = [children mutableCopy];
    for (MASConstraint *constraint in _childConstraints) {
        constraint.delegate = self;
    }

    return self;
}
複製代碼

這麼作的目的同時是爲了可以繼續鏈式調用,好比咱們再設置第三個約束屬性 .left

make.height.width.left.equalTo(@20);
複製代碼

這時候的調用棧以下:

  • MASConstraint.m
- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}
複製代碼
  • MASCompositeConstraint.m
- (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    [self constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
    return self;
}

- (MASConstraint *)constraint:(MASConstraint __unused *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    id<MASConstraintDelegate> strongDelegate = self.delegate;
    MASConstraint *newConstraint = [strongDelegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute];
    newConstraint.delegate = self;
    [self.childConstraints addObject:newConstraint];
    return newConstraint;
}
複製代碼

能夠發現,這裏又是經過 delegate 方式,調用 MASConstraintMaker 工廠類中的:

  • MASConstraintMaker.m
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    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;
}
複製代碼

此時,注意到兩個 if 體都沒有走進去,既不像第一次,也不像第二次約束設置的時候。因此,此次僅僅是初始化了個 MASViewConstraint 對象就直接返回了,而後回到上個方法中添加到 MASCompositeConstraint 的私有數組 self.childConstraints 中返回備用。

三處的同名方法最終調用的仍是MASConstraintMaker中的方法

關於三次 約束設置以後的 .equalTo(@20),由於執行完 .left 時,返回的是 MASCompositeConstraint 對象,到這一步的時候會有點變化,調用棧以下:

  • MASConstraint.m
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}
複製代碼
  • MASCompositeConstraint.m
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attr, NSLayoutRelation relation) {
        for (MASConstraint *constraint in self.childConstraints.copy) {
            constraint.equalToWithRelation(attr, relation);
        }
        return self;
    };
}
複製代碼

能夠發現,這裏會循環以前準備好的私有數組 self.childConstraints,調用 MASViewConstraint.m 的 equalToWithRelation 方法,和上面講的同樣了。

2.7 添加約束到視圖

mas_makeConstraints 方法的最後會調用 [constraintMaker install] 方法來添加全部存儲在 self.constraints 數組中的全部約束。

  • MASConstraintMaker.m
- (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;
}
複製代碼

(1). 若是須要從新構建約束,也就是 調用 mas_remakeConstraints:方法,會先取出視圖的全部約束,而後經過一個 for 循環,調用 uninstall 來清空全部約束:

(2). 若是不須要從新構建約束,會取出 self.constraints 數組中準備好的約束,經過 for 循環,調用 install 來把約束添加到視圖上。

關於 install ,是基類 MASConstraint 的抽象方法,方法體由MASViewConstraintMASCompositeConstraint 實現。而 MASCompositeConstraintinstall方法體中其實也是調用的由MASViewConstraint類實現的install

  • MASConstraint.m
- (void)install { MASMethodNotImplemented(); }
複製代碼
  • MASCompositeConstraint.m
- (void)install {
    for (MASConstraint *constraint in self.childConstraints) {
        constraint.updateExisting = self.updateExisting;
        [constraint install];
    }
}
複製代碼
  • MASViewConstraint.m

這裏代碼較多,就不分開解析了,直接分爲7步寫到源碼的註釋中,以下所示:

- (void)install {

    //【1】若是約束以及存在並是 active 會直接返回。
    if (self.hasBeenInstalled) {
        return;
    }
    
    //【2】若是 self.layoutConstraint 響應了 isActive 方法而且不爲空,會激活這條約束並添加到 mas_installedConstraints 數組中,最後返回。
    if ([self supportsActiveProperty] && self.layoutConstraint) {
        self.layoutConstraint.active = YES;
        [self.firstViewAttribute.view.mas_installedConstraints addObject:self];
        return;
    }
    
    //【3】這邊是獲取即將用於初始化 NSLayoutConstraint 的子類 MASLayoutConstraint 的幾個屬性。
    MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
    NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
    MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
    NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;
    

    // alignment attributes must have a secondViewAttribute
    // therefore we assume that is refering to superview
    // eg make.left.equalTo(@10)
    //【4】這邊是判斷當前即將添加的約束是不是 size 類型的而且 self.secondViewAttribute 也就是約束的第二個參數是 nil,(eg make.left.equalTo(@10))會自動將約束添加到約束的第一個參數視圖的 superview 上。
    if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
        secondLayoutItem = self.firstViewAttribute.view.superview;
        secondLayoutAttribute = firstLayoutAttribute;
    }
    

    //【5】而後就會初始化 NSLayoutConstraint 的子類 MASLayoutConstraint。
    MASLayoutConstraint *layoutConstraint
        = [MASLayoutConstraint constraintWithItem:firstLayoutItem
                                        attribute:firstLayoutAttribute
                                        relatedBy:self.layoutRelation
                                           toItem:secondLayoutItem
                                        attribute:secondLayoutAttribute
                                       multiplier:self.layoutMultiplier
                                         constant:self.layoutConstant];
    
    layoutConstraint.priority = self.layoutPriority;
    layoutConstraint.mas_key = self.mas_key;
    

    //【6】這段代碼會先判斷是否有約束第二個參數的視圖,有的話會尋找約束第一個和第二參數視圖的公共 Superview,至關於求兩個數的最小公倍數;若是不知足第一個條件,會判斷約束第一個參數是不是 size 類型的,是的話直接取到它的視圖;最後都不知足會直接取到約束第一個參數視圖父視圖。
    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】若是須要升級當前的約束就會獲取原有的約束,並替換爲新的約束,這樣就不須要再次爲 view 安裝約束。若是原來的 view 中不存在能夠升級的約束,那麼就會在上一步尋找到的 installedView 上面添加約束。
    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];
    }
}
複製代碼

其中第【6】步中的mas_closestCommonSuperview方法,它會尋找 firstLayoutItem 和 secondLayoutItem 兩個視圖的公共 superview, 至關於求兩個數的最小公倍數.

  • View+MASAdditions.m
- (instancetype)mas_closestCommonSuperview:(MAS_VIEW *)view {
    MAS_VIEW *closestCommonSuperview = nil;

    MAS_VIEW *secondViewSuperview = view;
    while (!closestCommonSuperview && secondViewSuperview) {
        MAS_VIEW *firstViewSuperview = self;
        while (!closestCommonSuperview && firstViewSuperview) {
            if (secondViewSuperview == firstViewSuperview) {
                closestCommonSuperview = secondViewSuperview;
            }
            firstViewSuperview = firstViewSuperview.superview;
        }
        secondViewSuperview = secondViewSuperview.superview;
    }
    return closestCommonSuperview;
}
複製代碼

3. 順藤再摸瓜:Masonry其它鏈式語法的調用棧解析(選讀)

3.1 make.edges.equalTo(view)

  • 例子
make.edges.equalTo(view)
複製代碼

咱們再來看看這種寫法,調用棧以下:

  • MASConstraintMaker.m
- (MASConstraint *)edges {
    return [self addConstraintWithAttributes:MASAttributeTop | MASAttributeLeft | MASAttributeRight | MASAttributeBottom];
}
- (MASConstraint *)addConstraintWithAttributes:(MASAttribute)attrs {
    __unused MASAttribute anyAttribute = (MASAttributeLeft | MASAttributeRight | MASAttributeTop | MASAttributeBottom | MASAttributeLeading
                                          | MASAttributeTrailing | MASAttributeWidth | MASAttributeHeight | MASAttributeCenterX
                                          | MASAttributeCenterY | 
                                          
                    ......
                        
    NSMutableArray *attributes = [NSMutableArray array];
    
    if (attrs & MASAttributeLeft) [attributes addObject:self.view.mas_left];
    if (attrs & MASAttributeRight) [attributes addObject:self.view.mas_right];
    if (attrs & MASAttributeTop) [attributes addObject:self.view.mas_top];
    
    				......    
    
    NSMutableArray *children = [NSMutableArray arrayWithCapacity:attributes.count];
    
    for (MASViewAttribute *a in attributes) {
        [children addObject:[[MASViewConstraint alloc] initWithFirstViewAttribute:a]];
    }
    
    MASCompositeConstraint *constraint = [[MASCompositeConstraint alloc] initWithChildren:children];
    constraint.delegate = self;
    [self.constraints addObject:constraint];
    return constraint;
}
複製代碼

代碼太多省略了一部分,能夠發現這段代碼做用就是返回一個包含多條約束的 MASCompositeConstraint 對象,接着後面的操做也都是同樣的了。

3.2 make.edges.equalTo(UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f));

上面3.1中例子的寫法還能夠改爲這樣:

make.edges.equalTo(UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f));
複製代碼

這裏的 equalTo 須要注意下,它是一個宏,定義在 MASConstraint.h 中:

  • MASConstraint.h
#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__)
複製代碼

代入上述宏定義,前面的代碼等效成:

make.edges.equalTo(MASBoxValue(UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f)));
複製代碼

能夠發現,其實裏面調用的是 MASBoxValue 這個宏,它將 C 和 Objective-C 語言中的一些基本數據結構好比說 double CGPoint CGSize 這些值用 NSValue 進行包裝。

這裏還支持直接調用 size、center 等,具體實現都差很少,就不熬述了:

make.center.equalTo(CGPointMake(0, 50));
make.size.equalTo(CGSizeMake(200, 100));
複製代碼

3.3 make.height.equalTo(@[redView, blueView])

make.height.equalTo(@[redView, blueView])
複製代碼

再來看看這種傳數組的,在走到 .equalTo 時,最終會調用 MASViewConstraint.m 裏面的 equalToWithRelation 方法

  • MASConstraint.m
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}
複製代碼
  • MASViewConstraint.m
- (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 {   ....    }
    };
}
複製代碼

這邊仍是遍歷數組,而且 MASViewConstraint 實現 NSCopying 協議,調用 [self copy] 會建立 MASViewConstraint 對象

- (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;
}
複製代碼

而後會根據傳的數組裏面的 Value 類型來作不一樣的操做,前面講過就不熬述了:

- (void)setSecondViewAttribute:(id)secondViewAttribute {
    if ([secondViewAttribute isKindOfClass:NSValue.class]) {
        [self setLayoutConstantWithValue:secondViewAttribute];
    } else if ([secondViewAttribute isKindOfClass:MAS_VIEW.class]) {
        _secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
    } else if ([secondViewAttribute isKindOfClass:MASViewAttribute.class]) {
        _secondViewAttribute = secondViewAttribute;
    } else {
        NSAssert(NO, @"attempting to add unsupported attribute: %@", secondViewAttribute);
    }
}
複製代碼

最後即是生成 MASCompositeConstraint 對象,並經過 delegate 方式,調用 MASConstraintMaker 的方法,替換 self.constraints 數組裏的約束:

- (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];
}
複製代碼

4. 觸類旁通:框架源碼的學習啓示

4.1 簡化的設計模式:工廠類&工廠方法

MASConstraintMaker類就是一個工廠類,負責建立MASConstraint類型的對象(依賴於MASConstraint接口,而不依賴於具體實現)。在UIView的View+MASAdditions分類中就是調用的MASConstraintMaker類中的一些方法。上述咱們在使用Masonry給subView添加約束時,mas_makeConstraints方法中的Block的參數就是MASConstraintMaker的對象。用戶能夠經過該Block回調過來的MASConstraintMaker對象給View指定要添加的約束以及該約束的值。該工廠中的constraints屬性數組就記錄了該工廠建立的全部MASConstraint對象。

核心類和分類的類圖

MASConstraintMaker 之因此成爲約束工廠類,由於MASConstraintMaker賦值建立NSLayoutConstraint對象,由於Masonry將NSLayoutConstraint類進一步封裝成了MASViewConstraint,因此MASConstraintMaker是負責建立MASViewConstraint的對象,並調用MASViewConstraint對象的Install方法將該約束添加到相應的視圖中。

說了這麼多,總結一下,若是你調用maker.top, maker.left等等這些方法都會調用下方的工廠方法來建立相應的MASViewConstraint對象,並記錄在工廠對象的約束數組中。之因此能鏈式調用,就是講當前的工廠對象(MASConstraintMaker)指定爲MASViewConstraint對象的代理,因此一個MASViewConstraint對象就能夠經過代理來調用工廠方法來建立另外一個新的MASViewConstraint對象了,此處用到了代理模式。

工廠對象MASConstraintMaker的工廠方法

角色分析

  • Client:UIView,經過分類View+MASAdditions來扮演

  • 工廠類:MASConstraintMaker

  • 抽象產品:MASConstraint

  • 具體產品:MASViewConstraintMASCompositeConstraint

工廠類與兩個子類

4.2 真正的設計模式:組合模式

換一種角度看,Masonry 並不是單純的工廠模式,而是採用了經典的 Composite 設計模式,中文可譯做組合模式

典型的 Composite 類圖

典型的 Composite 對象結構

4.2.1 經典 組合模式 中的參與者:
Client
  • 經過 Component 接口操縱組合部件的對象。
Component
  • 爲組合中的對象聲明接口。
  • 在適當的狀況下,實現全部類共有接口的缺省行爲
  • 聲明一個接口用於訪問和管理 Component 的子組件。
  • 在遞歸結構中定義一個接口,用於訪問一個父部件,並在合適的狀況下實現它。
Leaf
  • 在組合中表示葉節點對象,葉節點沒有子節點。
  • 在組合中定義圖元對象的行爲。
Composite
  • 定義有子部件的那些部件的行爲。
  • 在 Composite 接口中實現與子部件有關的操做。
4.2.2 從 組合模式 的角度看,Masonry 框架中的角色分析:

UIView,經過分類View+MASAdditions來調用Masonry

Client
  • MASConstraintMaker
Component
  • MASConstraint
Leaf
  • MASViewConstraint
Composite
  • MASCompositeConstraint

4.3 編程思想:鏈式編程

Objective-C是一門動態語言,它使用了一種動態的消息發送機制,即對象(object)或類(class)調用方法。而OC中的點語法則只能經過setter和getter方法做用於類的屬性,而不能做用於某個方法。想實現鏈式語法,只能經過相似block屬性的getter方法。

鏈式編程思想:核心思想爲將block做爲方法的返回值,且返回值的類型爲調用者自己,並將該方法以setter的形式返回,這樣就能夠實現了連續調用,即爲鏈式編程。

【舉例】簡單使用鏈式編程思想實現一個簡單計算器的功能:

4.3.1 新建一個名爲CaculateMaker的類,用於運算。

新建一個名爲CaculateMaker的類

4.3.2 在CaculateMaker.h文件中聲明一個方法add:
  • CaculateMaker.h
//  CaculateMaker.h
//  ChainBlockTestApp

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface CaculateMaker : NSObject

@property (nonatomic, assign) CGFloat result;

- (CaculateMaker *(^)(CGFloat num))add;

@end
複製代碼
4.3.3 在CaculateMaker.m文件中實現這個方法:
  • CaculateMaker.m
//  CaculateMaker.m
//  ChainBlockTestApp


#import "CaculateMaker.h"

@implementation CaculateMaker

- (CaculateMaker *(^)(CGFloat num))add;{
    return ^CaculateMaker *(CGFloat num){
        _result += num;
        return self;
    };
}

@end
複製代碼
4.3.4 在viewController裏面導入CaculateMaker.h文件,而後調用add方法就完成了鏈式語法:
  • ViewController.m
CaculateMaker *maker = [[CaculateMaker alloc] init];
maker.add(20).add(30);
複製代碼

經過上面Masonry佈局能夠看出,它爲UIView寫了一個category,拓展了mas_makeConstraints方法,並將MASConstraintMaker對象做爲block的參數傳遞,在block的實現裏完成UIView的佈局,提現了函數式編程思想

4.3.5 一樣,咱們也能夠給NSObject添加一個NSObject+Caculate的分類,完成加法操做:
  • NSObject+Caculate.h
//  NSObject+Caculate.h
//  ChainBlockTestApp

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "CaculateMaker.h"

@interface NSObject (Caculate)

- (CGFloat)caculate:(void (^)(CaculateMaker *make))block;

@end
複製代碼
  • NSObject+Caculate.m
//  NSObject+Caculate.m
//  ChainBlockTestApp

#import "NSObject+Caculate.h"

@implementation NSObject (Caculate)

- (CGFloat)caculate:(void (^)(CaculateMaker *make))block;{
    CaculateMaker *make = [[CaculateMaker alloc] init];
    block(make);
    return make.result;
}

@end
複製代碼
4.3.6 最後在viewController裏面調用,就很輕鬆的實現了鏈式語法:
  • ViewController.m
CGFloat result = [NSObject caculate:^(CaculateMaker *maker) {
    maker.add(10).add(20).add(30);
}];
NSLog(@"結果爲:%.2f",result);
複製代碼

5. 參考閱讀

  • Masonry解析

    • http://qiufeng.me/masonry
    • https://www.cnblogs.com/ludashi/p/5591572.html
  • 工廠模式

    • https://www.jianshu.com/p/7b89b7f587f9
    • https://www.jianshu.com/p/847af218b1f0
  • 組合模式

    • http://www.runoob.com/design-pattern/composite-pattern.html
    • http://www.cnblogs.com/gaochundong/p/design_pattern_composite.html
    • http://www.cnblogs.com/peida/archive/2008/09/09/1284686.html
  • 鏈式編程

    • https://www.jianshu.com/p/cb9252f5105b
    • https://www.jianshu.com/p/ac8bdd3430e7
相關文章
相關標籤/搜索