讀 SnapKit 和 Masonry 自動(dòng)布局框架源碼

前言

一直覺(jué)得 SnapKit 和 Masonry 這兩個(gè)框架設(shè)計(jì)和封裝的很好,用起來(lái)的體驗(yàn)也是一致的,翻了下它們的源碼,對(duì)其設(shè)計(jì)方式和涉及的技術(shù)做了下記錄。文章打算圍繞,給誰(shuí)做約束?如何設(shè)置約束?設(shè)置完后如何處理?這三個(gè)問(wèn)題看看 SnapKit 和 Masnory 分別是怎么做的,正好也能夠窺探下作者是如何利用 Swift 和 Objective-C 兩個(gè)不同語(yǔ)言的不同特性做到一致的使用體驗(yàn)的。

如果還不了解這兩個(gè)框架的使用的話可以參看它們項(xiàng)目 GitHub 說(shuō)明:GitHub - SnapKit/SnapKit: A Swift Autolayout DSL for iOS & OS XGitHub - SnapKit/Masonry: Harness the power of AutoLayout NSLayoutConstraints with a simplified, chainable and expressive syntax. Supports iOS and OSX Auto Layout

如果還不了解自動(dòng)布局或者還沒(méi)有用過(guò)的同學(xué)可以參看我三年前這篇文章,里面有詳細(xì)的介紹和相關(guān)資料:深入剖析Auto Layout,分析iOS各版本新增特性 | 星光社 - 戴銘的博客

進(jìn)入那三個(gè)問(wèn)題之前我們先看看兩個(gè)框架的整體結(jié)構(gòu)圖,對(duì)它們有個(gè)大概的印象。

SnapKit 源碼結(jié)構(gòu)圖

01.jpg

Masonry 源碼結(jié)構(gòu)圖

02.jpg

接下來(lái)我們來(lái)詳細(xì)看看兩個(gè)框架的內(nèi)部,首先來(lái)看看剛才那三個(gè)問(wèn)題中的第一個(gè)問(wèn)題。

給誰(shuí)做約束?

SnapKit

ConstraintView

這個(gè) View 實(shí)際上在 iOS 里就是 UIView,在 macOS 上就是 NSView。

#if os(iOS) || os(tvOS)
    public typealias ConstraintView = UIView
#else
    public typealias ConstraintView = NSView
#endif

對(duì) ConstraintView 做擴(kuò)展,里面定義里一個(gè)屬性 snp

public extension ConstraintView {
    public var snp: ConstraintViewDSL {
        return ConstraintViewDSL(view: self)
    }
}

這個(gè) snp 屬性的類型就是結(jié)構(gòu)體 ConstraintViewDSL。下面來(lái)看看 ConstraintViewDSL 這個(gè)結(jié)構(gòu)體做什么用的。

ConstraintViewDSL

這個(gè)結(jié)構(gòu)體會(huì)在初始化時(shí)通過(guò) view 屬性持有 ConstraintView。

internal let view: ConstraintView

internal init(view: ConstraintView) {
    self.view = view
    
}

同時(shí)還提供了那些我們必調(diào)用的 makeConstraints,contentHuggingHorizontalPriority 等等函數(shù)。這樣我們就可以在 UIView 中直接調(diào)用這些函數(shù)來(lái)進(jìn)行視圖的約束設(shè)置了。

public func makeConstraints(_ closure: (_ make: ConstraintMaker) -> Void) {
    ConstraintMaker.makeConstraints(item: self.view, closure: closure)
}

public var contentHuggingHorizontalPriority: Float {
    get {
        return self.view.contentHuggingPriority(for: .horizontal).rawValue
    }
    set {
        self.view.setContentHuggingPriority(LayoutPriority(rawValue: newValue), for: .horizontal)
    }
}
//還有 remakeConstraints,contentCompressionResistanceHorizontalPriority 等等就不一一列出了
...

ConstraintViewDSL 是繼承自 ConstraintAttributesDSL。

ConstraintAttributesDSL

ConstraintAttributesDSL 是個(gè)協(xié)議,繼承于 ConstraintBasicAttributesDSL 這個(gè)協(xié)議,為什么要多這一層呢,因?yàn)?ConstraintAttributesDSL 這個(gè)里面定了 iOS 8 系統(tǒng)出現(xiàn)的新的屬性,比如 lastBaseline,firstBaseline,leftMargin 等。而 ConstraintBasicAttributesDSL 里定義的是一開(kāi)始就有的那些屬性比如 left,top,centerX,size 等。

Masonry

接下來(lái)我們看看 Masonry 是給誰(shuí)做的約束。

View+MASAdditions

View+MASAdditions 就是 Masonry 的一個(gè)外部的入口,實(shí)質(zhì)上就是 UIView 的一個(gè) Category 作用就是用來(lái)設(shè)置 MASViewAttribute 的屬性,并實(shí)例化,并且指定當(dāng)前的 UIView 對(duì)應(yīng)的 LayoutAttribute。和 SnapKit 一樣, Masonry 也對(duì) iOS 和 macOS 做了兼容,在 macOS 里就是 NSView,相關(guān)代碼在 MASUtilities.h 文件里,這里除了平臺(tái)相關(guān)代碼外,還有些宏的定義和靜態(tài)方法。這里我們可以看看靜態(tài)方法 static inline id _MASBoxValue(const char *type, ...) 的作用:

static inline id _MASBoxValue(const char *type, ...) {
    va_list v;
    va_start(v, type);
    id obj = nil;
    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;
}

看這段代碼是不是就能猜出來(lái)是做什么的了,對(duì),它就是我們經(jīng)常使用的 mas_equalTo 這個(gè)方法,這里可以看到它是如何支持變參和如何將 float,double,int 這樣的值類型數(shù)據(jù)轉(zhuǎn)換成和 equalTo 一樣的對(duì)象 NSNumber 數(shù)據(jù)的。這個(gè)寫(xiě)法靈感來(lái)自GitHub - specta/expecta: A Matcher Framework for Objective-C/Cocoa。 mas_equalTo 和 equalTo 都是宏定義的。

#define mas_equalTo(...)                 equalTo(MASBoxValue((__VA_ARGS__)))

#define equalTo(...)                     mas_equalTo(__VA_ARGS__)

#define MASBoxValue(value) _MASBoxValue(@encode(__typeof__((value))), (value))

MASBoxValue 這個(gè)宏定義就是上面的 _MASBoxValue 這個(gè)方法。細(xì)心同學(xué)會(huì)發(fā)現(xiàn)這兩個(gè) equal 的宏對(duì)應(yīng)的方法是不同的,一個(gè)是 equalTo(MASBoxValue((VA_ARGS))) 另一個(gè)是 mas_equalTo(VA_ARGS) 但是這兩個(gè)方法的實(shí)現(xiàn)是一樣的。

- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

- (MASConstraint * (^)(id))mas_equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}

這樣寫(xiě)就是避免宏定義沖突的一種方式。

這個(gè) Category 還有那些我們總是調(diào)用的 mas_makeConstraints,mas_updateConstraints,mas_remakeConstraint 等方法。

mas_makeConstraints 的 block 參數(shù)會(huì)將創(chuàng)建的 MASConstraintMaker 這個(gè)工廠類對(duì)象暴露出去,讓我們?nèi)ピO(shè)置這個(gè)類對(duì)象中的 MASConstraint 屬性,然后通過(guò)該對(duì)象的 install 方法將當(dāng)前視圖中所有添加的約束添加到一個(gè)數(shù)組里。該數(shù)組里存儲(chǔ)是 MASViewConstraint 對(duì)象,對(duì)應(yīng)的就是 NSLayoutConstraint。具體代碼如下:

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}

這種設(shè)計(jì)模式和 SnapKit 的一樣使用了閉包來(lái)獲取用戶設(shè)置的數(shù)據(jù),在設(shè)計(jì)模式里叫做好萊塢原則。

mas_updateConstraints 和 mas_makeConstraints 差不多,不過(guò)里面多了一行:

constraintMaker.updateExisting = YES;

這樣當(dāng)添加約束時(shí)會(huì)通過(guò)這個(gè)屬性是否為真來(lái)檢查約束是否 intall 了,是的話就更新,沒(méi)有就添加。

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

mas_remakeConstraints 的話是添加了這一句:

constraintMaker.removeExisting = YES;

設(shè)置為 YES 后會(huì)將以前設(shè)置的約束 uninstall 掉,后面再把新設(shè)置的約束添加上。

if (self.removeExisting) {
    NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
    for (MASConstraint *constraint in installedConstraints) {
        [constraint uninstall];
    }
}

最后還有個(gè)方法 mas_closestCommonSuperview,這個(gè)方法一般我們都不會(huì)主動(dòng)調(diào)用,所以很多人應(yīng)該都太熟悉,不過(guò)這個(gè)斷言報(bào)錯(cuò)大家應(yīng)該會(huì)有很深刻的印象 couldn't find a common superview for … 。所以這個(gè)方法如其名就是去找共同的父視圖,還是最近的。框架內(nèi)部也就在 MASViewConstraint 的 install 方法里用了一次。

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

這個(gè)查找是 N 方的,誰(shuí)有辦法能夠優(yōu)化下么。

如何設(shè)置約束?

SnapKit

先看看這張圖,里面是我們使用框架時(shí)用的最多的設(shè)置 make 的過(guò)程,圖里將每個(gè)操作對(duì)應(yīng)的不同 ConstraintMaker 做了說(shuō)明。


03.jpg

下面來(lái)對(duì)這幾種 ConstraintMaker 來(lái)詳細(xì)說(shuō)下。

ConstraintMaker

這個(gè)是設(shè)置的入口,makeConstraints 函數(shù)一個(gè)閉包參數(shù)可以提供外部去設(shè)置ConstraintMaker 自己的 left,right,top 等屬性來(lái)描述約束。這些屬性的 getter 方法會(huì)返回 ConstraintMakerExtendable 實(shí)例。

先看看 ConstraintMaker 的構(gòu)造函數(shù):

internal init(item: LayoutConstraintItem) {
    self.item = item
    self.item.prepare()
}

LayoutConstraintItem 會(huì)通過(guò)給擴(kuò)展 ConstraintLayoutGuide 和 ConstraintView 來(lái)達(dá)到約束 item 類型的作用。下面看看 prepare 這個(gè)函數(shù)的作用。

internal func prepare() {
    if let view = self as? ConstraintView {
        view.translatesAutoresizingMaskIntoConstraints = false
    }
}

看,禁用 AutoresizeMask 是在這里統(tǒng)一處理了。
接下來(lái)看看閉包參數(shù)設(shè)置屬性的 getter 方法。

public var left: ConstraintMakerExtendable {
    return self.makeExtendableWithAttributes(.left)
}

internal func makeExtendableWithAttributes(_ attributes: ConstraintAttributes) -> ConstraintMakerExtendable {
    let description = ConstraintDescription(item: self.item, attributes: attributes)
    self.descriptions.append(description)
    return ConstraintMakerExtendable(description)
}

ConstraintMaker 包含了一個(gè) ConstraintDescription 數(shù)組,里面會(huì)記錄用戶設(shè)置的各個(gè)屬性,然后返回 ConstraintMakerExtendable。

OptionSet

這里的 ConstraintAttributes 是個(gè) OptionSet,ConstraintAttributes 結(jié)構(gòu)體來(lái)遵從 OptionSet 選項(xiàng)集合協(xié)議,為什么不用枚舉呢?因?yàn)樵谝淮沃挥幸粋€(gè)選項(xiàng)被選中是枚舉是 OK 的。但是在 Swift 里的枚舉是沒(méi)法將多個(gè)枚舉選項(xiàng)組成一個(gè)值的,比如 ConstraintAttributes 里的 edges,size 和 center 等就是組合而成的。而 OptionSet 結(jié)構(gòu)體使用了高效的位域來(lái)表示的。還有,OptionSet 繼承于 ExpressibleByArrayLiteral,這樣還能夠使用數(shù)組字面量來(lái)生成選項(xiàng)的集合。下面看看這個(gè) ConstraintAttributes 是如何定義的。

internal static var none: ConstraintAttributes { return self.init(0) }
internal static var left: ConstraintAttributes { return self.init(1) }
internal static var top: ConstraintAttributes {  return self.init(2) }
internal static var right: ConstraintAttributes { return self.init(4) }
internal static var bottom: ConstraintAttributes { return self.init(8) }
internal static var leading: ConstraintAttributes { return self.init(16) }
internal static var trailing: ConstraintAttributes { return self.init(32) }
internal static var width: ConstraintAttributes { return self.init(64) }
internal static var height: ConstraintAttributes { return self.init(128) }
internal static var centerX: ConstraintAttributes { return self.init(256) }
internal static var centerY: ConstraintAttributes { return self.init(512) }
//更多就不一一列出來(lái)
...
// 組合的
internal static var edges: ConstraintAttributes { return self.init(15) }
internal static var size: ConstraintAttributes { return self.init(192) }
internal static var center: ConstraintAttributes { return self.init(768) }

@available(iOS 8.0, *)
internal static var margins: ConstraintAttributes { return self.init(61440) }
//還有一些,先不列了
...

可以看到組合的 size 就是 width(64) + height(128)= size(192)。

重載和自定義操作符

ConstraintAttributes 重載了 +,+=,-= 和 == 這些操作符。我們先看看代碼

internal func + (left: ConstraintAttributes, right: ConstraintAttributes) -> ConstraintAttributes {
    return left.union(right)
}

internal func +=(left: inout ConstraintAttributes, right: ConstraintAttributes) {
    left.formUnion(right)
}

internal func -=(left: inout ConstraintAttributes, right: ConstraintAttributes) {
    left.subtract(right)
}

internal func ==(left: ConstraintAttributes, right: ConstraintAttributes) -> Bool {
    return left.rawValue == right.rawValue
}

這種重載很適合對(duì)自定義的結(jié)構(gòu)體進(jìn)行一些熟悉的簡(jiǎn)化符號(hào)操作。

如果希望自定義一些操作符的話就需要先聲明下,讓編譯器知道這是個(gè)操作符,比如我們自定義一個(gè)操作符?

struct A {
    var v:Int = 0
}
infix operator ?
func ?(left: A, right: A) -> Bool {
    return left.v + 1 == right.v
}

這里的 infix 是中間運(yùn)算符的意思,還有前置運(yùn)算符 prefix 和后置運(yùn)算符 postfix。自定義運(yùn)算符之能是類似,/,=,-,+,*,%,<,>,!,&,|,^,。,~ 等這樣的符號(hào)組成,也能支持一些特殊的字符比如剛才的用的?,還有?,? 這樣的特殊符號(hào)。

自定義運(yùn)算符還能夠指定優(yōu)先級(jí)分組 precedencegroup,如下:

infix operator ? : CPrecedence
precedencegroup CPrecedence {
    associativity: left
    higherThan: AdditionPrecedence
    lowerThan: MultiplicationPrecedence
}

下面列下常用類型對(duì)應(yīng)的group

// "Exponentiative"
infix operator  << : BitwiseShiftPrecedence
infix operator &<< : BitwiseShiftPrecedence
infix operator  >> : BitwiseShiftPrecedence
infix operator &>> : BitwiseShiftPrecedence

// "Multiplicative"
infix operator   * : MultiplicationPrecedence
infix operator  &* : MultiplicationPrecedence
infix operator   / : MultiplicationPrecedence
infix operator   % : MultiplicationPrecedence
infix operator   & : MultiplicationPrecedence

// "Additive"
infix operator   + : AdditionPrecedence
infix operator  &+ : AdditionPrecedence
infix operator   - : AdditionPrecedence
infix operator  &- : AdditionPrecedence
infix operator   | : AdditionPrecedence
infix operator   ^ : AdditionPrecedence

// FIXME: is this the right precedence level for "..." ?
infix operator  ... : RangeFormationPrecedence
infix operator ..< : RangeFormationPrecedence

完整的操作符的定義和 precedencegroup 之間的優(yōu)先級(jí)關(guān)系在 Swift 源碼的 swift/stdlib/public/core/Policy.swift 文件里,在線看地址是:https://github.com/apple/swift/blob/a7ff0da33488b9050cf83df95f46e5b9aa2348d5/stdlib/public/core/Policy.swift 。那些操作符優(yōu)先級(jí)高些或者低些在這個(gè)文件里是一目了然。

ConstraintMakerExtendable

ConstraintMakerExtendable 繼承 ConstraintMakerRelatable,它可以實(shí)現(xiàn)鏈?zhǔn)降亩鄬傩裕衛(wèi)eft,right,top 等等這樣的屬性,用以產(chǎn)生一個(gè) ConstraintMakerRelatable 類型的實(shí)例。
我們看看 left 屬性的 getter 定義:

public var left: ConstraintMakerExtendable {
    self.description.attributes += .left
    return self
}

這里可以看到通過(guò)重載的操作符 += 能夠?qū)?.left 加到 ConstraintAttributes 里。

ConstraintMakerRelatable

用于指定約束關(guān)系比如常用的 equalTo。equalTo 函數(shù)里面是調(diào)用的 relatedTo 函數(shù),返回 ConstraintMakerEditable 類型的實(shí)例。

@discardableResult
public func equalTo(_ other: ConstraintRelatableTarget, _ file: String = #file, _ line: UInt = #line) -> ConstraintMakerEditable {
    return self.relatedTo(other, relation: .equal, file: file, line: line)
}
internal func relatedTo(_ other: ConstraintRelatableTarget, relation: ConstraintRelation, file: String, line: UInt) -> ConstraintMakerEditable {
    let related: ConstraintItem
    let constant: ConstraintConstantTarget
    
    if let other = other as? ConstraintItem {
        guard other.attributes == ConstraintAttributes.none ||
            other.attributes.layoutAttributes.count <= 1 ||
            other.attributes.layoutAttributes == self.description.attributes.layoutAttributes ||
            other.attributes == .edges && self.description.attributes == .margins ||
            other.attributes == .margins && self.description.attributes == .edges else {
                fatalError("Cannot constraint to multiple non identical attributes. (\(file), \(line))");
        }
        
        related = other
        constant = 0.0
    } else if let other = other as? ConstraintView {
        related = ConstraintItem(target: other, attributes: ConstraintAttributes.none)
        constant = 0.0
    } else if let other = other as? ConstraintConstantTarget {
        related = ConstraintItem(target: nil, attributes: ConstraintAttributes.none)
        constant = other
    } else if #available(iOS 9.0, OSX 10.11, *), let other = other as? ConstraintLayoutGuide {
        related = ConstraintItem(target: other, attributes: ConstraintAttributes.none)
        constant = 0.0
    } else {
        fatalError("Invalid constraint. (\(file), \(line))")
    }
    
    let editable = ConstraintMakerEditable(self.description)
    editable.description.sourceLocation = (file, line)
    editable.description.relation = relation
    editable.description.related = related
    editable.description.constant = constant
    return editable
}

這里的 ConstraintRelatableTarget 是約束,equalTo 這個(gè)方法里面能傳的參數(shù)類型比較多,可以通過(guò)這個(gè)協(xié)議來(lái)擴(kuò)展下只支持的類型,達(dá)到限制類型的功能。ConstraintPriorityTarget,ConstraintInsetTarget,ConstraintOffsetTarget 和 ConstraintInsetTarget 也都有類似的作用,不過(guò)這幾個(gè)還有個(gè)作用就是將 Float,Double,Int 和 UInt 這幾種類型都轉(zhuǎn)成 CGFloat。我們拿 ConstraintInsetTarget 來(lái)看看實(shí)現(xiàn)如下:

extension ConstraintInsetTarget {
    
    internal var constraintInsetTargetValue: ConstraintInsets {
        if let amount = self as? ConstraintInsets {
            return amount
        } else if let amount = self as? Float {
            return ConstraintInsets(top: CGFloat(amount), left: CGFloat(amount), bottom: CGFloat(amount), right: CGFloat(amount))
        } else if let amount = self as? Double {
            return ConstraintInsets(top: CGFloat(amount), left: CGFloat(amount), bottom: CGFloat(amount), right: CGFloat(amount))
        } else if let amount = self as? CGFloat {
            return ConstraintInsets(top: amount, left: amount, bottom: amount, right: amount)
        } else if let amount = self as? Int {
            return ConstraintInsets(top: CGFloat(amount), left: CGFloat(amount), bottom: CGFloat(amount), right: CGFloat(amount))
        } else if let amount = self as? UInt {
            return ConstraintInsets(top: CGFloat(amount), left: CGFloat(amount), bottom: CGFloat(amount), right: CGFloat(amount))
        } else {
            return ConstraintInsets(top: 0, left: 0, bottom: 0, right: 0)
        }
    }
    
}

ConstraintMakerEditable

ConstraintMakerEditable 繼承 ConstraintMakerPriortizable,主要是設(shè)置約束的 offset 和 inset 還有 multipliedBy 和 dividedBy 函數(shù)。

public class ConstraintMakerEditable: ConstraintMakerPriortizable {

    @discardableResult
    public func multipliedBy(_ amount: ConstraintMultiplierTarget) -> ConstraintMakerEditable {
        self.description.multiplier = amount
        return self
    }
    
    @discardableResult
    public func dividedBy(_ amount: ConstraintMultiplierTarget) -> ConstraintMakerEditable {
        return self.multipliedBy(1.0 / amount.constraintMultiplierTargetValue)
    }
    
    @discardableResult
    public func offset(_ amount: ConstraintOffsetTarget) -> ConstraintMakerEditable {
        self.description.constant = amount.constraintOffsetTargetValue
        return self
    }
    
    @discardableResult
    public func inset(_ amount: ConstraintInsetTarget) -> ConstraintMakerEditable {
        self.description.constant = amount.constraintInsetTargetValue
        return self
    }
    
}

ConstraintMakerPriortizable

ConstraintMakerPriortizable 繼承 ConstraintMakerFinalizable,用來(lái)設(shè)置優(yōu)先級(jí),返回 ConstraintMakerFinalizable 類型的實(shí)例。

ConstraintMakerFinalizable

里面類型為 ConstraintDescription 的屬性的類是一個(gè)完整的約束描述,有了這個(gè)描述就可以做后面的處理了。里面的內(nèi)容是完整的,這個(gè)類是一個(gè)描述類, 用于描述一條具體的約束, 包含了包括 ConstraintAttributes 在內(nèi)的各種與約束有關(guān)的元素,一個(gè) ConstraintDescription 實(shí)例,就可以提供與一種約束有關(guān)的所有內(nèi)容。可以看到前面設(shè)置的屬性,關(guān)系,乘除系數(shù),優(yōu)先級(jí)等因有盡有,如下:

public class ConstraintDescription {
    
    internal let item: LayoutConstraintItem
    internal var attributes: ConstraintAttributes
    internal var relation: ConstraintRelation? = nil
    internal var sourceLocation: (String, UInt)? = nil
    internal var label: String? = nil
    internal var related: ConstraintItem? = nil
    internal var multiplier: ConstraintMultiplierTarget = 1.0
    internal var constant: ConstraintConstantTarget = 0.0
    internal var priority: ConstraintPriorityTarget = 1000.0
    internal lazy var constraint: Constraint? = {
        guard let relation = self.relation,
              let related = self.related,
              let sourceLocation = self.sourceLocation else {
            return nil
        }
        let from = ConstraintItem(target: self.item, attributes: self.attributes)
        
        return Constraint(
            from: from,
            to: related,
            relation: relation,
            sourceLocation: sourceLocation,
            label: self.label,
            multiplier: self.multiplier,
            constant: self.constant,
            priority: self.priority
        )
    }()
    
    // MARK: Initialization
    
    internal init(item: LayoutConstraintItem, attributes: ConstraintAttributes) {
        self.item = item
        self.attributes = attributes
    }
    
}

Masonry

在 Masonry 也有對(duì)應(yīng)的 ConstraintMaker。

MASConstraintMaker

MASConstraintMaker 是創(chuàng)建 MASConstraint 對(duì)象的。里面有個(gè) constraints 數(shù)組專門(mén)用來(lái)存儲(chǔ)創(chuàng)建的這些對(duì)象。前面 mas_makeConstraints 的那個(gè) Block 暴露出的就是 MASConstraintMaker 對(duì)象。

接下來(lái)看看 MASConstraint 屬性的 getter 方法:

- (MASConstraint *)left {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeLeft];
}

會(huì)發(fā)現(xiàn)這些 getter 方法都會(huì)調(diào)用 addConstraintWithLayoutAttribute 這個(gè)方法。

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

這里會(huì)發(fā)現(xiàn)每次 getter 都會(huì)創(chuàng)建一個(gè)新的 MASViewConstraint 對(duì)象,這里通過(guò)將新的 MASViewConstraint 對(duì)象的 delegate 設(shè)置成自己的方式讓新對(duì)象也能夠調(diào)用相同的方法創(chuàng)建一個(gè)新的 MASViewConstraint 對(duì)象,使得能夠支持進(jìn)行鏈?zhǔn)降恼{(diào)用。

設(shè)置完后如何處理?

SnapKit

下面通過(guò) makeConstraints 我們來(lái)看看 ConstraintMaker 是如何在外部通過(guò)一個(gè)閉包來(lái)寫(xiě)約束關(guān)系的。

internal static func makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) {
    let maker = ConstraintMaker(item: item)
    closure(maker)
    var constraints: [Constraint] = []
    for description in maker.descriptions {
        guard let constraint = description.constraint else {
            continue
        }
        constraints.append(constraint)
    }
    for constraint in constraints {
        constraint.activateIfNeeded(updatingExisting: false)
    }
}

這個(gè)閉包給叫做 maker 的 ConstraintMaker 實(shí)例寫(xiě)入了信息,遍歷 maker 的 descriptions 之后(我們之前說(shuō)一條約束語(yǔ)句最終得到一個(gè) self.description,但往往會(huì)有多條約束,所以 ConstraintMakerFinalizable 里面的 self.description,在 ConstraintMaker 里被一個(gè)數(shù)組維護(hù)),我們得到了 Constraint 數(shù)組。
跟進(jìn) Constraint 里的 activateIfNeeded 這個(gè)函數(shù)看看約束是怎么寫(xiě)出來(lái)的了

internal func activateIfNeeded(updatingExisting: Bool = false) {
    guard let item = self.from.layoutConstraintItem else {
        print("WARNING: SnapKit failed to get from item from constraint. Activate will be a no-op.")
        return
    }
    let layoutConstraints = self.layoutConstraints
    
    if updatingExisting {
        var existingLayoutConstraints: [LayoutConstraint] = []
        for constraint in item.constraints {
            existingLayoutConstraints += constraint.layoutConstraints
        }
        
        for layoutConstraint in layoutConstraints {
            let existingLayoutConstraint = existingLayoutConstraints.first { $0 == layoutConstraint }
            guard let updateLayoutConstraint = existingLayoutConstraint else {
                fatalError("Updated constraint could not find existing matching constraint to update: \(layoutConstraint)")
            }
            
            let updateLayoutAttribute = (updateLayoutConstraint.secondAttribute == .notAnAttribute) ? updateLayoutConstraint.firstAttribute : updateLayoutConstraint.secondAttribute
            updateLayoutConstraint.constant = self.constant.constraintConstantTargetValueFor(layoutAttribute: updateLayoutAttribute)
        }
    } else {
        NSLayoutConstraint.activate(layoutConstraints)
        item.add(constraints: [self])
    }
}

Masonry

MASViewConstraint

這個(gè)類是對(duì) NSLayoutConstriant 的封裝。它的父類是 MASConstraint,MASConstraint 是一個(gè)抽象不可實(shí)例的類,里面有接口和協(xié)議。它的兄弟類是 MASCompositeConstraint,里面有個(gè)數(shù)組專門(mén)存儲(chǔ) MASViewConstraint 對(duì)象。

MASViewConstraint 對(duì)象的 install 方法會(huì)將各個(gè)約束 install 到對(duì)應(yīng)的視圖上。我們看看 MASConstraintMaker 的 install 方法:

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

這個(gè)方法會(huì)遍歷 constraints 里每個(gè)約束進(jìn)行 install。在這個(gè) install 方法里會(huì)創(chuàng)建 MASLayoutConstraint 對(duì)象,然后把這個(gè)對(duì)象添加到對(duì)應(yīng)的的視圖上。

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;

創(chuàng)建完 MASLayoutConstraint 對(duì)象后,會(huì)根據(jù)約束的設(shè)置判斷將約束添加到哪個(gè)視圖上。

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

通過(guò)上面代碼里的條件判斷可以看出,如果有設(shè)置相對(duì)的那個(gè)視圖就用先前提到的那個(gè) mas_closestCommonSuperview 方法去找兩視圖的共同父視圖,不然如果只設(shè)置了高寬,就把約束加到當(dāng)前視圖上,其它情況就加到當(dāng)前視圖的父視圖上。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容