看到Effective這個詞坠非,大家一定會想到《Effective C++》希柿、《Effective Java》等業(yè)界名著腐芍,那些書里匯聚了多項實用技巧集侯,又系統(tǒng)而深入的講解了各種編程知識被啼。那么,《Effective Objective-C 2.0》也是如此浅悉。
作為Mac OS X與iOS應(yīng)用程序的開發(fā)語言趟据,Objective-C作為首選券犁。那么术健,它有哪些需要注意的呢?
起源
Objective-C與C++粘衬、Java一樣荞估,是面向?qū)ο蟮恼Z言,是由Smalltalk演化而來滤淳。Smalltalk是消息型語言的鼻祖履因。消息與函數(shù)調(diào)用之間的區(qū)別看上去就像這樣:
//Messaging (Objective-C)
Object *obj = [Object new];
[obj performWith:parameter1 and:parameter2];
//Function calling (C++)
Object *obj = new Object;
obj->perform(parameter1, parameter2);
關(guān)鍵區(qū)別在于:使用消息結(jié)構(gòu)的語言屋群,其運行時所應(yīng)執(zhí)行的代碼由運行環(huán)境來決定;而使用函數(shù)調(diào)用的語言飞醉,則由編譯器決定。
Objective-C是C的“超集”(superset)屯阀,所以C語言中的所有功能在編寫Objective-C代碼時依然適用缅帘。理解C語言的內(nèi)存模型(memory model),有助于理解Objective-C的內(nèi)存模型及其“引用計數(shù)”(reference counting)機制的工作原理难衰。Objective-C語言中的指針是用來指示對象的钦无。
關(guān)于使用頭文件
主要使用 import
關(guān)鍵字。然而盖袭,我們在 .h
文件中一般首選使用 @class
關(guān)鍵字失暂,它能“向前聲明”一個類。對于不需要知道類細節(jié)的情況下我們使用它鳄虱。否則不會輕易使用 import
來引入整個頭文件弟塞。
過多的引入頭文件,會增加編譯時間拙已。這就是我們多使用 @class
關(guān)鍵字的直接原因决记。
除非確有必要,否則不要引入頭文件悠栓。一般來說霉涨,應(yīng)在某個類的頭文件中使用“向前聲明”來提及別的類按价,并在實現(xiàn)文件中引入那些類的頭文件。這樣做可以盡量降低類之間的耦合(coupling)笙瑟。
有時無法使用“向前聲明”楼镐,比如要聲明某個類遵循一項協(xié)議。這種情況下往枷,盡量把“該類遵循某協(xié)議”的這條聲明移至“class-continuation分類”中框产。如果不行的話,就把協(xié)議單獨放在一個頭文件中错洁,然后將其引入秉宿。
字面量語法
在編寫Objective-C程序時,總會用到某幾個類屯碴,它們屬于Foundation框架描睦。雖然從技術(shù)上來說,不用Foundation框架也能寫出Objective-C代碼导而,但是實際上卻經(jīng)常要用到此框架忱叭。這幾個類是NSString、NUNumber今艺、NSArray韵丑、NSDictionary。從類名上即可看出各自所表示的數(shù)據(jù)結(jié)構(gòu)虚缎。
Objective-C以語法繁雜而著稱撵彻。不過從Objective-C 1.0起,有一種簡單的方式能創(chuàng)建NSString 對象实牡。這就是“字符串字面量”(string literal)陌僵,其語法如下:
NSString *string = @"Effective Objective-C 2.0";
字面數(shù)值
NSNumber *number = [NSNumber numberWithInt:10];
//等價于
NSNumber *number = @10;
更多表示:
NSNumber *intNumber = @11;
NSNumber *floatNumber = @2.5f;
NSNumber *doubleNumber = @3.1415926;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'ABC';
字面量語法也適用于下述表達式
int x =5;
float y = 6.5f
NSNumber *expressionNumber = @(x * y);
字面量數(shù)組
NSarray *animals = [NSArray arrayWithObjects:@"cat", @"dog", @"mouse", @"badger", nil];
// 等價于
NSarray *animals = @[@"cat", @"dog", @"mouse", @"badger"];
使用數(shù)組
NSString *dog = [animals objectAtIndex:1];
// 等價于
NSString *dog = animals[1];
字面量字典
NSDictionary *personData = [NSDictionary dictionaryWithObjectsAnsKeys:@"Matt", @"firstName", @"Galloway", @"lastName", [NSNumber numberWithInt:28], @"age", nil];
// 等價于
NSDictionary *personData = @{@"firstName":@"Matt", @"lastName":@"Galloway", @"age":[NSNumber numberWithInt:28]};
使用字典
NSString *lastName = [personData objectForKey:@"lastName"];
// 等價于
NSString *lastName = personData[@"lastName"];
可變數(shù)組和字典
[mutableArray replaceObjectAtIndex:1 withObject:@"dog"];
[mutableDictionary setObject:@"Galloway" forKey:@"lastName"];
// 等價于
mutableArray[1] = @"dog";
mutableDictionary[@"lastName"] = @"Galloway";
局限性
字面量語法有個小小的限制,就是除了字符串以外铲掐,所創(chuàng)建出來的對象必須屬于Foundation框架才行拾弃。如果自定義了這些類的子類,則無法用字面量語法創(chuàng)建其對象摆霉。要想創(chuàng)建自定義子類的實例豪椿,必須采用“非字面量語法”(nonliteral syntax)。
使用字面量語法創(chuàng)建出來的字符串携栋、數(shù)組搭盾、字典對象都是不可變的(immutable)。若想要可變版本的對象婉支,則需要復(fù)制一份:
NSMutableArray *mutable = [@[@1, @2, @3, @4] mutableCopy];
這么做會多調(diào)用一個方法鸯隅,而且還要再創(chuàng)建一個對象,不過使用字面量語法所帶來的好處還是多于上述缺點的。
用字面量語法創(chuàng)建數(shù)組或字典時蝌以,若值中有nil炕舵,則會拋出異常。因此跟畅,務(wù)必確保值里不含nil咽筋。
多用類型常量 少用#define預(yù)處理指令
編寫代碼時經(jīng)常要定義常量。掌握了Objective-C與其C語言的基礎(chǔ)的人徊件,也許會用這種方法來做:
#define ANIMATION_DURATION 0.3
上述預(yù)處理指令會把源代碼中的ANIMATION_DURATION字符串替換為0.3.預(yù)處理過程會把碰到的所有ANIMATION_DURATION一律替換成0.3奸攻,這樣的話,假設(shè)此指令聲明在某個頭文件中虱痕,那么所有引入了這個頭文件的代碼睹耐,其ANIMATION_DURATION都會被替換。
要解決此問題部翘,應(yīng)該設(shè)法利用編譯器的某些特性才對硝训。
static const NSTimeInterval kAnimationDuration = 0.3;
用此方式定義的常量包含類型信息,其好處的清楚地描述了常量的含義略就。
常用的命名法是:
- 若常量局限于某”編譯單元”(translation unit捎迫,也就是“實現(xiàn)文件”,implementation file)之內(nèi)表牢,則在前面加字母k;
- 若常量在類之外可見贝次,則通常以類名為前綴崔兴。
定義常量的位置很重要。在頭文件里聲明預(yù)處理指令蛔翅,這樣會增加常量名稱互相沖突的可能性敲茄。
在頭文件中使用extern來聲明全局常量,并在相關(guān)實現(xiàn)文件中定義其值山析。這種常量要出現(xiàn)在全局符號表中堰燎,所以其名稱應(yīng)加以區(qū)隔,通常用與之相關(guān)的類名做前綴笋轨。
枚舉使用
枚舉只是一種常量命名方式秆剪。某個對象所經(jīng)歷的各種狀態(tài)就可以定義為一個簡單的枚舉集(enumeration set)。
enum IHConnectionState {
IHConnectionStateDisconnected,
IHConnectionStateConnecting,
IHConnectionStateConnected
};
默認情況下爵政,枚舉起始值為0仅讽,以后依次遞增,1,2,3...
其實還可以我們自己指定枚舉值:
enum IHConnectionState {
IHConnectionStateDisconnected = 1,
IHConnectionStateConnecting,
IHConnectionStateConnected
};
也可以定義為位移值:
enum UIViewAutoresizing {
UIViewAutoresizing = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
關(guān)于枚舉钾挟,F(xiàn)oundation框架中定義了一些輔助的宏洁灵,用這些來定義枚舉類型時,也可以指定用于保存枚舉值的底層數(shù)據(jù)類型掺出。
typedef NS_ENUM(NSUInteger, IHConnectionState) {
IHConnectionStateDisconnected = 1,
IHConnectionStateConnecting,
IHConnectionStateConnected
};
typedef NS_OPTIONS(NSUInteger, IHPermittedDirection) {
IHPermittedDirectionUp = 1 << 0,
IHPermittedDirectionDown = 1 << 1,
IHPermittedDirectionLeft = 1 << 2,
IHPermittedDirectionRight = 1 << 3
};
這些宏的定義如下:
#if(__cplusplus && __cplusplus >= 201103L && (__has_extension(cxx_strong_enums) || __has_feature(objc_fixed_enum))) || (!__cplusplus && __has_feature(objc_fixed_enum))
#define NS_ENUM(_type, _name)
enum _name:_type _name; enum _name:_type
#if (__cplusplus)
#define NS_OPTIONS(_type, _name)
type _name; enum:_type
#else
#define NS_OPTIONS(_type, _name)
enum _name:_type _name; enum _name:_type
#endif
#else
#define NS_ENUM(_type, _name) _type _name; enum
#define NS_OPTIONS(_type, _name) _type _name; enum
#endif
第一個#if
用于判斷編譯器是否支持新式枚舉徽千。如果不支持苫费,那么就用老式語法來定義枚舉。
在處理枚舉類型的switch語句中不要實現(xiàn)default分支双抽。這樣的話黍衙,加入新枚舉之后,編譯器就會提示開發(fā)者:switch語句并未處理所有枚舉荠诬。