我是前言
看開源代碼時雳攘,總會看到一些大神級別的代碼再层,給人眼前一亮的感覺畔裕,多數(shù)都是被淡忘的C語言語法昧狮,總結(jié)下objc寫碼中遇到的各類非主流
代碼技巧和一些妙用:
- [娛樂向]objc最短的方法聲明
- [C]結(jié)構(gòu)體的初始化
- [C]三元條件表達式的兩元使用
- [C]數(shù)組的下標初始化
- [objc]可變參數(shù)類型的block
- [objc]readonly屬性支持擴展的寫法
- [C]小括號內(nèi)聯(lián)復合表達式
- [娛樂向]奇葩的C函數(shù)寫法
- [Macro]預處理時計算可變參數(shù)個數(shù)
- [Macro]預處理斷言
- [多重]帶自動提示的keypath宏
[娛樂向]objc最短的方法聲明
先來個娛樂向的。方法聲明時有一下幾個trick:
返回值的- (TYPE)
如果不寫括號萧诫,編譯器默認認為是- (id)
類型:
- init;
- (id)init; // 等價于
同理斥难,參數(shù)如果不寫類型默認也是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
是":"
<a id="結(jié)構(gòu)體初始化"></a>[C]結(jié)構(gòu)體的初始化
// 不加(CGRect)強轉(zhuǎn)也不會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
語句镀裤,同時也是可以兩元使用的:
NSString *string = inputString ?: @"default";
NSString *string = inputString ? inputString : @"default"; // 等價
[C]數(shù)組的下標初始化
const int numbers[] = {
[1] = 3,
[2] = 2,
[3] = 1,
[5] = 12306
};
// {0, 3, 2, 1, 0, 12306}
這個特性可以用來做枚舉值和字符串的映射
typedef NS_ENUM(NSInteger, XXType){
XXType1,
XXType2
};
const NSString *XXTypeNameMapping[] = {
[XXType1] = @"Type1",
[XXType2] = @"Type2"
};
[objc]可變參數(shù)類型的block
一個block像下面一樣聲明:
void(^block1)(void);
void(^block2)(int a);
void(^block3)(NSNumber *a, NSString *b);
如果block的參數(shù)列表為空的話,相當于可變參數(shù)(不是void)
void(^block)(); // 返回值為void缴饭,參數(shù)可變的block
block = block1; // 正常
block = block2; // 正常
block = block3; // 正常
block(@1, @"string"); // 對應上面的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);
} else if (api == 1) {
block(@"1", @2, @[@"3", @"4", @"5"]);
}
}
主調(diào)者知道自己請求的是哪個Api汪疮,那么根據(jù)約定
峭火,他就知道block里面應該接受哪幾個參數(shù):
[server requestDataWithApi:0 block:^(NSInteger a, NSInteger b){
// ...
}];
[server requestDataWithApi:1 block:^(NSString *s, NSNumber *n, NSArray *a){
// ...
}];
這個特性在Reactive Cocoa
的-combineLatest:reduce:
等類似方法中已經(jīng)使用的相當好了毁习。
+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock;
[objc]readonly屬性支持擴展的寫法
假如一個類有一個readonly
屬性:
@interface Sark : NSObject
@property (nonatomic, readonly) NSArray *friends;
@end
.m
中可以使用_friends
來使用自動合成的這個變量,但假如:
- 習慣使用
self.
來set實例變量時(只合成了getter) - 希望重寫getter進行懶加載時(重寫getter時則不會生成下劃線的變量,除非手動
@synthesize
) - 允許子類重載這個屬性來修改它時(編譯報錯屬性修飾符不匹配)
這種readonly
聲明方法就行不通了卖丸,所以下面的寫法更有通用性:
@interface Sark : NSObject
@property (nonatomic, readonly, copy/*加上setter屬性修飾符*/) NSArray *friends;
@end
如想在.m
中像正常屬性一樣使用:
@interface Sark ()
@property (nonatomic, copy) NSArray *friends;
@end
子類化時同理纺且。iOS SDK中很多地方都用到了這個特性。
[C]小括號內(nèi)聯(lián)復合表達式
A compound statement enclosed in parentheses
原諒我的渣翻譯- -稍浆,來自《gcc官方對此的說明》载碌,源自gcc對c的擴展,如今被clang繼承衅枫。
RETURN_VALUE_RECEIVER = {(
// Do whatever you want
RETURN_VALUE; // 返回值
)};
于是乎可以發(fā)揮想象力了:
self.backgroundView = ({
UIView *view = [[UIView alloc] initWithFrame:self.view.bounds];
view.backgroundColor = [UIColor redColor];
view.alpha = 0.8f;view;
});
有點像block和內(nèi)聯(lián)函數(shù)的結(jié)合體嫁艇,它最大的意義在于將代碼整理分塊
,將同一個邏輯層級的代碼包在一起弦撩;同時對于一個無需復用小段邏輯步咪,也免去了重量級的調(diào)用函數(shù),如:
self.result = ({
double result = 0;
for (int i = 0; i <= M_2_PI; i+= M_PI_4) {
result += sin(i);
}
result;
});
這樣使得代碼量增大時層次仍然能比較明確益楼。
PS: 返回值和代碼塊結(jié)束點必須在結(jié)尾
[娛樂向]奇葩的C函數(shù)寫法
正常編譯執(zhí)行:
int sum(a,b)
int a; int b;
{
return a + b;
}
[Macro]預處理時計算可變參數(shù)個數(shù)
#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]預處理斷言
下面的斷言在編譯前就生效
#define C_ASSERT(test) \
switch(0) {\
case 0:\
case test:;\
}
如斷言上面預處理時計算可變參數(shù)個數(shù):
C_ASSERT(COUNT_PARMS(1,2,3) == 2);
如果斷言失敗猾漫,相當于switch-case
中出現(xiàn)了兩個case:0
,則編譯報錯感凤。
[多重]帶自動提示的keypath宏
源自Reactive Cocoa
中的宏:
#define keypath2(OBJ, PATH) \
(((void)(NO && ((void)OBJ.PATH, NO)), # PATH))
原來寫過一篇《介紹RAC宏的文章》中曾經(jīng)寫過悯周。這個宏在寫PATH參數(shù)的同時是帶自動提示的:
逗號表達式
逗號表達式取后值,但前值的表達式參與運算陪竿,可用void忽略編譯器警告
int a = ((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
原創(chuàng)文章解总,轉(zhuǎn)載請注明原地址:blog.sunnyxx.com