第1條:了解Objective-C語(yǔ)言的起源
- Objective-C為C語(yǔ)言添加了面向?qū)ο筇匦灾⒒瑁瞧涑两荨@斫釩語(yǔ)言的內(nèi)存模型(memory model)有助于理解Objective-C的內(nèi)存模型及其引用計(jì)數(shù)(reference counting)機(jī)制的工作原理纠拔。
- Objective-C使用動(dòng)態(tài)綁定的消息結(jié)構(gòu)歧杏,也就是說(shuō)砰碴,在運(yùn)行時(shí)才會(huì)檢查對(duì)象類型九榔。在接收一條消息之后兽埃,究竟應(yīng)執(zhí)行何種代碼侥钳,有運(yùn)行期環(huán)境決定,而非編譯器決定柄错。
第2條:在類的頭文件中盡量少引入其他頭文件
-
當(dāng)我們?cè)诰幾g一個(gè)類的時(shí)候舷夺,不需要知道該類的全部細(xì)節(jié),只需要知道有一個(gè)這樣的類就好的時(shí)候售貌,我們就不用通過(guò)#import的方式來(lái)引入該類頭文件给猾。
這叫“向前聲明”(forward declaring)該類。
@class "EOCEmployer.h"
import和@class的區(qū)別
import會(huì)包含這個(gè)類的所有信息颂跨,包括實(shí)體變量和方法敢伸,而@class只是告訴編譯器,其后面聲明的名稱是類的名稱毫捣。
在頭文件中详拙, 一般只需要知道被引用的類的名稱就可以了。不需要知道其內(nèi)部的實(shí)體變量和方法蔓同,所以在頭文件中一般使用@class來(lái)聲明這個(gè)名稱是類的名稱饶辙。
而在實(shí)現(xiàn)類里面,因?yàn)闀?huì)用到這個(gè)引用類的內(nèi)部的實(shí)體變量和方法斑粱,所以需要使用#import來(lái)包含這個(gè)被引用類的頭文件弃揽。
如果存在循環(huán)依賴關(guān)系,如:A -> B, B -> A這樣的相互依賴關(guān)系,當(dāng)使用#import來(lái)相互包含矿微,那么就會(huì)出現(xiàn)編譯錯(cuò)誤痕慢,如果使用@class在兩個(gè)類的頭文件中相互聲明,則不會(huì)有編譯錯(cuò)誤出現(xiàn)涌矢。
所以掖举,一般來(lái)說(shuō),@class是放在interface中的娜庇,只是為了在interface中引用這個(gè)類塔次,把這個(gè)類作為一個(gè)類型來(lái)用的。在實(shí)現(xiàn)這個(gè)接口的實(shí)現(xiàn)類中名秀,如果需要引用這個(gè)類的實(shí)體變量或者方法之類的励负,還是需要import。
當(dāng)我們需要聲明某個(gè)類遵循一項(xiàng)協(xié)議時(shí)匕得,不能使用@class继榆。這種情況下,盡量把“該類遵循某協(xié)議”的這條聲明移至“class-continuation分類”中汁掠。如果不行的話略吨,就把協(xié)議單獨(dú)放在一個(gè)頭文件中,然后將其通過(guò)#import的方式引入调塌。
第3條:多用字面量語(yǔ)法晋南,少用與之等價(jià)的方法
- 使用字面量語(yǔ)法來(lái)聲明可以縮減源代碼長(zhǎng)度,使其更為易讀羔砾。
字面數(shù)值
NSNumber *Number = [NSNumber numberWithInt:1];
NSNumber *newNumber = @1;
字面數(shù)組
如果數(shù)組元素對(duì)象中有nil负间,則會(huì)拋出異常,因?yàn)樽置媪空Z(yǔ)法實(shí)際上只是一種“語(yǔ)法糖”(syntactic sugar)姜凄,其效果等于是先創(chuàng)建了一個(gè)數(shù)組政溃,然后把方括號(hào)內(nèi)的所有對(duì)象都加入到這個(gè)數(shù)組中。
因此态秧,如下面這兩個(gè)數(shù)組的第二位都未nil時(shí)董虱,array里面會(huì)只有@“one”一個(gè)對(duì)象,而newArray將會(huì)拋出異常申鱼。這種微妙的差別表明愤诱,使用字面量語(yǔ)法更為安全。拋出異常令程序中止執(zhí)行捐友,這比創(chuàng)建好數(shù)組之后才發(fā)現(xiàn)元素個(gè)數(shù)少了要好淫半,因?yàn)橄驍?shù)組中插入nil通常就是說(shuō)明程序有錯(cuò),最終結(jié)果往往就是數(shù)組越界崩潰匣砖,而現(xiàn)在我們卻可以通過(guò)異晨瓶裕可以更快地發(fā)現(xiàn)這個(gè)錯(cuò)誤昏滴。
NSArray *array = [NSArray arrayWithObjects:@"one",@"two",@"three", nil];
NSArray *newArray = @[@"one",@"two",@"three"];
字面字典
與數(shù)組一樣,用字面量語(yǔ)法創(chuàng)建字典時(shí)也有一個(gè)問(wèn)題对人,那就是一旦有值為nil谣殊,便會(huì)拋出異常。
NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:
@"Mark",@"firstname",
@"Lin",@"lastname",nil];
NSDictionary *newDic = @{@"firstname" : @"Mark",
@"lastname" : @"Lin",
@"age" : @28};
大家可以很直觀地看出兩種創(chuàng)建實(shí)例變量的方法有什么區(qū)別牺弄,使用字面量的方式不僅可以令到代碼看起來(lái)更加地整潔姻几,而且字面量語(yǔ)法也更為精簡(jiǎn),因?yàn)槁暶髦兄话瑪?shù)值猖闪,而沒(méi)有多余的語(yǔ)法成分鲜棠。
局限性
字面量語(yǔ)法有一個(gè)小小的局限肌厨,就是除了字符串以外培慌,所創(chuàng)建出來(lái)的對(duì)象必須屬于Foundation框架才行。
而使用字面量語(yǔ)法創(chuàng)建的字符串柑爸、數(shù)組吵护、字典都是不可變的(immutable),若想要可變版本的對(duì)象可以參考以下寫(xiě)法:
NSMutableArray *mutable = [@[@"one", @"two", @"three"] mutableCopy];
第4條:多用類型常量表鳍,少用#define預(yù)處理指令
-
編寫(xiě)代碼時(shí)經(jīng)常需要定義一些常量馅而,很多人都會(huì)用下面這種方式來(lái)實(shí)現(xiàn):
#define ANIMATION_DURATION 0.3
這種實(shí)現(xiàn)方式可能就是你想要的效果,但是這樣定義出來(lái)的常量沒(méi)有類型信息譬圣,此外瓮恭,預(yù)處理過(guò)程會(huì)把所有ANIMATION_DURATION一律改成0.3,這樣的話厘熟,假設(shè)此指令在某個(gè)頭文件中屯蹦,那么所有引入了這個(gè)頭文件的代碼,其ANIMATION_DURATION都會(huì)被替換绳姨。
要想解決這個(gè)問(wèn)題登澜,可以使用下面這種方式來(lái)實(shí)現(xiàn):
static const NSTimeInterval kAnimationDuration = 0.3;
此方式定義的常量包含類型信息,清晰描述常量的含義飘庄。而且定義常量的位置很重要脑蠕,在頭文件中聲明預(yù)處理指令是一種很糟糕的做法,常量名稱有可能互相沖突跪削。因?yàn)镺bjective-C沒(méi)有“名稱空間”(namespace)這一概念谴仙,所以這樣做等于聲明了一個(gè)全局變量。
所以若不打算公開(kāi)某常量碾盐,則應(yīng)將其定義在使用該常量的實(shí)現(xiàn)文件里面晃跺,如下:
// MarkAnimatedView.h #import <UIKit/UIKit.h> @interface MarkAnimatedView : UIView - (void)animate; @end // MarkAnimatedView.m #import "MarkAnimatedView.h" static const NSTimeInterval kAnimationDuration = 0.3; @implementation MarkAnimatedView - (void)animate { [UIView animateWithDuration:kAnimationDuration animations:^{ //Perform animations }]; } @end
常量為什么一定要同時(shí)使用static與const來(lái)聲明呢?廓旬?哼审?
因?yàn)槿绻腥嗽噲D修改有const修飾符所聲明的變量時(shí)谐腰,那編譯就會(huì)報(bào)錯(cuò),而這就是我們所需要達(dá)到的目的I堋J!而static修飾符則是意味著該常量?jī)H在定義此常量的編譯單元中可見(jiàn)春霍。
編譯器每收到一個(gè)編譯單元砸西,就會(huì)輸出一份“目標(biāo)文件”(object file),在Objective-C的語(yǔ)境下址儒,“編譯單元”一詞通常指每個(gè)類的實(shí)現(xiàn)文件芹枷,因此在上述代碼中聲明的kAnimationDuration,其作用域僅限于由MarkAnimatedView.m所生成的目標(biāo)文件中莲趣。
假如聲明此常量時(shí)不加static鸳慈,則編譯器會(huì)為它創(chuàng)建一個(gè)“外部符號(hào)”(external symbol),此時(shí)如若另一個(gè)編譯單元中聲明了同名常量喧伞,則會(huì)報(bào)錯(cuò)走芋。
而有時(shí)候確實(shí)是需要對(duì)外公開(kāi)某個(gè)常量的時(shí)候,我們有應(yīng)該如何處理呢潘鲫?(如在類代碼中調(diào)用NSNotificationCenter)
此類常量則應(yīng)放在“全局符號(hào)表”(global symbol table)中翁逞,以便可以在定義常量的編譯單元之外使用。具體實(shí)現(xiàn)如下:
```
// MarkAnimatedView.h
#import <UIKit/UIKit.h>
extern NSString *const MarkLoginNotification;
@interface MarkAnimatedView : UIView
- (void)animate;
@end
// MarkAnimatedView.m
#import "MarkAnimatedView.h"
static const NSTimeInterval kAnimationDuration = 0.3;
NSString *const MarkLoginNotification = @"MarkLoginNotification";
@implementation MarkAnimatedView
- (void)animate
{
[UIView animateWithDuration:kAnimationDuration animations:^{
//Perform animations
}];
}
- (void)doNotificationAction
{
[[NSNotificationCenter defaultCenter] postNotificationName:MarkLoginNotification object:nil];
}
@end
```
注意const修飾符在常量類型中的位置溉仑,常量定義應(yīng)從右至左解讀挖函,所以在本例子中,MarkLoginNotification和kAnimationDuration就是“一個(gè)常量浊竟,而這個(gè)常量是指針怨喘,指向NSString對(duì)象”。
extern這個(gè)修飾符會(huì)告訴編譯器逐沙,在全局符號(hào)表中將會(huì)有一個(gè)MarkLoginNotification的符號(hào)哲思,編譯器無(wú)須查看其定義,因?yàn)樗喇?dāng)鏈接成二進(jìn)制文件之后吩案,肯定能找到這個(gè)常量棚赔。
這樣定義常量要優(yōu)于#define預(yù)處理指令,因?yàn)榫幾g器會(huì)確保常量值不變徘郭,而且一旦定義好之后靠益,即可隨處使用。
第5條:用枚舉表示狀態(tài)残揉、選項(xiàng)胧后、狀態(tài)碼
-
枚舉只是一種常量命名方式, 使用枚舉來(lái)表示各種狀態(tài)碼可以便于程序猿們更好地去理解代碼抱环。如使用枚舉來(lái)表示“套接字連接”(socket connection)的狀態(tài):
enum MarkConnectionState { MarkConnectionDisconnected, MarkConnectionConnecting, MarkConnectionConnected, };
-
要想每次不用敲入enum而只需寫(xiě)MarkConnectionState的話壳快,則需要使用typedef關(guān)鍵字重新定義枚舉類型:
enum MarkConnectionState { MarkConnectionDisconnected, MarkConnectionConnecting, MarkConnectionConnected, }; typedef enum MarkConnectionState MarkConnectionState;
現(xiàn)在就可以使用簡(jiǎn)寫(xiě)的MarkConnectionState來(lái)代替完整的enum MarkConnectionState了:
MarkConnectionState state = MarkConnectionConnected;
-
C++11標(biāo)準(zhǔn)修訂了枚舉的某些特性纸巷,其中就包括了很重要的一點(diǎn):可以指明使用何種“底層數(shù)據(jù)類型”(underlying type)來(lái)保存枚舉類型的變量。這樣做的好處就是可以向前聲明枚舉變量了:
enum MarkConnectionState : NSInteger;
當(dāng)然眶痰,還可以不使用編譯器所分配的序號(hào)瘤旨,而手工指定某個(gè)枚舉成員所對(duì)應(yīng)的值:
enum MarkConnectionState { MarkConnectionDisconnected = 22, MarkConnectionConnecting, MarkConnectionConnected, };
如前所述,由于第一個(gè)枚舉值為22竖伯,所以接下來(lái)的幾個(gè)枚舉值都會(huì)在上一個(gè)的基礎(chǔ)上遞增1存哲。
-
還有一種情況是非常推薦使用枚舉類型的,那就是-----定義選項(xiàng)七婴!
enum MarkAutoresizing { MarkAutoresizin = 0, MarkAutoresizinWidth = 1 << 0, MarkAutoresizinHeight = 1 << 1, MarkAutoresizinTop = 1 << 2, MarkAutoresizinBottom = 1 << 3, };
在iOS UI框架中的UIKit里也有一個(gè)非常常見(jiàn)的例子祟偷,那就是用枚舉值來(lái)告訴系統(tǒng)視圖所支持的設(shè)備顯示方向,這個(gè)枚舉類型叫做UIInterfaceOrientationMask打厘,開(kāi)發(fā)者可以通過(guò)supported InterfaceOrientations的方法修肠,將視圖所支持的顯示方向告訴系統(tǒng)。
-
最后再講一種枚舉的用法婚惫,在swith中使用枚舉:
typedef NS_ENUM(NSUInteger, MarkConnectionState){ MarkConnectionDisconnected, MarkConnectionConnecting, MarkConnectionConnected, }; switch (_currentState) { case MarkConnectionDisconnected: break; case MarkConnectionConnecting: break; case MarkConnectionConnected: break; default: break; }
使用NS_ENUM與NS_OPTIONS宏來(lái)定義枚舉類型氛赐,并指明其底層數(shù)據(jù)類型,這樣做可以確保枚舉是用開(kāi)發(fā)者所選的底層數(shù)據(jù)類型實(shí)現(xiàn)出來(lái)先舷,而不會(huì)采用編譯器所選的類型。
在處理枚舉類型的swith語(yǔ)句中不要實(shí)現(xiàn)default分支滓侍,這樣就可以在加入新枚舉之后蒋川,編譯器自動(dòng)提示開(kāi)發(fā)者:swith語(yǔ)句并未處理所有枚舉。