本系列是根據《Effective Objective-C 2.0》一書中的系列文章,選開發中實踐的經驗之談,聚集於此,便於查閱,或者爲來訪者提供一份參考。編排按《Effective Objective-C 2.0》中條目。html
在類的頭文件中儘可能少引用其餘頭文件程序員
@class
objective-c
"向前聲明"或「前向引用」,僅僅是聲明一個類名,並不會包含類的完整聲明。 @class
還能解決循環包含的問題。在實現這個接口的實現類中,若是須要引用這個類的實體變量或者方法之類的,仍是須要#import
在@class
中聲明的類進來。express
那麼爲何還須要@class
呢?由於做聲明某個類來用,編譯器並不會將類的實例變量或方法引入,其能夠加快編譯,減小編譯時間。數組
另外,在實際中,會遇到——當解析某個文件時,編譯器會發現它引入了另外一個頭文件,而那個頭文件又回過頭來引入了第一個頭文件——循環包含,這時候,使用#import
而非#include
指令雖然不會致使死循環,但卻意味着兩個類裏有一個沒法被正確編譯。這時候,採用@class
僅做聲明。安全
#import
和#include
都能完整地包含某個文件的內容,#import
能防止同一個文件被包含屢次;#import <>
用來包含系統自帶的文件,#import 「」
用來包含自定義的文件多用字面量語法,少用與之等價的方法。app
字面量語法,實際上是Objective-C 2.0
添加的「語法糖」,方便程序員書寫,提升可讀性以及編譯時檢查等特性。框架
其中,涉及到類NSString
、NSNumber
、NSArray
、NSDictionary
。post
NSString *siteTitle = @"Mobiletuts+";
//假如不用字面量語法,那麼上面可能會寫成
NSString *siteTitle = [NSString stringWithUTF8String:"Mobiletuts"];
複製代碼
NSNumber *boolYES = [NSNumber numberWithBool:YES];
NSNumber *boolNO = [NSNumber numberWithBool:NO];
NSNumber *charX = [NSNumber numberWithChar:'X'];
NSNumber *fortySevenInt = [NSNumber numberWithInt:47];
NSNumber *fortySevenUnsigned = [NSNumber numberWithUnsignedInt:47U];
NSNumber *fortySevenLong = [NSNumber numberWithLong:47L];
NSNumber *goldenRatioFloat = [NSNumber numberWithFloat:1.61803F];
NSNumber *goldenRatioDouble = [NSNumber numberWithDouble:1.61803];
複製代碼
採用字面量語法,上面可寫爲:spa
NSNumber *boolYES = @YES;
NSNumber *boolNO = @NO;
NSNumber *charX = @'X';
NSNumber *fortySevenInt = @47;
NSNumber *fortySevenUnsigned = @47U;
NSNumber *fortySevenLong = @47L;
NSNumber *goldenRatioFloat = @1.61803F;
NSNumber *goldenRatioDouble = @1.61803;
複製代碼
字面量也適用於下述表達式:
int x = 5
float y = 6.32f
NSNumber *expressionNumber = @{x * y}
複製代碼
NSArray *instruments = [NSArray arrayWithObjects: @"Ocarina", @"Flute", @"Harp", nil];
複製代碼
使用字面量語法來建立:
NSArray *instruments = @[ @"Ocarina", @"Flute", @"Harp" ];
複製代碼
假如,要聲明一個NSMutableArray
數組,能夠採用下面: NSMutableArray *instrumentsMutable = [ @[ @"Ocarina", @"Flute", @"Harp" ] mutableCopy];
最後,須要注意的是,在用字面量語法建立數組時,若數組中有元素有nil,則會拋出異常,由於字面量語法實際上只是一種語法糖,其效果等同因而先建立了一個數組,而後把方括號內的多有對象都加到這個數組中。拋出的異常以下:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException',reason:'*** - [__NSPlaceholderArray initWithObjects:count:]:attempt to insert nil object form objects[0]'
複製代碼
在改用字面量語法來建立數組時就會遇到這個問題,下面這段代碼分別以兩種語法建立數組:
id object1 = /*...*/
id object2 = /*...*/
id object3 = /*...*/
NSArray *arrayA = [NSArray arrayWithObjects:object1,object2,object3,nil];
NSArray *arrayB = @[object1,object2,object3];
複製代碼
假如,object1
和object3
都指向了有效對象,而object2
是nil
,會出現什麼狀況?
按字面量建立的arrayB會拋出異常,arrayA
雖然能建立出來,可是其中只有object1
一個對象。緣由在於,「arrayWithObjects」方法會一次處理各個參數,直到發現nil
,object2
是nil
,因此方法提早結束。
這個微秒的差異代表,使用字面量語法更爲安全。拋出異常令應用程序終止執行,這比建立好數組以後才發現元素個數少了要好。
建立一個字典:
NSArray *keys = [NSArray arrayWithObjects:@"Character", @"Weapon", @"Hitpoints", nil];
NSArray *objects = [NSArray arrayWithObjects:@"Zelda", @"Sword", [NSNumber numberWithInt:50], nil];
NSDictionary *stats = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
複製代碼
或者,採用更簡潔的寫法:
NSDictionary *stats = [NSDictionary dictionaryWithObjectsAndKeys:
@"Zelda", @"Character",
@"Sword", @"Weapon",
[NSNumber numberWithInt:50], @"Hitpoints",
nil];
複製代碼
而採用字面量語法:
NSDictionary *stats = @{ @"Character" : @"Zelda",
@"Weapon" : @"Sword",
@"Hitpoints" : @50 };
複製代碼
一樣,在用字面量語法中,若是值是nil
,與字面量建立數組表現類似,會拋出異常。
「取下標」操做通常會用objectAtIndex
方法:
NSString * flute = [instruments objectAtIndex:1];
NSString * flute = instruments[1];
複製代碼
假如,是可變數組,採用字面量寫法:
instrumentsMutable[0] = @"Ocarina of Time";
//其對應的方法爲:replaceObjectAtIndex:withObject
複製代碼
字典的讀寫也相似。
NSString *name = stats[@"Character"]; // Returns 'Zelda'
statsMutable[@"Weapon"] = @"Hammer";
//分別對應方法:
//objectForKey:
//setObject:forkey:
複製代碼
多用類型常量,少用#define預處理指令。
編寫代碼中常將常量寫爲:
#define ANIMATION_DURATION 0.3
複製代碼
預處理過程會把全部ANIMATION_DURATION
一概替換成0.3
,假設該指令聲明在某個頭文件中,那麼全部引入了這個頭文件的代碼,其ANIMATION_DURATION
都會被替換。
更好的方式是:
static const NSTimerInterval kAnimationDuration = 0.3
複製代碼
首先,添加了類型信息,清楚地描述了常量的含義。
其次,要注意常量的名稱,經常使用的命名法是:若常量侷限於某「編譯單元(通常只實現文件,即.m)」以內,則在前面加k
;若常量在類以外可見,則一般以「類名」爲前綴。
最後,要注意常量的位置。咱們總喜歡在頭文件裏聲明預處理指令,這是至關糟糕的,當常量名稱有可能互相沖突更是如此。因爲OC沒有「命名空間」這一律念,因此避免將常量聲明放在頭文件裏。即便採用static const
這種方式也是如此。若不打算公開某個常量,則應該將其定義在使用該常量的實現文件裏。
那麼,爲何要用static
和const
來修飾常量?
static
代表的是做用域,意味着該變量盡在定義此變量的編譯單元中可見,編譯器每收到一個編譯單元,就會輸出一份「目標文件」。在Objective-C語境下,「編譯單元」一次一般指每一個類的實現文件。假如聲明此變量時,不加static,那麼編譯器會爲它建立一個「外部符號(external symbol)」。此時,若其餘編譯單元也聲明瞭同名變量,就會拋出一條錯誤消息:
duplicate symbol _kAnimationDuration in:
EOCAnimatedView.o
EOCOtherView.o
複製代碼
const
則聲明爲不可修改。
實際上,若是一個變量既聲明爲static
,又聲明爲const
,那麼編譯器根本不會建立符號,而是會像#define
預處理指令同樣,把全部遇到的變量頭替換爲常值,可是,這種方式具備類型信息。
那麼,假如要對外公開一個常量要怎麼辦?
有時候,須要對外公開常量。常見的情景就是在類代碼中調用NSNotificationCenter
以通知他人。那麼通知名通常聲明一個外界可見的常值變量。
此類變量須要放在「全局符號表(global symbol table)」中,以即可以在定義該常量的編譯單元以外使用。其定義方式:
//in the header file
extern NSString *const EOCStringConstant;
//in the implementation file
NSSting *const EOCStringConstant = @"VALUE"
複製代碼
編譯器發現頭文件中含有extern
,就知道,在全局符號表中將會有一個EOCStringConstant
的符號。即編譯器無須查看其定義,就容許代碼中使用此常量。由於它知道,當連接二進制文件後,確定能找到這個常量。
此類常量必需要定義,並且只能定義一次。一般將其定義在與聲明該常量的頭文件相關的實現文件裏。由實現文件生成目標文件時,編譯器會在「數據段」爲字符串分配存儲空間。
static const
來定義「只在編譯單元內可見的常量」。因爲此類常量不在全局符號表中,因此無須爲其名稱加前綴;extern
來聲明全局變量,並在相關實現文件中定義其值。這種常量要出如今全局符號表中,因此其名稱應加以區隔,一般用與之相關的類名作前綴。用枚舉表示狀態、選項、狀態碼
enum {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
typedef enum {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
} UITableViewCellStyle;
typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
UITableViewCellStyleDefault,
UITableViewCellStyleValue1,
UITableViewCellStyleValue2,
UITableViewCellStyleSubtitle
};
NS_OPTIONS
typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
複製代碼
在iOS開發中,凡是須要以按位或操做來組合的枚舉都應使用NS_OPTIONS
定義。
如果枚舉不須要互相組合,則應使用NS_ENUM
來定義。
NS_ENUM
與NS_OPTIONS
宏定義了來定義枚舉類型,並指明底層數據類型。以確保枚舉是用開發者所選的底層數據類型實現出來的,而不會採用編譯器所選的類型;switch
語句中不要實現default
分支。這樣的話,假如新枚舉以後,編譯器就會提示開發者:switch
語句並未處理全部枚舉。《Effective Objective-C 2.0》