這個文件區(qū)別于Masonry.h文件蚀瘸。打個比方:Masonry主外,是個爺們绢掰。MASUtilities主內(nèi)痒蓬,是個姑娘童擎。
接下來,讓我們來了解一下這個渾身上下都是“干貨”的姑娘攻晒。
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE || TARGET_OS_TV
#import <UIKit/UIKit.h>
#define MAS_VIEW UIView
#define MAS_VIEW_CONTROLLER UIViewController
#define MASEdgeInsets UIEdgeInsets
typedef UILayoutPriority MASLayoutPriority;
static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 500;
static const MASLayoutPriority MASLayoutPriorityDefaultLow = UILayoutPriorityDefaultLow;
static const MASLayoutPriority MASLayoutPriorityFittingSizeLevel = UILayoutPriorityFittingSizeLevel;
#elif TARGET_OS_MAC
#import <AppKit/AppKit.h>
#define MAS_VIEW NSView
#define MASEdgeInsets NSEdgeInsets
typedef NSLayoutPriority MASLayoutPriority;
static const MASLayoutPriority MASLayoutPriorityRequired = NSLayoutPriorityRequired;
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = NSLayoutPriorityDefaultHigh;
static const MASLayoutPriority MASLayoutPriorityDragThatCanResizeWindow = NSLayoutPriorityDragThatCanResizeWindow;
static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 501;
static const MASLayoutPriority MASLayoutPriorityWindowSizeStayPut = NSLayoutPriorityWindowSizeStayPut;
static const MASLayoutPriority MASLayoutPriorityDragThatCannotResizeWindow = NSLayoutPriorityDragThatCannotResizeWindow;
static const MASLayoutPriority MASLayoutPriorityDefaultLow = NSLayoutPriorityDefaultLow;
static const MASLayoutPriority MASLayoutPriorityFittingSizeCompression = NSLayoutPriorityFittingSizeCompression;
#endif
/**
* Allows you to attach keys to objects matching the variable names passed.
*
* view1.mas_key = @"view1", view2.mas_key = @"view2";
*
* is equivalent to:
*
* MASAttachKeys(view1, view2);
*/
#define MASAttachKeys(...) \
{ \
NSDictionary *keyPairs = NSDictionaryOfVariableBindings(__VA_ARGS__); \
for (id key in keyPairs.allKeys) { \
id obj = keyPairs[key]; \
NSAssert([obj respondsToSelector:@selector(setMas_key:)], \
@"Cannot attach mas_key to %@", obj); \
[obj setMas_key:key]; \
} \
}
/**
* Used to create object hashes
* Based on http://www.mikeash.com/pyblog/friday-qa-2010-06-18-implementing-equality-and- hashing.html
*/
#define MAS_NSUINT_BIT (CHAR_BIT * sizeof(NSUInteger))
#define MAS_NSUINTROTATE(val, howmuch) ((((NSUInteger)val) << howmuch) | (((NSUInteger)val) >> (MAS_NSUINT_BIT - howmuch)))
/**
* Given a scalar or struct value, wraps it in NSValue
* Based on EXPObjectify: https://github.com/specta/expecta
*/
/**
* inline關鍵字用來定義一個類的內(nèi)聯(lián)函數(shù)顾复,引入它的主要原因是用它替代C中表達式形式的宏定義。
*/
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;
}
#define MASBoxValue(value) _MASBoxValue(@encode(__typeof__((value))), (value))
1炎辨, 先看臉捕透。這位姑娘不僅有一張白皙的臉蛋,還情商極高碴萧。
///見人說人話乙嘀,見鬼說鬼話
///這里你要了解蘋果公司的生態(tài),不僅僅有iphone還有TV和mac破喻。這也表明了虎谢,這個庫是可以伺候這三大平臺的。
#if TARGET_OS_IPHONE || TARGET_OS_TV
#elif TARGET_OS_MAC
#endif
///但愿你不是一個死讀書的孩子曹质。我上一篇文章列舉了#define很多缺點婴噩。你不會看到這幾行代碼就吐槽我或者吐槽這個庫的作者吧。你要理解羽德,這兩個地方是完全不同的兩種考慮几莽。
#define MAS_VIEW UIView
#define MAS_VIEW_CONTROLLER UIViewController
#define MASEdgeInsets UIEdgeInsets
///別名,給系統(tǒng)的類取個別名宅静。一度覺得別名這個功能沒啥用≌买迹現(xiàn)在想想,無知比博學更容易使人自信姨夹∠舜梗看看這里,作者怎么使用別名這個功能的磷账。
typedef UILayoutPriority MASLayoutPriority;
///這里也是常量峭沦,可以算是第三種定義常量的方式。不要再問哪個會好一點了逃糟。沉下心來看看代碼吼鱼,好好思考一下。沒有什么是絕對好的绰咽,我們要追求的是合適蛉抓,而不是偏執(zhí)的知識理論。
static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;
static const MASLayoutPriority MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
static const MASLayoutPriority MASLayoutPriorityDefaultMedium = 500;
static const MASLayoutPriority MASLayoutPriorityDefaultLow = UILayoutPriorityDefaultLow;
static const MASLayoutPriority MASLayoutPriorityFittingSizeLevel = UILayoutPriorityFittingSizeLevel;
比較一下if里的代碼和else if里面的代碼剃诅。是不是很容易發(fā)現(xiàn)一個問題,if里面定義了MAS_VIEW_CONTROLLER而else if里面卻沒有驶忌。對矛辕,這個姑娘還很聰明笑跛。這里的用處,我留給聰明的你聊品。你最好自己思考一下飞蹂,我在后面的文章會回顧這里,到時候翻屈,看看我們是否心有靈犀陈哑。(鄭重聲明:我不是大神,也沒有故意擺架子伸眶,更沒有逗你玩惊窖。我是希望能夠讓你有提升,好對得起你與我的這份緣分)厘贼。
2界酒, 再看胸。這里拒絕評論嘴秸。
/**
* Allows you to attach keys to objects matching the variable names passed.
* view1.mas_key = @"view1", view2.mas_key = @"view2";
* is equivalent to:
* MASAttachKeys(view1, view2);
*/
#define MASAttachKeys(...) \
{ \
NSDictionary *keyPairs = NSDictionaryOfVariableBindings(__VA_ARGS__); \
for (id key in keyPairs.allKeys) { \
id obj = keyPairs[key]; \
NSAssert([obj respondsToSelector:@selector(setMas_key:)], \
@"Cannot attach mas_key to %@", obj); \
[obj setMas_key:key]; \
} \
}
這個宏簡直就是一件作品毁欣。
先說作用:這個宏是為了給每一個view一個名字。干什么用岳掐?當你約束添加有誤時凭疮,會有錯誤提示,但卻無法告訴你哪一個view的約束出現(xiàn)了問題串述。然后你開始一個一個的排查执解。大部分情況下你的運氣比較好,很快就找到問題剖煌。但每個月總有那么幾天材鹦,你感覺渾身無力,頭腦發(fā)熱耕姊,怎么也找不到哪里約束出了問題桶唐。怎么辦?如何找到這個有問題的約束茉兰,然后你應該看代碼尤泽。對,源碼面前了無秘密规脸。然后你就發(fā)現(xiàn)了這個宏坯约。然后你就知道了這個宏是干啥用的。那么莫鸭,現(xiàn)在你是不是已經(jīng)猜到了這個宏是干什么的了闹丐。這個宏會將當前view對象的名字轉(zhuǎn)化為字符串,并賦值給view的一個屬性被因。當這個view的約束報錯時卿拴,會打印出這個字符串衫仑,讓開發(fā)者快速定位到出問題的view。
這里定義的是一個方法堕花,用宏的方式定義方法文狱。
NSDictionaryOfVariableBindings看明白這個的作用了嗎?
NSDictionaryOfVariableBindings(v1缘挽,v2瞄崇,v3)相當于[NSDictionary dictionaryWithObjectsAndKeys:v1,@“v1”壕曼,v2苏研,@“v2”,v3窝稿,@“v3”楣富,nil];
斷言
NSAssert([obj respondsToSelector:@selector(setMas_key:)], @"Cannot attach mas_key to %@", obj);
如果發(fā)現(xiàn)obj這個對象沒有實現(xiàn)對應的setMas_key方法,這里會拋異常出來伴榔!看一眼異常信息纹蝴,慢慢體會斷言的作用。
LGMasonryDome[6607:1939185] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Cannot attach mas_key to <ViewController: 0x104e08770>'
重點了W偕佟L涟病!T荨<娣浮!
obj是如何擁有mas_key這個屬性的集漾。是系統(tǒng)的嗎切黔?看一眼前綴mas,難道是巧合具篇,別鬧了纬霞,這個明顯是這個庫的前綴嘛。既然不是系統(tǒng)的,那作者是通過何種手段為其添加的mas_key屬性呢?
答案是分類+runtime
分類請親自行百度的业栅,這是個比較重要的概念。分類的理解不難也切,但結(jié)合代碼構(gòu)建過程,慢慢的就會體會出其巨大的威力。我曾見過有人用這個特性做組建開發(fā)。
我們來看下作者的源碼翠桦。源碼面前了無秘密。
- (id)mas_key {
return objc_getAssociatedObject(self, @selector(mas_key));
}
- (void)setMas_key:(id)key {
objc_setAssociatedObject(self, @selector(mas_key), key, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
runtime也是一個很大的概念胳蛮,關于它的爭論也很多秤掌〕钇蹋總的來說,理解方面你應該比較清晰的知曉其內(nèi)部構(gòu)造闻鉴。這個是你理解OC這門語言的基礎和難點。但使用方面茂洒,我的觀點是盡量克制孟岛。
如果要論證,mas_key作為成員變量被添加到obj督勺,那這也是個比較大的概念渠羞。要清楚一個對象的runtime組成,了解runtime的API智哀,理解obj和mas_key的內(nèi)存分布關系次询,我盡量在后期寫一下這方 面的自己的理解。如果在這里扯瓷叫,就偏離的我們的主題了屯吊。
3,我們先看腿摹菠,屁股就留在最后盒卸。我自己都覺得這么寫是不是不合適啊。
/**
* inline關鍵字用來定義一個類的內(nèi)聯(lián)函數(shù)次氨,引入它的主要原因是用它替代C中表達式形式的宏定義蔽介。
*/
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;
}
#define MASBoxValue(value) _MASBoxValue(@encode(__typeof__((value))), (value))
C++的代碼,大家有時間可以了解一下煮寡。
這里的內(nèi)聯(lián)函數(shù)虹蓄,相當于上面對于MASAttachKeys方法宏定義一樣。這里你可以理解為C++中對方法的宏定義使用inline幸撕。
static inline id _MASBoxValue(const char *type, ...) 最后這三個點薇组,是怎么回事?在C中杈帐,當我們無法列出傳遞函數(shù)的所有實參的類型和數(shù)目時,可以用省略號指定參數(shù)表体箕。
函數(shù)參數(shù)是以數(shù)據(jù)結(jié)構(gòu):棧的形式存取,從右至左入棧。
首先是參數(shù)的內(nèi)存存放格式:參數(shù)存放在內(nèi)存的堆棧段中挑童,在執(zhí)行函數(shù)的時候累铅,從最后一個開始入棧。因此棧底高地址站叼,棧頂?shù)偷刂贰?舉個例子如下:void func(int x, float y, char z);
那么娃兽,調(diào)用函數(shù)的時候,實參 char z 先進棧尽楔,然后是 float y投储,最后是 int x第练,因此在內(nèi)存中變量的存放次序是 x->y->z,因此玛荞,從理論上說娇掏,我們只要探測到任意一個變量的地址,并且知道其他變量的類型勋眯,通過指針移位運算婴梧,則總可以順藤摸瓜找到其他的輸入變量。
這里給了第一個參數(shù)type客蹋。我們可以順藤摸瓜了塞蹭。
看看作者怎么摸腿的。哦讶坯,不番电,摸瓜。
下面是 <stdarg.h> 里面重要的幾個宏定義如下:
typedef char* va_list;
void va_start ( va_list ap, prev_param ); /* ANSI version */
type va_arg ( va_list ap, type );
void va_end ( va_list ap );
va_list 是一個字符指針辆琅,可以理解為指向當前參數(shù)的一個指針漱办,取參必須通過這個指針進行。
<Step 1> 在調(diào)用參數(shù)表之前涎跨,定義一個 va_list 類型的變量洼冻,(假設va_list 類型變量被定義為ap);
<Step 2> 然后應該對ap 進行初始化隅很,讓它指向可變參數(shù)表里面的第一個參數(shù)撞牢,這是通過 va_start 來實現(xiàn)的,第一個參數(shù)是 ap 本身叔营,第二個參數(shù)是在變參表前面緊挨著的一個變量,即“...”之前的那個參數(shù)屋彪;
<Step 3> 然后是獲取參數(shù),調(diào)用va_arg绒尊,它的第一個參數(shù)是ap畜挥,第二個參數(shù)是要獲取的參數(shù)的指定類型,然后返回這個指定類型的值婴谱,并且把 ap 的位置指向變參表的下一個變量位置蟹但;
<Step 4> 獲取所有的參數(shù)之后,我們有必要將這個 ap 指針關掉谭羔,以免發(fā)生危險华糖,方法是調(diào)用 va_end,他是輸入的參數(shù) ap 置為 NULL瘟裸,應該養(yǎng)成獲取完參數(shù)表之后關閉指針的習慣客叉。說白了,就是讓我們的程序具有健壯性。通常va_start和va_end是成對出現(xiàn)兼搏。
回過頭再看看作者代碼卵慰,是不是“又細又長”。
4佛呻,最后一步了裳朋。各位看客,辦理VIP才可以一探究竟哦吓著。這個作者好污再扭。讀這種技術文章,很頭痛的夜矗。所以作者也算是煞費苦心了。
看看哪里使用了让虐?
- (NSUInteger)hash {
return MAS_NSUINTROTATE([self.view hash], MAS_NSUINT_BIT / 2) ^ self.layoutAttribute;
}