原文地址: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宏
先來個娛樂向的。
方法聲明時有一下幾個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是":"
// 不加(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}
三元條件表達(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);
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"
};
一個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;
假如一個類有一個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é)尾
正常編譯執(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
下面的斷言在編譯前就生效
#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,則編譯報錯。
源自Reactive Cocoa中的宏:
#definekeypath2(OBJ, PATH) \
(((void)(NO && ((void)OBJ.PATH, NO)), # PATH))
原來寫過一篇《介紹RAC宏的文章》中曾經(jīng)寫過。這個宏在寫PATH參數(shù)的同時是帶自動提示的:
逗號表達(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í)行多余的代碼。