iOS進階之masonry細緻入微_MASUtilities.h

weixin_33782386發表於2019-01-15

這個檔案區別於Masonry.h檔案。打個比方:Masonry主外,是個爺們。MASUtilities主內,是個姑娘。

接下來,讓我們來了解一下這個渾身上下都是“乾貨”的姑娘。
#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關鍵字用來定義一個類的行內函數,引入它的主要原因是用它替代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, 先看臉。這位姑娘不僅有一張白皙的臉蛋,還情商極高。
 ///見人說人話,見鬼說鬼話
 ///這裡你要了解蘋果公司的生態,不僅僅有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
///別名,給系統的類取個別名。一度覺得別名這個功能沒啥用。現在想想,無知比博學更容易使人自信。看看這裡,作者怎麼使用別名這個功能的。
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;
比較一下if裡的程式碼和else if裡面的程式碼。是不是很容易發現一個問題,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的約束出現了問題。然後你開始一個一個的排查。大部分情況下你的運氣比較好,很快就找到問題。但每個月總有那麼幾天,你感覺渾身無力,頭腦發熱,怎麼也找不到哪裡約束出了問題。怎麼辦?如何找到這個有問題的約束,然後你應該看程式碼。對,原始碼面前了無祕密。然後你就發現了這個巨集。然後你就知道了這個巨集是幹啥用的。那麼,現在你是不是已經猜到了這個巨集是幹什麼的了。這個巨集會將當前view物件的名字轉化為字串,並賦值給view的一個屬性。當這個view的約束報錯時,會列印出這個字串,讓開發者快速定位到出問題的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);

如果發現obj這個物件沒有實現對應的setMas_key方法,這裡會拋異常出來!看一眼異常資訊,慢慢體會斷言的作用。
LGMasonryDome[6607:1939185] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Cannot attach mas_key to <ViewController: 0x104e08770>'
重點了!!!!!!
obj是如何擁有mas_key這個屬性的。是系統的嗎?看一眼字首mas,難道是巧合,別鬧了,這個明顯是這個庫的字首嘛。既然不是系統的,那作者是通過何種手段為其新增的mas_key屬性呢?

答案是分類+runtime

分類請親自行百度的,這是個比較重要的概念。分類的理解不難,但結合程式碼構建過程,慢慢的就會體會出其巨大的威力。我曾見過有人用這個特性做組建開發。
我們來看下作者的原始碼。原始碼面前了無祕密。
- (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也是一個很大的概念,關於它的爭論也很多。總的來說,理解方面你應該比較清晰的知曉其內部構造。這個是你理解OC這門語言的基礎和難點。但使用方面,我的觀點是儘量剋制。
如果要論證,mas_key作為成員變數被新增到obj,那這也是個比較大的概念。要清楚一個物件的runtime組成,瞭解runtime的API,理解obj和mas_key的記憶體分佈關係,我儘量在後期寫一下這方 面的自己的理解。如果在這裡扯,就偏離的我們的主題了。
3,我們先看腿,屁股就留在最後。我自己都覺得這麼寫是不是不合適啊。
/**
 *  inline關鍵字用來定義一個類的行內函數,引入它的主要原因是用它替代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++的程式碼,大家有時間可以瞭解一下。
這裡的行內函數,相當於上面對於MASAttachKeys方法巨集定義一樣。這裡你可以理解為C++中對方法的巨集定義使用inline。
static inline id _MASBoxValue(const char *type, ...) 最後這三個點,是怎麼回事?在C中,當我們無法列出傳遞函式的所有實參的型別和數目時,可以用省略號指定參數列。
函式引數是以資料結構:棧的形式存取,從右至左入棧。
首先是引數的記憶體存放格式:引數存放在記憶體的堆疊段中,在執行函式的時候,從最後一個開始入棧。因此棧底高地址,棧頂低地址。
舉個例子如下:void func(int x, float y, char z);   
那麼,呼叫函式的時候,實參 char z 先進棧,然後是 float y,最後是 int x,因此在記憶體中變數的存放次序是 x->y->z,因此,從理論上說,我們只要探測到任意一個變數的地址,並且知道其他變數的型別,通過指標移位運算,則總可以順藤摸瓜找到其他的輸入變數。
這裡給了第一個引數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 是一個字元指標,可以理解為指向當前引數的一個指標,取參必須通過這個指標進行。
<Step 1> 在呼叫參數列之前,定義一個 va_list 型別的變數,(假設va_list 型別變數被定義為ap);
<Step 2> 然後應該對ap 進行初始化,讓它指向可變參數列裡面的第一個引數,這是通過 va_start 來實現的,第一個引數是 ap 本身,第二個引數是在變參表前面緊挨著的一個變數,即“...”之前的那個引數;
<Step 3> 然後是獲取引數,呼叫va_arg,它的第一個引數是ap,第二個引數是要獲取的引數的指定型別,然後返回這個指定型別的值,並且把 ap 的位置指向變參表的下一個變數位置;
 <Step 4> 獲取所有的引數之後,我們有必要將這個 ap 指標關掉,以免發生危險,方法是呼叫 va_end,他是輸入的引數 ap 置為 NULL,應該養成獲取完參數列之後關閉指標的習慣。說白了,就是讓我們的程式具有健壯性。通常va_start和va_end是成對出現。
回過頭再看看作者程式碼,是不是“又細又長”。
4,最後一步了。各位看客,辦理VIP才可以一探究竟哦。這個作者好汙。讀這種技術文章,很頭痛的。所以作者也算是煞費苦心了。
看看哪裡使用了?
- (NSUInteger)hash {
    return MAS_NSUINTROTATE([self.view hash], MAS_NSUINT_BIT / 2) ^ self.layoutAttribute;
}
這是我最頭痛的地方了。作者已經哭暈在廁所。請大家自行百度吧。

相關文章