談ObjC對象的兩段構造模式

前言

Objective-c語言在申請對象的時,須要使用兩段構造(Two Stage Creation)的模式。一個對象的建立,須要先調用alloc方法或allocWithZone方法,再調用init方法或initWithSomething方法。以下是一個NSString對象的建立示例:html

1
NSString * str = [[NSString alloc] initWithString:@"http://blog.devtang.com"]; 

因爲該語言的對象建立方法和大多數其它語言(如C、C++、Java、JavaScript)都不同,因此引發了個人好奇。是什麼緣由促使Objective-C作了這種設計,而又是什麼緣由促使大多數其它語言都採用」new」方法來一次性建立對象呢?程序員

在看了《Cocoa Design Patterns》一書(順便吐槽一下該書中文版翻譯質量不高,建議看英文版),而且作了一些調研以後,我將總結分享給你們,歡迎你們討論。編程

對象的建立

咱們先來看看在對象的建立過程當中,alloc和init到底作了哪些事情。設計模式

alloc方法

根據蘋果的官方文檔。當對象建立時,cocoa會從應用程序的虛擬地址空間上爲該對象分配足夠的內存。cocoa會遍歷該對象全部的成員變量,經過成員變量的類型來計算所需佔用的內存。數組

當咱們經過alloc或allocWithZone方法建立對象時,cocoa會返回一個未」初使化「過的對象。在這個過程當中,cocoa除了上面提到的申請了一塊足夠大的內存外,還作了如下3件事:app

  1. 將該新對象的引用計數(Retain Count)設置成1。
  2. 將該新對象的isa成員變量指向它的類對象。
  3. 將該新對象的全部其它成員變量的值設置成零。(根據成員變量類型,零有多是指nil或Nil或0.0)

isa成員變量是在NSObject中定義的,因此保證Cocoa的全部對象都帶有此成員變量。藉助該變量能夠實現Cocoa對象在運行時的自省(Introspection)功能。框架

init方法

大部分狀況下,咱們都不但願全部成員變量都是零,因此init方法會作真正的初使化工做,讓對象的成員變量的值符合咱們程序邏輯中的初始化狀態。例如,NSMutableString可能就會額外再申請一塊字符數組,用於動態修改字符串。函數

init還有一個須要注意的問題。某些狀況下,init會形成alloc的本來空間不夠用,而第二次分配內存空間。因此下面的寫法是錯的:post

1
2 
NSString * s = [NSString alloc]; [s init]; // 這兒init返回的地址可能會變。s本來的指針地址多是無效的地址。 

爲此,蘋果引入了一個編程規範,讓你們寫的時候將alloc 和init寫在一行。因此上面的代碼正確的寫法是ui

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

new

在後來,蘋果也引入了類方法:new。可是因爲歷史緣由,init方法是實例方法而非類方法,因此做爲類方法的new,只能簡單地等價於 alloc + init,不能指定init的參數,因此用處不大。蘋果在設計上也禁止屢次調用init方法,例如以下代碼會拋出 NSInvalidArgumentException。

1
2 
NSString * str = [NSString new]; str = [str initWithString:@"Bar"]; 

爲何這麼設計

說回來文章開始時提出來問題,爲何蘋果要這麼設計而其它語言不這麼設計?

上面提到,alloc其實不僅幹了申請內存的事情,還作了: 1. 內存管理的事情,設置Retain Count。 2. 運行時自省的功能,設置isa變量。 3. 非邏輯性的初使化功能,設置全部成員變量爲零。

簡單看來,根據設計模式的Single Responsibility的設計原則,蘋果以爲alloc和init是作的2件不一樣的事情,把這兩件事情分開放在2個函數中,對於程序員更加清楚明瞭。更詳細查閱文檔後,我以爲這是因爲歷史緣由,讓蘋果以爲alloc方法過於複雜,在歷史上,alloc不只僅是分配內存,還能夠詳細的指定該內存所在的內存分區(用NSZone表示)。這就是下面要提到的allocWithZone方法。

《Cocoa Design Patterns》一書也提到,早期蘋果是建議程序員使用 allocWithZone來管理內存分配的,每一個NSZone表示一塊內存分區,allowWithZone方法能夠容許對象從指定分區分配內存。瞭解到這段歷史後,也不難理解蘋果這麼設計的緣由了。由於在這種狀況下,alloc要處理的狀況複雜,和init放到一塊兒不合適。

而對於大多數出生在90年代的語言來講(例如Java,JavaScript,C#),因爲內存具體的分配方案都不須要程序員操心了,因此就不須要單獨爲內存分配實現一個alloc方法了。

後記

allocWithZone被廢棄

自從Mac OS X 10.5上引入了垃圾回收機制後,蘋果就不建議程序員使用allocWithZone了,事實上,cocoa框架也會忽略allocWithZone指定的分區。蘋果在文檔中也提到,allocWithZone僅僅是一個歷史遺留設計了。下圖是蘋果的文檔截圖:

Objective-C的歷史

Objective-C是一門很是老的語言。若是你查閱文檔,你會發現它和C++出生在同一時代(兩種語言的發行年份都是1983年),都是做爲C語言的面向對象的接班人被推出。固然,最終C++勝出。因爲歷史久遠,Objective-C也沒法有太多優秀的語言作參考,因此,有不少歷史遺留的設計。在2007年蘋果公司發佈了Obj-C 2.0, 對其進行了大量改進。

在最近幾年的WWDC大會上,每一年蘋果都會對Objective-C和其對應的LLVM編譯器進行改進,例如WWDC2011推出的ARC,WWDC2012推出的Object Literals等。因此如今使用Objective-C作開發已經很是舒服了。期待蘋果給開發者帶來更多驚喜。

 

http://blog.devtang.com/blog/2013/01/13/two-stage-creation-on-cocoa/

相關文章
相關標籤/搜索