objc非主流代碼技巧

本文原文發表自個人【自建博客】(http://blog.sunnyxx.com/2014/08/02/objc-weird-code/),cnblogs同步發表,格式未經調整,內容以原博客爲準

 

我是前言

看開源代碼時,總會看到一些大神級別的代碼,給人眼前一亮的感受,多數都是被淡忘的C語言語法,總結下objc寫碼中遇到的各種非主流代碼技巧和一些妙用:html

  • [娛樂向]objc最短的方法聲明
  • [C]結構體的初始化
  • [C]三元條件表達式的兩元使用
  • [C]數組的下標初始化
  • [objc]可變參數類型的block
  • [objc]readonly屬性支持擴展的寫法
  • [C]小括號內聯複合表達式
  • [娛樂向]奇葩的C函數寫法
  • [Macro]預處理時計算可變參數個數
  • [Macro]預處理斷言
  • [多重]帶自動提示的keypath宏

[娛樂向]objc最短的方法聲明

先來個娛樂向的。
方法聲明時有一下幾個trick:api

返回值的- (TYPE)若是不寫括號,編譯器默認認爲是- (id)類型:數組

1
2
- init;
- (id)init; // 等價於 

同理,參數若是不寫類型默認也是id類型:ruby

1
2
- (void)foo:arg; - (void)foo:(id)arg; // 等價於 

還有,有多參數時方法名參數提示語能夠爲空網絡

1
2
- (void):(id)arg1 :(id)arg2; - (void)foo:(id)arg1 bar:(id)arg2; // 省略前 

綜上,最短的函數能夠寫成這樣:app

1
2
3
4
5
- _; // 沒錯,這是一個oc方法聲明 - :_; // 這是一個帶一個參數的oc方法聲明 // 等價於 - (id)_; - (id) :(id)_; 

PS: 方法名都沒的方法只能靠performSelector來調用了,selector":"函數


[C]結構體的初始化

1
2
3
4
// 不加(CGRect)強轉也不會warning
CGRect rect1 = {1, 2, 3, 4}; CGRect rect2 = {.origin.x=5, .size={10, 10}}; // {5, 0, 10, 10} CGRect rect3 = {1, 2}; // {1, 2, 0, 0} 

[C]三元條件表達式的兩元使用

三元條件表達式?:是C中惟一一個三目運算符,用來替代簡單的if-else語句,同時也是能夠兩元使用的:優化

1
2
NSString *string = inputString ?: @"default"; NSString *string = inputString ? inputString : @"default"; // 等價 

[C]數組的下標初始化

1
2
3
4
5
6
7
const int numbers[] = { [1] = 3, [2] = 2, [3] = 1, [5] = 12306 }; // {0, 3, 2, 1, 0, 12306} 

這個特性能夠用來作枚舉值和字符串的映射ui

1
2
3
4
5
6
7
8
typedef NS_ENUM(NSInteger, XXType){ XXType1, XXType2 }; const NSString *XXTypeNameMapping[] = { [XXType1] = @"Type1", [XXType2] = @"Type2" }; 

[objc]可變參數類型的block

一個block像下面同樣聲明:atom

1
2
3
void(^block1)(void); void(^block2)(int a); void(^block3)(NSNumber *a, NSString *b); 


若是block的參數列表爲空的話,至關於可變參數(不是void)

1
2
3
4
5
6
void(^block)(); // 返回值爲void,參數可變的block
block = block1; // 正常 block = block2; // 正常 block = block3; // 正常 block(@1, @"string"); // 對應上面的block3 block(@1); // block3的第一個參數爲@1,第二個爲nil 

這樣,block的主調和回調之間能夠經過約定來決定block回傳回來的參數是什麼,有幾個。如一個對網絡層的調用:

1
2
3
4
5
6
7
8
9
- (void)requestDataWithApi:(NSInteger)api block:(void(^)())block { if (api == 0) { block(1, 2); } else if (api == 1) { block(@"1", @2, @[@"3", @"4", @"5"]); } } 

主調者知道本身請求的是哪一個Api,那麼根據約定,他就知道block裏面應該接受哪幾個參數:

1
2
3
4
5
6
[server requestDataWithApi:0 block:^(NSInteger a, NSInteger b){
    // ... }]; [server requestDataWithApi:1 block:^(NSString *s, NSNumber *n, NSArray *a){ // ... }]; 

這個特性在Reactive Cocoa-combineLatest:reduce:等相似方法中已經使用的至關好了。

1
+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock; 

[objc]readonly屬性支持擴展的寫法

假如一個類有一個readonly屬性:

1
2
3
@interface Sark : NSObject @property (nonatomic, readonly) NSArray *friends; @end 


.m中可使用_friends來使用自動合成的這個變量,但假如:

- 習慣使用self.來set實例變量時(只合成了getter)
- 但願重寫getter進行懶加載時(重寫getter時則不會生成下劃線的變量,除非手動@synthesize
- 容許子類重載這個屬性來修改它時(編譯報錯屬性修飾符不匹配)

這種readonly聲明方法就行不通了,因此下面的寫法更有通用性:

1
2
3
@interface Sark : NSObject @property (nonatomic, readonly, copy/*加上setter屬性修飾符*/) NSArray *friends; @end 


如想在.m中像正常屬性同樣使用:

1
2
3
@interface Sark () @property (nonatomic, copy) NSArray *friends; @end 

子類化時同理。iOS SDK中不少地方都用到了這個特性。


[C]小括號內聯複合表達式

A compound statement enclosed in parentheses原諒個人渣翻譯- -,來自《gcc官方對此的說明》,源自gcc對c的擴展,現在被clang繼承。

1
2
3
4
RETURN_VALUE_RECEIVER = {(
    // Do whatever you want RETURN_VALUE; // 返回值 )}; 


因而乎能夠發揮想象力了:

1
2
3
4
5
6
self.backgroundView = ({ UIView *view = [[UIView alloc] initWithFrame:self.view.bounds]; view.backgroundColor = [UIColor redColor]; view.alpha = 0.8f; view; }); 


有點像block和內聯函數的結合體,它最大的意義在於將代碼整理分塊,將同一個邏輯層級的代碼包在一塊兒;同時對於一個無需複用小段邏輯,也免去了重量級的調用函數,如:

1
2
3
4
5
6
7
self.result = ({
    double result = 0; for (int i = 0; i <= M_2_PI; i+= M_PI_4) { result += sin(i); } result; }); 

這樣使得代碼量增大時層次仍然能比較明確。

PS: 返回值和代碼塊結束點必須在結尾

[娛樂向]奇葩的C函數寫法

正常編譯執行:

1
2
3
4
5
int sum(a,b) int a; int b; { return a + b; } 

[Macro]預處理時計算可變參數個數

1
2
3
#define COUNT_PARMS2(_a1, _a2, _a3, _a4, _a5, RESULT, ...) RESULT #define COUNT_PARMS(...) COUNT_PARMS2(__VA_ARGS__, 5, 4, 3, 2, 1) int count = COUNT_PARMS(1,2,3); // 預處理時count==3 

[Macro]預處理斷言

下面的斷言在編譯前就生效

1
2
3
4
5
#define C_ASSERT(test) \ switch(0) {\ case 0:\ case test:;\ } 

如斷言上面預處理時計算可變參數個數:

1
C_ASSERT(COUNT_PARMS(1,2,3) == 2); 

若是斷言失敗,至關於switch-case中出現了兩個case:0,則編譯報錯。

[多重]帶自動提示的keypath宏

源自Reactive Cocoa中的宏:

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

原來寫過一篇《介紹RAC宏的文章》中曾經寫過。這個宏在寫PATH參數的同時是帶自動提示的:

逗號表達式

逗號表達式取後值,但前值的表達式參與運算,可用void忽略編譯器警告

1
int a = ((void)(1+2), 2); // a == 2 

因而上面的keypath宏的輸出結果是#PATH也就是一個c字符串

邏輯最短路徑

以前的文章沒有弄清上面宏中NO&&NO的含義,其實這用到了編譯器優化的特性:

1
2
3
if (NO && [self shouldDo]/*不執行*/) { // 不執行 } 

編譯器知道在NO後且什麼的結果都是NO,因而後面的語句被優化掉了。也就是說keypath宏中這個NO && ((void)OBJ.PATH, NO)就使得在編譯後後面的部分不出如今最後的代碼中,因而乎既實現了keypath的自動提示功能,又保證編譯後不執行多餘的代碼。


References

https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html


原創文章,轉載請註明源地址,blog.sunnyxx.com

相關文章
相關標籤/搜索