3.用枚舉表示狀態,選項,狀態碼
用枚舉來表示狀態機的狀態,傳遞給方法的選項以及狀態碼等值時,能使這些狀態更加通俗易懂
如果把傳遞給某個方法的選項表示為枚舉類型,而多個選項又可同時使用,那么就將各選項值定義為2的冪,以便通過按位操作將其組合起來
enum UIViewAutoresizing {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
}
- 用NS_ENUM與NS_OPTIONS宏來定義枚舉類型,并指明其底層數據類型.這樣做可以確保枚舉是用開發者所選的底層數據類型實現出來的,而不會采用編譯器所選的類型.
用按位或運算來操作兩個枚舉值時,C++編譯模式的處理辦法與非C++模式不一樣.作為選項的枚舉值經常需要用按位或運算來組合.在用或運算操作兩個枚舉值時,C++認為運算結果的數據類型應該是枚舉的底層數據類型,也就是NSUInteger.而且C++不允許將這個底層類型"隱式轉換"為枚舉類本身.這時使用NS_ENUM定義枚舉,而且編譯器按照C++模式(也可能是Objective-C模式)編譯時,則會給出以下報錯:
error: cannot initialize a variable of type '枚舉類型' with an rvalue of type 'int'
如果想編譯這行代碼,就要將按位或操作的結果顯示轉換.所以在C++模式下應該用另一種方式定義NS_OPTIONS宏,以便省去類型轉換操作.
鑒于此,凡是需要按位或操作來組合的枚舉都應使用NS_OPTIONS定義.若是枚舉不需要互相組合,則應使用NS_ENUM來定義.
-
在處理枚舉類型的switch語句中不要實現default分支.這樣的話,加入新枚舉之后,編譯器就會提示開發者:switch語句并未處理所有枚舉.
switch語句中處理枚舉類型
4.屬性
關鍵字
1.@property
它可以自動寫出一套存取方法
@interface EOCPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
@interface EOCPerson : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end
上面兩種寫法對使用者來說是等效的
2.點語法
在使用了點語法之后,編譯器會把點語法轉換為對存取方法的調用
EOCPerson *aPerson = [EOCPerson new];
aPerson.firstName = @"Bob";//等同于
[aPerson setFirstName:@"Bob"];
NSString *lastName = aPerson.lastName;//等同于
NSString *lastName = [aPerson lastName];
3.自動合成
使用屬性之后,編譯器會自動編寫訪問這些屬性所需的方法.這個過程由編譯器在編譯期執行,所以在編輯器里是看不到這些"合成方法"的源代碼的.除了生成方法代碼之外,編譯器還會自動向類中添加適當類型的實例變量,并且在屬性名前面加下劃線,以此作為實例變量的名字.
@interface EOCPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
//生成的實例變量為_firstName和_lastName
4.@synthesize
它用來指定實例變量名字
@implementation EOCPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
//此時的實例變量名變為了_myFirstName和_myLastName,而不是之前的_firstName和_lastName
5.@dynamic(協議的繼承會用到此語法)
它會告訴編譯器,不要自動創建實現屬性所用的實例變量,也不要為其創建存取方法.而且,在編譯訪問屬性的代碼時,即使編譯器發現沒有定義存取方法,也不會報錯.
更多關于dynamic關鍵字的知識請看:Objective-C中的@dynamic
屬性特質
@property (nonatomic, readwrite, copy) NSString *firstName;
1.原子性
在默認情況下,由編譯器所合成的方法會通過鎖定機制確保其原子性(atomicity).如果屬性具備nonatomic特質,則不使用同步鎖.注意,盡管沒有名為"atomic"的特質,但是仍然可以在屬性特質中寫明這一點,編譯器不會報錯(如果某屬性不具備nonatomic特質,那它就是"原子的"(atomic)).
在并發操作中,如果某操作具備整體性,也就是說,系統其他部分無法觀察到其中間步驟所生成的臨時結果,而只能看到操作前與操作后的結果,那么該操作就是"原子的"(atomic).原子性
在開發iOS程序時應該使用nonatomic屬性,因為atomic屬性會嚴重影響性能.
2.讀/寫權限
- 具備readwrite(讀寫)特質的屬性擁有"獲取方法"(getter)與"設置方法"(setter).若該屬性由@synthesize實現,則編譯器會自動生成這兩個方法.
- 具備readonly(只讀)特質的屬性僅擁有獲取方法,只有當屬性由@synthesize實現時,編譯器才會為其合成獲取方法.你可以用此特質把某個屬性對外公開為只讀屬性,然后再"class-continuation分類"中將其重新定義為讀寫屬性.
3.內存管理語義
屬性用于封裝數據,而數據則要有"具體所有權語義"(concrete ownership semantic).下面的這些特質只會影響"設置方法"
- assign "設置方法"只會執行針對"純量類型"(例如CGFloat或NSInteger等)的簡單賦值操作
- strong 此特質表明該屬性定義了一種"擁有關系".為這種屬性設置新值時,設置方法會先保留新值,并釋放舊值,然后再將新值設置上去.
- weak 此特質表明該屬性定義了一種"非擁有關系".為這種屬性設置新值時,設置方法既不保留新值,也不釋放舊值.此特質同assign類似,然而在屬性所指的對象遭到摧毀時,屬性也會清空.
- unsafe_unretained 此特質的寓意和assign相同,但是它適用于"對象類型",該特質表達一種"非擁有關系",當目標對象遭到摧毀時,屬性值不會自動清空,這一點與weak有區別.
- copy 此特質所表達的所屬關系與strong類似.然而設置方法并不保留新值,而是將其"拷貝".當屬性類型為NSString *時,經常用此特質來保護其封裝性,因為傳遞給設置方法的新值有可能指向一個NSMutableNSString類的實例.這個類似NSString的子類,表示一種可以修改其值的字符串,此時若是不拷貝字符串,那么設置完屬性之后,字符串的值就可能會在對象不知情的情況下遭人更改.所以,這是就要拷貝一份"不可變"的字符串,確保對象中的字符串值不會無意間變動.只要實現屬性所用的對象是"可變的",就應該在設置新屬性值時拷貝一份.
4.方法名
- getter=<name> 指定"獲取方法"的方法名.
@property (nonatomic, getter=isOn) BOOL on;
- setter=<name> 指定"設置方法"的方法名.這種用法不太常見
在設置屬性鎖對應的實例變量時,一定要遵從該屬性所聲明的語義.
@interface EOCPerson : NSObject
@property (nonatomic,copy) NSString *firstName;
@property (nonatomic,copy) NSString *lastName;
- (id)initWithFirstName:(NSString *)firstName
lastName:(NSString *)lastName;
@end
在實現上述初始化方法時,一定要遵循屬性定義中宣稱的"copy"語義
- (id)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName {
if (self = [super init]) {
_firstName = [firstName copy];
_lastName = [lastName copy];
}
return self;
}