Masonry源碼解析

Masonry源碼解析

    Masonry的核心依然是使用原生的NSLayoutConstraint類來進行添加約束,經過統一的封裝和鏈式函數式編程的方式讓開發者添加約束佈局更加方便。ios

1、核心的View+MASAdditions類別

    這個類別是Masonry中用來添加,更新和重置約束的核心類別。其中提供了咱們最經常使用的佈局函數。首先從類別命名上也能夠看出,此類別擴展的類是經過宏來設置的:編程

@interface MAS_VIEW (MASAdditions)

MAS_VIEW宏作到了平臺屏蔽的做用,在iOS上,其爲UIView,在MacOS上其實NSView。安全

    MASAdditions類別中定義了許多佈局屬性,例如上,下,左,右邊距,寬度高度等等。這些屬性被抽象爲MASViewAttribute對象,關於這個對象,後面會具體介紹。less

//左
@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;
//這個是一個鏈式編程的通用轉換方法,使用這個屬性將系統的NSLayoutAttribute轉換成抽象的MASViewAttribute對象
@property (nonatomic, strong, readonly) MASViewAttribute *(^mas_attribute)(NSLayoutAttribute attr);

//基線相關
@property (nonatomic, strong, readonly) MASViewAttribute *mas_firstBaseline;
@property (nonatomic, strong, readonly) MASViewAttribute *mas_lastBaseline;

//安全區 相關
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuide API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideTop API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideBottom API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideLeft API_AVAILABLE(ios(11.0),tvos(11.0));
@property (nonatomic, strong, readonly) MASViewAttribute *mas_safeAreaLayoutGuideRight API_AVAILABLE(ios(11.0),tvos(11.0));

//關聯的key值
@property (nonatomic, strong) id mas_key;

下面是3個最常使用的佈局方法:ide

//建立約束
- (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;

這3個函數的具體實現基本一致,其核心流程都是:關閉視圖Autoresizing特性->建立約束生成器->配置約束生成器->回調開發者約束設置->進行約束加載。這3個函數不一樣的地方只在配置約束生成器部分,配置了updateExisting參數爲YES,表示要進行已有約束的更新,配置了removeExisting爲YES表示要從新建立約束。約束生成器被抽象爲MASConstraintMaker對象,下面來具體看這個類。函數式編程

2、MASConstraintMaker約束生成器

    MASConstraint類主要用來構建約束對象。其中雖然和MASAdditions擴展相似,也是定義了約束屬性對象,可是其全部的Get方法都被從新實現了,當咱們經過Get方法調用約束屬性時,會執行下面核心函數:函數

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

上面函數的設計能夠巧妙的實現複合約束,例如make.width.height.equalTo(@100)這樣一條約束,實際上從width開始後面的屬性都被複合進了MASCompositeConstraint對象。約束的屬性建立出來後,須要對其進行值的設置,下面來看MASViewConstraint對象。佈局

3、MASConstraint約束對象

    MASViewConstraint類繼承自MASConstraint類,MASConstraint類還有一個子類爲MASCompositeConstraint類。MASConstraint中定義了基礎的約束值設置方法,都是採用block回調的方式,所以能夠進行鏈式編程:學習

//位置
- (MASConstraint * (^)(MASEdgeInsets insets))insets;
//尺寸偏移
- (MASConstraint * (^)(CGSize offset))sizeOffset;
//中心位置偏移
- (MASConstraint * (^)(CGPoint offset))centerOffset;
//比例 *
- (MASConstraint * (^)(CGFloat multiplier))multipliedBy;
//比例 /
- (MASConstraint * (^)(CGFloat divider))dividedBy;
//優先級
- (MASConstraint * (^)(MASLayoutPriority priority))priority;
//直接設置爲低優先級
- (MASConstraint * (^)(void))priorityLow;
//直接設置爲中優先級
- (MASConstraint * (^)(void))priorityMedium;
//直接設置爲高優先級
- (MASConstraint * (^)(void))priorityHigh;
//設置絕對等於
- (MASConstraint * (^)(id attr))equalTo;
//大於等於
- (MASConstraint * (^)(id attr))greaterThanOrEqualTo;
//小於等於
- (MASConstraint * (^)(id attr))lessThanOrEqualTo;

閱讀這個屬性的Get方法,你會發現他們最後都返回了當前對象自己,這也是爲鏈式編程所準備,MASConstraint中還有兩個屬性比較有趣:ui

- (MASConstraint *)with;
- (MASConstraint *)and;

這兩個屬性沒有實際的做用也沒有任何影響,他們的實現是直接返回當前對象,加強代碼可讀性。

    MASConstraint類中的install和uninstall函數是核心的約束添加方法,其中會進行系統原生約束對象的轉換添加或者刪除操做。核心的install函數解析以下:

- (void)install {
    //若是已經被加載 直接返回
    if (self.hasBeenInstalled) {
        return;
    }
    //若是系統layout對象已經建立 直接添加以後 返回
    if ([self supportsActiveProperty] && self.layoutConstraint) {
        self.layoutConstraint.active = YES;
        [self.firstViewAttribute.view.mas_installedConstraints addObject:self];
        return;
    }
    //獲取佈局的視圖與屬性
    MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
    NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
    MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
    NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;
    //若是不是尺寸佈局而且 相對視圖不存在 默認對父視圖進行相對佈局
    if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
        secondLayoutItem = self.firstViewAttribute.view.superview;
        secondLayoutAttribute = firstLayoutAttribute;
    }
    //建立佈局對象
    MASLayoutConstraint *layoutConstraint
        = [MASLayoutConstraint constraintWithItem:firstLayoutItem
                                        attribute:firstLayoutAttribute
                                        relatedBy:self.layoutRelation
                                           toItem:secondLayoutItem
                                        attribute:secondLayoutAttribute
                                       multiplier:self.layoutMultiplier
                                         constant:self.layoutConstant];
    //設置key和優先級
    layoutConstraint.priority = self.layoutPriority;
    layoutConstraint.mas_key = self.mas_key;
    //設置約束對象對用於的視圖
    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;
    }


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

4、一個小技巧

    Masonry中的一個函數值得咱們學習,其做用是對任何類型的值進行一層對象包裝,函數以下:

//值包裝宏
#define MASBoxValue(value) _MASBoxValue(@encode(__typeof__((value))), (value))

static inline id _MASBoxValue(const char *type, ...) {
    va_list v;
    va_start(v, type);
    id obj = nil;
    //進行類型判斷 結構體包裝成NSValue 基本類型包裝成NSNumber
    if (strcmp(type, @encode(id)) == 0) {
        id actual = va_arg(v, id);
        obj = actual;
    } else if (strcmp(type, @encode(CGPoint)) == 0) {
        CGPoint actual = (CGPoint)va_arg(v, CGPoint);
        obj = [NSValue value:&actual withObjCType:type];
    } else if (strcmp(type, @encode(CGSize)) == 0) {
        CGSize actual = (CGSize)va_arg(v, CGSize);
        obj = [NSValue value:&actual withObjCType:type];
    } else if (strcmp(type, @encode(MASEdgeInsets)) == 0) {
        MASEdgeInsets actual = (MASEdgeInsets)va_arg(v, MASEdgeInsets);
        obj = [NSValue value:&actual withObjCType:type];
    } else if (strcmp(type, @encode(double)) == 0) {
        double actual = (double)va_arg(v, double);
        obj = [NSNumber numberWithDouble:actual];
    } else if (strcmp(type, @encode(float)) == 0) {
        float actual = (float)va_arg(v, double);
        obj = [NSNumber numberWithFloat:actual];
    } else if (strcmp(type, @encode(int)) == 0) {
        int actual = (int)va_arg(v, int);
        obj = [NSNumber numberWithInt:actual];
    } else if (strcmp(type, @encode(long)) == 0) {
        long actual = (long)va_arg(v, long);
        obj = [NSNumber numberWithLong:actual];
    } else if (strcmp(type, @encode(long long)) == 0) {
        long long actual = (long long)va_arg(v, long long);
        obj = [NSNumber numberWithLongLong:actual];
    } else if (strcmp(type, @encode(short)) == 0) {
        short actual = (short)va_arg(v, int);
        obj = [NSNumber numberWithShort:actual];
    } else if (strcmp(type, @encode(char)) == 0) {
        char actual = (char)va_arg(v, int);
        obj = [NSNumber numberWithChar:actual];
    } else if (strcmp(type, @encode(bool)) == 0) {
        bool actual = (bool)va_arg(v, int);
        obj = [NSNumber numberWithBool:actual];
    } else if (strcmp(type, @encode(unsigned char)) == 0) {
        unsigned char actual = (unsigned char)va_arg(v, unsigned int);
        obj = [NSNumber numberWithUnsignedChar:actual];
    } else if (strcmp(type, @encode(unsigned int)) == 0) {
        unsigned int actual = (unsigned int)va_arg(v, unsigned int);
        obj = [NSNumber numberWithUnsignedInt:actual];
    } else if (strcmp(type, @encode(unsigned long)) == 0) {
        unsigned long actual = (unsigned long)va_arg(v, unsigned long);
        obj = [NSNumber numberWithUnsignedLong:actual];
    } else if (strcmp(type, @encode(unsigned long long)) == 0) {
        unsigned long long actual = (unsigned long long)va_arg(v, unsigned long long);
        obj = [NSNumber numberWithUnsignedLongLong:actual];
    } else if (strcmp(type, @encode(unsigned short)) == 0) {
        unsigned short actual = (unsigned short)va_arg(v, unsigned int);
        obj = [NSNumber numberWithUnsignedShort:actual];
    }
    va_end(v);
    return obj;
}

其中@encode()是一個編譯時特性,其能夠將傳入的類型轉換爲標準的OC類型字符串。

相關文章
相關標籤/搜索