本章著重介紹了用于開發(fā)類的關(guān)鍵元素和獨有特性踢匣,其中包括 Objective-C 類的結(jié)構(gòu)咖为、類的設計與實現(xiàn)钳宪,以及其它一些支持類開發(fā)和OOP的語言特性靠柑。
<h3 id="classdevelopment">類的定義</h3>
在 Objective-C 語言中认臊,類由接口和實現(xiàn)代碼組成圃庭,一般分別放在2個文件中:類接口 .h 文件,類實現(xiàn) .m文件失晴。類接口聲明了類的屬性和方法剧腻;類實現(xiàn)定義了類的實例變量、屬性和方法涂屁。
表3.1列出了常用的擴展名书在。
擴展名 | 含義 | 擴展名 | 含義 |
---|---|---|---|
.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
類實現(xiàn):類的實現(xiàn)使用關(guān)鍵字@implementation和@end來實現(xiàn)儒旬。
// 代碼清單-3.2
//
@ implementation ClassName
//
// 實例變量(最好在類的實現(xiàn)部分聲明實例變量)
// 在類接口中聲明的所有方法都必須在類的實現(xiàn)文件中定義
//
@end
<h3 id="classvariable">實例變量</h3>
實例變量是指為類聲明的變量,它們在相應的類實例(即對象)的生命周期中存在并擁有值帖族。實例變量擁有與對象對應的作用范圍和命名空間栈源。當對象被創(chuàng)建時,系統(tǒng)會為實例變量分配內(nèi)存佩伤,當對象被釋放時系統(tǒng)也會釋放變量占用的內(nèi)存镶殷。
實例變量可以在類的接口或?qū)崿F(xiàn)部分中聲明驮肉,不過全释,在類的接口中聲明實例變量會違反OOP的關(guān)鍵宗旨之一(封裝)艰亮,因此闭翩,最好在類的實現(xiàn)部分中聲明實例變量,而當語句塊緊跟類的 @implementation 指令時迄埃,尤其如此疗韵。
// 代碼清單-3.3
//
@ implementation ClassName
{
// 聲明實例變量的代碼(最好在類的實現(xiàn)部分聲明實例變量)
@private NSString *description;
@protected float temperature;
@public int counter;
...
}
...
@end
Objective-C 定義了多條編譯指令,使用這些指令可以控制實例變量的范圍调俘,即在程序中控制變量的可見性伶棒。
- @private: 實例變量只能在聲明它的類和該類的其他實例中被訪問。
- @protected: 實例變量可以在聲明它的類和該類子類的其他實例方法中被訪問彩库。如果沒有為實例變量指定保護級別肤无,這是默認的變量范圍。
- @public: 可以在任何位置訪問實例變量骇钦。
- @package: 可在通過任何類實例和函數(shù)訪問實例變量宛渐,但在包之外,實例變量會被當成私有變量眯搭。該范圍可用于庫和框架中的類窥翩。
這些指令稱為訪問修飾符,用于修飾在實例變量聲明語句塊中聲明的實例變量鳞仙。
<h3 id="classproperty">屬性</h3>
Objective-C 中屬性與實例變量的區(qū)別是:實例變量存儲了對象的內(nèi)部狀態(tài)寇蚊,可以直接獲取對象內(nèi)部狀態(tài)。而屬性無法直接訪問對象的內(nèi)部狀態(tài)棍好,但提供了訪問這類數(shù)據(jù)的方便機制(即讀取和設置方法)仗岸,因而可以含有其它邏輯。
大多數(shù)屬性都是由實例變量支持的借笙,屬性通過這種機制隱藏對象的內(nèi)部狀態(tài)扒怖。除非專門設置,否則實例變量就擁有與屬性相同的名稱(但會帶下劃線前綴)业稼。
屬性的聲明:使用關(guān)鍵字 @property 后跟一組可選的特性(用圓括號括起來)盗痒、屬性的類型和名稱。
// 代碼清單-3.4
//
@property(特性)屬性的類型 屬性的名稱;
// 屬性的特性
//
// 原子性特性 nonatomic 使用該特性可以在多線程并發(fā)的情況中低散,將訪問器設置為非原
// 子性的俯邓,因而能夠提供不同的結(jié)果。如果不設置該特性谦纱,訪問
// 器就會擁有原子性看成,換言之,賦值值和返回結(jié)果永遠都會完全同步
// 設置器語義 assign 通過該特性可以在不使用copy和retain特性的情況下跨嘉,使屬
// 性的設置器方法執(zhí)行簡單的賦值操作川慌。這個特性是默認設置吃嘿。
// 設置器語義 retain 在賦值時,輸入值會被發(fā)送一條消息梦重,而上一個值會被發(fā)送一條
// 釋放信息
// 設置器語義 copy 在賦值時兑燥,輸入值會被發(fā)送一條消息的副本,而上一個值會被
// 發(fā)送一條釋放信息
// 設置器語義 strong 當屬性使用ARC內(nèi)存管理功能時琴拧,該特性等同于retain特性
// 設置器語義 weak 當屬性使用ARC內(nèi)存管理功能時降瞳,該特性的作用與assign特性類似,
// 但如果引用對象被釋放了蚓胸,屬性的值會被設置為nil
// 可讀寫特性 readwrite 使用該特性時挣饥,屬性可以被讀取也可以被寫入,而且必須實
// 現(xiàn)getter和setter方法沛膳。這個特性是默認設置
// 可讀寫特性 readonly 使用該特性時扔枫,會將屬性設置為只讀。必須實現(xiàn)getter方法
// 方法名稱性 getter=getterName 將getter方法重命名為新設置器的名稱
// 方法名稱性 setter=setterName 將setter方法重命名為新設置器的名稱
其中特性有如下幾種取值锹安,各個特性的含義涉及到 Objective-C 中內(nèi)存管理的相關(guān)知識短荐,后面會有詳細的講解,所以這里只是簡單的介紹叹哭。只有對 Objective-C 的內(nèi)存管理有了比較全面的了解之后忍宋,才能很好的理解這里各個特生的含義。
? 讀寫屬性:(readwrite/readonly)決定是否生成set訪問器
? setter語意:(assign/retain/copy)set訪問器的語義风罩,決定以何種方式對數(shù)據(jù)成員賦予新值
? 原子性:(atomic/nonatomic)
屬性特性詳細講解:
readwrite:生成 setter\getter 方法(默認)
使用該特性時糠排,屬性可以被讀取也可以被寫入,而且必須實現(xiàn)getter和setter方法超升。這個特性是默認設置乳讥。
readonly:只生成 getter 方法.
此標記說明屬性是只讀的,如果你指定了 readonly廓俭,在 @implementation 中只需要一個 getter“ぃ或者如果你使用@synthesize關(guān)鍵字研乒,也只會生成getter方法。如果你試圖使用點操作符為屬性賦值淋硝,你將得到一個編譯錯誤雹熬。readonly關(guān)鍵字代表 setter 不會被生成, 所以它不可以和 copy/retain/assign 組合使用谣膳。
assign:簡單賦值竿报,不更改索引計數(shù)
此標記說明設置器直接進行賦值,這也是默認值继谚。在使用垃圾收集的應用程序中烈菌,如果你要一個屬性使用 assign,且這個類符合 NSCopying 協(xié)議,你就要明確指出這個標記芽世,而不是簡單地使用默認值挚赊,否則的話,你將得到一個編譯警告济瓢。這再次向編譯器說明你確實需要賦值荠割,即使它是可拷貝的。
retain:釋放舊的對象旺矾,將舊對象的值賦予輸入對象蔑鹦,再增加輸入對象的索引計數(shù)為1
指定 retain 會在賦值時喚醒傳入值的retain 消息。此屬性只能用于Objective-C 對象類型箕宙,而不能用于Core Foundation 對象嚎朽。(原因很明顯,retain 會增加對象的引用計數(shù)扒吁,而基本數(shù)據(jù)類型或者 Core Foundation 對象都沒有引用計數(shù))火鼻。
copy:建立一個索引計數(shù)為1的對象,然后釋放舊對象
它指出雕崩,在賦值時使用傳入值的一份拷貝魁索。拷貝工作由 copy 方法執(zhí)行盼铁,此屬性只對那些實行了 NSCopying 協(xié)議的對象類型有效粗蔚。更深入的討論,請參考”復制”部分饶火。
atomic/nonatomic:原子操作
指出訪問器不是原子操作鹏控,atomic 表示屬性是原子的,支持多線程并發(fā)訪問肤寝,而默認地 nonatomic当辐,訪問器是原子操作。這也就是說鲤看,在多線程環(huán)境下缘揪,解析的訪問器提供一個對屬性的安全訪問,從獲取器得到的返回值或者通過設置器設置的值可以一次完成义桂,即便是別的線程也正在對其進行訪問找筝。如果你不指定 nonatomic,在自己管理內(nèi)存的環(huán)境中慷吊,解析的訪問器保留并自動釋放返回的值袖裕,如果指定了 nonatomic,那么訪問器只是簡單地返回這個值溉瓶。沒有特別的多線程要求建議用 nonatomic 有助于提高性能急鳄。
在 iOS5 引入了自動引用計算 ARC(Automatic Reference Counting)之后谤民,對象變量屬性新增了 strong 和 weak,strong 與 retain 作用類似攒岛,可以說是用來代替retain赖临;weak 與 assign 特性類似.
<h3 id="classgetproperty">訪問屬性</h3>
屬性的定義:大多數(shù)屬性是由實例變量支持的,因此屬性的定義中會含有屬性的 getter 和 setter 方法的定義灾锯、實例變量的聲明兢榨,并在 getter/setter 方法中使用這些變量。Objective-C 提供了多種定義屬性的方式:顯示定義顺饮、通過關(guān)鍵字(@synthesize)補全吵聪、自動補全和動態(tài)生成(@danamic)。
Objective-C 提供了兩種訪問屬性的機制:訪問器方法和點語法兼雄。
訪問器方法:用于讀取值的方法(getter方法)擁有與屬性相同的名字吟逝;用于設置值的方法(setter方法)其名稱以set開頭、后跟首字母大寫的屬性名赦肋。
// 代碼清單-3.5
//
// 假設屬性名稱為 color
[myObject color];
[myObject setColor:輸入值];
點語法:類似于Java語言中的方法.
// 代碼清單-3.6
//
// 假設屬性名稱為 color
myObject.color;
myObject.color = 輸入值;
Objective-C 語法關(guān)于點表達式的說明:如果點表達式出現(xiàn)在等號 = 左邊块攒,該屬性名稱的 setter 方法將被調(diào)用。如果點表達式出現(xiàn)在 = 右邊佃乘,那么該屬性名稱的 getter 方法將被調(diào)用囱井。所以在 Objective-C 中點表達式其實就是調(diào)用對象的 setter/getter 方法的一種快捷方式。
<h3 id="classvariableproperty">實例變量與屬性的關(guān)系</h3>
在老版本的 Objective-C 語言中趣避,我們需要同時聲明屬性和實例變量庞呕,那時屬性是 Objective-C 語言的一個新的機制,并且要求你必須聲明與之對應的實例變量程帕,例如:
// 代碼清單-3.7
//
@interface MyNoteBook : NSObject
{
@protected NSString * _content;
}
@property (nonatomic, retain) NSString * content;
...
@end
后來住练,蘋果將默認編譯器從GCC轉(zhuǎn)換為Clang/LLVM(low level virtual machine),從此不再需要手動為屬性聲明實例變量了愁拭,它支持對已聲明屬性進行自動補全讲逛。如果編譯器發(fā)現(xiàn)一個沒有實例變量支持的屬性,它將自動創(chuàng)建一個作用范圍為 @priavte 并且與屬性名稱相同的支持實例變量(但會帶下劃線前綴)岭埠。編譯器可以自動補全以下聲明的屬性:1妆绞、沒有使用關(guān)鍵字(如@synthesize)進行補全的屬性;2枫攀、不是(通過@dynamic屬性指令)動態(tài)生成的屬性。3株茶、沒有用戶編寫的 getter 和 setter 方法的屬性来涨。因此,使用該特性就無需手動補全已聲明的屬性启盛。編譯器會自行補全已聲明的屬性和相應的實例變量蹦掐。
例如 MyNoteBook.h 文件:
// 代碼清單-3.8
//
@interface MyNoteBook : NSObject
@property (nonatomic, retain) NSString * content;
...
@end
在 MyNoteBook.m 文件中技羔,編譯器也會自動的生成一個實例變量 _content ,那么在 .m 文件中可以直接的使用 _content 實例變量卧抗,也可以通過屬性 self.content 來訪問和設置藤滥。
注意:這里的 self.content 其實是調(diào)用屬性 content 的 getter/setter 方法。這與 C++ 中點的使用是有區(qū)別的社裆,C++中的點可以直接訪問成員變量(也就是實例變量)拙绊。
例如 MyNoteBook.h 文件:
// 代碼清單-3.9
//
@interface MyNoteBook : NSObject
{
__strong NSString * content;
}
...
@end
在 MyNoteBook.m 文件中,self.content 這樣的表達式是錯誤的泳秀。Xcode會提示你使用 ->标沪,改成 self->content 就可以了。因為 Objective-C 中點表達式是表示調(diào)用方法嗜傅,而上面的代碼中沒有 content 這個方法金句。
以前的用法,聲明屬性跟與之對應的實例變量(代碼清單-3.7)吕嘀,這種方法基本上使用最多违寞,現(xiàn)在大部分也是在使用,因為很多開源的代碼都是這種方式偶房。但是在 iOS5 更新之后趁曼,蘋果是建議用以下的方式來使用:
// 代碼清單-3.10
//
@interface MyNoteBook : NSObject
@property (nonatomic, retain) NSString * title;
@property (nonatomic, retain) NSString * author;
@property (nonatomic, retain) NSString * content;
...
@end
因為編譯器會自動為你生成與屬性名稱相同(但帶下劃線前綴)的實例變量;也會自動為你生成 setter/getter 方法蝴悉。
如果你想自己設定與屬性相關(guān)聯(lián)的變量的名稱彰阴,則可以通過關(guān)鍵字 @synthesize 來實現(xiàn)。
// 代碼清單-3.11
//
// 語法:@synthesize 屬性名稱 [=實例變量名稱];
// 例如:
@synthesize myIntProperty; // 省略[=實例變量名稱]
@synthesize myIntProperty = myPropertyOne;
如果省略了可選項[=實例變量名稱]拍冠,編譯器會根據(jù)屬性實例變量標準命名慣例尿这,自動生成實例變量的名稱。如果你設置了可選項[=實例變量名稱]庆杜,編譯器就會使用該名稱創(chuàng)建實例變量射众。
<h3 id="classmethod">方法</h3>
方法的聲明由方法類型、返回值類型和一個或多個(提供名稱晃财、參數(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ā)送信息與其它對象(接收器)進行交互,從而調(diào)用指定的方法断盛。
// 代碼清單-3.13
//
// 語法:[接收器 方法代碼段名稱:參數(shù)值 ...]
// 說明:如果擁有多個代碼段罗洗,可將它們的名稱和參數(shù)值以空格為分隔符連續(xù)排列。
// 例如:
[myNoteBook initWithTitle:@"Title"];
[myNoteBook initWithTitle:@"Title" andContent:@"BookContent"];
<h3 id="classprotocol">協(xié)議</h3>
使用協(xié)議聲明的方法和屬性可以由任何類實現(xiàn)钢猛。協(xié)議不與特定的類關(guān)聯(lián)伙菜,因此,使用它可以捕捉無繼承關(guān)系的類之間的相似之處命迈。協(xié)議使 Objective-C 支持多重繼承規(guī)范的概念(方法聲明)贩绕。
// 代碼清單-3.14
//
// 語法:
@protocol 協(xié)議名稱
// 屬性聲明
@required
// 方法的聲明(必選方法)
@optional
// 方法的聲明(可選方法)
@end
通過在尖括號中設置已經(jīng)聲明的協(xié)議的名稱火的,可以使一個協(xié)議與其它協(xié)議合并——這稱為接受協(xié)議∈缜悖可使用逗號分隔多個協(xié)議馏鹤。如代碼清單-3.15所示
// 代碼清單-3.15 合并其它協(xié)議
//
// 語法:
// 合并單個協(xié)議
@protocol 協(xié)議名稱 <協(xié)議名稱>
// 方法的聲明
@end
// 合并多個協(xié)議
@protocol 協(xié)議名稱 <協(xié)議名稱1, 協(xié)議名稱2, 協(xié)議名稱3, ... >
// 方法的聲明
@end
使用類似的語法可以令接口接受其它協(xié)議,如代碼清單-3.16所示
// 代碼清單-3.16 接口接受其它協(xié)議
//
// 語法:
// 接口接受單個協(xié)議
@interface 類的名稱 :父類的名稱 <協(xié)議名稱>
// 方法的聲明
@end
// 接口接受多個協(xié)議
@interface 類的名稱 :父類的名稱 <協(xié)議名稱1, 協(xié)議名稱2, 協(xié)議名稱3, ... >
// 方法的聲明
@end
協(xié)議不引用任何類娇哆,它是類無關(guān)的湃累,任何類都可以實現(xiàn)定義好的 Protocol。如果我們想知道某個類是否實現(xiàn)了某個 Protocol迂尝,可以使用 conformsToProtocol 進行判斷脱茉,如下:
if(YES == [obj conformsToProtocol:@protocol(ProtocolName)]) {
// 在這里插入代碼
}
這里使用 @protocol 指令用于獲取一個協(xié)議名稱,并產(chǎn)生一個 Protocol 對象垄开,并作為conformsToProtocol: 的參數(shù)琴许。如果為了測試 obj 是否實現(xiàn)了協(xié)議中的某一個方法,可以編寫以下代碼:
if ([obj respondsToSelector:@selector(methodName)]) {
[obj methodName];
}
<h3 id="classcategory">分類</h3>
使用分類可以在不進行子類化的情況下溉躲,為已經(jīng)存在類增加功能榜田。分類通常用于:1、擴展其他人定義的類(即使你無法訪問它們的源代碼)锻梳;2箭券、替代子類;3疑枯、將新類的實現(xiàn)代碼分發(fā)給多個源文件(通過多人分工辩块,簡化大型類的開發(fā)工作)。
分類接口的聲明以關(guān)鍵字 @interface 開頭荆永,后跟已存在的類的名稱废亭、帶括號的分類名稱以及它所接受的協(xié)議(如果有的話),以關(guān)鍵字 @end 結(jié)束具钥。方法的聲明放在這些語句之間豆村。
// 代碼清單-3.17 分類聲明的語法
//
// 語法:
@interface 類的名稱 (分類的名稱) <協(xié)議名稱1, 協(xié)議名稱2, 協(xié)議名稱3, ... >
// 方法的聲明
@end
擴展視為一種匿名(即未命名的)分類。在擴展中聲明的方法必須在相應類的主 @implementation 塊中實現(xiàn)(它們無法在分類中實現(xiàn))骂删。
// 代碼清單-3.18
//
// 語法:
@interface 類的名稱 () <協(xié)議名稱>
{
// 實例變量的聲明
}
// 屬性的聲明
// 方法的聲明
@end
擴展與分類的區(qū)別是它能聲明實例變量和屬性掌动。編譯器會檢查在擴展中聲明的方法(和屬性)是否被實現(xiàn)。類擴展通常應存儲在類實現(xiàn)文件中宁玫,并用于組織和聲明在類中獨立使用的其它私有方法(例如粗恢,不是公用API的一部分)。
<h3 id="classsummary">小結(jié)</h3>
本章主要介紹了 Objective-C 程序開發(fā)中使用的類欧瘪。
- Objective-C 的類由接口和實現(xiàn)代碼構(gòu)成适滓。接口聲明了類的屬性和方法;實現(xiàn)定義了類的實例變量、屬性和方法凭迹。按照慣例,應將類的接口代碼存儲在 .h 文件中苦囱,類的實現(xiàn)代碼存儲在 .m 文件中嗅绸。
- 在協(xié)議中聲明的方法和屬性可以由任何類實現(xiàn)。使用協(xié)議通乘和可以在無繼承關(guān)系的類之間實現(xiàn)通用行為鱼鸠。
- 使用分類可以在不進行了類化的情況下,為已經(jīng)存在的類增加功能羹铅。分類通常用于:1蚀狰、擴展其他人定義的類(即使你無法訪問它們的源代碼);2职员、替代子類麻蹋;3、將新類的實現(xiàn)代碼分發(fā)給多個源文件焊切。擴展可以被視為一種匿名分類扮授,不過它聲明的方法必須在類的主實現(xiàn)塊中實現(xiàn)。擴展還可以聲明實例變量和屬性专肪。