Objective-C對象之類對象和元類對象

來源:互聯網
上載者:User

標籤:

wangzz原文地址:http://blog.csdn.net/wzzvictory/article/details/8592492轉載請註明出處如果覺得文章對你有所協助,請通過留言或關注公眾帳號wangzzstrive來支援我,謝謝! 

作為C語言的超集,物件導向成為Objective-C與C語言的最大區別,因此,對象是Objective-C中最重要的部分之一。目前物件導向的語言有很多,Objective-C中的對象又和其他語言中的對象有什麼區別呢?下面來簡單介紹Objective-C中對象的實現。
1、Objective-C中的類

誰都知道,所有的對象都是由其對應的類執行個體化而來,殊不知類本身也是一種對象,先不要對這句話感到驚訝。
首先我們來關注Objective-C中的類。在Objective-C中,我們用到的幾乎所有類都是NSObject類的子類,NSObject類定義格式如下(忽略其方法聲明):
@interface NSObject <NSObject> {
Class isa;
}
這個Class為何物?在objc.h中我們發現其僅僅是一個結構(struct)指標的typedef定義:
typedef struct objc_class *Class;
同樣的,objc_class又是什麼呢?在Objective-C2.0中,objc_class的定義如下:
struct objc_class {
Class isa;
}
寫到這裡大家可能就暈了,怎麼又有一個isa??這些isa到底是什嗎?之間有什麼區別和聯絡?接下來解答這一連串的疑問。
其實在Objective-C中任何的類定義都是對象。即在程式啟動的時候任何類定義都對應於一塊記憶體。在編譯的時候,編譯器會給每一個類產生一個且只產生一個”描述其定義的對象”,也就是蘋果公司說的類對象(class object),他是一個單例(singleton), 而我們在C++等語言中所謂的對象,叫做執行個體對象(instance object)。對於執行個體對象我們不難理解,但類對象(class object)是幹什麼吃的呢?我們知道Objective-C是門很動態語言,因此程式裡的所有執行個體對象(instace object)都是在運行時由Objective-C的執行階段程式庫產生的,而這個類對象(class object)就是執行階段程式庫用來建立執行個體對象(instance object)的依據。
再回到之前的問題,腫麼這個執行個體對象(instance object)的isa指標指向的類對象(class object)裡面還有一個isa呢?這個類對象(class objec)的isa指向的依然是一個objc-class,它就是“元類對象”(metaclass object),它和類對象(class object)的關係是這樣的:

2、類對象(class object)
①類對象的實質
我們知道了:類對象是由編譯器建立的,即在編譯時間所謂的類,就是指類對象(官方文檔中是這樣說的: The class object is the compiled version of the class)。任何直接或間接繼承了NSObject的類,它的執行個體對象(instance objec)中都有一個isa指標,指向它的類對象(class object)。這個類對象(class object)中儲存了關於這個執行個體對象(instace object)所屬的類的定義的一切:包括變數,方法,遵守的協議等等。因此,類對象能訪問所有關於這個類的資訊,利用這些資訊可以產生一個新的執行個體,但是類對象不能訪問任何執行個體對象的內容。
當你調用一個 “類方法” 例如 [NSObject alloc],你事實上是發送了一個訊息給他的類對象。

②類對象和執行個體對象的區別
當然有區別了,儘管類對象保留了一個類執行個體的原型,但它並不是執行個體本身。它沒有自己的執行個體變數,也不能執行那些類的執行個體的方法(只有執行個體對象才可以執行執行個體方法)。然而,類的定義能包含那些特意為類對象準備的方法–類方法( 而不是的執行個體方法)。類對象從父類那裡繼承類方法,就像執行個體從父類那裡繼承執行個體方法一樣。
③類對象與類名
在原始碼中,類對象由類名表示。
在下面的例子中,Retangle類 用從NSObject那裡繼承來的方法來返回類的版本號碼:
int versionNumber = [Rectangle version];
只有在訊息運算式中作為接收者,類名才代表類對象。其他地方,你需要要求一個執行個體或者類返回class id。 響應class訊息:
id aClass = [anObject class];
id rectClass = [Rectangle class];
如同上面的例子顯示的那樣,類對象像其他對象一樣,也是id類型。

總之,類對象是一個功能完整的對象,所以也能被動態識別(dynamically typed),接收訊息,從其他類繼承方法。特殊之處在於它們是由編譯器建立的,缺少它們自己的資料結構(執行個體變數),只是在運行時產生執行個體的代理。

3、元類對象(metaclass object)
①元類對象的實質
實際上,類對象是元類對象的一個執行個體!!元類描述了 一個類對象,就像類對象描述了普通對象一樣。不同的是元類的方法列表是類方法的集合,由類對象的選取器來響應。當向一個類發送訊息時,objc_msgSend會通過類對象的isa指標定位到元類,並檢查元類的方法列表(包括父類)來決定調用哪個方法。元類代替了類對象描述了類方法,就像類對象代替了執行個體對象描述了執行個體化方法。
很顯然,元類也是對象,也應該是其他類的執行個體,實際上元類是根元類(root class’s metaclass)的執行個體,而根元類是其自身的執行個體,即根元類的isa指標指向自身。
類的super_class指向其父類,而元類的super_class則指向父類的元類。元類的super class鏈與類的super class鏈平行,所以類方法的繼承與執行個體方法的繼承也是並行的。而根元類(root class’s metaclass)的super_class指向根類(root class),這樣,整個指標鏈就連結起來了!!

記住,當一個訊息發送給任何一個對象, 方法的檢查 從對象的 isa 指標開始,然後是父類。執行個體方法在類中定義, 類方法 在元類和根類中定義。(根類的元類就是根類自己)。在一些電腦語言的原理中,一個類和元類階層可以更自由的組成,更深元類鏈和從單一的元類繼承的更多的執行個體化的類。Objective-C 的類方法 是使用元類的根本原因,在其他方面試圖在隱藏元類。例如 [NSObject class] 完全相等於 [NSObject self],所以,在形式上他還是返回的 NSObject->isa 指向的元類。 Objective-C語言是一組實用的折中方案。

還有些不明白? 下面這個表徵圖可能會有些協助:



綜上所述,類對象(class object)中包含了類的執行個體變數,執行個體方法的定義,而元類對象(metaclass object)中包括了類的類方法(也就是C++中的靜態方法)的定義。類對象和元類對象中當然還會包含一些其它的東西,蘋果以後也可能添加其它的內容,但對於我們只需要記住:類對象存的是關於執行個體對象的資訊(變數,執行個體方法等),而元類對象(metaclass object)中儲存的是關於類的資訊(類的版本,名字,類方法等)。要注意的是,類對象(class object)和元類對象(metaclass object)的定義都是objc_class結構,其不同僅僅是在用途上,比如其中的方法列表在類對象(instance object)中儲存的是執行個體方法(instance method),而在元類對象(metaclass object)中則儲存的是類方法(class method)。關於元類對象可以參考蘋果官方文檔" The Objective-‐C Programming Language "

4、類對象和元類對象的相關方法

①object_getClass跟隨執行個體的isa指標,返回此執行個體所屬的類,對於執行個體對象(instance)返回的是類(class),對於類(class)則返回的是元類(metaclass),
②-class方法對於執行個體對象(instance)會返回類(class),但對於類(class)則不會返回元類(metaclass),而只會返回類本身,即[@"instance" class]返回的是__NSCFConstantString,而[NSString class]返回的是NSString。
③class_isMetaClass可判斷某類是否為元類.                                     

④使用objc_allocateClassPair可在運行時建立新的類與元類對,使用class_addMethod和class_addIvar可向類中增加方法和執行個體變數,最後使用objc_registerClassPair註冊後,就可以使用此類了。看到動態語言牛逼的地方了嗎,可以在需要時更改已經定義好的類!Objective-C的類別方法估計底層就是這麼實現的,只是不知道為什麼類別不能增加執行個體變數,有高手請留言。

 

 

Objective-C為我們提供了兩種初始化對象的方法:Objective-C2.0以後可用的new方法和兩段構造法。既然要比較這兩種初始化方法,就從它們本身的異同出發吧。

一、兩段構造法

這是Objective-C特有的對象建立方法,書寫形式如下:

NSString*s=[[NSString alloc] init];

所謂的兩段構造,就是指將alloc和init分開來寫,這和大多數其它語言(如C、C++、Java、JavaScript)都不一樣。先來看看alloc和init都幹了什麼吧:

1、alloc方法

當對象建立時,cocoa會從應用程式的虛擬位址空間上為該對象分配足夠的記憶體。cocoa會遍曆該對象所有的成員變數,通過成員變數的類型來計算所需佔用的記憶體。
當我們通過alloc或allocWithZone方法建立對象時,cocoa會返回一個未”初使化“過的對象。在這個過程中,cocoa除了上面提到的申請了一塊足夠大的記憶體外,還做了以下3件事:
①將該新對象的引用計數(Retain Count)設定成1。
②將該新對象的isa成員變數指向它的類對象。isa成員變數指向分配記憶體的類對象(class object),這是在NSObject類中定義的,所以保證Cocoa的所有對象都帶有此成員變數。它與Objective-C的運行時是一體的,藉助該變數可以實現Cocoa對象在運行時的自省(Introspection)功能。
③將該新對象的所有其它成員變數的值設定成零。(根據成員變數類型的不同,零有可能是指nil或0)
④返回指向該對象的一個指標。

2、init方法

大部分情況下,我們都不希望所有成員變數都是零,所以

①init方法會做真正的初使化工作,讓對象的成員變數的值符合我們程式邏輯中的初始化狀態。例如,NSMutableString可能就會額外再申請一塊字元數組,用於動態修改字串。

②返回真正可以使用的指向該對象的指標

init還有一個需要注意的問題,某些情況下,init會造成alloc的原本空間不夠用,而進行第二次分配記憶體空間。所以下面的寫法是錯的:
NSString  *s=[NSString alloc];

[s init];// 這兒init返回的地址可能會變。s原本的指標地址可能是無效的地址。

為此,蘋果引入了一個編程規範,讓大家寫的時候將alloc 和init寫在一行。所以上面的代碼正確的寫法是
NSString  *s=[[NSString alloc] init];

二、new方法

可能是為了和其他語言保持一致,蘋果後來也推出了new方法來初始化對象。作為類方法的new,只是簡單地等價於 alloc + init,卻不能指定init的參數,所以實際使用中很少見到。

三、使用兩段構造法的原因

有人可能要問,Objective-C的對象建立方法和大多數其它語言(如C、C++、Java、JavaScript)都不一樣,是什麼原因促使Objective-C做了這種設計?

1、曆史原因

這裡面多多少少就有曆史的因素了。Objective-C是一門非常老的語言。如果你查閱文檔,你會發現它和C++出生在同一時代(兩種語言的發行年份都是1983年),都是作為C語言的物件導向的接班人被推出。當然,最終C++勝出。由於曆史久遠,Objective-C也無法有太多優秀的語言做參考,所以,有很多曆史遺留的設計。

2、設計原則

簡單看來,根據設計模式的Single Responsibility的設計原則,蘋果覺得alloc和init是做的2件不同的事情,把這兩件事情分開放在2個函數中,對於程式員更加清楚明了。更詳細查閱文檔後,我覺得這是由於曆史原因,讓蘋果覺得alloc方法過於複雜,在曆史上,alloc不僅僅是分配記憶體,還可以詳細的指定該記憶體所在的記憶體分區(用NSZone表示)。

同時由於分配和初始化階段是分開的,初始化方法的實現只需處理新執行個體的變數,並完全忽略有關分配的問題,簡化了初始化方法的過程。

四、NSZone簡介

早期蘋果是建議程式員使用 allocWithZone來管理記憶體配置的,每個NSZone表示一塊記憶體分區,+allocWithZone:(NSZone *)zone方法可以允許對象從指定分區分配記憶體。記憶體區是Cocoa的一個功能組件,它能使同時使用的對象或電腦的地址空間中相鄰的對象保持在記憶體中,以此提高程式的效能。要解釋對象在記憶體中的位置會如何影響效能,需要解釋應用程式需要比實體記憶體更大的記憶體時會發生什麼情況。

每個Cocoa應用程式都有很大的可定址記憶體,當應用程式動態分配記憶體時,即使電腦的所有實體記憶體都已經被佔用,作業系統仍然會提供記憶體。要滿足該分配要求,作業系統會使用頁面調度(paging)或者交換(swapping)操作將一些實體記憶體中的內容複寫到硬碟,之前正在使用的實體記憶體就可以被提供出來使用了,而之前的那些資料應經被寫入硬碟。如果有需要先前複製到硬碟的那部分記憶體資料,作業系統會將另外一塊實體記憶體複製到硬碟,並將先前的舊記憶體再度調回記憶體。即時記憶體在硬碟間調度,作業系統仍然能為每個應用程式對應地址空間到實體記憶體。作業系統的這一功能即是虛擬記憶體(virtual memory)。

由於從實體記憶體額硬碟中相互調度是很消耗時間的,因此,使用虛擬記憶體會影響效能。過多的頁面調度會降低系統效能,這稱為抖動(thrashing)。如果一起使用的兩個或多個對象在記憶體中的位置很遠,抖動發生的可能性將會大大增加,因此對象執行個體的記憶體配置的位置也很重要。

分區用於確保分配給同時使用的對象的記憶體位於相鄰位置。當需要某個對象時,另外相鄰的對象也基本會用到,需要的所有對象同時調入記憶體的可能性就更大,當不需要時,又可以都同時調出記憶體,Cocoa中的NSZone類型是指定標識記憶體區的C結構的對象,+allocWithZone:(NSZone *)zone方法允許NSZone變數從指定分區分配記憶體。已達到減少抖動的目的。可見當年蘋果的設計師們的良苦用心!!!

只是,分區是一個十分底層的東西,而且,隨著硬體裝置的發展,實體記憶體的不斷增大,以及作業系統記憶體配置函數複雜性的提高,使用分區的最初目的已經逐漸消失了。自從Mac OS X 10.5上引入了記憶體回收機制後,蘋果就不建議程式員使用allocWithZone了,事實上,cocoa架構也會忽略+allocWithZone:(NSZone *)zone指定的分區。蘋果在文檔中也提到,+allocWithZone:(NSZone *)zone僅僅是一個曆史遺留設計了。           

Objective-C對象之類對象和元類對象

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.