在開發(fā) iOS 和 Mac OS X 應用時,你并非是全部由自己從頭做起。你的工作是基于蘋果公司創(chuàng)建和收集的 Objective-C 框架基礎之上的?蚣芫褪且粋類庫,在運行時可以被多個進程共用;框架里還包括支持軟件開發(fā)用的資源文件。Cocoa Touch 和 Cocoa 框架為你帶來了一套相互支撐的類,它們共同構成你應用的一部分,通常是最重要的那一部分。
利用 C 語言的函數庫,你可以根據所編寫的應用種類隨意選擇所需函數并調用它們。而框架則不同,它是將你的程序限定在一個設計范圍之內,至少是限定在根據你的應用的作用而定的一個范圍內。當使用面向對象框架時,程序的大部分工作都可以通過調用框架里面類的方法來完成,這和面向過程編程類似。但是你仍然需要對一般框架行為進行自定義,通過實現若干方法讓它們能夠在適當的時間被調用。這些方法就是將你的代碼與框架事先設定的結構相連接的鉤子,為其添加更加適合你的應用的獨特行為方式。
下邊的內容將帶你探尋框架代碼和應用代碼之間的關系。
應用的驅動力是「事件」
要探尋在你的代碼和框架代碼之間的關系,首先可以思考一下當應用啟動時究竟發(fā)生了什么。最基本的事情是,應用建立好一組核心對象,然后把控制權交給那些對象。隨著程序的運行會產生越來越多的對象,但是在最初階段最需要的東西是完整的結構,要有足夠數量的核心代碼網絡來處理初始任務。最基本的任務一共由兩種:
- 繪制應用的初始用戶界面。
- 當用戶和用戶界面產生交互時,處理接收到的事件。
在初始用戶界面顯示在屏幕上之后,應用就由內部的事件所驅動。其中最重要的事件是來自用戶的操作,比如點按按鈕。系統(tǒng)會向應用報告這樣的事件,同時伴有它們各自的信息。同時含有你編寫的代碼以及框架代碼的應用就會處理這些事件,并根據需要更新用戶界面。
應用獲取事件并做出響應,一般的響應是繪制用戶界面,然后等待下一個事件。只要用戶或者其他什么事件來源(比如計時器)在發(fā)出各種事件,應有就會不停地、一個接一個地收到這些事件。在應用運行到退出期間,幾乎所有行動都來自于用戶發(fā)出的事件。
獲取事件并做出響應的這種機制叫做主事件循環(huán)。核心組里的一個對象,即全局應用對象負責管理主事件循環(huán)。它會獲取事件,將事件派送到相應對象或者最適合處理這種事件的對象,然后去獲取下一個事件。下圖反映了 iOS 系統(tǒng)中 Cocoa Touch 應用的主事件循環(huán)過程。
使用 Objective-C 框架
Cocoa Touch 和 Cocoa 框架只不過是提供各類服務的各個類的收納袋。它們構成了面向對象的框架,類的集合構建了一個問題空間,并為其提供了一個整合的解決方案?蚣懿⒎翘峁└鞣N分散的服務,在你需要時去利用(比如函數庫就是這樣),框架勾勒出并實現了整個應用架構,你自己的代碼必須適應這個架構。由于應用架構是個大的一般類,因此你可以按照需要來定制自己的每一個應用。在設計應用時,你并非向應用插入一個函數庫,而是向框架提供設計的一般類插入你的代碼。
要使用框架,則必須接受其定義的應用架構,并按照自己的需要使用和自定義框架里的類,并將自己的模子應用到這個架構上去。框架中的每個類都互相依賴,成組出現,而不是形單影只的分散體。起初,要讓自己的代碼適應框架的架構看上去很受限制,但事實卻正好相反。框架能夠讓你用無數種方法變換和擴展其設定的一般類行為。你唯一要做的就是接受的條件就是所有應用行為都要在同一個基礎之上,因為它們都基于同一個架構。舉一個較為寬泛的例子,Objective-C 框架就好比一座房屋的骨架,你的應用代碼就是門、窗、墻壁以及一切使這棟房子變得獨特的元素。
從使用和整合框架到自己代碼的角度來看,總共有兩種類:
- 復活對象。有些類定義了復活(Off-the-shelf)對象,也就是可以直接拿來使用的對象。你只需要創(chuàng)建該類的實例,并按照自己的需要使用該實例即可。
- 一般對象。使用一般框架中的類時,你可以創(chuàng)建它們的子類并重寫部分所需方法的實現(有時甚至必須創(chuàng)建子類并重寫)。通過創(chuàng)建它們的子類,你就可以將自己的代碼引入應用的架構中了?蚣軙诤线m的時機調用你創(chuàng)建的子類中的方法。
為一般類框架創(chuàng)建子類是將自己編寫的程序代碼整合到框架提供的架構中去的一項主要技術,但并不是唯一的方式。在后邊的文章中你會學到,Cocoa Touch 和 Cocoa 框架還含有一些結構和機制,可以讓你自定的對象和框架對象更好地協(xié)同工作。
當你建立子類時,一般需要事先決定好兩件事:從哪個類繼承(即父類),以及要重寫該類的哪些方法。下文將著重探索如何做這兩個決定。
從 Cocoa 或 Cocoa Touch 框架繼承
像 UIKit 這樣的框架定義了一種可供多種應用利用的架構,因為它是個一般類。正因為此,你在看到某些框架的類十分抽象并且有意保持不完整時也不必驚訝。這樣的類通常實現了若干段通用代碼,但是會留下明顯未完成或沒有寫成安全默認樣式的部分代碼。
要為應用添加特定的行為,最主要的做法就是給框架類創(chuàng)建自定義子類。子類為父類提供其缺少的內容,填補了父類在應用中存在的一些溝壑。你創(chuàng)建的自定義子類的實例會出現在框架定義的對象網絡中,并且從框架中繼承與其他對象協(xié)同工作的能力。要讓一個應用變得有用處,它至少要包含一個子類,或許更多。
接下來的部分將討論和探索關于創(chuàng)建、使用子類的一些決策和謀略,和它們的基本需求。不過我們不會談到創(chuàng)建子類的具體細節(jié)!禩he Objective-C Programming Language》中的「定義一個類」章節(jié)講述了這些技術。
何時需要創(chuàng)建子類
創(chuàng)建和使用子類其實是對現有類的重復利用,并按照自己的需求定制它的過程。有時子類的全部職責就是重寫從父類中繼承而來的某個方法,將它進行輕微的改動。其他子類則可能向父類添加一兩個屬性(比如實例變量),然后讓定義的方法對這些屬性進行操作,將它們整合到父類的行為當中去。
創(chuàng)建和使用子類時,首先要做的是確定父類框架。你在考慮時可以參考下邊的向導:
- 了解父類框架。你必須熟知框架中的每個類都帶有什么目的,以及能做哪些事情。閱讀開發(fā)者資源庫里的框架介紹和框架本身各個類的代碼就是個很好的開始。也許某個類早已實現了你想要做到的事情。如果你發(fā)現某個類基本能夠做到你需要的事情,也就意味著你很幸運,這個類就可以拿來當作自定義子類的父類。比如,當學習《你的第一個 iOS 應用》時就遇到過 UIViewController 等 UIKit 框架的類。要更深入地了解這些類,你可以這樣做:
- 在 Xcode 中,選擇 Window > Organizer。
- 點按工具欄里的 Documentation 按鈕。
- 點按導航區(qū)頂部的 Browse,開始瀏覽已經安裝的開發(fā)者資源庫。
- 點按最近的 iOS 資源庫(你可能需要首先登錄到 Apple Developer 才可以)。iOS Developer Library 將在內容區(qū)域里顯示出來。
- 在文檔列表上方的過濾選項里,輸入“UIKit Framework Reference”。篩選后的列表就只會顯示符合輸入內容的文檔了。
- 點按一個文檔的名稱,其頁面就會顯示出來,包含 UIKit 的所有類、協(xié)議列表。
- 仔細閱讀介紹,并點按列出的類和協(xié)議,這樣就能了解更多關于它們的知識。
- 對自己的應用將要做什么非常明確。這條建議既可以說是針對整個應用的,也可以說是針對應用中每個獨立功能的。某些框架的架構中強制規(guī)定了自己的子類需要符合哪些要求。比如,如果你的應用是基于文檔的,你就必須為抽象文檔類創(chuàng)建子類。
- 定義你的子類產生的實例將要扮演的角色。在 iOS 和 Mac OS X 應用開發(fā)中,模型-視圖-控制器(MVC)設計模式就是對象的三大角色。視圖對象會顯示在用戶界面上;模型對象則掌握用戶數據(并實現一定的數據處理算法);控制器對象則是連接視圖和模型對象的中間人。搞清楚各個對象的角色之后,決定使用哪個子類就會非常容易了。比如,當你需要在 iOS 應用中進行一些自定義繪制時,你就需要一個 UIView 的子類,UIView 是 UIKit 框架中的基本視圖類。
雖然子類操作在 iOS 和 Mac OS X 編程中非常重要,但它并不永遠都是解決問題的最好方法。如果你需要向某個類添加少量的便捷方法,你可以創(chuàng)建范疇類(Category), 而不是創(chuàng)建它的子類;蛘撸斈阈枰獢r截應用的某個特殊行為時,你可以在設計模式的基礎上,選用框架中其他大量資源里的某一個,比如委托(這些設計模式在《用設計模式讓應用開發(fā)流水線化》一文中有詳細描述)。時刻牢記,有些框架的類是不可以用來創(chuàng)建子類的。參考文檔中會告訴你各個類是否能夠創(chuàng)建子類。
重寫一個方法
你可以創(chuàng)建一個子類但不實現任何父類的方法;比如說某個子類聲明了一些額外屬性并定義了訪問這些屬性的新方法,并在父類中調用這些方法。然而,有些子類的首要任務是實現一系列已由父類聲明的方法(或父類采用的某個協(xié)議里)。重新實現一個繼承而來的方法就叫做重寫該方法。
框架的類里定義的大部分方法都已被全面實現;你可以直接調用它們,以獲得該類能夠提供的所有便利。對于這些方法,你不需要也不應該嘗試重寫它們。其他的框架方法就可以被重寫了,但也并非時刻都有必要重寫。
但是,有些框架方法在使用之前要求先進行重寫;它們存在的目的就是方便你向框架添加特定的行為。這些由框架實現的方法常常只做很少的事情或者什么有用的功能也沒有。為了給這些方法增添內容,每個應用都需要對它們進行特性的實現?蚣軙趹眠\行時生命期內的合適時機調用這些方法。
調用還是重寫?
你在子類里重寫的框架方法通常不是由你親自調用的,至少不是由你直接調用。你只是重新實現該方法,并讓框架處理剩下的所有事情。實際上,基本上你重寫的特定版本的方法很少由你自己在代碼中進行調用。通常來說,對于框架類聲明的公共方法,開發(fā)者們可以做下邊這兩件事:
- 調用它們,讓類提供的功能發(fā)揮作用
- 重寫它們,將你自己的代碼引入框架定義的程序模型中
有時某個方法會同時適用于這兩種情況;如果主動調用,它會是一個非常有用的功能,同時也可以策略性地重寫它。但是在大部分情況下,如果你能夠調用某個方法,那么它就是由框架完全定義好的,無需在你的代碼中重復定義了。如果你必須在子類中重新實現某個方法,那么這個方法在框架中就是有著特定職責的,會在合適的時機由框架來調用。下圖描繪了這兩種框架方法。
在這張圖片里,假定你的自定義類里有個方法 myMethod,它調用了 setNeedsDisplay 方法,后者是由框架實現的。框架首先準備好了繪制環(huán)境,然后調用框架聲明的 drawRect: 方法,這是已經由自定義類重寫后的方法,能夠實際繪制圖形。
重寫方法并不是難事。只要在重新實現方法時格外小心,僅用一兩行代碼往往就能明顯改變父類定義的方法行為。
調用父類的實現
當你重寫某個框架方法時,你需要決定是否替換掉繼承到的方法行為,還是選擇擴展或補充該行為。如果想要替換已有的行為,只需在方法的實現中輸入你自己的代碼;如果是想擴展該行為,你需要調用父類的實現然后再提供自己的代碼。
如何調用父類的實現呢?只需向 super 發(fā)送一條同樣的消息調用該方法即可。向 super 發(fā)送消息時,你就在調用的那個點上將父類的代碼“插入”了你重新實現的代碼。打個比方,假設有一個叫做 Celebrate 的類定義了一個方法performFireworks。當框架在視圖中繪制并動畫化一個煙火時,你想在視圖中顯示一個橫幅。下邊的圖片顯示了此例中調用 super 的作用。
因此,決定調用 super 是基于你如何重新實現方法的:
- 如果你想要補充父類實現中的行為,則要調用 super。
- 如果你想要替換父類實現里的行為,那么就不要調用 super。
如果你是補充和擴展父類的行為,還需要注意另外一件重要的事,那就是何時調用父類方法的實現。因為,你可能需要在自己的代碼執(zhí)行之前讓父類的代碼產生作用,反之亦然。