之前閱讀過(guò)《Effective Objective-C 2.0》废岂,覺(jué)得有些知識(shí)點(diǎn)忘記了,在此再把每個(gè)章節(jié)的內(nèi)容都整理一遍
了解Objective-C語(yǔ)言的起源
大家可能知道狱意,Objective-C語(yǔ)言使用的是消息結(jié)構(gòu)
那我們看下消息與函數(shù)調(diào)用的卻別湖苞,看上去就像這樣:
//Messageing (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ǔ)言,其運(yùn)行時(shí)所應(yīng)執(zhí)行的代碼由運(yùn)行環(huán)境來(lái)決定详囤;而使用函數(shù)調(diào)用的語(yǔ)言财骨,則由編譯器決定。如果范例代碼中調(diào)用的函數(shù)是多態(tài)的藏姐,那么運(yùn)行時(shí)就要按照“虛方法表”(virtual table)來(lái)查出到底應(yīng)該執(zhí)行哪個(gè)函數(shù)實(shí)現(xiàn)隆箩。而采用消息結(jié)構(gòu)的語(yǔ)言,不論是否多態(tài)羔杨,總是運(yùn)行時(shí)才會(huì)去查找所要執(zhí)行的方法捌臊。
Objective-C的重要工作是由“運(yùn)行期組件”(runtime component)而非編譯器來(lái)完成的。使用Objective-C的面向?qū)ο筇匦运璧娜繑?shù)據(jù)結(jié)構(gòu)以及函數(shù)都在運(yùn)行期組件里面兜材。舉例來(lái)說(shuō)理澎,運(yùn)行期組件中含有全部?jī)?nèi)存管理方法。運(yùn)行期組件本質(zhì)上就是一種與開(kāi)發(fā)者所編代碼相鏈接的“動(dòng)態(tài)庫(kù)”(dynamic library),其代碼能把開(kāi)發(fā)者編寫的所有程序粘合起來(lái)曙寡。這樣的話糠爬,只需要更新運(yùn)行期組件,即可提升應(yīng)用程序性能举庶。而那種許多工作都在“編譯器”(compile time)完成的語(yǔ)言执隧,若想要獲得類似的性能提升,則要重新編譯應(yīng)用程序代碼灯变。
要點(diǎn)
- Objective-C為C語(yǔ)言添加了面向?qū)ο筇匦耘孤辏瞧涑bjective-C使用動(dòng)態(tài)綁定的消息結(jié)構(gòu)添祸,也就是說(shuō)滚粟,在運(yùn)行時(shí)才會(huì)檢查對(duì)象類型。接受一條消息之后刃泌,究竟應(yīng)執(zhí)行何種代碼凡壤,由運(yùn)行期環(huán)境而非編譯器決定署尤。
- 理解C語(yǔ)言的核心概念有助于寫好Objective-C程序。尤其要掌握內(nèi)存模型與指針亚侠。
在類的頭文件中盡量少引入其他頭文件
要點(diǎn)
- 除非確有必要曹体,否則不要引入頭文件。一般來(lái)說(shuō)硝烂,應(yīng)在某個(gè)類的頭文件中使用向前聲明來(lái)提及類別的類箕别,并在實(shí)現(xiàn)文件中引入那些類的頭文件。這樣做可以盡量降低類之間的耦合(coupling)滞谢。
- 有時(shí)無(wú)法使用向前聲明串稀,比如要聲明某個(gè)類遵循一項(xiàng)協(xié)議。這種情況下狮杨,盡量把“該類遵循某協(xié)議”的這條聲明移至“class-continuation分類”中母截。如果不行的話,就把協(xié)議單獨(dú)放在頭文件中橄教,然后將其引入清寇。
什么是向前聲明
在編譯一個(gè)使用了ClassA類的文件時(shí),不需要知道ClassA類的全部實(shí)現(xiàn)細(xì)節(jié)护蝶,只需要知道有一個(gè)類名叫ClassA就好华烟。如下代碼:
#import <UIKit/UIKit.h>
@class ClassA;
@interface ClassB : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, strong) ClassA *classA;
@end
其中@class ClassA就是向前聲明了
將引入頭文件的時(shí)機(jī)盡量延后,只在確有需要時(shí)才引入滓走,這樣就可以減少類的使用者所需引入的頭文件數(shù)量垦江。如果直接引入頭文件,則可能要引入許多根本用不到的內(nèi)容搅方,從而增加編譯時(shí)間比吭。
同時(shí),向前聲明也解決了兩個(gè)類相互引用的問(wèn)題姨涡。假設(shè)要為ClassB類中添加新增以及刪除ClassA的方法衩藤,那么其頭文件中會(huì)加入下述定義:
- (void)addClassA:(ClassA *)classA;
- (void)removeClassA:(ClassA *)classA;
此時(shí),若要編譯ClassB涛漂,則編譯器必須知道ClassA這個(gè)類赏表,而要編譯ClassA,則又必須知道ClassB匈仗。如果在各自頭文件中引入對(duì)方的頭文件瓢剿,則會(huì)導(dǎo)致“循環(huán)引用”(chicken-and-egg situation)。當(dāng)解析其中一個(gè)頭文件時(shí)悠轩,編譯器會(huì)發(fā)現(xiàn)它引入了另一個(gè)頭文件间狂,而那個(gè)頭文件又回過(guò)來(lái)引用第一個(gè)頭文件。使用#import而非#include指令雖然不會(huì)導(dǎo)致死循環(huán)火架,但這意味著兩個(gè)類里有一個(gè)無(wú)法被正確編譯鉴象。如果不信的話忙菠,讀者可以自己試試。
但是有的時(shí)候必須要將頭文件中引入其他頭文件纺弊。
- 如果你寫的類繼承自某個(gè)超類牛欢,則必須引入定義那個(gè)超類的頭文件。
- 同理淆游,如果要聲明某個(gè)類遵循某個(gè)協(xié)議(protocol)傍睹,那么該協(xié)議必須有完整定義,且不能使用向前聲明稽犁。
向前聲明只能告訴編輯器有個(gè)某個(gè)協(xié)議焰望,而此時(shí)編譯器卻要知道該協(xié)議中定義的方法骚亿。
例如已亥,要從圖形類中繼承一個(gè)矩形類,且令其遵循繪制協(xié)議:
// EOCRectangle.h
#import "EOCShape.h"
#import "EOCDrawable.h"
@interface EOCRectangle : EOCShape<EOCDrawable>
@property (nonatomic, assign) CGFloat width;
@property (nonatomic, assign) CGFloat height;
@end
第二條#import是難免的来屠。鑒于此虑椎,最好把協(xié)議單獨(dú)放在一個(gè)頭文件中。要是把EOCDrawable協(xié)議放在了某個(gè)大的頭文件里俱笛,那么只要引入此協(xié)議捆姜,就必定會(huì)引入那個(gè)頭文件中的全部?jī)?nèi)容,如此一來(lái)迎膜,就像上面說(shuō)的那樣泥技,會(huì)產(chǎn)生相互依賴問(wèn)題,而且還會(huì)增加編譯時(shí)間磕仅。
對(duì)于沒(méi)有必要協(xié)議暴露出來(lái)的情況珊豹,可以將協(xié)議放在“class-continuation分類”中。這樣的話榕订,只要在實(shí)現(xiàn)文件中引入包含委托協(xié)議的頭文件即可店茶,而不需要將其放在公共頭文件里。如下代碼:
// EOCRectangle.m
#import "EOCDrawable.h"
@interface EOCRectangle ()<EOCDrawable>
@end
此時(shí)劫恒,協(xié)議EOCDrawable就沒(méi)有暴露在頭文件中贩幻,可以避免相互依賴和增加編譯時(shí)間的問(wèn)題
多用字面量語(yǔ)法,少用與之等價(jià)的方法
要點(diǎn):
- 應(yīng)該使用字面量語(yǔ)法來(lái)創(chuàng)建字符串两嘴、數(shù)值丛楚、數(shù)組、字典憔辫。與創(chuàng)建此類對(duì)象的常規(guī)方法相比趣些,那么做更加簡(jiǎn)明扼要。
- 應(yīng)該通過(guò)取下標(biāo)操作來(lái)訪問(wèn)數(shù)組下標(biāo)或字典中的鍵所對(duì)應(yīng)的元素螺垢。
- 用字面量語(yǔ)法創(chuàng)建數(shù)組或字典時(shí)喧务,若值中有nil赖歌,則會(huì)拋出異常。因此功茴。務(wù)必確保值不含nil庐冯。
字面量語(yǔ)法
//字符串
NSString *someString = @"Effective Obejctive-C 2.0";
//數(shù)值
NSNumber *intNumber = @1;
NSNumber *floatNumber = @1.5f;
NSNumber *doubleFloatNumber = @1.5f;
NSNumber *boolNumber = @YES;
NSNumber *charNumber = @'a';
//數(shù)組
NSArray *animals = @[@"cat", @"dog", @"mouse", @"badger"];
//字典
NSDictionary *personData = @{@"firstNmae":@"Matt", @"lastName":@"Galloway", @"age":@28};
字面量語(yǔ)法取值
//數(shù)值
NSArray *animals = @[@"cat", @"dog", @"mouse", @"badger"];
NSString *dog = animals[1];
//字典
NSDictionary *personData = @{@"firstNmae":@"Matt", @"lastName":@"Galloway", @"age":@28};
NSString *lastName = personData[@"lastName"];```
#####注意點(diǎn)
數(shù)組和字典使用字面量語(yǔ)法來(lái)初始化時(shí),如果其他包含nil元素坎穿,會(huì)導(dǎo)致crash
#####局限性
使用字面量語(yǔ)法創(chuàng)建的對(duì)象為不可變的(immutable)展父,如果需要獲得可變對(duì)象,需要復(fù)制一份玲昧,如下代碼:
NSMutableArray *mutable = @[@1, @2, @3, @4, @5].mutableCopy;
####多用類型變量栖茉,少用#define預(yù)處理命令
#####要點(diǎn)
* 不要用預(yù)處理指令定義常量。這樣定義出來(lái)的常量不含類型信息孵延,編譯器只會(huì)在編譯前據(jù)此執(zhí)行查找與替換操作吕漂。即使有人重新定義了常量值,編譯器也不會(huì)產(chǎn)生警告信息尘应,這將導(dǎo)致應(yīng)用程序中的常量不一致惶凝。
* 在實(shí)現(xiàn)文件中使用static const來(lái)定義“只在編譯編譯單元內(nèi)可見(jiàn)的常量”(translation-unit-specific constant)。由于此類常量不在全局符號(hào)表中犬钢,所以無(wú)需為其名稱加前綴苍鲜。
* 在頭文件中使用extern來(lái)聲明全局常量,并在相關(guān)實(shí)現(xiàn)文件中定義其值玷犹。這種常量要出現(xiàn)在全局符號(hào)表中混滔,所以其名稱應(yīng)加以區(qū)隔,通常用與之相關(guān)的類名做前綴歹颓。
比如坯屿,定義一個(gè)動(dòng)畫時(shí)間常量
//不應(yīng)該使用
define ANIMATION_DURATION 0.3
//應(yīng)該使用
static const NSTimeInterval kAnimationDuration = 0.3;```
還要注意常量名稱。常用的命名是:若變量局限于某“編譯單元”(translation unit晴股,也就是“實(shí)現(xiàn)文件”愿伴,implement file)之內(nèi),則在前面加字母k电湘;若常量在類之外可見(jiàn)隔节,則通常以類名為前綴。
若局限于 實(shí)現(xiàn)文件內(nèi)寂呛,則可以用以上代碼怎诫,若作為全局變量,為了防止類名沖突贷痪,命名應(yīng)該添加類名前綴幻妓,如下所示:
//EOCAnimatedView.h
extern const NSTimeInterval EOCAnimatedViewAnimationDuration;
//EOCAnimatedView.m
const NSTimeInterval EOCAnimatedViewAnimationDuration = 0.3;```
此時(shí)作為全局變量,不應(yīng)該添加static來(lái)修飾劫拢。而且需要在頭文件中聲明該變量肉津,添加extern前綴强胰,這個(gè)可以參照C語(yǔ)言的語(yǔ)法。
這樣定義常量對(duì)于使用#define預(yù)處理命令來(lái)說(shuō)妹沙,可以確保常量值不變偶洋。
####用枚舉表示狀態(tài)、選線距糖、狀態(tài)碼
#####要點(diǎn)
* 應(yīng)該用枚舉來(lái)表示狀態(tài)機(jī)的狀態(tài)玄窝、傳遞給方法的選項(xiàng)以及狀態(tài)碼等值,給這些值起個(gè)易懂的名字悍引。
* 如果傳遞給某個(gè)方法的選項(xiàng)表示為枚舉類型恩脂,而多個(gè)選項(xiàng)又可以同時(shí)使用,那么就將各選項(xiàng)值定義為2的冪趣斤,以便通過(guò)按位或操作將其組合起來(lái)俩块。
* 用NS_ENUM于NS_OPTIONS宏來(lái)定義枚舉類型,并指明底層數(shù)據(jù)類型唬渗。這樣做可以確保枚舉是用開(kāi)發(fā)者所選的底層數(shù)據(jù)類型實(shí)現(xiàn)出來(lái)的典阵,而不會(huì)采用編譯器所選的類型。
* 在處理枚舉類型的switch語(yǔ)句中不要實(shí)現(xiàn)default分支镊逝。這樣的話,加入新枚舉之后嫉鲸,編譯器就會(huì)提示開(kāi)發(fā)者:switch語(yǔ)句并未處理所有枚舉撑蒜。
#####樣例
//枚舉
typedef NS_ENUM(NSUInteger, EOCConnectionState) {
EOCConnectionStateDisconnected,
EOCConnectionStateConnecting,
EOCConnectionStateConnected,
};
//枚舉使用switch
EOCConnectionState state = EOCConnectionStateDisconnected;
switch (state) {
case EOCConnectionStateDisconnected:
//Disconnected
break;
case EOCConnectionStateConnected:
//Connected
break;
case EOCConnectionStateConnecting:
//Connecting
break;
}
//選項(xiàng),可以用來(lái)組合的枚舉
typedef NS_OPTIONS(NSUInteger, EOCPermittedDirection) {
EOCPermittedDirectionUp = 1 << 0,
EOCPermittedDirectionDown = 1 << 1,
EOCPermittedDirectionLeft = 1 << 2,
EOCPermittedDirectionRight = 1 << 2,
};
//選項(xiàng)使用方法
EOCPermittedDirection direction = EOCPermittedDirectionUp | EOCPermittedDirectionDown;
if (direction & EOCPermittedDirectionUp) {
//Direction is up
}
if (direction & EOCPermittedDirectionDown) {
//Direction is down
}