看開源代碼時,總會看到一些大神級別的代碼,給人眼前一亮的感受,多數都是被淡忘的C語言語法,總結下objc寫碼中遇到的各種非主流
代碼技巧和一些妙用:html
先來個娛樂向的。
方法聲明時有一下幾個trick:api
返回值的- (TYPE)
若是不寫括號,編譯器默認認爲是- (id)
類型:數組
1 2 |
- init;
- (id)init; // 等價於 |
同理,參數若是不寫類型默認也是id
類型:ruby
1 2 |
- (void)foo:arg; - (void)foo:(id)arg; // 等價於 |
還有,有多參數時方法名
和參數提示語
能夠爲空網絡
1 2 |
- (void):(id)arg1 :(id)arg2; - (void)foo:(id)arg1 bar:(id)arg2; // 省略前 |
綜上,最短的函數能夠寫成這樣:app
1 2 3 4 5 |
- _; // 沒錯,這是一個oc方法聲明 - :_; // 這是一個帶一個參數的oc方法聲明 // 等價於 - (id)_; - (id) :(id)_; |
PS: 方法名都沒的方法只能靠performSelector
來調用了,selector
是":"
函數
1 2 3 4 |
// 不加(CGRect)強轉也不會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中惟一一個三目運算符,用來替代簡單的if-else
語句,同時也是能夠兩元使用的:優化
1 2 |
NSString *string = inputString ?: @"default"; NSString *string = inputString ? inputString : @"default"; // 等價 |
1 2 3 4 5 6 7 |
const int numbers[] = { [1] = 3, [2] = 2, [3] = 1, [5] = 12306 }; // {0, 3, 2, 1, 0, 12306} |
這個特性能夠用來作枚舉值和字符串的映射
ui
1 2 3 4 5 6 7 8 |
typedef NS_ENUM(NSInteger, XXType){ XXType1, XXType2 }; const NSString *XXTypeNameMapping[] = { [XXType1] = @"Type1", [XXType2] = @"Type2" }; |
一個block像下面同樣聲明:atom
1 2 3 |
void(^block1)(void); void(^block2)(int a); void(^block3)(NSNumber *a, NSString *b); |
若是block的參數列表爲空的話,至關於可變參數(不是void)
1 2 3 4 5 6 |
void(^block)(); // 返回值爲void,參數可變的block
block = block1; // 正常 block = block2; // 正常 block = block3; // 正常 block(@1, @"string"); // 對應上面的block3 block(@1); // block3的第一個參數爲@1,第二個爲nil |
這樣,block的主調和回調之間能夠經過約定
來決定block回傳回來的參數是什麼,有幾個。如一個對網絡層的調用:
1 2 3 4 5 6 7 8 9 |
- (void)requestDataWithApi:(NSInteger)api block:(void(^)())block { if (api == 0) { block(1, 2); } else if (api == 1) { block(@"1", @2, @[@"3", @"4", @"5"]); } } |
主調者知道本身請求的是哪一個Api,那麼根據約定
,他就知道block裏面應該接受哪幾個參數:
1 2 3 4 5 6 |
[server requestDataWithApi:0 block:^(NSInteger a, NSInteger b){
// ... }]; [server requestDataWithApi:1 block:^(NSString *s, NSNumber *n, NSArray *a){ // ... }]; |
這個特性在Reactive Cocoa
的-combineLatest:reduce:
等相似方法中已經使用的至關好了。
1 |
+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(id (^)())reduceBlock; |
假如一個類有一個readonly
屬性:
1 2 3 |
@interface Sark : NSObject @property (nonatomic, readonly) NSArray *friends; @end |
.m
中可使用_friends
來使用自動合成的這個變量,但假如:
- 習慣使用self.
來set實例變量時(只合成了getter)
- 但願重寫getter進行懶加載時(重寫getter時則不會生成下劃線的變量,除非手動@synthesize
)
- 容許子類重載這個屬性來修改它時(編譯報錯屬性修飾符不匹配)
這種readonly
聲明方法就行不通了,因此下面的寫法更有通用性:
1 2 3 |
@interface Sark : NSObject @property (nonatomic, readonly, copy/*加上setter屬性修飾符*/) NSArray *friends; @end |
如想在.m
中像正常屬性同樣使用:
1 2 3 |
@interface Sark () @property (nonatomic, copy) NSArray *friends; @end |
子類化時同理。iOS SDK中不少地方都用到了這個特性。
A compound statement enclosed in parentheses
原諒個人渣翻譯- -,來自《gcc官方對此的說明》,源自gcc對c的擴展,現在被clang繼承。
1 2 3 4 |
RETURN_VALUE_RECEIVER = {(
// Do whatever you want RETURN_VALUE; // 返回值 )}; |
因而乎能夠發揮想象力了:
1 2 3 4 5 6 |
self.backgroundView = ({ UIView *view = [[UIView alloc] initWithFrame:self.view.bounds]; view.backgroundColor = [UIColor redColor]; view.alpha = 0.8f; view; }); |
有點像block和內聯函數的結合體,它最大的意義在於將代碼整理分塊
,將同一個邏輯層級的代碼包在一塊兒;同時對於一個無需複用小段邏輯,也免去了重量級的調用函數,如:
1 2 3 4 5 6 7 |
self.result = ({
double result = 0; for (int i = 0; i <= M_2_PI; i+= M_PI_4) { result += sin(i); } result; }); |
這樣使得代碼量增大時層次仍然能比較明確。
PS: 返回值和代碼塊結束點必須在結尾
正常編譯執行:
1 2 3 4 5 |
int sum(a,b) int a; int b; { return a + b; } |
1 2 3 |
#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 |
下面的斷言在編譯前就生效
1 2 3 4 5 |
#define C_ASSERT(test) \ switch(0) {\ case 0:\ case test:;\ } |
如斷言上面預處理時計算可變參數個數:
1 |
C_ASSERT(COUNT_PARMS(1,2,3) == 2); |
若是斷言失敗,至關於switch-case
中出現了兩個case:0
,則編譯報錯。
源自Reactive Cocoa
中的宏:
1 2 |
#define keypath2(OBJ, PATH) \
(((void)(NO && ((void)OBJ.PATH, NO)), # PATH)) |
原來寫過一篇《介紹RAC宏的文章》中曾經寫過。這個宏在寫PATH參數的同時是帶自動提示的:
逗號表達式取後值,但前值的表達式參與運算,可用void忽略編譯器警告
1 |
int a = ((void)(1+2), 2); // a == 2 |
因而上面的keypath宏的輸出結果是#PATH
也就是一個c字符串
以前的文章沒有弄清上面宏中NO&&NO
的含義,其實這用到了編譯器優化的特性:
1 2 3 |
if (NO && [self shouldDo]/*不執行*/) { // 不執行 } |
編譯器知道在NO後且什麼的結果都是NO,因而後面的語句被優化掉了。也就是說keypath宏中這個NO && ((void)OBJ.PATH, NO)
就使得在編譯後後面的部分不出如今最後的代碼中,因而乎既實現了keypath的自動提示功能,又保證編譯後不執行多餘的代碼。
https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
原創文章,轉載請註明源地址,blog.sunnyxx.com