本章著重介紹了用于開發(fā)類的關(guān)鍵元素和獨(dú)有特性,其中包括 Objective-C 類的結(jié)構(gòu)、類的設(shè)計(jì)與實(shí)現(xiàn),以及其它一些支持類開發(fā)和OOP的語言特性。
<h3 id="classdevelopment">類的定義</h3>
在 Objective-C 語言中,類由接口和實(shí)現(xiàn)代碼組成,一般分別放在2個(gè)文件中:類接口 .h 文件,類實(shí)現(xiàn) .m文件。類接口聲明了類的屬性和方法;類實(shí)現(xiàn)定義了類的實(shí)例變量、屬性和方法。
表3.1列出了常用的擴(kuò)展名。
| 擴(kuò)展名 | 含義 | 擴(kuò)展名 | 含義 |
|---|---|---|---|
| .c | C 語言源文件 | .mm | Objective-C++ 源文件 |
| .cc、.cpp | C++ 語言源文件 | .pl | Per 源文件 |
| .h | 頭文件 | .o | Object(編譯后)文件 |
| .m | Objective-C 源文件 | —— | —— |
類接口:類的聲明使用關(guān)鍵字@interface和@end來聲明。
// 代碼清單-3.1
//
@interface ClassName : SuperClassName
//
// 屬性和方法的聲明
//
@end
類實(shí)現(xiàn):類的實(shí)現(xiàn)使用關(guān)鍵字@implementation和@end來實(shí)現(xiàn)。
// 代碼清單-3.2
//
@ implementation ClassName
//
// 實(shí)例變量(最好在類的實(shí)現(xiàn)部分聲明實(shí)例變量)
// 在類接口中聲明的所有方法都必須在類的實(shí)現(xiàn)文件中定義
//
@end
<h3 id="classvariable">實(shí)例變量</h3>
實(shí)例變量是指為類聲明的變量,它們在相應(yīng)的類實(shí)例(即對象)的生命周期中存在并擁有值。實(shí)例變量擁有與對象對應(yīng)的作用范圍和命名空間。當(dāng)對象被創(chuàng)建時(shí),系統(tǒng)會為實(shí)例變量分配內(nèi)存,當(dāng)對象被釋放時(shí)系統(tǒng)也會釋放變量占用的內(nèi)存。
實(shí)例變量可以在類的接口或?qū)崿F(xiàn)部分中聲明,不過,在類的接口中聲明實(shí)例變量會違反OOP的關(guān)鍵宗旨之一(封裝),因此,最好在類的實(shí)現(xiàn)部分中聲明實(shí)例變量,而當(dāng)語句塊緊跟類的 @implementation 指令時(shí),尤其如此。
// 代碼清單-3.3
//
@ implementation ClassName
{
// 聲明實(shí)例變量的代碼(最好在類的實(shí)現(xiàn)部分聲明實(shí)例變量)
@private NSString *description;
@protected float temperature;
@public int counter;
...
}
...
@end
Objective-C 定義了多條編譯指令,使用這些指令可以控制實(shí)例變量的范圍,即在程序中控制變量的可見性。
- @private: 實(shí)例變量只能在聲明它的類和該類的其他實(shí)例中被訪問。
- @protected: 實(shí)例變量可以在聲明它的類和該類子類的其他實(shí)例方法中被訪問。如果沒有為實(shí)例變量指定保護(hù)級別,這是默認(rèn)的變量范圍。
- @public: 可以在任何位置訪問實(shí)例變量。
- @package: 可在通過任何類實(shí)例和函數(shù)訪問實(shí)例變量,但在包之外,實(shí)例變量會被當(dāng)成私有變量。該范圍可用于庫和框架中的類。
這些指令稱為訪問修飾符,用于修飾在實(shí)例變量聲明語句塊中聲明的實(shí)例變量。
<h3 id="classproperty">屬性</h3>
Objective-C 中屬性與實(shí)例變量的區(qū)別是:實(shí)例變量存儲了對象的內(nèi)部狀態(tài),可以直接獲取對象內(nèi)部狀態(tài)。而屬性無法直接訪問對象的內(nèi)部狀態(tài),但提供了訪問這類數(shù)據(jù)的方便機(jī)制(即讀取和設(shè)置方法),因而可以含有其它邏輯。
大多數(shù)屬性都是由實(shí)例變量支持的,屬性通過這種機(jī)制隱藏對象的內(nèi)部狀態(tài)。除非專門設(shè)置,否則實(shí)例變量就擁有與屬性相同的名稱(但會帶下劃線前綴)。
屬性的聲明:使用關(guān)鍵字 @property 后跟一組可選的特性(用圓括號括起來)、屬性的類型和名稱。
// 代碼清單-3.4
//
@property(特性)屬性的類型 屬性的名稱;
// 屬性的特性
//
// 原子性特性 nonatomic 使用該特性可以在多線程并發(fā)的情況中,將訪問器設(shè)置為非原
// 子性的,因而能夠提供不同的結(jié)果。如果不設(shè)置該特性,訪問
// 器就會擁有原子性,換言之,賦值值和返回結(jié)果永遠(yuǎn)都會完全同步
// 設(shè)置器語義 assign 通過該特性可以在不使用copy和retain特性的情況下,使屬
// 性的設(shè)置器方法執(zhí)行簡單的賦值操作。這個(gè)特性是默認(rèn)設(shè)置。
// 設(shè)置器語義 retain 在賦值時(shí),輸入值會被發(fā)送一條消息,而上一個(gè)值會被發(fā)送一條
// 釋放信息
// 設(shè)置器語義 copy 在賦值時(shí),輸入值會被發(fā)送一條消息的副本,而上一個(gè)值會被
// 發(fā)送一條釋放信息
// 設(shè)置器語義 strong 當(dāng)屬性使用ARC內(nèi)存管理功能時(shí),該特性等同于retain特性
// 設(shè)置器語義 weak 當(dāng)屬性使用ARC內(nèi)存管理功能時(shí),該特性的作用與assign特性類似,
// 但如果引用對象被釋放了,屬性的值會被設(shè)置為nil
// 可讀寫特性 readwrite 使用該特性時(shí),屬性可以被讀取也可以被寫入,而且必須實(shí)
// 現(xiàn)getter和setter方法。這個(gè)特性是默認(rèn)設(shè)置
// 可讀寫特性 readonly 使用該特性時(shí),會將屬性設(shè)置為只讀。必須實(shí)現(xiàn)getter方法
// 方法名稱性 getter=getterName 將getter方法重命名為新設(shè)置器的名稱
// 方法名稱性 setter=setterName 將setter方法重命名為新設(shè)置器的名稱
其中特性有如下幾種取值,各個(gè)特性的含義涉及到 Objective-C 中內(nèi)存管理的相關(guān)知識,后面會有詳細(xì)的講解,所以這里只是簡單的介紹。只有對 Objective-C 的內(nèi)存管理有了比較全面的了解之后,才能很好的理解這里各個(gè)特生的含義。
? 讀寫屬性:(readwrite/readonly)決定是否生成set訪問器
? setter語意:(assign/retain/copy)set訪問器的語義,決定以何種方式對數(shù)據(jù)成員賦予新值
? 原子性:(atomic/nonatomic)
屬性特性詳細(xì)講解:
readwrite:生成 setter\getter 方法(默認(rèn))
使用該特性時(shí),屬性可以被讀取也可以被寫入,而且必須實(shí)現(xiàn)getter和setter方法。這個(gè)特性是默認(rèn)設(shè)置。
readonly:只生成 getter 方法.
此標(biāo)記說明屬性是只讀的,如果你指定了 readonly,在 @implementation 中只需要一個(gè) getter。或者如果你使用@synthesize關(guān)鍵字,也只會生成getter方法。如果你試圖使用點(diǎn)操作符為屬性賦值,你將得到一個(gè)編譯錯(cuò)誤。readonly關(guān)鍵字代表 setter 不會被生成, 所以它不可以和 copy/retain/assign 組合使用。
assign:簡單賦值,不更改索引計(jì)數(shù)
此標(biāo)記說明設(shè)置器直接進(jìn)行賦值,這也是默認(rèn)值。在使用垃圾收集的應(yīng)用程序中,如果你要一個(gè)屬性使用 assign,且這個(gè)類符合 NSCopying 協(xié)議,你就要明確指出這個(gè)標(biāo)記,而不是簡單地使用默認(rèn)值,否則的話,你將得到一個(gè)編譯警告。這再次向編譯器說明你確實(shí)需要賦值,即使它是可拷貝的。
retain:釋放舊的對象,將舊對象的值賦予輸入對象,再增加輸入對象的索引計(jì)數(shù)為1
指定 retain 會在賦值時(shí)喚醒傳入值的retain 消息。此屬性只能用于Objective-C 對象類型,而不能用于Core Foundation 對象。(原因很明顯,retain 會增加對象的引用計(jì)數(shù),而基本數(shù)據(jù)類型或者 Core Foundation 對象都沒有引用計(jì)數(shù))。
copy:建立一個(gè)索引計(jì)數(shù)為1的對象,然后釋放舊對象
它指出,在賦值時(shí)使用傳入值的一份拷貝??截惞ぷ饔?copy 方法執(zhí)行,此屬性只對那些實(shí)行了 NSCopying 協(xié)議的對象類型有效。更深入的討論,請參考”復(fù)制”部分。
atomic/nonatomic:原子操作
指出訪問器不是原子操作,atomic 表示屬性是原子的,支持多線程并發(fā)訪問,而默認(rèn)地 nonatomic,訪問器是原子操作。這也就是說,在多線程環(huán)境下,解析的訪問器提供一個(gè)對屬性的安全訪問,從獲取器得到的返回值或者通過設(shè)置器設(shè)置的值可以一次完成,即便是別的線程也正在對其進(jìn)行訪問。如果你不指定 nonatomic,在自己管理內(nèi)存的環(huán)境中,解析的訪問器保留并自動(dòng)釋放返回的值,如果指定了 nonatomic,那么訪問器只是簡單地返回這個(gè)值。沒有特別的多線程要求建議用 nonatomic 有助于提高性能。
在 iOS5 引入了自動(dòng)引用計(jì)算 ARC(Automatic Reference Counting)之后,對象變量屬性新增了 strong 和 weak,strong 與 retain 作用類似,可以說是用來代替retain;weak 與 assign 特性類似.
<h3 id="classgetproperty">訪問屬性</h3>
屬性的定義:大多數(shù)屬性是由實(shí)例變量支持的,因此屬性的定義中會含有屬性的 getter 和 setter 方法的定義、實(shí)例變量的聲明,并在 getter/setter 方法中使用這些變量。Objective-C 提供了多種定義屬性的方式:顯示定義、通過關(guān)鍵字(@synthesize)補(bǔ)全、自動(dòng)補(bǔ)全和動(dòng)態(tài)生成(@danamic)。
Objective-C 提供了兩種訪問屬性的機(jī)制:訪問器方法和點(diǎn)語法。
訪問器方法:用于讀取值的方法(getter方法)擁有與屬性相同的名字;用于設(shè)置值的方法(setter方法)其名稱以set開頭、后跟首字母大寫的屬性名。
// 代碼清單-3.5
//
// 假設(shè)屬性名稱為 color
[myObject color];
[myObject setColor:輸入值];
點(diǎn)語法:類似于Java語言中的方法.
// 代碼清單-3.6
//
// 假設(shè)屬性名稱為 color
myObject.color;
myObject.color = 輸入值;
Objective-C 語法關(guān)于點(diǎn)表達(dá)式的說明:如果點(diǎn)表達(dá)式出現(xiàn)在等號 = 左邊,該屬性名稱的 setter 方法將被調(diào)用。如果點(diǎn)表達(dá)式出現(xiàn)在 = 右邊,那么該屬性名稱的 getter 方法將被調(diào)用。所以在 Objective-C 中點(diǎn)表達(dá)式其實(shí)就是調(diào)用對象的 setter/getter 方法的一種快捷方式。
<h3 id="classvariableproperty">實(shí)例變量與屬性的關(guān)系</h3>
在老版本的 Objective-C 語言中,我們需要同時(shí)聲明屬性和實(shí)例變量,那時(shí)屬性是 Objective-C 語言的一個(gè)新的機(jī)制,并且要求你必須聲明與之對應(yīng)的實(shí)例變量,例如:
// 代碼清單-3.7
//
@interface MyNoteBook : NSObject
{
@protected NSString * _content;
}
@property (nonatomic, retain) NSString * content;
...
@end
后來,蘋果將默認(rèn)編譯器從GCC轉(zhuǎn)換為Clang/LLVM(low level virtual machine),從此不再需要手動(dòng)為屬性聲明實(shí)例變量了,它支持對已聲明屬性進(jìn)行自動(dòng)補(bǔ)全。如果編譯器發(fā)現(xiàn)一個(gè)沒有實(shí)例變量支持的屬性,它將自動(dòng)創(chuàng)建一個(gè)作用范圍為 @priavte 并且與屬性名稱相同的支持實(shí)例變量(但會帶下劃線前綴)。編譯器可以自動(dòng)補(bǔ)全以下聲明的屬性:1、沒有使用關(guān)鍵字(如@synthesize)進(jìn)行補(bǔ)全的屬性;2、不是(通過@dynamic屬性指令)動(dòng)態(tài)生成的屬性。3、沒有用戶編寫的 getter 和 setter 方法的屬性。因此,使用該特性就無需手動(dòng)補(bǔ)全已聲明的屬性。編譯器會自行補(bǔ)全已聲明的屬性和相應(yīng)的實(shí)例變量。
例如 MyNoteBook.h 文件:
// 代碼清單-3.8
//
@interface MyNoteBook : NSObject
@property (nonatomic, retain) NSString * content;
...
@end
在 MyNoteBook.m 文件中,編譯器也會自動(dòng)的生成一個(gè)實(shí)例變量 _content ,那么在 .m 文件中可以直接的使用 _content 實(shí)例變量,也可以通過屬性 self.content 來訪問和設(shè)置。
注意:這里的 self.content 其實(shí)是調(diào)用屬性 content 的 getter/setter 方法。這與 C++ 中點(diǎn)的使用是有區(qū)別的,C++中的點(diǎn)可以直接訪問成員變量(也就是實(shí)例變量)。
例如 MyNoteBook.h 文件:
// 代碼清單-3.9
//
@interface MyNoteBook : NSObject
{
__strong NSString * content;
}
...
@end
在 MyNoteBook.m 文件中,self.content 這樣的表達(dá)式是錯(cuò)誤的。Xcode會提示你使用 ->,改成 self->content 就可以了。因?yàn)?Objective-C 中點(diǎn)表達(dá)式是表示調(diào)用方法,而上面的代碼中沒有 content 這個(gè)方法。
以前的用法,聲明屬性跟與之對應(yīng)的實(shí)例變量(代碼清單-3.7),這種方法基本上使用最多,現(xiàn)在大部分也是在使用,因?yàn)楹芏嚅_源的代碼都是這種方式。但是在 iOS5 更新之后,蘋果是建議用以下的方式來使用:
// 代碼清單-3.10
//
@interface MyNoteBook : NSObject
@property (nonatomic, retain) NSString * title;
@property (nonatomic, retain) NSString * author;
@property (nonatomic, retain) NSString * content;
...
@end
因?yàn)榫幾g器會自動(dòng)為你生成與屬性名稱相同(但帶下劃線前綴)的實(shí)例變量;也會自動(dòng)為你生成 setter/getter 方法。
如果你想自己設(shè)定與屬性相關(guān)聯(lián)的變量的名稱,則可以通過關(guān)鍵字 @synthesize 來實(shí)現(xiàn)。
// 代碼清單-3.11
//
// 語法:@synthesize 屬性名稱 [=實(shí)例變量名稱];
// 例如:
@synthesize myIntProperty; // 省略[=實(shí)例變量名稱]
@synthesize myIntProperty = myPropertyOne;
如果省略了可選項(xiàng)[=實(shí)例變量名稱],編譯器會根據(jù)屬性實(shí)例變量標(biāo)準(zhǔn)命名慣例,自動(dòng)生成實(shí)例變量的名稱。如果你設(shè)置了可選項(xiàng)[=實(shí)例變量名稱],編譯器就會使用該名稱創(chuàng)建實(shí)例變量。
<h3 id="classmethod">方法</h3>
方法的聲明由方法類型、返回值類型和一個(gè)或多個(gè)(提供名稱、參數(shù)和參數(shù)類型信息的)方法代碼段構(gòu)成。
// 代碼清單-3.12
//
// 語法:方法類型 (返回類型) 方法代碼段名稱 : (參數(shù)類型) 參數(shù)名稱 ...
// 例如:
- (id) initWithTitle: (NSString *) title;
- (id) initWithTitle: (NSString *) title andContent: (NSString *) content;
+ (void) readMyNotBook: (NSString *) title;
調(diào)用方法:對象(發(fā)送器)通過發(fā)送信息與其它對象(接收器)進(jìn)行交互,從而調(diào)用指定的方法。
// 代碼清單-3.13
//
// 語法:[接收器 方法代碼段名稱:參數(shù)值 ...]
// 說明:如果擁有多個(gè)代碼段,可將它們的名稱和參數(shù)值以空格為分隔符連續(xù)排列。
// 例如:
[myNoteBook initWithTitle:@"Title"];
[myNoteBook initWithTitle:@"Title" andContent:@"BookContent"];
<h3 id="classprotocol">協(xié)議</h3>
使用協(xié)議聲明的方法和屬性可以由任何類實(shí)現(xiàn)。協(xié)議不與特定的類關(guān)聯(lián),因此,使用它可以捕捉無繼承關(guān)系的類之間的相似之處。協(xié)議使 Objective-C 支持多重繼承規(guī)范的概念(方法聲明)。
// 代碼清單-3.14
//
// 語法:
@protocol 協(xié)議名稱
// 屬性聲明
@required
// 方法的聲明(必選方法)
@optional
// 方法的聲明(可選方法)
@end
通過在尖括號中設(shè)置已經(jīng)聲明的協(xié)議的名稱,可以使一個(gè)協(xié)議與其它協(xié)議合并——這稱為接受協(xié)議??墒褂枚禾柗指舳鄠€(gè)協(xié)議。如代碼清單-3.15所示
// 代碼清單-3.15 合并其它協(xié)議
//
// 語法:
// 合并單個(gè)協(xié)議
@protocol 協(xié)議名稱 <協(xié)議名稱>
// 方法的聲明
@end
// 合并多個(gè)協(xié)議
@protocol 協(xié)議名稱 <協(xié)議名稱1, 協(xié)議名稱2, 協(xié)議名稱3, ... >
// 方法的聲明
@end
使用類似的語法可以令接口接受其它協(xié)議,如代碼清單-3.16所示
// 代碼清單-3.16 接口接受其它協(xié)議
//
// 語法:
// 接口接受單個(gè)協(xié)議
@interface 類的名稱 :父類的名稱 <協(xié)議名稱>
// 方法的聲明
@end
// 接口接受多個(gè)協(xié)議
@interface 類的名稱 :父類的名稱 <協(xié)議名稱1, 協(xié)議名稱2, 協(xié)議名稱3, ... >
// 方法的聲明
@end
協(xié)議不引用任何類,它是類無關(guān)的,任何類都可以實(shí)現(xiàn)定義好的 Protocol。如果我們想知道某個(gè)類是否實(shí)現(xiàn)了某個(gè) Protocol,可以使用 conformsToProtocol 進(jìn)行判斷,如下:
if(YES == [obj conformsToProtocol:@protocol(ProtocolName)]) {
// 在這里插入代碼
}
這里使用 @protocol 指令用于獲取一個(gè)協(xié)議名稱,并產(chǎn)生一個(gè) Protocol 對象,并作為conformsToProtocol: 的參數(shù)。如果為了測試 obj 是否實(shí)現(xiàn)了協(xié)議中的某一個(gè)方法,可以編寫以下代碼:
if ([obj respondsToSelector:@selector(methodName)]) {
[obj methodName];
}
<h3 id="classcategory">分類</h3>
使用分類可以在不進(jìn)行子類化的情況下,為已經(jīng)存在類增加功能。分類通常用于:1、擴(kuò)展其他人定義的類(即使你無法訪問它們的源代碼);2、替代子類;3、將新類的實(shí)現(xiàn)代碼分發(fā)給多個(gè)源文件(通過多人分工,簡化大型類的開發(fā)工作)。
分類接口的聲明以關(guān)鍵字 @interface 開頭,后跟已存在的類的名稱、帶括號的分類名稱以及它所接受的協(xié)議(如果有的話),以關(guān)鍵字 @end 結(jié)束。方法的聲明放在這些語句之間。
// 代碼清單-3.17 分類聲明的語法
//
// 語法:
@interface 類的名稱 (分類的名稱) <協(xié)議名稱1, 協(xié)議名稱2, 協(xié)議名稱3, ... >
// 方法的聲明
@end
擴(kuò)展視為一種匿名(即未命名的)分類。在擴(kuò)展中聲明的方法必須在相應(yīng)類的主 @implementation 塊中實(shí)現(xiàn)(它們無法在分類中實(shí)現(xiàn))。
// 代碼清單-3.18
//
// 語法:
@interface 類的名稱 () <協(xié)議名稱>
{
// 實(shí)例變量的聲明
}
// 屬性的聲明
// 方法的聲明
@end
擴(kuò)展與分類的區(qū)別是它能聲明實(shí)例變量和屬性。編譯器會檢查在擴(kuò)展中聲明的方法(和屬性)是否被實(shí)現(xiàn)。類擴(kuò)展通常應(yīng)存儲在類實(shí)現(xiàn)文件中,并用于組織和聲明在類中獨(dú)立使用的其它私有方法(例如,不是公用API的一部分)。
<h3 id="classsummary">小結(jié)</h3>
本章主要介紹了 Objective-C 程序開發(fā)中使用的類。
- Objective-C 的類由接口和實(shí)現(xiàn)代碼構(gòu)成。接口聲明了類的屬性和方法;實(shí)現(xiàn)定義了類的實(shí)例變量、屬性和方法。按照慣例,應(yīng)將類的接口代碼存儲在 .h 文件中,類的實(shí)現(xiàn)代碼存儲在 .m 文件中。
- 在協(xié)議中聲明的方法和屬性可以由任何類實(shí)現(xiàn)。使用協(xié)議通常可以在無繼承關(guān)系的類之間實(shí)現(xiàn)通用行為。
- 使用分類可以在不進(jìn)行了類化的情況下,為已經(jīng)存在的類增加功能。分類通常用于:1、擴(kuò)展其他人定義的類(即使你無法訪問它們的源代碼);2、替代子類;3、將新類的實(shí)現(xiàn)代碼分發(fā)給多個(gè)源文件。擴(kuò)展可以被視為一種匿名分類,不過它聲明的方法必須在類的主實(shí)現(xiàn)塊中實(shí)現(xiàn)。擴(kuò)展還可以聲明實(shí)例變量和屬性。