Objective-C 學(xué)習(xí)筆記 - 第3章 類的定義與使用

本章著重介紹了用于開發(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í)例變量和屬性。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容