objc非主流程式碼技巧

weixin_34236869發表於2017-04-24

原文地址:http://blog.sunnyxx.com/2014/08/02/objc-weird-code/ 

我是前言

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

[娛樂向]objc最短的方法宣告

[C]結構體的初始化

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

[C]陣列的下標初始化

[objc]可變引數型別的block

[objc]readonly屬性支援擴充套件的寫法

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

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

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

[Macro]預處理斷言

[多重]帶自動提示的keypath巨集

[娛樂向]objc最短的方法宣告

先來個娛樂向的。

方法宣告時有一下幾個trick:

返回值的- (TYPE)如果不寫括號,編譯器預設認為是- (id)型別:

- init;

- (id)init;// 等價於

同理,引數如果不寫型別預設也是id型別:

- (void)foo:arg;

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

還有,有多引數時方法名和引數提示語可以為空

- (void):(id)arg1 :(id)arg2;

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

綜上,最短的函式可以寫成這樣:

- _;// 沒錯,這是一個oc方法宣告

- :_;// 這是一個帶一個引數的oc方法宣告

// 等價於

- (id)_;

- (id) :(id)_;

PS: 方法名都沒的方法只能靠performSelector來呼叫了,selector是":"

[C]結構體的初始化

// 不加(CGRect)強轉也不會warning

CGRectrect1 = {1,2,3,4};

CGRectrect2 = {.origin.x=5, .size={10,10}};// {5, 0, 10, 10}

CGRectrect3 = {1,2};// {1, 2, 0, 0}

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

三元條件表示式?:是C中唯一一個三目運算子,用來替代簡單的if-else語句,同時也是可以兩元使用的:

NSString*string = inputString ?:@"default";

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

利用這個特性,我們還腦洞出了一個一行程式碼的 block 呼叫,平時我們的 block 是這樣呼叫:

if(block0) {

block0();

}

// or

if(block1) {

intresult = block1(1,2);

}

居然可以簡化成下面的樣子:

!block0 ?: block0();

intresult = !block1 ?: block1(1,2);

[C]陣列的下標初始化

constintnumbers[] = {

[1] =3,

[2] =2,

[3] =1,

[5] =12306

};

// {0, 3, 2, 1, 0, 12306}

這個特性可以用來做列舉值和字串的對映

typedefNS_ENUM(NSInteger, XXType){

XXType1,

XXType2

};

constNSString*XXTypeNameMapping[] = {

[XXType1] =@"Type1",

[XXType2] =@"Type2"

};

[objc]可變引數型別的block

一個block像下面一樣宣告:

void(^block1)(void);

void(^block2)(inta);

void(^block3)(NSNumber*a,NSString*b);

如果block的引數列表為空的話,相當於可變引數(不是void)

void(^block)();// 返回值為void,引數可變的block

block = block1;// 正常

block = block2;// 正常

block = block3;// 正常

block(@1,@"string");// 對應上面的block3

block(@1);// block3的第一個引數為@1,第二個為nil

這樣,block的主調和回撥之間可以通過約定來決定block回傳回來的引數是什麼,有幾個。如一個對網路層的呼叫:

- (void)requestDataWithApi:(NSInteger)api block:(void(^)())block {

if(api ==0) {

block(1,2);

}

elseif(api ==1) {

block(@"1", @2, @[@"3",@"4",@"5"]);

}

}

主調者知道自己請求的是哪個Api,那麼根據約定,他就知道block裡面應該接受哪幾個引數:

[server requestDataWithApi:0block:^(NSIntegera,NSIntegerb){

// ...

}];

[server requestDataWithApi:1block:^(NSString*s,NSNumber*n,NSArray*a){

// ...

}];

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

+ (RACSignal *)combineLatest:(id)signals reduce:(id(^)())reduceBlock;

[objc]readonly屬性支援擴充套件的寫法

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

@interfaceSark:NSObject

@property(nonatomic,readonly)NSArray*friends;

@end

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

習慣使用self.來set例項變數時(只合成了getter)

希望重寫getter進行懶載入時(重寫getter時則不會生成下劃線的變數,除非手動@synthesize)

允許子類過載這個屬性來修改它時(編譯報錯屬性修飾符不匹配)

這種readonly宣告方法就行不通了,所以下面的寫法更有通用性:

@interfaceSark:NSObject

@property(nonatomic,readonly,copy/*加上setter屬性修飾符*/)NSArray*friends;

@end

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

@interfaceSark()

@property(nonatomic,copy)NSArray*friends;

@end

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

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

A compound statement enclosed in parentheses原諒我的渣翻譯- -,來自《gcc官方對此的說明》,源自gcc對c的擴充套件,如今被clang繼承。

RETURN_VALUE_RECEIVER = {(

// Do whatever you want

RETURN_VALUE;// 返回值

)};

於是乎可以發揮想象力了:

self.backgroundView = ({

UIView*view = [[UIViewalloc] initWithFrame:self.view.bounds];

view.backgroundColor = [UIColorredColor];

view.alpha =0.8f;

view;

});

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

self.result = ({

doubleresult =0;

for(inti =0; i <= M_2_PI; i+= M_PI_4) {

result += sin(i);

}

result;

});

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

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

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

正常編譯執行:

intsum(a,b)

inta;intb;

{

return a + b;

}

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

#defineCOUNT_PARMS2(_a1, _a2, _a3, _a4, _a5, RESULT, ...) RESULT

#defineCOUNT_PARMS(...) COUNT_PARMS2(__VA_ARGS__, 5, 4, 3, 2, 1)

intcount = COUNT_PARMS(1,2,3);// 預處理時count==3

[Macro]預處理斷言

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

#defineC_ASSERT(test) \

switch(0) {\

case 0:\

case test:;\

}

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

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

如果斷言失敗,相當於switch-case中出現了兩個case:0,則編譯報錯。

[多重]帶自動提示的keypath巨集

源自Reactive Cocoa中的巨集:

#definekeypath2(OBJ, PATH) \

(((void)(NO && ((void)OBJ.PATH, NO)), # PATH))

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

3038868-1c504386eb8fb661.png

逗號表示式

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

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

於是上面的keypath巨集的輸出結果是#PATH也就是一個c字串

邏輯最短路徑

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

if (NO && [self shouldDo]/*不執行*/) {

// 不執行

}

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

References

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

相關文章