Masonry
是iOS在控件佈局中常用的一個輕量級框架。Masonry
讓NSLayoutConstraint
使用起來更爲簡潔。Masonry
簡化了NSLayoutConstraint
的使用方式,讓咱們能夠以鏈式的方式爲咱們的控件指定約束。本篇是對Masonry
框架的源碼進行解析,讓你明白Masonry
是如何對NSLayoutConstraint
進行封裝的,以及Masonry
框架中各個部分所扮演的角色是什麼樣的。Masonry
框架是Objective-C
版本的,若是你的項目是Swift
語音的,那就得使用SnapKit佈局框架了。git
今天對Masonry
框架源碼的解析思路是:先對比,給一個View添加一樣的約束時,使用Masonry
與系統原生的區別。而後就開門見山給出Masonry
框架主要部分的類圖,從類圖中咱們來總體的分析Masonry
框架的結構。而後再由總體到部分逐漸的細化,窺探其內部的實現細節。經過上述步驟,咱們將對Masonry
框架的內部實現進行詳細的瞭解。其實Masonry
框架是輕量級的,總共的源碼也沒有多少行,可是仔細的閱讀它的實現細節,仍是能夠吸收不少實用的東西的。github
首先Masonry
在github上的地址是https://github.com/SnapKit/Masonry, 你能夠經過上述連接Clone
到Masonry
框架,其中有Masonry
框架介紹以及一些Masonry
的使用示例。關於Masonry
具體的使用方式請參考上述github上的連接。今天咱們就剖析一下Masonry
框架的源碼。數組
Masonry
框架與NSLayoutConstraint
調用方式的對比首先咱們NSLayoutConstraint
爲咱們的View添加一個約束,而後再給出Masonry的代碼。咱們要對一個View添加一個top約束,這個約束關係咱們用表達式來表示就是subView.top = superView.top + 10
。也就是子視圖的top與父視圖的top中間隔着10個pt。閉包
NSLayoutConstraint
添加約束:下方這段代碼就是給subView添加了一個相對於superView的Top約束。一個View要想肯定位置,一個約束是不夠的,因此可想而知,咱們要寫多個下方的這樣的約束來肯定一個View的相對位置。其實下方就是一個表達式,NSLayoutConstraint
構造器中每一個參數構成這個表達式的一個組成部分。由上到下咱們對個個參數進行解析,參數constraintWithItem
用來指定所約束的對象,在此就是subView。第一個attribute參數則指定約束該對象的那個屬性,在此就是subView的Top屬性。參數relatedBy用來指定約束關係,好比大於等於,小於等於或者等於某個約束值。參數toItem則指定的是約束相對的對象,在此是相對superView的,因此此處的參數是superView。第二個attribute參數就是指定superView的Top屬性。multiplier指定相對約束的倍數關係,constant則是約束的偏移量。框架
由上到下,NSLayoutConstraint的構造器中的參數會構成一個數學表達式,那就是subView.top = superView.top * 1 + 10,該表達式就直觀的給出了subView.top與superView.top的關係。經下方的代碼咱們就爲subView添加了一個相對於superView的Top約束,約束的偏移量是10。
ide
Masonry框架中支持約束的添加,約束的更新,約束的重建以及基本動畫的實現等等。功能仍是蠻強大的。在Masonry框架中主要採用了鏈式調用和匿名閉包的方式來簡化約束的添加。有關Masonry更爲詳細的使用方式請參見上述Masonry框架的Github連接,具體使用方式在此就不作過多的贅述了。
函數
經過上述的Masonry的使用方式咱們能夠看出,UIView的對象能夠直接調用mas_makeConstraints方法來爲相應的View對象添加約束。由於mas_makeConstraints方法位於UIView的View+MASAdditions類目中,因此UIView的對象能夠直接調用。一樣在View+MASAdditions類目還有其餘方法供UIView的對象使用,稍後會進行詳細的介紹。佈局
下方就是Masonry框架核心類以及類目之間的關係,下方的類圖是在閱讀Masonry源碼時畫的,僅此一份,若有雷同純屬巧合。若是下圖中的文字比較小的話,你能夠圖片另存到本地,而後放大後進行查看,廢話少說,進入咱們類圖的主題。下方的類圖中沒有包括Masonry框架中的全部的類,不過全部核心的類都在下方了。咱們從左往右依次對下方的類圖進行解說。動畫
最左邊那一坨大類,也就是綠框中的部分,就是Masonry框架對UIView的公有類目,也就是源文件中的View+MASAdditions的部分,在該類目中爲添加了類型爲MASViewAttribute的成員屬性(稍後會介紹MASViewAttribute是個神馬東西)。除了添加一系列的成員屬性外,還添加了四個公有的方法:mas_closestCommonSuperview方法負責尋找兩個視圖的最近的公共父視圖(類比兩個數字的最小公倍數)、mas_makeConstraints方法負責建立安裝約束、mas_updateConstraints負責更新已經存在的約束(若約束不存在就Install)、mas_remakeConstraints方法則負責移除原來已經建立的約束並添加上新的約束。上述方式是UIView對象設置約束主要調用的方法,稍後會詳細介紹其實現方式。ui
介紹完用戶直接使用的UIView的公共類目,接下來咱們來看一下用戶看不到的部分,那就是下方類圖中右邊的那一撮類。右邊的四個小類的耦合性比較高,咱們先看一下MASViewAttribute類。MASViewAttribute類的結構比較簡單,主要包括三個屬性,三個方法。從MASViewAttribute這個類名中咱們就能看出,這個類是對UIView和NSLayoutAttribute的封裝。使用等式來表示就是MASViewAttribute = UIView + NSLayoutAttribute + item。在MASViewAttribute類中的view屬性表示所約束的對象,而item就是該對象上能夠被約束的部分。
此處的item成員屬性咱們稍後要做爲NSLayoutConstriant構造器中的constraintWithItem與toItem的參數。固然對於UIView來講該item就是UIView自己。而對於UIViewController,該出Item就topLayoutGuide,bottomLayoutGuide稍後會給出詳細的介紹。該類中除了兩個構造器外還有一個isSizeAttribute方法,該方法用來判斷MASViewAttribute類中的layoutAttribute屬性是不是NSLayoutAttributeWidth或者NSLayoutAttributeHeight,若是是Width或者Height的話,那麼約束就添加到當前View上,而不是添加在父視圖上。
接着咱們看一下MASViewConstraint類,該類是對NSLayoutConstriant類的進一步封裝。MASViewConstraint作的最核心的一件事情就是初始化NSLayoutConstriant對象,並將該對象添加在相應的視圖上。由於NSLayoutConstriant在初始化時須要NSLayoutAttribute和所約束的View,而MASViewAttribute正是對View與NSLayoutAttribute進行的封裝,因此MASViewConstraint類要依賴於MASViewAttribute類,二者的關係以下所示。
由下方的類圖咱們能夠看出MASConstraint是MASViewConstraint的父類,MASConstraint是一個抽象類,不可被實例化。咱們能夠將MASConstraint看作是一個接口或者協議。MASConstraint抽象類還有一個子類,也就是MASViewConstraint的兄弟類MASCompositeConstraint,從MASCompositeConstraint的命名中咱們就能夠看出來MASCompositeConstraint是約束的一個組合,也就是其中存儲的是一系列的約束。MASCompositeConstraint類的結構比較簡單,其核心就是一個存儲MASViewConstraint對象的數組,MASCompositeConstraint就是對該數組的一個封裝而已。
兩邊的看完了,接下來咱們來看一下中間的部分,也就是MASConstraintMaker類。該類就是一個工廠類,負責建立MASConstraint類型的對象(依賴於MASConstraint接口,而不依賴於具體實現)。在UIView的View+MASAdditions類目中就是調用的MASConstraintMaker類中的一些方法。上述咱們在使用Masonry給subView添加約束時,mas_makeConstraints方法中的Block的參數就是MASConstraintMaker的對象。用戶能夠經過該Block回調過來的MASConstraintMaker對象給View指定要添加的約束以及該約束的值。該工廠中的constraints屬性數組就記錄了該工廠建立的全部MASConstraint對象。
Masonry框架中的核心類以及類目間的關係就介紹完了,下方就是核心類和類目的類圖。下方將會逐步的窺探其代碼實現。
咱們先對UIView的公共類目View+MASAdditions中的源碼進行解析,也就是對應着上方紅框中的部分。用戶是經過 View+MASAdditions中的東西來爲View添加約束的,View+MASAdditions也就是Masonry框架與外界交互的通道。該部分主要對View+MASAdditions源碼進行解析,先介紹其成員屬性,而後介紹主要的方法。進入該部分的主題。
下方截圖中是View+MASAdditions類目中的部分紅員屬性,其餘的也與下方相似,這些屬性都是MASViewAttribute類型的。如下方的mas_left成員屬性爲例,由於MASViewAttribute是View與NSLayoutAttribute的合體,因此mas_left就表明着當前View的NSLayoutAttributeLeft屬性,也就是mas_left存儲的是當前View的NSLayoutAttributeLeft屬性。同理,mas_top就表明着當前View的NSLayoutAttributeTop屬性,其餘成員屬性也是同樣。
經過上述成員屬性所對應的getter方法,咱們能夠對其中所存儲的內容一目瞭然。下方是mas_left、mas_top和mas_right成員屬性所對應的getter方法,其中所作的事情就是對MASViewAttibute進行實例化,在實例化時指定當前視圖所對應的LayoutAttribute。也就是mas_left = self + NSLayoutAttributeLeft, mas_top = self +NSLayoutAttributeTop, 固然此處的self就表明當前視圖。
上面在介紹類圖的時候也提到了,用戶是經過調用mas_makeConstraints方法來爲當前視圖添加約束的。下方代碼就是mas_makeConstraints函數的代碼實現,根據我的理解,對每行代碼進行了中文註釋,接下來咱們來好好的看一下該函數的結構.mas_makeConstraints方法的返回值是一個數組(NSArray),數組中所存放的就是當前視圖中所添加的全部約束。由於Masonry框架對NSLayoutConstraint封裝成了MASViewConstraint,全部此處數組中存儲的是MASViewConstraint對象。
接下來來看mas_makeConstraints的參數,mas_makeConstraints測參數是一個類型爲void(^)(MASConstraintMaker *)的匿名Block(也就是匿名閉包),該閉包的返回值爲Void, 而且須要一個MASConstraintMaker工廠類的一個對象。該閉包的做用就是可讓mas_makeConstraints方法經過該block給MASConstraintMaker工廠類對象中的MAConstraint屬性進行初始化。請參加下方block的使用。
在mas_makeConstraints方法體中,首先將當前View的translatesAutoresizingMaskIntoConstraints屬性設置成No, 而後建立了一個MASConstraintMaker工廠類對象constraintMaker,而後經過block將constraintMaker對象回調給用戶讓用戶對constraintMaker中的MAConstraint類型的屬性進行初始化。換句話說block中所作的事情就是以前用戶設置約束是所添加的代碼,好比make.top(@10) == ( constraintMaker.top = 10 )。最後調用constraintMaker的install方法對用戶指定的約束進行安裝。
這兩個函數內部的實現與mas_makeConstraints相似,就是多了一個屬性的設置。mas_updateConstraints中將constraintMaker中的updateExisting設置爲YES, 也就是說當添加約束時要先檢查約束是否已經被安裝了,若是被添加了就更新,若是沒有被添加就添加。而mas_remakeConstraints中所作的事情是將removeExisting屬性設置成YES, 表示將當前視圖上的舊約束進行移除,而後添加上新的約束。
mas_closestCommonSuperview方法負責計算出兩個視圖的公共父視圖,這個相似求兩個數字的最小公倍數。下方的代碼就是尋找兩個視圖的公共父視圖,固然是最近的那個公共父視圖。若是找到了就返回,若是找不到就返回nil。尋找兩個視圖的公共父視圖對於約束的添加來講是很是重要的,由於相對的約束是添加到其公共父視圖上的。好比舉個列子 viewA.left = viewB.right + 10, 由於是viewA與viewB的相對約束,那麼約束是添加在viewA與viewB的公共父視圖上的,若是viewB是viewA的父視圖,那麼約束就添加在viewB上從而對viewA起到約束做用。
上一個部分咱們分析了View+MASAdditions類目,在該類目中主要使用到了約束的工廠類MASConstraintMaker,接下咱們就來窺探一下MASConstraintMaker中的內容。MASConstraintMaker之因此成爲約束工廠類,由於MASConstraintMaker賦值建立NSLayoutConstraint對象,由於Masonry將NSLayoutConstraint類進一步封裝成了MASViewConstraint,因此MASConstraintMaker是負責建立MASViewConstraint的對象,並調用MASViewConstraint對象的Install方法將該約束添加到相應的視圖中。
下方截圖是MASConstraintMaker中的部分屬性,能夠看出下方的屬性都是MSAConstriant類型,MSAConstriant是抽象類,因此下方成員變量存儲的實質上是MSAConstriant子類MASViewConstraint的對象。MASConstraintMaker就負責對MASViewConstraint進行實例化。一句話解釋MASViewConstraint,MASViewConstraint = View + NSLayoutConstraint + Install。稍後會給出MASViewConstraint具體技術細節的實現。在MASConstraintMaker還有一個私有數組constraints,該數組就用來記錄以及建立的Constraint對象。
工廠類確定有工廠方法,接下來咱們來介紹MASConstraintMaker中的工廠方法方法,上面每一個MASConstraint類型的屬性都對應一個getter方法,在getter方法中都會調用addConstraintWithLayoutAttribute方法,而addConstraintWithLayoutAttribute會調用第二個截個圖中的方法,而截圖中的這個方法就是MASConstraintMaker工廠類的工廠方法,根據提供的參數建立MSAViewConstraint對象,若是該函數的第一個參數不爲空的話就會將新建立的MSAViewConstraint對象與參數進行合併組合成MASCompositeConstraint類(MASCompositeConstraint本質上是MSAViewConstraint對象的數組)的對象。
下方就是MASConstraintMaker工廠類的工廠方法,負責建立MASConstraint類的對象。下方的方法能夠建立MASCompositeConstraint和MASViewConstraint對象,上面也說了,MASCompositeConstraint對象就是MASViewConstraint對象的數組。下方建立完MASConstraint類的相應的對象後,會把該建立的對象添加進MASConstraintMaker工廠類的私有constraints數組,來記錄該工廠對象建立的全部約束。newConstraint.delegate = self; 這句話是很是重要的,因爲爲MASConstraint對象設置了代理,因此才支持鏈式調用(例如:maker.top.left.right.equalTo(@10))。
關於鏈式調用咱就以maker.top.left.right爲例。此處的maker, 就是咱們的MASConstraintMaker工廠對象,maker.top會返回帶有NSLayoutAttributeTop屬性的MASViewConstraint類的對象,咱們先作一個轉換:newConstraint = maker.top。那麼maker.top.left 等價於newConstraint.left,須要注意的是此刻調用的left方法就不在是咱們工廠MASConstraintMaker中的left的getter方法了,而是被換到MASViewConstraint類中的left屬性的getter方法了。給newConstraint設置代理就是爲了能夠在MASViewConstraint類中經過代理來調用MASConstraintMaker工廠類的工廠方法來完成建立。下方代碼若是沒有newConstraint.delegate = self;代理的設置的話,那就不支持鏈式調用。
說了這麼多,總結一下,若是你調用maker.top, maker.left等等這些方法都會調用下方的工廠方法來建立相應的MASViewConstraint對象,並記錄在工廠對象的約束數組中。之因此能鏈式調用,就是講當前的工廠對象指定爲MASViewConstraint對象的代理,因此一個MASViewConstraint對象就能夠經過代理來調用工廠方法來建立另外一個新的MASViewConstraint對象了,此處用到了代理模式。
雖然咱們將MASConstraintMake視爲工廠類,不過該工廠類的功能不只僅建立MASConstraint的對象,還負責調用MASConstraint對象的install方法來將相應的約束安裝到想要的視圖上。在MASConstraintMake類中的install方法就是遍歷工廠對象所建立全部約束對象並調用每一個約束對象的install方法來進行約束的安裝。下方就是該工廠類中的install方法。
在安裝約束時,若是self.removeExisting == Yes, 那麼用戶就經過mas_remakeConstraints方法調用的install方法,就先將原來的約束進行移除掉,而後添加上新的約束。在安裝約束時,將updateExisting賦值給每一個約束,每一個約束在調用自己的install方法時會判斷是否更新。下方就是MASConstraintMake的install方法的實現和註釋。
MASConstraintMaker工廠類所建立的對象實質上是MASViewConstraint類的對象。而MASViewConstraint類實質上是對MASLayoutConstraint的封裝,進一步說MASViewConstraint負責爲MASLayoutConstraint構造器組織參數並建立MASLayoutConstraint的對象,並將該對象添加到相應的視圖中。接下來咱們將對MASViewConstraint類中的內容進行解析。
MASViewConstraint的對象是支持鏈式調用的,好比constraint.top.left.equalTo(superView).offset(10); 上面的這種方式就是鏈式調用,並且像equalTo(superView)這種形式也不是Objective-C中函數調用的方式,在Objective-C中是經過[]來調用函數的,而此處使用了()。接下來說分析這種鏈式的調用是如何實現的。
在MASViewConstraint類中的left, top等約束的getter方法都會調用下方的這個方法,而這個方法中所作的事情就是經過代理來調用工廠中的工廠方法來根據LayoutAttribute建立相應的MASConstraint對象。
而像offset(10)這種調用方式是如何實現的呢?咱們知道在OC中是不能經過小括號來調用方法的,那邊閉包是能夠的,不過offset()不是一個簡單的閉包。在offset()的代碼分析後咱們不難發現offset() = offset + (); offset的代碼實現方式以下。offset是一個getter方法的名,offset函數的返回值是一個匿名Block, 也就是offset後邊的()。這個匿名閉包有一個CGFloat的參數,爲了支持鏈式調用該匿名閉包返回一個MASConstraint的對象。
MASViewConstraint中install方法負責建立MASLayoutConstraint對象,而且將該對象添加到相應的View上。下方代碼就是install中根據MASViewConstraint所收集的參數來建立NSLayoutConstraint對象,下方的MASLayoutConstraint其實就是NSLayoutConstraint的別名。下方就是調用系統的NSLayoutConstraint爲建立相應的約束對象,下方的構造器與第一部分中的NSLayoutConstraint一致。
建立完約束對象後,咱們要尋找該約束添加到那個View上。下方的代碼段就是獲取接收該約束對象的視圖。若是是兩個視圖相對約束,就獲取兩種的公共父視圖。若是添加的是Width或者Height,那麼久添加到當前視圖上。若是既沒有指定相對視圖,也不是Size類型的約束,那麼就將該約束對象添加到當前視圖的父視圖上。代碼實現以下:
建立完約束對象,而且找到承載約束的視圖後,接下來就是將該約束添加到該視圖上。子啊添加約束是咱們要判斷是否是對約束的更新,若是是對約束的更新的話就先獲取已經存在的約束並對該約束進行更新,若是被更新的約束不存在就進行添加。添加成功後咱們將經過mas_installedConstraints屬性記錄一下本安裝的約束。mas_installedConstraints是經過運行時爲UIView關聯的一個NSMutable類型的屬性,用來記錄約束該視圖的全部約束。
在MASViewConstraint中定義了一個UIView的私有類目UIView+MASConstraints,該類目的功能爲UIView經過運行時來關聯一個NSMutableSet類型的mas_installedConstraints屬性。該屬性中記錄了約束該View的全部約束。代碼實現以下。