定義類
當你為 OS X 或 iOS 編寫應(yīng)用時雄卷,大部分時間都將與對象打交道菲饼。Objective-C 中的對象與其他面向?qū)ο笳Z言中的對象一樣:他們將數(shù)據(jù)與相關(guān)的行為打包惨好。
一個應(yīng)用其實是一個很大的由很多內(nèi)在相連的對象組成的生態(tài)系統(tǒng)品擎,這些對象通過相互之間的交流來解決特定的問題蛮原,比如展示一個虛擬的交互界面仰剿,響應(yīng)用戶輸入创淡,或存儲信息。在 OS X 或 iOS 開發(fā)過程中南吮,你并不需要從頭創(chuàng)建對象來解決可能出現(xiàn)的問題琳彩。Cocoa (for OS X) 和 Cocoa Touch (for iOS) 已經(jīng)提供了一個巨大的對象庫供你使用。
在這個庫中部凑,有一些對象立即就可以使用露乏,比如基本數(shù)據(jù)類型如 strings 和 numbers,或是用戶界面元素比如 buttons 或table views涂邀。有一些則還需要你完成一些自定義的代碼來滿足特定的需求瘟仿。應(yīng)用開發(fā)的過程包括了決定如何才能最好的自定義以及如何將底層框架提供的對象與你自己的對象結(jié)合來給你的應(yīng)用賦予獨特的特性和功能。
在面向?qū)ο缶幊讨斜让悖粋€對象是類的實例劳较。這一章將說明如何通過聲明一個接口類來定義 Objective-C類,這個接口將描述你將要使用的類及其實例浩聋。這個接口包括這個類能接收的消息的列表观蜗,所以你還需要提供類的實習(xí),以包含每條消息對應(yīng)的處理代碼赡勘。
類是對象的藍圖
一個類描述了特定對象的普遍屬性和行為嫂便。對于一個字符串對象(在 Objective-C 中,字符串對象是NSString
類的實例)闸与,它的類提供了各種各樣的方法來檢查和轉(zhuǎn)換組成它的字符毙替。同樣的,用來描述數(shù)字對象的類(NSNumber)提供圍繞其內(nèi)部數(shù)值的功能践樱,比如將數(shù)值轉(zhuǎn)換為另一種數(shù)值類型厂画。
與建造建筑同理,同一個藍圖建造出來的建筑結(jié)構(gòu)完全一致拷邢,一個類的所有實例都擁有同樣的屬性和行為袱院。除了內(nèi)部存的字符串不同,每一個NSString
實例的行為都是一致的瞭稼。
任何特定的對象都是為了特定的使用而設(shè)計的忽洛。你知道一個字符串對象表示了一串字符,但你不需要知道存儲這些字符所用的內(nèi)在機制环肘。你不知道字符串對象為了與字符交互使用了什么內(nèi)在行為欲虚,但你需要知道你應(yīng)該怎樣與對象進行交互,比如提取特定的字符悔雹,或獲取字符串對應(yīng)的大寫字符串复哆。
在 Objective-C中,類接口明確說明了一個給定的對象應(yīng)該如何被其他對象使用腌零。換句話說梯找,它定義了類實例與外界的公共接口。
可變性決定了一個代表值能否被改變
有些類將對象定義為不可變的益涧。這意味著這個對象的內(nèi)容必須在對象被創(chuàng)建時設(shè)置好锈锤,并且之后不能被其他對象所改變。在 Objective-C中闲询,所有基本的NSString
和NSNumber
對象都是不可改變的久免。如果你想表示一個不同的數(shù)值,你需要新建一個NSNumber
對象嘹裂。
有些不可變類提供一個可變版本妄壶。如果你明確需要在運行時改變字符串的內(nèi)容,比如將從網(wǎng)絡(luò)連接上獲得的字符添加到字符串末尾寄狼,你可以使用NSMutableString
類丁寄。這個類的實例和NSString
類行為一致,只不過它提供了改變對象所表示字符的功能泊愧。
盡管NSString
和NSMutableString
是不同的類伊磺,他們還是有很多的相似之處删咱。與其從頭開始編寫兩個具有相似行為的類,還不如利用繼承摘能。
繼承自其他類的類
在自然世界中续崖,分類學(xué)將動物按照如物種,屬严望,科進行分組逻恐。這些組是分層的像吻,許多的物種可能屬于同一屬,許多的屬又屬于同一族复隆。
比如,大猩猩惭每,人類洪鸭,星星,有很多相似之處览爵。盡管他們屬于不同的物種镇饮,甚至不同的屬,不同科俱济,不同的亞科钙勃,他們在分類學(xué)上還是相關(guān)的辖源,因為它們屬于同一個族(人科),如Figure 1-1 所示酝蜒。
Figure 1-1 物種間的分類學(xué)關(guān)系
在面向?qū)ο缶幊痰氖澜缋锿瞿裕瑢ο笠矔粴w類為分等級的組。與使用明確的屬于標示不同的分級比如科或種蛙紫,對象僅僅是被組織進了類惊来。與人類作為人科的一員可以繼承特定的屬性一樣棺滞,一個類也可以從他的父類那里繼承功能继准。
當一個類繼承了另一個類矮男,子類會繼承其父類定義的所有屬性和行為毡鉴。它也可以添加自己的屬性和行為,或是重寫父類的屬性和行為憎瘸。
對于 Objective-C 類來說陈瘦,NSMutableString
的類描述指明了它繼承自NSString
痊项,如Figure 1-2 所示。NSString
提供的所有功能NSMutableString
都有皱埠,比如取出特定的字符或獲取大寫字符串边器,但是NSMutableString
添加了用于添加游沿,插入诀黍,替換,或刪除子字符串或獨立字符的方法枣宫。
Figure 1-1 NSMutableString繼承關(guān)系
根類提供基礎(chǔ)的功能
與所有的生物體共享某些基本的生命特征一樣也颤,Objective-C 中的一些功能也是在所有對象中通用的翅娶。
當一個 Objective-C 對象需要和其他類的對象合作時,它需要其他類提供基本的特征和行為燥翅。處于這個原因蜕提,Objective-C 定義了一個根類NSObject
谎势,用于被大部分其他類集成。當一個對象與另一個對象遭遇猖毫,它起碼能通過被 NSObject
定義的方法與其他對象交互鄙麦。
定義自己的類時镊折,至少應(yīng)該繼承NSObject
恨胚。一般來說赃泡,你應(yīng)該找一個提供最接近你想要的功能的 Cocoa 或 Cocoa Touch 類,然后繼承它俄烁。
如果你想自定義一個iOS應(yīng)用的按鈕级野,并且已有的UIButton
類不能提供足夠的可定制的特性來滿足你的需求,通過繼承UIButton
來創(chuàng)建新的類比繼承NSObject
要合理的多风纠。如果你只是繼承自NSObject
你需要自己完全復(fù)制UIButton
定義的復(fù)雜的可視化操作和通信功能來使其按用戶預(yù)期的狀態(tài)工作竹观。不僅如此潜索,通過繼承UIButton
帮辟,你的子類將自動地獲得未來可能應(yīng)用到UIButton
內(nèi)部行為的增強和對bug的修復(fù)。
UIButton
類繼承自UIControl
,后者定義了對iOS中所有界面控制器通用的行為蔓榄。UIControl
類又繼承自UIView
默刚,給了它被展示在屏幕上的控件的通用行為荤西。UIView
繼承自UIResponder
邪锌,使它可以相映用戶的輸入比如點擊,手勢饵溅,或晃動蜕企。最后冠句,在繼承數(shù)的根部懦底,UIResponder
繼承自NSObject
,如圖Figure 1-3 所示奋构。
Figure 1-3 UIButton 類繼承鏈
這條繼承鏈條表明弥臼,任何 UIButton
的子類除了繼承了UIButton
的功能径缅,還繼承了之上的所有父類的功能。你會得到一個類氧卧,對應(yīng)一個表現(xiàn)為按鈕的對象沙绝,能將自己展示在屏幕上鼠锈,響應(yīng)用戶的輸入购笆,并且與其他基本的 Cocoa Touch 對象交流同欠。
對于任何要使用的類,要想知道它能做什么衫哥,弄清楚繼承鏈很重要炕檩。通過Cocoa 和 Cocoa Touch提供的文檔捌斧,可以很容易的定位到每個類的父類捞蚂。如果你不能在一個類的接口或參考中找到你想找的姓迅,很有可能它其實是在繼承鏈的父類中被很好的定義了俊马。
類接口定義預(yù)期的交互
之前提到的面向?qū)ο蟮木幊谭绞降暮锰幹痪褪遣裎遥阒恍枰廊绾闻c一個類的實例交互就可以知道如何使用一個類艘儒。更具體的說夫偶,一個對象應(yīng)該被設(shè)計為隱藏其內(nèi)部的實現(xiàn)細節(jié)兵拢。
比如说铃,當你在iOS應(yīng)用中使用標準的UIButton
,你不需要知道每一個像素是如何被操作的以使對象被顯示在屏幕上疾牲。你只需要知道你能改變某些屬性,比如按鈕的標題和顏色蚓峦,并相信它能在被加入到可視化界面時能按你預(yù)期的方式正確表現(xiàn)济锄。
定義自己的類時荐绝,你需要從弄清需要的共有屬性和行為開始低滩。什么屬性需要被公開訪問恕沫?這些屬性應(yīng)該允許被改變嗎?其他的對象如何與你的類的實例交流鲸阔。
這些信息將體現(xiàn)在你的類的接口上褐筛,它定義了你的類與其他類交互的方式。類公共的接口與內(nèi)部的行為是被分開表述的硫狞,后者組成了類的實現(xiàn)妓忍。在 Objective-C 中世剖,接口和實現(xiàn)一般被放在不同的文件中笤虫,這樣你只需要使接口公開琼蚯。
基本語法
Objective-C 中聲明一個類接口的語法是這樣的:
@interface SimpleClass : NSObject
@end
這個例子定義了一個名為SimpleClass
的類遭庶,繼承自NSObject
公開的屬性和行為被定義在@interface
聲明內(nèi)部峦睡。在這個例子中,除了繼承夫類沒有定義任何屬性和行為煎谍,所以該類可用的功能只有從NSObject
類繼承來的功能呐粘。
屬性控制對對象值的訪問
對象一般會有被設(shè)計來供公共訪問的屬性作岖。如果你為一個記錄保持app定義一個類表示人鳍咱,你可能會需要字符串屬性來表示一個人的姓和名与柑,像這樣:
@interface Persion: NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
在這個例子中,Person 類定義了兩個公有屬性涡戳,它們都是NSString的實例渔彰。
這兩個屬性都是 Objective-C對象推正,使用星號來表示它們是C指針植榕。它們的聲明語句和C對象的定義一樣尊残,所以尾部需要有一個分號寝衫。
也許你決定加入一個表示人出生年份的屬性,這樣就可以通過年份來為人們分組隘截,而不是只通過年齡技俐。你可以使用一個數(shù)字對象屬性:
@property NSNumber *yearOfBirth;
但是僅僅存一個簡單的數(shù)值,這樣就有點大材小用了贱勃。一個替代的方式就是使用C提供的原始類型贵扰,這種類型能夠保存數(shù)值流部,比如一個整數(shù):
@property int yearOfBirth;
屬性特性表明了數(shù)據(jù)訪問和存儲的注意事項
到目前為止的例子中枝冀,聲明的屬性都是保證了能被完整的公開訪問。這意味著其他的對象能夠讀寫這些屬性谷誓。
有的時候捍歪,你可能需要一個對象不被改變糙臼。在現(xiàn)實世界中恩商,一個人要想改變他的名字痕届,必須填一大堆文件研叫。編寫正式記錄保持應(yīng)用時嚷炉,你應(yīng)該讓公開的人名屬性被設(shè)定為只讀申屹,并讓一個中間對象負責所有變換請求的確認,以及請求的通過和拒絕哗讥。
Objective-C 屬性聲明可以包括屬性特性杆煞,用于指示一個屬性是否只是刻度的决乎。在一個正式記錄保持應(yīng)用鐘构诚,一個 Person 類的接口可能看起來是這樣的:
@interface Person: NSObject
@property (readonly) NSString *firstName;
@property (readonly) NSString *lastName;
@end
屬性特性在@property
關(guān)鍵字之后的括號中被指定范嘱,詳情見Declare Public Properties for Exposed Data。
方法聲明表明了對象可以接收的消息
目前為止展示的類描述了一個典型的model對象(model對象是一種典型的包含應(yīng)用數(shù)據(jù)逆趋,提供數(shù)據(jù)訪問闻书,并實現(xiàn)操縱數(shù)據(jù)邏輯的對象)魄眉,或者說是一個主要用于封裝數(shù)據(jù)的對象坑律。在Person類中晃择,除了用于訪問兩個被聲明的屬性也物,并不需要其他別的功能滑蚯。但是告材,大部分的類斥赋,除了屬性還將包括行為疤剑。
鑒于 Objective-C 軟件是構(gòu)建在對象的網(wǎng)絡(luò)之上的骚露,保證對象之間能通過發(fā)送消息來交互是很重要的棘幸。在 Objective-C 中倦零,一個對象通過調(diào)用另一個對象的方法來給那個對象發(fā)送消息误续。
Objective-C 方法在概念上和標準的C方法以及其他語言的方法很相似吨悍,盡管語法有點不一樣。一個C語言的方法聲明:
void SomeFunction();
等價的 Objective-C 方法聲明:
- (void) someMethod;
在這個例子中蹋嵌,方法沒有參數(shù)育瓜。來自C的void
關(guān)鍵字被放在聲明開頭的括號中表明方法結(jié)束之后不會返回任何值。
方法名墻面的減號(-)表明這是一個實例方法栽烂,只能通過類的實例來調(diào)用躏仇。這將它與類方法區(qū)分開來腺办,類方法可以通過類本身進行調(diào)用焰手,詳見:Objective-C Classes Are also Objects。
方法可以接收參數(shù)
接收一個或多個參數(shù)的方法的語法與標準的C方法非常不一樣怀喉。
C方法中书妻,參數(shù)被指定在括號中,如:
void SomeFunction(SomeType value);
一個 Objective-C 方法的聲明使用冒號將參數(shù)作為其名稱的一部分躬拢,比如:
- (void)someMethodWithValue:(SomeType)value;
如返回值類型躲履,參數(shù)類型也被指定在括號中,就像標準的C類型轉(zhuǎn)換聊闯。
如果參數(shù)有多個工猜,語法和C更不一樣。C方法中的多個參數(shù)全部被定義在括號內(nèi)馅袁,通過逗號隔開域慷;在 Objective-C 中,一個接收兩個參數(shù)的方法:
- (void) someMethodWithFirstValue: (SomeType) value1 secondValue:(AnotherType) value2;
在這個例子中汗销,value1
和value2
是當方法被調(diào)用是在方法實現(xiàn)中被用到的犹褒,用于獲取調(diào)用者提供的參數(shù)值,他們就像變量弛针。
一些編程語言允許功能定義參數(shù)名叠骑;需要注意的是,Objective-C 中并不允許這樣削茁,調(diào)用方法時宙枷,參數(shù)的順序必須符合方法的聲明,事實上茧跋,secondValue:
是方法名的一部分综苔。
someMethodWithFirstValue:secondValue:
這是使得 Objective-C 可讀性高的其中一個特性,因為通過方法調(diào)用傳遞的信息被指定在了一行之中轧拄,就在與方法名相關(guān)的部分旁邊汰具,詳見:You Can Pass Objects for Method Parameters。
注意:value1和value2這兩個參數(shù)名并不直接是方法聲明的一部分,這意味著實現(xiàn)方法時并不一定需要用和聲明完全一樣的方法名贤笆。你只需要保持聲明和實現(xiàn)的簽名一致蝇棉,也就是說你需要保持方法名以及參數(shù)和返回值的類型完全一致。
比如芥永,這個方法和之前的方法有著一樣的簽名
- (void)someMethodWithFirstValue:(SomeType)info1 secondValue:(AnotherType) info2;
這兩個方法有著不同的簽名
- (void)someMethodWithFirstValue:(SomeType)info1 anotherValue:(AnotherType) info2;
- (void)someMethodWithFirstValue:(SomeType)info1 secondValue:(YetAnotherType)info2;
類名必須是獨一無二的
在一個應(yīng)用程序中篡殷,每一個類的名字都必須是獨一無二的,即使是在包含的類庫和框架之間埋涧。如果你創(chuàng)建了一個與項目中已有類重名的類板辽,你會得到一個編譯錯誤。
因此飞袋,建議為你的類添加三個或三個以上字幕組成的前綴戳气。這些字母可以與你正在寫的應(yīng)用有有關(guān),或者與一個可重用的庫的名字有關(guān)巧鸭,或著直接是你名字的大寫首字母瓶您。
這份文檔接下來給出的例子都將使用命名前綴,比如:
@interface XYZPersion: NSObject
@property (readonly) NSString *firstName;
@property (readonly) NSString *lastName;
@end
歷史遺留問題:你可能好奇為什么使用到的很多類都有NS前綴纲仍,這與 Cocoa 和 Cocoa Touch的起源 有關(guān)呀袱。Cocoa 最初是作為為 NeXTStep 操作系統(tǒng)搭建應(yīng)用的庫被創(chuàng)造出來的。1996年郑叠,蘋果收購了NeXT夜赵,包括已經(jīng)存在的類名。Cocoa Touch 相當于 Cocoa 在 iOS 上的等價庫乡革;有一些類在 Cocoa 和 Cocoa Touch中都可用寇僧,不過也有很大一部分的類只適用于各自的平臺。
兩個字母的前綴比如NS和UI(iOS中的界面元素)已經(jīng)被蘋果保留沸版。
與類名不同的是嘁傀,方法和屬性名只需要在類內(nèi)保持獨一無二。盡管在C應(yīng)用中视粮,每一個方法的名字都應(yīng)該保持獨特细办,但在多個 Objective-C類中使用相同的方法名是完全可以的(通常是更合適的)。但你不能在一個類的聲明中超過一次定義同一個方法蕾殴,并且如果要重寫從父類繼承來的方法笑撞,必須使用與父類聲明完全一致的方法名。
和方法一樣钓觉,一個對象的屬性和實例變量(詳見Most Properties Are Backed by Instance Variables)需要在被定義的類中保證獨一無二茴肥。但是如果你使用全局變量,他們必須在應(yīng)用或項目中保持獨一無二荡灾。
更多的命名約定和建議見Conventions炉爆。
一個類的實現(xiàn)提供了它的內(nèi)部行為
一旦你定義了一個類的借口堕虹,包括為公開訪問而定義的屬性和方法,你就需要編寫類行為實現(xiàn)的代碼了芬首。
如之前所說,一個類的借口經(jīng)常被放置在一個專用的文件中逼裆,通常被稱為頭文件郁稍,通常帶有文件拓展名.h
。Objective-C 類的實現(xiàn)在編寫在一個帶有.m
拓展名的源代碼文件中胜宇。
無論何時耀怜,只要接口被定義在頭文件中,你就需要告訴編譯器在編譯實現(xiàn)中的源代碼之前閱讀頭文件中的接口桐愉。Objective-C 為此提供一個預(yù)處理指令财破,#import
。它和C的#include
執(zhí)行相似从诲,不過可以確保一個文件在編譯的過程中只被包含一次左痢。
注意編譯預(yù)處理指令和C的傳統(tǒng)語句不同,并不適用結(jié)尾分號系洛。
基本語法
類實現(xiàn)的基本語法是這樣:
#import "XXZPersion.h"
@implementation XYZPerson
@end
在接口中聲明的方法需要在這里提供實現(xiàn)
方法實現(xiàn)
對于一個只有一個方法的類俊性,比如:
@interface XYZPersion : NSObject
- (void)sayHello;
@end
實現(xiàn)可以是這樣:
#import "XYZPerson.h"
@implementation XYZPersion
- (void)sayHello {
NSLog(@"Hello, World!");
}
@end
這個例子使用NSLogz()
方法輸出一條消息到控制臺。它和C標準庫的方法printf()
類似描扯,并且接受一系列不同類型的參數(shù)定页,但第一個必須是 Objective-C 字符串。
方法實現(xiàn)和C方法定義相似绽诚,使用花括號包含相關(guān)的代碼典徊。此外恩够,方法名必須與聲明完全一致卒落,參數(shù)和返回值的類型也要一致导绷。
Objective-C 從 C 繼承了大小寫敏感,所以這個方法:
- (void) sayhello {
}
編譯器會將其看待為與之前sayHello
方法安全不同的方法。
一般來說,方法名應(yīng)該以小寫字母開頭滨嘱。Objective-C協(xié)定建議使用比典型的C方法名更具有描述性的方法名宪拥。如果一個方法名包括多個單詞校镐,使用駝峰命名法(每一個單詞的首字母大寫)提高可讀性贮预。
注意空白在 Objective-C 中的靈活性。習(xí)慣上,任何塊內(nèi)的代碼都應(yīng)該使用制表符或空格進行縮進挟伙,并且將左花括號放在獨立的一行楼雹,如:
- (void)sayHello
{
NSLog(@"Hello, World!");
}
Xcode,蘋果為編寫 OS X 和 iOS軟件提供的集成開發(fā)環(huán)境尖阔,會根據(jù)你的偏好自動的幫你縮緊代碼贮缅。改變縮緊和制表符寬度見Xcode Workspace Guide。
下一章將展示更多實現(xiàn)的例子介却, Working with Objects谴供。
Objective-C 類也是對象
在 Objective-C 中,類自身也是一個Class
類型的對象齿坷,這種類型是不透明的桂肌。類對象不能擁有用之前介紹的語法定義出來的實例屬性,但是可以接收消息永淌。
類方法的典型應(yīng)用即為工廠方法崎场,工廠方法是 Objects Are Created Dynamically 中描繪的對象內(nèi)存分配與初始化過程的一種替代方式。比如遂蛀,NSString
類有一系列工廠方法可以用來創(chuàng)建一個空字符串谭跨,或用特定字符初始化的字符串,包括:
+ (id)string;
+ (id)stringWithString:(NSString *)aString;
+ (id)stringWithString:(NSString *)format, ...;
+ (id)stringWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error;
+ (id)stringWithCString:(const char *)cString encoding:(NSStringEncoding)enc;
如上所示李滴,類方法被+
號標示螃宙,使他們區(qū)分于用-
號標示的實例方法。
類方法可能像實力方法原型一樣被包含在類接口中悬嗓。類方法實現(xiàn)的方式與實例方法一樣污呼,在類的@implentation
塊中提供實現(xiàn)。
練習(xí)
注意:為了能完成每章末尾給出的練習(xí)包竹,你需要創(chuàng)建一個Xcode項目燕酷。這樣可以保證你的代碼可以正確編譯籍凝。
使用 Xcode 的新項目模版窗口選擇一個可選的 OS X 應(yīng)用模版來創(chuàng)建一個命令行工具。當出現(xiàn)提示時苗缩,指明項目的類型是Foundation饵蒂。
使用 Xcode 的 New File 模版窗口創(chuàng)建繼承自
NSObject
類 的 Objective-C 類XYZPersion
的接口和實現(xiàn)文件。向
XYZPersion
類接口添加姓酱讶,名的屬性退盯,以及生日(用NSDate
表示)。聲明
sayhello
方法泻肯,并按本章所示實現(xiàn)它渊迁。加入一個類工廠方法“
person
”的聲明。在閱讀下一章之前灶挟,不用實現(xiàn)它琉朽。
注意:如果你編譯代碼,會因為沒有提供這個實現(xiàn)得到一個關(guān)于 “未完成的實現(xiàn)” 的警告稚铣。