iOS開發過程當中很大一部份內容就是界面佈局和跳轉,iOS的佈局方式也經歷了 顯式座標定位方式 --> autoresizingMask --> iOS 6.0推出的自動佈局(Auto Layout)的逐步優化,至於爲何推出自動佈局,確定是由於以前的方法很差用(哈哈 簡直是廢話),具體如何很差用以及怎麼變化你們能夠瞅瞅 這篇文章。iOS6.0推出的自動佈局實際上用佈局約束(Layout Constraint)來實現,經過佈局約束(Layout Constraint)能夠肯定兩個視圖之間精確的位置的相對距離,爲此,iOS6.0推出了NSLayoutConstraint來定義約束,使用方法以下:html
[NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeRight multiplier:1 constant:10]; //翻譯過來就是:view1的左側,在,view2的右側,再多10個點,的地方。
佈局約束的添加規則:ios
(1)對於兩個同層級 view 之間的約束關係,添加到它們的父 view 上
數組
(2)對於兩個不一樣層級 view 之間的約束關係,添加到他們最近的共同父 view 上
(3)對於有層次關係的兩個 view 之間的約束關係,添加到層次較高的父 view 上
(4)對於好比長寬之類的,只做用在該 view 本身身上的話,添加到該 view 本身上
具體關於NSLayoutConstraint的詳細使用方法參見:NSLayoutConstraint-代碼實現自動佈局。今天咱們文章的主角——Masonry框架其實是在NSLayoutConstraint的基礎上進行封裝的,這一點在後面的源碼分析中咱們詳細解釋。架構
當我們須要對控件的top,bottom,left,right進行約束就特別麻煩,在OC中有一個庫Masonry
對NSLayoutConstraint
進行了封裝,簡化了約添加約束的方式和流程。用Masonry框架進行佈局很是簡單,主要特色是採用鏈式語法進行佈局,這一點使得咱們在使用和代碼佈局上更爲方便,利用Masonry進行佈局的前提條件之一是 佈局視圖必須先被添加到父視圖中。簡單示例以下代碼,關於Masonry框架的使用並非本文的重點,詳情能夠參見:Masonry介紹與使用實踐:快速上手Autolayout。若是你的項目是Swift語言的,那麼就得使用SnapKit佈局框架了,SnapKit其實就是Masonry的Swift版本,二者雖然實現語言不一樣,可是實現思路大致一致。框架
UIView *sv1 = [UIView new]; //利用Masonry進行佈局的前提條件之一是 佈局視圖必須先被添加到父視圖中 [sv addSubview:sv1]; [sv1 mas_makeConstraints:^(MASConstraintMaker *make) { make.edges.equalTo(sv).with.insets(UIEdgeInsetsMake(10, 10, 10, 10)); /* 等價於 make.top.equalTo(sv).with.offset(10); make.left.equalTo(sv).with.offset(10); make.bottom.equalTo(sv).with.offset(-10); make.right.equalTo(sv).with.offset(-10); */ /* 也等價於 make.top.left.bottom.and.right.equalTo(sv).with.insets(UIEdgeInsetsMake(10, 10, 10, 10)); */ }];
Masonry框架是在NSLayoutConstraint的基礎上進行封裝的,其涉及到的內容也是很是繁多。在進行源碼剖析時咱們從咱們常常用到的部分出發,一層一層進行解析和研究。ide
首先,咱們先大致瞭解一下調用 mas_makeConstraints 進行佈局時的流程步驟,其實另外兩個 mas_updateConstraints 和 mas_remakeConstraints 的流程也基本上是同樣的。函數
MASContraintMaker
的實例對象make)@implementation MAS_VIEW (MASAdditions) - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block { //關閉AutoresizingMask的佈局方法是咱們進行Auto Layout佈局的前提步驟 self.translatesAutoresizingMaskIntoConstraints = NO; //建立一個約束建立器 MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self]; //在block中配置constraintMaker對象,即將constraintMaker傳入block中(其實就是咱們在block中用來添加約束的make)進行約束配置 block(constraintMaker); //約束安裝並以數組形式返回 return [constraintMaker install]; } ...
約束安裝方法 [constraintMaker install]; 的源代碼以下,這部分的代碼很簡單,主要就是對當前約束建立器中的約束進行更新,由於除了咱們這個 mas_makeConstraints 方法中會調用該方法以外, mas_updateConstraints 和 mas_remakeConstraints 中都會調用該方法進行約束的安裝,因此在該約束安裝方法中考慮了約束的刪除和是否有更新等狀況的處理。工具
//install方法主要就是對下面這個約束數組進行維護 @property (nonatomic, strong) NSMutableArray *constraints; - (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; }
- (void)install { if (self.hasBeenInstalled) { return; } ... //MASLayoutConstraint其實就是在NSLayoutConstraint基礎上添加了一個屬性而已 //@interface MASLayoutConstraint : NSLayoutConstraint 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; ... //添加約束 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]; } }
經過上面的分析和研究,咱們基本上已經把Masonry框架中主要佈局方法的主流程瞭解清楚了。由於這是第一次學習iOS第三方框架的源碼,在這個學習過程當中也走了不少彎路,最開始是從最基本的類開始看,後來發現越看越不懂,不知道這個屬性的定義在何時用到,是什麼含義((ノへ ̄、)捂臉。。。)。後來經過摸索才知道源碼學習應該直接從用到的方法着手,而後一步一步深刻分析源碼中每一步的目的和意義,順藤摸瓜,逐個擊破。源碼分析
下面的代碼是比較經常使用的幾種Masonry的佈局格式,咱們能夠看到都是經過點語法的鏈式調用進行佈局的。以前在學習Java和Android的過程當中接觸過鏈式語法,在Java中要實現這種鏈式語法很簡單,無非就是每一個方法的返回值就是其自己,由於Java的方法調用是經過點語法調用的,因此很容易實現。可是在OC中,方法調用都是經過 [clazz method:parm]; 的形式進行調用的,那麼Masonry框架中是怎麼實現的呢?佈局
make.top.equalTo(sv).with.offset(10); make.left.right.mas_equalTo(sv).mas_offset(0.0f); make.top.left.bottom.and.right.equalTo(sv).with.insets(UIEdgeInsetsMake(10, 10, 10, 10));
一樣的學習方法,咱們來看一下源碼中各個屬性或方法是怎麼實現的,最重要的緣由就是getter方法和Objective-C 裏面,調用方法是可使用點語法的,但這僅限於沒有參數的方法。
//MASConstraintMaker.h文件 @interface MASConstraintMaker : NSObject @property (nonatomic, strong, readonly) MASConstraint *left; @property (nonatomic, strong, readonly) MASConstraint *top; @property (nonatomic, strong, readonly) MASConstraint *right; @property (nonatomic, strong, readonly) MASConstraint *bottom; @property (nonatomic, strong, readonly) MASConstraint *leading; @property (nonatomic, strong, readonly) MASConstraint *trailing; @property (nonatomic, strong, readonly) MASConstraint *width; @property (nonatomic, strong, readonly) MASConstraint *height; @property (nonatomic, strong, readonly) MASConstraint *centerX; @property (nonatomic, strong, readonly) MASConstraint *centerY; @property (nonatomic, strong, readonly) MASConstraint *baseline; #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100) @property (nonatomic, strong, readonly) MASConstraint *firstBaseline; @property (nonatomic, strong, readonly) MASConstraint *lastBaseline; #endif #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) @property (nonatomic, strong, readonly) MASConstraint *leftMargin; @property (nonatomic, strong, readonly) MASConstraint *rightMargin; @property (nonatomic, strong, readonly) MASConstraint *topMargin; @property (nonatomic, strong, readonly) MASConstraint *bottomMargin; @property (nonatomic, strong, readonly) MASConstraint *leadingMargin; @property (nonatomic, strong, readonly) MASConstraint *trailingMargin; @property (nonatomic, strong, readonly) MASConstraint *centerXWithinMargins; @property (nonatomic, strong, readonly) MASConstraint *centerYWithinMargins; #endif @property (nonatomic, strong, readonly) MASConstraint *edges; @property (nonatomic, strong, readonly) MASConstraint *size; @property (nonatomic, strong, readonly) MASConstraint *center; ... @end //MASConstraintMaker.m文件 @implementation MASConstraintMaker //每一個方法返回的也是MASConstraint對象,其實是MASViewConstraint、MASCompositeConstraint類型的對象,見最後的函數中標紅的註釋 - (MASConstraint *)top { //將對應的系統自帶的約束佈局的屬性NSLayoutAttributeTop傳入 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) { //設置約束的代理是self newConstraint.delegate = self; [self.constraints addObject:newConstraint]; } //返回MASViewConstraint類型的對象 return newConstraint; } ... @end
@interface MASViewConstraint : MASConstraint <NSCopying>
//MASConstraint.h文件 @interface MASConstraint : NSObject /** * Creates a new MASCompositeConstraint with the called attribute and reciever */ - (MASConstraint *)left; - (MASConstraint *)top; - (MASConstraint *)right; - (MASConstraint *)bottom; - (MASConstraint *)leading; - (MASConstraint *)trailing; - (MASConstraint *)width; - (MASConstraint *)height; - (MASConstraint *)centerX; - (MASConstraint *)centerY; - (MASConstraint *)baseline; #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100) - (MASConstraint *)firstBaseline; - (MASConstraint *)lastBaseline; #endif #if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) - (MASConstraint *)leftMargin; - (MASConstraint *)rightMargin; - (MASConstraint *)topMargin; - (MASConstraint *)bottomMargin; - (MASConstraint *)leadingMargin; - (MASConstraint *)trailingMargin; - (MASConstraint *)centerXWithinMargins; - (MASConstraint *)centerYWithinMargins; #endif ... @end
//MASConstraint.m文件 - (MASConstraint *)top { //這裏會調用MASViewConstraint中的addConstraintWithLayoutAttribute:方法 return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop]; } //MASViewConstraint.m文件 - (MASConstraint *)addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { NSAssert(!self.hasLayoutRelation, @"Attributes should be chained before defining the constraint relation"); //調用代理的方法,以前咱們說過設置的代理是MASConstraintMaker對象make,因此調用的其實是MASConstraintMaker添加約束的方法,這就是咱們再上面第一步講到的方法 return [self.delegate constraint:self addConstraintWithLayoutAttribute:layoutAttribute]; } //MASConstraintMaker.m文件 - (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute { MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute]; MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute]; //當傳入的constraint不爲空時,即此調用不是第一個,make.toip.left在left時的調用 if ([constraint isKindOfClass:MASViewConstraint.class]) { //則用MASCompositeConstraint做爲返回值,即組約束 NSArray *children = @[constraint, newConstraint]; MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children]; //設置代理 compositeConstraint.delegate = self; //從新設置 [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint]; //返回 MASCompositeConstraint對象return compositeConstraint; } if (!constraint) { //設置代理 newConstraint.delegate = self; //設置約束 [self.constraints addObject:newConstraint]; } return newConstraint; }
在上一小節咱們提到了鏈式語法的主要緣由在於在Objective-C 裏面,調用方法是可使用點語法的,但這僅限於沒有參數的方法,可是相似mas_equalTo、mas_offset等帶參數傳遞的方法依舊能夠用鏈式語法又是怎麼一回事呢?最關鍵的一環就是 block。block就是一個代碼塊,可是它的神奇之處在於在內聯(inline)執行的時候還能夠傳遞參數。同時block自己也能夠被做爲參數在方法和函數間傳遞。block做爲參數傳遞很常見,就是在咱們的Masonry框架中添加約束的方法 - (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block 中就是講一個block做爲參數進行傳遞的。
一樣在MASConstraint中,咱們能夠看到mas_equalTo、mas_offset等帶參方法的定義以下,咱們能夠看到,方法的定義中並無參數,可是返回值是一個帶參的block,而且該block還返回一個MASConstraint對象(MASViewConstraint或者MASCompositeConstraint對象),因此方法的定義和使用都沒有什麼問題,和上一小節分析的內容差很少。最主要的區別就是這裏返回值爲帶參數的block,而且該block的參數能夠經過咱們的方法進行傳值。關於帶參block做爲返回值得用法能夠參見 此連接的文章。
- (MASConstraint * (^)(MASEdgeInsets insets))insets; - (MASConstraint * (^)(CGFloat inset))inset; - (MASConstraint * (^)(CGSize offset))sizeOffset; - (MASConstraint * (^)(CGPoint offset))centerOffset; - (MASConstraint * (^)(CGFloat offset))offset; - (MASConstraint * (^)(NSValue *value))valueOffset; - (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.m文件 - (MASConstraint * (^)(id))mas_equalTo { return ^id(id attribute) { //多態調用子類MASViewConstraint或者MASCompositeConstraint的對應方法 return self.equalToWithRelation(attribute, NSLayoutRelationEqual); }; } //MASViewConstraint.m中對應的方法,MASCompositeConstraint其實也相似,只是循環調用每個子約束的該方法 - (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; } }; }
進過上面的原理分析,大致理解了其調用和實現眼裏,接下來,咱們經過下面的這句代碼在容器中的演變過程來進行總體感覺一下,下面的演變過程來自:Masonry源碼學習,原文有幾處有點小問題修改過,你們參考的時候注意甄別和判斷。
make.top.right.bottom.left.equalTo(superview)
在上面的過程當中能夠看到:
盜用iOS開發之Masonry框架源碼解析中的一張圖,這張圖將Masonry框架的架構闡述的很清晰,Masonry框架主要分爲4個部分:
MASConstraintMaker
類就是一個工廠類,負責建立和安裝MASConstraint
類型的對象(依賴於MASConstraint
接口,而不依賴於具體實現)。