objc非主流代碼技巧

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

我是前言

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

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

[C]結(jié)構(gòu)體的初始化

[C]三元條件表達(dá)式的兩元使用

[C]數(shù)組的下標(biāo)初始化

[objc]可變參數(shù)類型的block

[objc]readonly屬性支持?jǐn)U展的寫法

[C]小括號內(nèi)聯(lián)復(fù)合表達(dá)式

[娛樂向]奇葩的C函數(shù)寫法

[Macro]預(yù)處理時計算可變參數(shù)個數(shù)

[Macro]預(yù)處理斷言

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

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

先來個娛樂向的。

方法聲明時有一下幾個trick:

返回值的- (TYPE)如果不寫括號,編譯器默認(rèn)認(rèn)為是- (id)類型:

- init;

- (id)init;// 等價于

同理,參數(shù)如果不寫類型默認(rèn)也是id類型:

- (void)foo:arg;

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

還有,有多參數(shù)時方法名和參數(shù)提示語可以為空

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

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

綜上,最短的函數(shù)可以寫成這樣:

- _;// 沒錯,這是一個oc方法聲明

- :_;// 這是一個帶一個參數(shù)的oc方法聲明

// 等價于

- (id)_;

- (id) :(id)_;

PS: 方法名都沒的方法只能靠performSelector來調(diào)用了,selector是":"

[C]結(jié)構(gòu)體的初始化

// 不加(CGRect)強(qiáng)轉(zhuǎn)也不會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]三元條件表達(dá)式的兩元使用

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

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

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

利用這個特性,我們還腦洞出了一個一行代碼的 block 調(diào)用,平時我們的 block 是這樣調(diào)用:

if(block0) {

block0();

}

// or

if(block1) {

intresult = block1(1,2);

}

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

!block0 ?: block0();

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

[C]數(shù)組的下標(biāo)初始化

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]可變參數(shù)類型的block

一個block像下面一樣聲明:

void(^block1)(void);

void(^block2)(inta);

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

如果block的參數(shù)列表為空的話,相當(dāng)于可變參數(shù)(不是void)

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

block = block1;// 正常

block = block2;// 正常

block = block3;// 正常

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

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

這樣,block的主調(diào)和回調(diào)之間可以通過約定來決定block回傳回來的參數(shù)是什么,有幾個。如一個對網(wǎng)絡(luò)層的調(diào)用:

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

if(api ==0) {

block(1,2);

}

elseif(api ==1) {

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

}

}

主調(diào)者知道自己請求的是哪個Api,那么根據(jù)約定,他就知道block里面應(yīng)該接受哪幾個參數(shù):

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

// ...

}];

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

// ...

}];

這個特性在Reactive Cocoa的-combineLatest:reduce:等類似方法中已經(jīng)使用的相當(dāng)好了。

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

[objc]readonly屬性支持?jǐn)U展的寫法

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

@interfaceSark:NSObject

@property(nonatomic,readonly)NSArray*friends;

@end

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

習(xí)慣使用self.來set實例變量時(只合成了getter)

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

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

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

@interfaceSark:NSObject

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

@end

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

@interfaceSark()

@property(nonatomic,copy)NSArray*friends;

@end

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

[C]小括號內(nèi)聯(lián)復(fù)合表達(dá)式

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

RETURN_VALUE_RECEIVER = {(

// Do whatever you want

RETURN_VALUE;// 返回值

)};

于是乎可以發(fā)揮想象力了:

self.backgroundView = ({

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

view.backgroundColor = [UIColorredColor];

view.alpha =0.8f;

view;

});

有點像block和內(nèi)聯(lián)函數(shù)的結(jié)合體,它最大的意義在于將代碼整理分塊,將同一個邏輯層級的代碼包在一起;同時對于一個無需復(fù)用小段邏輯,也免去了重量級的調(diào)用函數(shù),如:

self.result = ({

doubleresult =0;

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

result += sin(i);

}

result;

});

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

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

[娛樂向]奇葩的C函數(shù)寫法

正常編譯執(zhí)行:

intsum(a,b)

inta;intb;

{

return a + b;

}

[Macro]預(yù)處理時計算可變參數(shù)個數(shù)

#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);// 預(yù)處理時count==3

[Macro]預(yù)處理斷言

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

#defineC_ASSERT(test) \

switch(0) {\

case 0:\

case test:;\

}

如斷言上面預(yù)處理時計算可變參數(shù)個數(shù):

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

如果斷言失敗,相當(dāng)于switch-case中出現(xiàn)了兩個case:0,則編譯報錯。

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

源自Reactive Cocoa中的宏:

#definekeypath2(OBJ, PATH) \

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

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

逗號表達(dá)式

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

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

于是上面的keypath宏的輸出結(jié)果是#PATH也就是一個c字符串

邏輯最短路徑

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

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

// 不執(zhí)行

}

編譯器知道在NO后且什么的結(jié)果都是NO,于是后面的語句被優(yōu)化掉了。也就是說keypath宏中這個NO && ((void)OBJ.PATH, NO)就使得在編譯后后面的部分不出現(xiàn)在最后的代碼中,于是乎既實現(xiàn)了keypath的自動提示功能,又保證編譯后不執(zhí)行多余的代碼。

References

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,789評論 0 9
  • 鏈接objc非主流代碼技巧[objc]readonly屬性支持?jǐn)U展的寫法 假如一個類有一個readonly屬性: ...
    iStig閱讀 601評論 0 51
  • (001) 2015年,冬。 我騎著單車穿過一條又一條街...
    雨天吖閱讀 376評論 3 1
  • 昨晚和室友一起看了一場夜場電影,周冬雨和金城武主演的《喜歡你》,聽著片名就知道又是一個瑪麗蘇愛情故事,其實內(nèi)心深處...
    素簡db閱讀 216評論 0 0
  • 若問中國歷史上最顯赫的公主是誰?我想答案只能是太平公主。因為不僅她的父親和兩個哥哥都是大唐的天子,她的母親還是中國...
    柳馥閱讀 865評論 0 5