RAC中的宏定義學習

RAC 的中有提示的宏定義的寫法

// -  RACObserve的聲明
#if __clang__ && (__clang_major__ >= 8)
#define RACObserve(TARGET, KEYPATH) _RACObserve(TARGET, KEYPATH)
#else
#define RACObserve(TARGET, KEYPATH) \
({ \
	_Pragma("clang diagnostic push") \
	_Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \
	_RACObserve(TARGET, KEYPATH) \
	_Pragma("clang diagnostic pop") \
})
#endif

// - _RACObserve的聲明
#define _RACObserve(TARGET, KEYPATH) \
({ \
	__weak id target_ = (TARGET); \
	[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
})

// - keypath的聲明
#define keypath(...) \
    metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__))

#define keypath1(PATH) \
    (((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1))

#define keypath2(OBJ, PATH) \
    (((void)(NO && ((void)OBJ.PATH, NO)), # PATH))

使用(有代碼提示)在這裏插入圖片描述

以上宏定義的解析

// - 主要牛逼的地方在@keypath(TARGET, KEYPATH)中 
[解析]
 @keypath(TARGET, KEYPATH)中的@() 可以將一個 C 語言字符串轉成 OC 字符串
1. keypath1 解析  (((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1))
	1.void 是用來 強制類型轉換的, 方式未使用時候的警告
	2. (NO && (PATH, NO)) 是一個表達式, 之所以會有代碼提示, 就是因爲這個表達式;
	3. # PATH 表示將 PATH 的內容轉成字符串;
2. keypath1解析 (((void)(NO && ((void)OBJ.PATH, NO)), # PATH))
	1.void 是用來 強制類型轉換的, 方式未使用時候的警告
	2. (NO && (PATH, NO)) 是一個表達式, 之所以會有代碼提示, 就是因爲這個表達式;
	3. # PATH 表示將 PATH 的內容轉成字符串;

以下是別人對這個宏定義的解析 :

在這裏插入圖片描述

自定義的這種宏定義

// - 一個參數和兩個參數的宏定義
#define keypath1(PATH) (((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1))
#define keypath2(OBJ, PATH) @(((void)(NO && ((void)OBJ.PATH, NO)), # PATH))

宏定義使用方式二

// - 使用方式 1 限制傳入參數個數
// - 定義 : 
#define COUNT_PARMS2(_a1, _a2, _a3, _a4, _a5, RESULT, ...) RESULT
#define COUNT_PARMS(...) COUNT_PARMS2(__VA_ARGS__, 5, 4, 3, 2, 1)
#define C_ASSERT(test) \
    switch(0) {\
          case 0:\
          case test:;\
    }
    
// - 使用
C_ASSERT(COUNT_PARMS(1,2,3) == 2);   一下使用 , 以爲會有兩個 case 0, 所以會報錯, (這種報錯是在編譯時期報錯的!!!!)


// -  使用方式 2 (顯示傳入數字必須是枚舉中的值)
// - 定義枚舉
typedef enum : NSUInteger {
    MyEnumValueA = 1,
    MyEnumValueB = 2,
    MyEnumValueC = 3,
} MyEnum;

// - 定義宏
#define valueLimiter(x)    \
switch (0) {    \
    case 0: \
        break;  \
    case ((x >= MyEnumValueA && x <= MyEnumValueC)):    \
        break;  \
}

// - 使用宏定義
 valueLimiter(0); // - 報錯
 valueLimiter(1);
 valueLimiter(2);
 valueLimiter(3);
 valueLimiter(4);// - 報錯

限定傳入參數數類型的宏定義

// - 限定類型的宏定義的書寫
#define verifyCNum(num) autoreleasepool{}((void)(NO && (num * 0)))
#define verifyObj(obj) autoreleasepool{}((void)(NO && (object_getClassName(obj))))
#define verifyClass(class) autoreleasepool{}((void)(NO && ([class alloc])))

// - 宏定義的使用 
#define propertyGetter(class, instance, type, param) \
-(class *)instance##Mgr{ \
    @verifyCNum(type); \
    @verifyObj(param); \
    @verifyClass(class); \
    class <QIELiveRoomMgrProtocol> *instance = (class <QIELiveRoomMgrProtocol> *)[QIELiveRoomMgr mgrForType:type]; \
    if (!instance) { \
        if ([instance respondsToSelector:@selector(initWithParam:)]) { \
            instance = [[class alloc]initWithParam:param]; \
        }else{ \
            instance = [[class alloc]init]; \
        } \
        [QIELiveRoomMgr registerMgr:instance forType:type]; \
    } \
    return instance; \
}

// - 定義一個方法
@implementation DYNewPlayerViewController (Managers)
propertyGetter(QIEThemeRoomConfig, config, QIELiveRoomMgrTypeTheme, nil);
@end

限定方法必須實現或者聲明

#ifndef Header_h
#define Header_h

#define shouldImplement1(obj, sel) (NO && ([obj sel], NO));
#define shouldImplement2(obj, sel) (NO && ([obj sel], NO), getSelName(@# sel));

/** selDic中如果有, 就直接返回, 如果沒有就賦值 */
SEL getSelName(NSString *selName){
    
    // - 定義變量
    static NSMutableDictionary *selDic = NULL;
    __block NSString *funcName = [selDic objectForKey:selName];
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        selDic = [NSMutableDictionary dictionary];
    });
    
    // - 已經緩存 直接返回
    if (funcName) return NSSelectorFromString(funcName);
    
    // - 未緩存
    funcName= @"";
    NSCharacterSet *separator = [NSCharacterSet characterSetWithCharactersInString:@": "];
    NSArray <NSString *> * strArray = [selName componentsSeparatedByCharactersInSet: separator];
    [strArray enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        if(idx % 2 == 0) funcName = [funcName stringByAppendingFormat:@"%@:", obj];
    }];
    
    // - 緩存起來
    funcName = [funcName substringToIndex:funcName.length -1];
    [selDic setObject:funcName forKey:selName];
    
    // - 返回
    return NSSelectorFromString(funcName);
}
#endif /* Header_h */

// - 使用 
-(void)test:(SEL)selector{
    NSLog(@"-(void)test:(SEL)selector");
}

-(int)aa:(NSString *)xx aa:(int)aa rect:(CGRect)rect{
    NSLog(@"-(int)aa:(NSString *)xx aa:(int)aa rect:(CGRect)rect");
    return 1;
}

-(void)bb{
    NSLog(@"bb");
}
SEL s = shouldImplement2(self, aa:0 aa:0 rect:CGRectZero);
[self test:s];
shouldImplement2(self, bb);
shouldImplement2(self, aa:0 aa:0 rect:CGRectZero);
shouldImplement2(self, test:nil);

AFN 中使用宏定義調用 goto 語句 很妙

// - 定義宏
#define __Require_noErr_Quiet(errorCode, exceptionLabel)                      \
	  do                                                                          \
	  {                                                                           \
		  if ( __builtin_expect(0 != (errorCode), 0) )                            \
		  {                                                                       \
			  goto exceptionLabel;                                                \
		  }                                                                       \
	  } while ( 0 )

// - 在函數中使用
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
    BOOL isValid = NO;
    SecTrustResultType result;
	// - 是用宏定義
    __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);

    isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);

_out:
    return isValid;
}

宏定義中使用 do{} while(0)的原因(喫掉分號)

************************* 方案一 ************************* 
// - 原始定義 : 
#define action(a,b)\
printf(a);\
printf(b);\

// - 使用 : 
if (x > y)
		action(x, y);
		
// - 宏定義展開爲 此時代碼不會同時執行
if (x > y)
printf(a);
printf(b);

************************* 方案二 ************************* 
// - 優化定義 : 
#define action(a,b)\
{printf(a)\
printf(b)}\

// - 使用 : 
if (x > y)
		action(x, y);
// - 宏定義展開爲 看起來好像是沒有問題
if (x > y){
	printf(a)
	printf(b)
}

// - 但是以下情況會報錯
if (x > y)
		action(x, y);
else{
	printf(xx);
}

// - 宏定義展開爲 此時多了個分號,編譯不了
if (x > y){
	printf(a);
	printf(b);
};else{
	printf(xx);
}

************************* 方案三************************* 
// - 再次優化
#define action(a,b) do{printf(a); printf(b);}while(0)

// - 使用
if (x > y)
	action(x, y);
else{
	printf(xx);
}
// - 展開不會報錯,可以正常使用
if (x > y)
	do{printf(a); printf(b);}while(0);
else{
	printf(xx);
}

rac 中使用attribute((cleanup(…)))

attribute((cleanup(…))) :作用域結束時自動執行一個指定方法
rac 中使用 block 使這個方法可以將本來要寫在函數頭和函數尾並且成對出現的代碼寫在一起

// - 定義宏
#define onExit \
    rac_keywordify \
    __strong rac_cleanupBlock_t metamacro_concat(rac_exitBlock_, __LINE__) __attribute__((cleanup(rac_executeCleanupBlock), unused)) = ^

// - 定義執行的函數
static inline void rac_executeCleanupBlock (__strong rac_cleanupBlock_t *block) {
    (*block)();
}

// - 使用 將本來要寫在函數頭和函數尾並且成對出現的代碼寫在一起
// -  (以爲這裏的函數傳遞的參數是指針類型, 很方便)受到這個啓發,  可以應用於之前寫的  [釋放 strong 修飾的變量]  (https://blog.csdn.net/qq_27074387/article/details/99311843)
	[objectLock lock];
	@onExit {
			  [objectLock unlock];
			};

使用宏定義做版本判斷

// - 以下語句用來做版本判斷可以, 因爲 development target 的 iOS 版本中有tintColor 函數, 但是當把development target 降低到 iOS 6.0 時候, 函數又找不到, 這樣寫就會報錯,  所以就有用宏定義的寫法

// - 普通寫法
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {    
    self.window.tintColor = [UIColor redColor];    
}   

// - 使用宏定義的寫法 
+(UIStatusBarStyle)defaultStyle{
#ifdef __IPHONE_13_0
    if (@available(iOS 13.0, *)) {
        return UIStatusBarStyleDarkContent;
    }else{
        return UIStatusBarStyleDefault;
    }
#endif
    return UIStatusBarStyleDefault;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章