objc非主流程式碼技巧
原文地址: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巨集
先來個娛樂向的。
方法宣告時有一下幾個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是":"
// 不加(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中唯一一個三目運算子,用來替代簡單的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);
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的引數列表為空的話,相當於可變引數(不是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;
假如一個類有一個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中很多地方都用到了這個特性。
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: 返回值和程式碼塊結束點必須在結尾
正常編譯執行:
intsum(a,b)
inta;intb;
{
return a + b;
}
#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
下面的斷言在編譯前就生效
#defineC_ASSERT(test) \
switch(0) {\
case 0:\
case test:;\
}
如斷言上面預處理時計算可變引數個數:
C_ASSERT(COUNT_PARMS(1,2,3) ==2);
如果斷言失敗,相當於switch-case中出現了兩個case:0,則編譯報錯。
源自Reactive Cocoa中的巨集:
#definekeypath2(OBJ, PATH) \
(((void)(NO && ((void)OBJ.PATH, NO)), # PATH))
原來寫過一篇《介紹RAC巨集的文章》中曾經寫過。這個巨集在寫PATH引數的同時是帶自動提示的:
逗號表示式取後值,但前值的表示式參與運算,可用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的自動提示功能,又保證編譯後不執行多餘的程式碼。
相關文章
- Objc中格式化數字的技巧OBJ
- 程式設計技巧│提高 Javascript 程式碼效率的技巧程式設計JavaScript
- objc系列譯文(1.2):整潔的表檢視程式碼OBJ
- 程式碼重構技巧(二)
- objc原始碼解析-ObjectiveC物件結構原始碼Object物件
- OC 看objc原始碼認識weakOBJ原始碼
- objc原始碼解讀-物件生命週期OBJ原始碼物件
- 重構 - 程式碼優化技巧優化
- Python 程式碼除錯技巧Python除錯
- 註釋程式碼的13技巧
- Hbuilder快速程式碼編寫技巧UI
- Java程式碼編寫、程式碼優化技巧總結Java優化
- 用程式碼理解 ObjC 中的傳送訊息和訊息轉發OBJ
- 用程式碼理解ObjC中的傳送訊息和訊息轉發OBJ
- ObjC Runtime簡析-- objc_MsgSendOBJGse
- ObjC runtime原始碼 閱讀筆記(一)OBJ原始碼筆記
- OC 看objc原始碼認識retain、release、deallocOBJ原始碼AI
- J2ObjC - 谷歌的Java轉Objective-C的程式碼轉換工具谷歌JavaObject
- PHP 程式碼優化技巧總結PHP優化
- PHP程式碼優化技巧大盤點PHP優化
- Python 程式碼效能優化技巧Python優化
- 再談非主流工業語言
- objc系列譯文(2.5):測試併發程式OBJ
- 從原始碼看 ObjC 中訊息的傳送原始碼OBJ
- 有助於改善效能的Java程式碼技巧Java
- 24個PHP程式碼最佳化技巧PHP
- 「奇淫技巧」如何寫最少的程式碼
- idea 中程式碼快速補全技巧Idea
- 程式碼除錯技巧【OI縮水版】除錯
- PHP程式碼優化的小技巧分享PHP優化
- 提高 Java 程式碼效能的各種技巧Java
- 嵌入式程式碼最佳化技巧
- Golang 非主流 打包靜態資源方案Golang
- objc系列譯文(7.5):自定義格式化程式OBJ
- ObjC RunLoop簡析OBJOOP
- objc物件說明OBJ物件
- Clang -rewrite-objcOBJ
- _objc_autoreleasePoolPringOBJ