iOS開發(fā)中用戶界面開發(fā)很重要的一部分,也是工作量很大的一部分,將系統(tǒng)提供的控件或自定義控件在界面做精準布局是一項基礎而重要的工作,原始方法就是使用系統(tǒng)提供的frame進行坐標及大小布局定位,這也是早期開發(fā)人員常用的方法,但隨著iPone手機屏幕的大小多樣化,使用frame絕對布局也越來越難以滿足實際的工程開發(fā),這時候就需要借助自動布局來更優(yōu)雅的解決多種屏幕適配問題,同時大大減輕工作量。此時蘋果也不失時機的退出了自動布局的API,也就是NSLayoutConstraint的相關使用。
首先具體分析一下蘋果的原生實現(xiàn):
看一個具體例子:


其核心代碼主要是這行



NSLayoutConstraint其實描述的是兩個view之間的關系,其中第一個參數(shù)view1 是將要被約束的對象,參數(shù)view2是提供約束的依據(jù),ralation、attr2、multiplier、constant提供了約束關系,其公式為:view1.attribute1 = multiplier × view2.attribute2 + constant,其中attribute1和attribute2分別為NSLayoutAttribute類型的參數(shù),

可以看出這是一個枚舉類型,提供了約束的元素,在代碼示例中attr1和attr2都是NSLayoutAttributeTop,也就是說都是依據(jù)上邊緣加約束的,relation是NSLayoutRelation類型的,

表示了attr1和attr2之間的關系,示例中使用NSLayoutRelationEqual表明是完全equal的,multiplier為CGFloat類型的,表示了乘數(shù)也就是倍數(shù)關系,constant表示偏移量。
在這個示例中,subView是被約束對象,self.view就是約束的依據(jù),整個約束意思就是,subView的上邊緣距離self.view的上邊緣向下偏離40個point。這行代碼最終就是構建這樣一個NSLayoutConstraint類型的約束,然后加到數(shù)組array中,最終調用[self.view addConstraints:array];把這些約束加到父視圖上。
ok,原生API的約束就是這些,但從代碼看來明顯比較繁瑣復雜,不過無關緊,masonry的橫空出世為我們提供了簡便易用的API,在理解了autolayout的原理后在工程中直接使用masonry即可。
接下來就是本文要重點講解的masonry源碼。
首先,還是來大致瀏覽一下masonry的框架結構:

接下來,還是因循舊例,從一個API的簡單調用為入口逐步分析其工作原理。

上面是對self.collectButton加的一個約束,self是其superview,下面開始逐行分析其代碼。
首先看mas_makeConstraints這個方法:

可以看出這個方法是View+MASAdditions分類的方法,設置self.translatesAutoresizingMaskIntoConstraints = NO;意思是將絕對布局轉換為自動布局,也就是如果使用了setFrame的方法,會將其自動轉化為autoLayout。
緊接著,MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];這行是構建一個MASConstraintMaker *類型的對象constraintMaker,下面仔細分析一下這個類以及其構造方法:


從源碼注釋和類提供的接口以及使用的方法中,都可以看出這個類主要是用來構造MASViewConstraint約束的。
再看構造方法:


可以看出構造方法中主要是將加約束的view傳遞給maker,并初始化一個數(shù)組constraints,初步可以看出這個數(shù)組是用來存儲約束的。

緊接著就執(zhí)行block(constraintMaker);方法了,再回頭看這個block。

這里使用了鏈式調用,使Objective-C語言在這里實現(xiàn)了函數(shù)式調用,這是massory很經典的一部分。
以第一行為例make.right.equalTo(self).offset(-YZLAYOUTRATE(90));點進去繼續(xù)分析:


可以看出是MASConstraintMaker類先聲明一個MASConstraint類型的只讀屬性right。然后在其getter方法中調用[self addConstraintWithLayoutAttribute:NSLayoutAttributeRight];方法返回這個對象。
進入這個方法:

其參數(shù)是上文提到的原生接口的約束對象類型NSLayoutAttribute,這里傳的也是對應的NSLayoutAttributeRight。再進入:

首先構建一個對象MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];進入可看到:


這個MASViewAttribute *viewAttribute對象其實就是記錄一個視圖控件view和一個約束項,相當于將一個view和layoutAttribute綁定起來,也就是在這個view上加上這樣一個約束。
緊接著這構造一個對象MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute]。進去可看到:


可以看出MASViewConstraint類是對NSLayoutConstraint的封裝管理,layoutPriority是約束等級,默認是MASLayoutPriorityRequired,其實對應的就是原生接口中的UILayoutPriorityRequired,layoutMultiplier為倍數(shù),也就是原生API中的參數(shù)multiplier。
緊接著

從前邊的調用可知constraint為空,所以直接走下面一個選擇支,將剛創(chuàng)建的newConstraint的id<MASConstraintDelegate>類型的delegate賦給maker,并將其加入到make初始化話創(chuàng)建的self.constraints數(shù)組中。最后將newConstraint返回,這樣回到從前make.right.equalTo(self).offset(-YZLAYOUTRATE(90));中make.right就返回了。
下面接著分析這個鏈式調用中make.left.equalTo(self).offset(YZLAYOUTRATE(18));中的equal部分。點開這個方法:


首先可以看出這一步的鏈式調用實現(xiàn)是由于make.right返回一個MASConstraint*類型的對象,而這個類有一個equalTo的成員方法,因此可以使用點語法實現(xiàn)這一步的鏈式調用。從源碼和注釋中也可以看出這一步主要是設置relation關系為NSLayoutRelationEqual,對應也就是原生API中的relation參數(shù)。進一步查看其實現(xiàn),發(fā)現(xiàn)這一步返回的是一個MASConstraint * (^)(id)類型的block。這一步就比較繞了,要仔細分析。
下邊燒腦的游戲就開始了,看官且耐心聽,首先是上一步返回的MASConstraint * 類型的constraint對象調用equalTo方法返回一個MASConstraint * (^)(id)的block,可看出這個block有一個id類型的入?yún)?b>attr(從注釋中看,其類型可以為MASViewAttribute, UIView, NSValue, NSArray這幾種類型),一旦返回這個block便立即調用,在這個示例中是將self傳了進去(書中代言,self其實是這個視圖的superView,符合參數(shù)要求),這一點是我理解時間比較長的時間,剛開始看時發(fā)現(xiàn)equalTo方法沒有參數(shù),如何后邊跟著參數(shù),后來發(fā)現(xiàn)是直接調用equalTo方法返回的block,貽笑大方了。
下面邊開始分析其返回的block調用:


由于傳入的是UIView*類型的attr,所以直接走else的選擇支,將relation也就是默認的NSLayoutRelationEqual賦給self.layoutRelation,需要仔細分析的是self.secondViewAttribute = attribute;

同上,直接走第二個選擇支:_secondViewAttribute = [[MASViewAttribute alloc] initWithView:secondViewAttribute layoutAttribute:self.firstViewAttribute.layoutAttribute];
再次來到這個方法:

是否覺得似曾相識呢,對的就是第一個鏈式調用make.right的調用中調用過的,完成對@property (nonatomic, strong, readonly) MASViewAttribute *firstViewAttribute的賦值,而這里完成對@property (nonatomic, strong, readonly) MASViewAttribute *secondViewAttribute;的賦值。前面講過,MASViewAttribute其實就是將一個視圖控件view和一個約束綁定起來,從調用的參數(shù)可以看出是_firstViewAttribute的layoutAttribute,在這個實例中也就是NSLayoutAttributeRight。
然后回到block的執(zhí)行方法,最后return self;將MASConstraint *類型的constraint返回供下一步調用。
現(xiàn)在走到make.right.equalTo(self).offset(-YZLAYOUTRATE(90));這行鏈式調用的最后一個調用.offset(-YZLAYOUTRATE(90))。


同equalTo方法一樣,也是返回一個block并立即調用,這個block的入?yún)⑹莻€CGFloat類型,所以參數(shù)傳入(-YZLAYOUTRATE(90)(其實最終也是個CGFloat類型)。現(xiàn)在來看這個block,首先調用self.offset= offset;,其實是多態(tài)調用了


最終設置@property (nonatomic, assign) CGFloat layoutConstant;這個參數(shù)。
到此為止第一行的鏈式調用都分析完了,總結一下就是:先通過maker構造一個MASViewConstraint *類型的constraint,然后將constraint加入maker的數(shù)組constraints,然后通過一系列的鏈式調用將一個約束的各個參數(shù)加到constraint,maker其實是構造器,每個constraint都是一個約束的的抽象。
緊接著在回到工程中的調用,希望同學們不要迷路

緊接著又是兩行類似的鏈式調用,可以看出第二行鏈式調用和第一行的相類似,是根據(jù)top這個屬性加約束的。最后一行是約束其size的,下面具體分析:
進入make.size:

不同于上兩行的約束,這里使用的是composite attributes組合約束,下面分析其代碼:




除去系統(tǒng)版本判斷及安全性相關部分,由于傳入的參數(shù)是MASAttributeWidth | MASAttributeHeight,所以會進入if (attrs & MASAttributeWidth) [attributes addObject:self.view.mas_width]; if (attrs & MASAttributeHeight) [attributes addObject:self.view.mas_height];這兩個選擇支,簡單看一下self.view.mas_width中代碼:

同樣調用的是View+MASAdditions類別中方法,和前邊相同返回是一個MASViewAttribute *類型的對象,其_layoutAttribute屬性是NSLayoutAttributeWidth,self.view.mas_height相同。接著走到這里:

同上兩行的鏈式調用相同,構造一個constraint并將其加入到self.constraints中,最后返回constraint。
下面重點分析一下后邊make.size.mas_equalTo(CGSizeMake(YZLAYOUTRATE(42), YZLAYOUTRATE(42)));的mas_equalTo部分:

這里是一個宏,繼續(xù)展開這個宏:



可以看出這個宏主要作用就是將基本數(shù)據(jù)類型轉化為NSObject*類型,這樣我們在工程中就可以直接調用基本數(shù)據(jù)類型,比如這里make.size.mas_equalTo(CGSizeMake(YZLAYOUTRATE(42), YZLAYOUTRATE(42)));直接傳入的就是CGSize類型,會在這個內聯(lián)函數(shù)中被轉化為NSValue類型,然后回到這個宏:#define mas_equalTo(...)? ?equalTo(MASBoxValue((__VA_ARGS__))),可以看出和上兩行的鏈式調用中的equalTo是相同的不再贅述。
以上block中代碼都已經分析完畢,再次回到工程中調用


是否還有印象呢,這就是文章伊始分析這個框架時剛進入框架時的分析,現(xiàn)在mas_makeConstraints函數(shù)中前三行已經分析完。
現(xiàn)在要分析最后一行return [constraintMaker install];


self.removeExisting默認是NO,至于啥時候為YES下邊會有具體分析,這里暫且繼續(xù)往下走,


self.updateExisting默認也是NO,至于啥時候是YES下邊也會有分析,現(xiàn)在繼續(xù)往下走,
就進入[constraint install]這個方法。


這是最后一個難啃的重要方法,想著即將吃透框架的喜悅,且暫且平復激動的心情逐行分析這個方法:
self.hasBeenInstalled默認是NO。

在這個示例中此時self.layoutConstraint還不存在,所以不進入選擇支。繼續(xù)往下走,

這里大家是否看到熟悉的身影,回到文章開篇那個原生自動布局的示例:


這里就是根據(jù)之前構造的MASViewConstraint *類型的contraint對象構造一個約束layoutConstraint。不用想,下一步肯定就是要把這個layoutConstraint約束加到具體的view視圖上。

在這個示例中進入第一個選擇支,進入這個方法:

這個方法是用來尋找距離self.firstViewAttribute.view和self.secondViewAttribute.view最近的共同superview。可以猜想一下步是不是要將約束加到這個superview上。緊接著便有self.installedView= closestCommonSuperview;這個賦值。果然是要將這個約束添加到這個共同superview上。往下走:

走到第二個選擇支:[self.installedView addConstraint:layoutConstraint];將約束添加到共同superview上。然后將self.layoutConstraint= layoutConstraint;賦值是回應這個方法開頭

防止反復布局同一個約束減少性能消耗。就此這個方法也就分析完了。再回到

在for循環(huán)中將所有的約束全部添加,然后方法結束回到

就此示例

這個約束添加過程全部分析結束。
下邊再額外分析兩個重要的API,同時回答前邊兩個問題。

這個方法是更新之前的約束??梢钥吹皆谶@里將這個constraintMaker.updateExisting=YES;屬性設置為YES。

這個方法是刪除之前建立的約束,從新創(chuàng)建約束,可以看到在這里將這個屬性constraintMaker.removeExisting=YES;設置為YES。
就此這個框架的源碼的一個分析就先到這里,日后有時間可以繼續(xù)補充細節(jié)。