10個迷惑新手的Cocoa&Objective-c開發問題

 

本文轉載至  http://blog.csdn.net/lvxiangan/article/details/27964733
 
 
  1. language background
  2. run­time
  3. thread
  4. run­loop
  5. del­e­gate, protocol
  6. event respon­der
  7. mem­ory management
  8. class heritage, category and extensions
  9. drawing issue
  10. design pattern

首先請諒解我可能使用不少英文,畢竟英文資料未來會是你的主要資料來源。html

這篇教程將描述一些我見到的衆多Cocoa開發新手遇到的問題和障礙。並不會手把手教你:「這個函數什麼意思,哪一個函數如何使用」,而是站在必定高度,統觀各類技術所處的角色,讓你不會迷失在各類技術細節中。在你繼續深刻學習MacOS編程以前,請停下腳步看清楚這些問題。若是你是新手,這個教程不要但願一次能看的很是透徹,學必定階段反回來再看看又會有新的體會的。java

 

 

1. language background

首先c語言背景,必須。 不少人問:「沒有任何語言基礎,我不想學c直接學objective-c。是否能夠?」 這裏我簡單說幾句,objc是c的超集,也就是說大部分objc代碼實際上是c、並且衆多傳統開源項目都是c寫成的。你不學好c在unix世界裏只能是個二流開發者!也許說得過於嚴厲,不過本身斟酌把。c++呢大概瞭解一下便可,由於它太龐雜了。ios

接着English,必須。 蘋果不會把它們文檔都寫成中文的。「什麼,有人翻譯?」 等有人閒着翻譯出來了的時候,你們都已經賣了不少軟件了。你也是跟着人家屁股後面作開發。c++

 

2. runtime(運行時)

Objective-c是動態語言, 不少新手或者開發人員經常被runtime這個東西所迷惑。而偏偏這是一個很是重要的概念。我能夠這麼問:「若是讓你(設計)實現一個計算機語言,你要如何下手?」 不多程序員這麼思考過。可是這麼一問,就會強迫你從更高層次思考1之前的問題了。 注意我這句話‘設計’括起來了,稍微次要點,關鍵是實現。程序員

我把實現分紅3種不一樣的層次:web

第一種是傳統的面向過程的語言開發,例如c語言。實現c語言編譯器很簡單,只要按照語法規則實現一個LALR語法分析器就能夠了,編譯器優化是很是難的topic,不在這裏討論範圍內,忽略。 這裏咱們實現了編譯器其中最最基礎和原始的目標之一就是把一份代碼裏的函數名稱,轉化成一個相對內存地址,把調用這個函數的語句轉換成一個jmp跳轉指令。在程序開始運行時候,調用語句能夠正確跳轉到對應的函數地址。 這樣很好,也很直白,可是太死板了。Everything is predetermined.objective-c

咱們但願語言更加靈活,因而有了第二種改進,開發面向對象的語言,例如c++。 c++在c的基礎上增長了類的部分。但這到底意味着什麼呢?咱們再寫它的編譯器要如何考慮呢?其實,就是讓編譯器多繞個彎,在嚴格的c編譯器上增長一層類處理的機制,把一個函數限制在它處在的class環境裏,每次請求一個函數調用,先找到它的對象, 其類型,返回值,參數等等,肯定了這些後再jmp跳轉到須要的函數。這樣不少程序增長了靈活性一樣一個函數調用會根據請求參數和類的環境返回徹底不一樣的結果。增長類機制後,就模擬了現實世界的抽象模式,不一樣的對象有不一樣的屬性和方法。一樣的方法,不一樣的類有不一樣的行爲! 這裏你們就能夠看到做爲一個編譯器開發者都作了哪些進一步的思考。雖然面相對象語言有所改進,但仍是死板, 咱們仍然叫c++是static language.算法

但願更加靈活!因而咱們徹底把上面哪一個類的實現部分抽象出來,作成一套完整運行階段的檢測環境,造成第三種,動態語言。此次再寫編譯器甚至保留部分代碼裏的sytax名稱,名稱錯誤檢測,runtime環境註冊因此全局的類,函數,變量等等信息等等,咱們能夠無限的爲這個層增長必要的功能。調用函數時候,會先從這個運行時環境裏檢測因此可能的參數再作jmp跳轉。這,就是runtime。編譯器開發起來比上面更加彎彎繞。可是這個層極大增長了程序的靈活性。 例如當調用一個函數時候,前2種語言,頗有可能一個jmp到了一個非法地址致使程序crash, 可是在這個層次裏面,runtime就過濾掉了這些可能性。 這就是爲何dynamic langauge更增強壯。 由於編譯器和runtime環境開發人員已經幫你處理了這些問題。編程

好了上面說着這麼多,咱們再返回來看objective-c的這些語句:設計模式

 

 

看似很簡單的語句,可是爲了讓語言實現這個能力,語言開發者要付出不少努力實現runtime環境。這裏運行時環境處理了弱類型、函數存在檢查工做。runtime會檢測註冊列表裏是否存在對應的函數,類型是否正確,最後肯定下來正確的函數地址,再進行保存寄存器狀態,壓棧,函數調用等等實際的操做。

 

 

用c,c++完成這個功能仍是比較很是麻煩的,可是動態語言處理卻很是簡單而且這些語句讓objc語言更加intuitive。

在Objc中針對對象的函數調用將再也不是普通的函數調用,[obj function1With:var1]; 這樣的函數調用將被運行時環境轉換成objc_msgSend(target,@selector(function1With:),var1);。Objc的runtime環境是開源的,因此咱們能夠拿出一下實現作簡單介紹,能夠看到objc_msgSend由彙編語言實現,咱們甚至沒必要閱讀代碼,只需查看註釋就能夠了解,運行時環境在函數調用前作了比較全面的安全檢查,已確保動態語言函數調用不會致使程序crash。對於但願深刻學習的朋友能夠自行下載Objc-runtime源代碼來閱讀,這裏就再也不深刻講解。

 

 

如今說一下runtime的負面影響: 1. 關於執行效率問題。 「靜態語言執行效率要比動態語言高」,這句沒錯。由於一部分cpu計算損耗在了runtime過程當中,而從上面的彙編代碼也能夠看出,大概損耗在哪些地方。而靜態語言生成的機器指令更簡潔。正由於知道這個緣由,因此開發語言的人付出很大一部分努力爲了保持runtime小巧上。因此objecitve-c是c的超集+一個小巧的runtime環境。 可是,換句話說,從算法角度考慮,這點複雜度不算差異的,Big O notation結果不會有差異。( It’s not log(n) vs n2) 2. 另一個就是安全性。動態語言因爲運行時環境的需求,會保留一些源碼級別的程序結構。這樣就給破解帶來的方便之門。一個現成的說明就是,java,你們都知道java運行在jre上面。這就是典型的runtime例子。它的執行文件.class所有能夠反編譯回近似源代碼。因此這裏的額外提示就是若是你須要寫和安全有關的代碼,離objc遠點,直接用c去。

簡單理解:「Runtime is everything between your each function call.」

可是你們要明白,第二點我提到runtime並不僅是由於它帶來了這些簡便的語言特性。而是這些簡單的語言特性,在實際運用中須要你從徹底不一樣的角度考慮和解決問題。只是計算1+1,不少語言都是同樣的,可是隨着問題的複雜,項目的增加,靜態語言和動態語言就會演化出徹底不一樣的風景。

 

3. thread

「thread synchronization another notorious trouble!」

記得上學時候學操做系統這門課,裏面都會有專門一章介紹任務調度和生產者消費者的問題。 這就是爲從此使用進程、線程開發打基礎。概念很簡單,但難點在synchronization(同步),由於死鎖檢測算法不是100%有效,不然就根本沒有死鎖這個說法了。另外一個緣由是每每這類錯誤很隱晦,靜態分析很難找到。同時多線程開發抽象度較高須要經驗去把握。

整體來講,我見到的在這方面的問題能夠分爲一下幾點:

1. 對系統總體結構認識模糊

不知道多線程開發的幾個基點,看別人代碼越看越糊塗的。一會NSThread,一會Grand Central Dispatch、block,一會又看到了pthread等等。Apple封裝了不少線程的API, 多線程開發的基本結構入下圖:

Mac OS Thread Architecture

Mac OS Thread Architecture

能夠看到在多線程開發中你能夠選擇這上面這4種不一樣的方式。

Mach是核心的操做系統部分。其實這個我也不是很是熟悉,至少我尚未讀到過直接使用mach作多線程的代碼。

pthread(POSIX Threads)是傳統的多線程標準,靈活、輕巧,可是須要理論基礎,開發複雜。須要注意一點,根據apple文檔提示,在Cocoa下使用pthread須要先啓動至少一個NSThread,肯定進入多線程環境後才能夠。

NSThread是Mac OS 10.0後發佈的多線程API較爲高層,可是缺少靈活性,並且和pthread相比效率低下。

Grand Central Dispatch 是10.6後引入的開源多線程庫,它介於pthread和NSThread之間。比NSThread更靈活、小巧,而且不須要像pthread同樣考慮不少lock的問題。同時objective-c 2.0發佈的新語法特性之一blocks,也正是根據Grand Central Dispatch需求推出的。

因此在你寫多線程代碼或者閱讀多線程代碼時候,心理要先明確使用哪一種技術。

2. thread和Reference Counting內存管理形成的問題。

線程裏面的方法都要放到NSAutoreleasePool裏面嗎?

這類問題很常見,迷惑的緣由是不明白 NSAutoreleasePool 究竟是幹什麼用的。NSAutoreleasePool跟thread其實關係並不顯著,它提供一個臨時內存管理空間,比如一個沙箱,確保不會有不當的內存分配泄露出來,在這個空間內新分配的對象要向這個pool作一下注冊告訴:「pool,我新分配一塊空間了」。當pool drain掉或者release,它裏面分配過的內存一樣釋放掉。可見和thread沒有很大關係。可是,咱們閱讀代碼的時候常常會看到,新開線程的函數內老是以NSAutoreleasePool開始結束。這又是爲何呢!? 由於thread內剛好是最適合須要它的地方! 線程函數應該計算量大,時間長(supposed to be heavy)。在線程裏面可能會有大量對象生成,這時使用autoreleasepool管理更簡潔。因此這裏的答案是,不必定非要在線程裏放NSAutoreleasePool,相對的在cocoa環境下任意地方均可以使用NSAutoreleasePool。若是你在線程內不使用NSAutoreleasePool,要記得在內部alloc和relase配對出現保證沒有內存泄露。

3. 線程安全

每一個程序都有一個主線程(main thread),它負責處理事件響應,和UI更新。

更新UI問題。不少新手會由於這個問題,致使程序崩潰或出現各類問題。並且逛論壇會看到因此人都會這麼告訴你:「不要在後臺線程更新你的UI」。其實這個說法不嚴密,在多線程環境裏處理這個問題須要謹慎,並且要了解線程安全特性。

首先咱們須要把「UI更新」這個詞作一個說明,它能夠有2個層次理解,首先是繪製,其次是顯示。這裏繪製是能夠在任何線程裏進行,可是要向屏幕顯示出來就須要在主線程操做了。我舉個例子說明一下,例如如今咱們有一個NSImageView,裏面設置了一個NSImage, 這時我想給NSImage加個變色濾鏡,這個過程就能夠理解爲繪製。那麼我徹底能夠再另一個線程作這個比較費時的操做,濾鏡增長完畢再通知NSImageView顯示一下。另外一個例子就是,Twitter客戶端會把每一條微博顯示成一個cell,可是速度很快,這就是由於它先對cell作了offscreen的渲染,而後再拿出來顯示。

因此經過這點咱們也能夠獲得進一步的認識,合理設計view的更新是很是重要的部分。不少新手寫得代碼片斷沒錯,只是由於放錯了地方就致使整個程序出現各類問題。

根據蘋果線程安全摘要說明,再其它線程更新view須要使用lockFocusIfCanDraw和unlockFocus鎖定,確保不會出現安全問題。

另外還要知道經常使用容器的線程安全狀況。immutable的容器是線程安全的,而mutable容器則不是。例如NSArray和NSMutableArray。

4. Asynchronous(異步) vs. Synchronous(同步)

我在一個view要顯示多張web圖片,我想問一下,我是應該採用異步一個一個下載的方式,仍是應該採用多線程同時下載的方式,仍是2個都用,那種方式好呢?

實際上單獨用這2個方法都很差。並非簡單的用了更多線程就提升速度。這個問題同時涉及客戶端和服務器的狀況。

處理這種類型的程序,比較好的結構應該是:非主線程創建一個隊列(至關於Asynchronous任務),隊列裏同時啓動n個下載任務(至關於Synchronous任務)。這裏的n在2~8左右就夠了。這個結構下能夠認爲隊列裏面每n個任務之間是異步關係,可是這n個任務之間又是同步關係,每次同時下載2~8張圖片。這樣處理基本能夠知足速度要求和各種服務器限制。

5. thread和run­loop

runloop是線程裏的一部分,但我以爲有必要單獨拿出來寫,是由於它涉及的東西比較容易誤解,而說明它的文章又很少。

4. run­loop

thread和runloop在之前,開發者根本不太當成一個問題。由於沒有靜態語言裏runloop就是固定的線程執行loop。而如今Cocoa新手搞不明白的太多了,由於沒有從動態角度看它,首先回想一下第2點介紹的runtime概念,接着出一個思考題。

如今有一個程序片斷以下:

 

 

如今要求,作某些設計,使得當這個線程運行的同時,還能夠從其它線程裏往它裏面隨意增長或去掉不一樣的計算任務。 這,就是NSRunloop的最原始的開發初衷。讓一個線程的計算任務更加靈活。 這個功能在c, c++裏也許能夠作到可是很是難,最主要的是由於語言能力的限制,之前的程序員不多這麼去思考。

好,如今咱們對上面代碼作一個很是簡單的進化:

 

 

注意,這裏沒有作線程安全處理,記住Mutable container is not thread safe. 這個簡單的擴展,讓咱們看到了如何利用runtime能力讓線程靈活起來。當咱們從另外線程向targetQueue和actionQueue同時加入對象和方法時候,這個線程函數就有了執行一個額外代碼的能力。

有人會問,哪裏有runloop? 那個是nsrunloop? 看不出來啊。

 

 

這個結構就叫線程的runloop, 它和NSRunloop這個類雖然名字很像,但徹底不是一個東西。之前在使用靜態語言開始時候,程序員沒有什麼迷惑,由於沒有NSRunloop這個東西。 我接着來講,這個NSRunloop是如何來得。

第二段擴展代碼裏面確實沒有NSRunloop這個玩意兒,咱們接着作第3次改進。 此次咱們的目的是把其中動態部分抽象出來。

 

 

走到這裏,咱們就算是基本把Runloop結構抽象出來了。例如我有一個MyNSThread實例,myThread1。我能夠給這個實例的線程添加須要的任務,而myThread1內部的MyNSRunloop對象會管理好這些任務。

 

 

當你看懂了上面的代碼也許會感嘆,‘原來是這麼回事啊!爲何把這麼簡單的功能搞這麼複雜呢?’ 其實就是這麼回事,把Runloop抽象出來可使得線程任務管理更加loose coupling,給設計模式提供更大的空間。這樣第三方開發者不須要過深刻的涉及線程內部代碼而輕鬆管理線程任務。另外請注意,這裏MyNSRunloop, MyNSTimer等類是我寫得一個模擬狀況,真實的NSRunloop實現確定不是這麼簡單。這裏爲了說明一個思想。這種思想貫穿整個cocoa framework,從界面更新到event管理。

 

5. delegate, protocol

這個會列出來由於,我感受問它的數量僅此於內存管理部分,它們用得很頻繁,而且這些是設計模式的重要組成部分。

待寫…

 

6. event respon­der

Interface Builder First Responder

Interface Builder First Responder

使用過Xcode的開發者都知道Interface Builder這個開發組件,在Xcode4版本之後該組件已經和xcode整合到一塊兒。它是蘋果軟件開發中很是重要的部分。ib爲開發者減輕了很大一部分界面設計工做。可是其中有一個東西讓新接觸ib的開發者一頭霧水,那就是First Responder, 它是什麼東西,爲什麼它會有那麼多Actions。這節我會詳細介紹如何理解Responder和Cocoa下的事件響應鏈。

First Responder在IB屬性爲Placeholders,這意味着它屬於一個虛擬實例。就比如TextField裏面的string placeholder同樣,只是臨時顯示一下。真正的first responder會被其它對象代替。實際上,任何派生自NSResponder類的對象均可以替代First Responder。而First Responder裏面的全部Actions就是NSResponder提供的接口函數,固然你也能夠定義本身的響應函數。

MacOS在系統內部會維護一個稱爲「The Responder Chain」的鏈表。該列表內容爲responder對象實例,它們會對各類系統事件作出響應。最上面的哪一個對象就叫作first responder,它是最早接收到系統事件的對象。若是該對象不處理該事件,系統會將這個事件向下傳遞,直到找到響應事件的對象,咱們能夠理解爲該事件被該這個對象截取了。

The Responder Chain基本結構以下圖所示:

The Responder Chain

The Responder Chain

在理解了上面的概念以後,我但願使用一個例子讓你們對responder有更加具體的認識。你們都知道NSTextField這個控件,它是最多見的控件之一。它最基本功能是顯示一個字符串,若是啓用可選,那麼用戶能夠選中文本,拷貝文本,若是開啓編輯選項,還能夠運行用戶編輯文本,等等基本操做。

下面展現給你們的例子是建立一個咱們本身建立的簡單textfield叫LXTextField。它不屬於NSTextField而是派生自NSView,具備功能顯示字符串,全選字符串,響應用戶cmd+c的拷貝操做,三個基本功能。注意NSView派生自NSResponder。

 

 

運行實例,能夠看到隨着LXTextField收到系統發送的becomeFirstResponder消息,LXTextField變成responder chain中的frist responder, 這時候能夠理解爲IB裏的哪一個First Responder虛擬實例被該LXTextField取代。這時候mainMenu上哪些菜單項,例如:全選(cmd+a), 拷貝(cmd+a)等事件都會最早發給當前這個LXTextField。一旦你的LXTextField實現了NSResponder的哪些默認函數,那麼該對象就會截取系統事件。固然這些事件具體如何實現仍是須要你本身寫代碼實現。例如這裏的 – (IBAction)copy:(id)sender; 顯然我手動實現了textfield的copy能力。

注意上述代碼中我實現了一個空函數- (void)keyDown:(NSEvent *)theEvent 這意味着咱們但願LXTextField截取鍵盤事件而再也不傳遞給responder chain後續對象。固然,若是咱們但願LXTextField響應特定鍵盤事件,而其餘事件繼續傳給其餘響應對象,咱們能夠編寫以下代碼。

 

 

待寫…

 

7. mem­ory management

內存管理問題,也許是問得最多的問題了吧。

  1. 內存管理規則 Cocoa下程序開發內存管理使用ref­er­ence counting(RC)機制。從10.7之後apple開始推薦auto­matic ref­er­ence counting(ARC)機制。你們是否知道從舊時代的RC到ARC機制到底意味着什麼呢?爲何ARC從開發速度,到執行速度和穩定性都要優於rc?

開發速度不言而喻,你少寫不少release代碼,甚至不多去操心這部分。

執行速度呢?這個還要從runtime提及,還記得我在第2點說得一句話麼:「Runtime is everything between your each function call.」

RC有一個古老的內存管理哲學:誰分配誰釋放。 經過counting來肯定一個資源有幾個使用者。道理很簡單,可是每每簡單的東西人卻會犯錯。歷來沒有一個程序員能夠充滿信心的說,我寫得代碼歷來沒有過內存泄露。這樣來看,咱們就更須要讓程序能夠本身處理這個管理機制,這就須要把這個機制放到runtime裏。

因此RC->ARC就是把內存管理部分從普通開發者的函數中移到了函數外的runtime中。由於runtime的開發原型簡單,邏輯層次更高,因此作這個開發和管理出錯的機率更小。實際上編譯器開發人員對這部分通過無數次測試,因此能夠說用arc幾乎不會出錯。另外因爲編譯的額外優化,使得這個部分比程序員本身寫得代碼要快速不少。並且對於一些通用的開發模式,例如autorelease對象,arc有更優秀的算法保證autoreleasepool裏的對象更少。

  1. RC規則 首先說一下rc是什麼,r-Reference參照,引用 c-counting計數, rc就是引用計數。俗話說就是記錄使用者的數量。 例如如今我有一個房間空着,你們能夠進去隨意使用,可是你進門前,須要給門口的計數牌子+1, 出門時候-1。 這時候這個門口的牌子就是該房間裏的人數。一但這個牌子變爲0我就能夠把房間關閉。

這個規則可讓NSObject決定是否是要釋放內存。當一個對象alloc時候,系統分配其一塊內存而且object自動計數retainCount=1 這時候每當[object retain]一次retainCount+1(這裏雖然簡寫也是rc不過是巧合或者當時開發人員故意選的retain這個詞吧)每次[object release]時候retainCount-1 當retainCount==0時候object就真正把這快內存還給系統。

  1. 經常使用container的Reference Counting特性 這個規則很簡單把。可是這塊確實讓新手最頭疼的地方。問題出在,新手總想去驗證rc規則,又老是發現和本身的指望不符合。 無數次看到有人寫下以下句子

    NSLog(@」%d」,[object retainCount]);

    while([object retainCount]>0){ [object release]; }

固然了,我也作過相似的動做,那種但願一切盡在掌握中的心態。可是你會看到其餘人告訴這麼作徹底沒有意義,RC規則並非這麼用的。

首先,這個數字也許並非你心目中的哪一個。由於很難跟蹤到底哪些地方引用的該資源。你創建的資源不光只有你的代碼纔會用到,你調用的各類Framework,Framework調用的Framework,都有可能改變這個資源的retainCount。

其次,這裏每一個數字意味着有其它對象引用該資源,這樣的暴力釋放很容易致使程序崩潰。就比如,其它人也許能夠翻牌子把門口哪一個牌子上的數字改變,可是這會出現問題。還有不少人在裏面,把牌子變成0房間鎖告終果誰也出不來。又或者,減小牌子上的數字,人進的過多房間變得過於擁擠。

因此去驗證rc規則,或者單純的改變retainCount並非明智之舉。你能作的就是理解規則,使用規則,讀文檔瞭解container的引用特性。或者乾脆移到 Automatic Reference Counting (ARC) 上面。

我有一個NSMutableArray裏面保存了1000個NSString對象,我在release的時候須要循環釋放1000個string麼?仍是隻須要release NSMutableArray。

就像上面提到的,若是你瞭解container的引用特性,這個問題天然就解決了。「NSMutableArray在添加、插入objects時會作retain操做。」 經過這一句話就分析出,用戶不否須要幫助NSMutableArray釋放1000個string。回憶上面提到的管理哲學,「誰分配誰釋放」 編寫NSMutableArray的程序員很是熟悉這個規則,NSMutableArray內部retain了,NSMutableArray天然要負責release。可是NSMutableArray纔不會管你在外面什麼地方引用了這1000個string,它只管理好內部的rc就夠了。因此若是你在NSMutableArray外面對1000個string retain了,你天然須要release。相應的,你做爲建立這個NSMutableArray的程序員,你只管release這個NSMutableArray就能夠了。

最後說一下不用arc的狀況。目前狀況來看,有很多第三方的庫並未支持arc,因此若是你的舊項目使用了這些庫,請檢查是否做者發佈了新版本,或者你須要本身修正支持arc。

 

8. class heritage, category and extensions

Objective-C 的 OOP 特性提供 subclass 和 category 這2個很是重要的部分。subclass 應該反覆被各類編程書籍介紹過。它是 OOP 繼承特性的關鍵語法,它給類添加了延續而且多樣化本身的方法。能夠說沒有繼承就沒有 OOP 這玩意。而 category 相對於 subclass 就不那麼出名了。其實 category 思想出世於 smalltalk,因此它不能算是一個新生事物。

先說一下這2個特性最主要的區別。簡單能夠這麼理解,subclass 體現了類的上下級關係,而 category 是類間的平級關係。

Subclass and Category

Subclass and Category

如上圖所示,左側是subclass,能夠看到class, subclass1, subclass2是遞進關係。同時下面的子類徹底繼承父類的方法,而且能夠覆蓋父類的方法。subclass2擁有function1,function2,function3三個函數方法。function1的執行代碼來自subclass1, function2的執行代碼來自於subclass2。

右側是category。能夠看到,不管如何擴展類的category,最終就只有一個類class。category能夠說是類的不一樣方法的小集合,它把一個類的方法劃分紅不一樣的區塊。請注意觀察,每一個category塊內的方法名稱都沒有重複的。這正是category的重要要求。

通過上面簡單解釋瞭解了這2點的基本區別,如今深刻說一下category。

在Objective-c語言設計之初一個主要的哲學觀點就是儘可能讓一個程序員維護龐大的代碼集。(對於龐大的項目‘原則’和‘協議’是很是重要的東西。甚至編寫良好的文件名都是很是重要的開發技巧)根據結構化程序設計的經驗出發,把一個大塊代碼劃分紅一些小塊的代碼更便於程序員管理。因而objc借用了smalltalk的categories概念。容許程序員把一系列功能相近的方法組織到一個單獨的文件內,使得這些代碼更容易識別。

更進一步的,和c,c++這種靜態語言相比。objc把class categories功能集成到了run-time裏面。所以,objc的categories容許程序員爲已經存在的類添加新的方法而不須要從新編譯舊的類。一旦一個category加入,它能夠訪問該類全部方法和實例變量,包括私有變量。

category不只能夠爲原有class添加方法,並且若是category方法與類內某個方法具備一樣的method signature,那麼category裏的方法將會替換類的原有方法。這是category的替換特性。利用這個特性,category還能夠用來修復一些bugs。例如已經發布的Framework出現漏洞,若是不便於從新發布新版本,可使用category替換特性修復漏洞。另外,因爲category有run-time級別的集成度,因此使得cocoa程序安全性有所降低。許多黑客就是利用 category、posting2、Method Swizzling 等方法破解軟件,或者爲軟件增長新功能。一個很典型的例子就是,我原來發布的QQ表情管理器(目前已經再也不維護)。

值得注意的一點是,因爲一個類的categories之間是平級關係。因此若是不一樣categories擁有相同的方法,這個調用結果是未知的:

Category methods should not override existing methods (class or instance). Two different categories implementing the same method results in undefined behavior.

(由於posting、Method Swizzling這個話題有些深刻,本文裏我就不介紹了。有興趣自行Google)

Objc中Categories有其侷限的部分,就是你不能爲原有的class添加變量,只能添加方法。固然方法裏能夠添加局部變量。在這個侷限基礎上就有其它語言作了進一步改進,例如TOM語言就爲Categories增長了添加類變量的能力。

自從Objc 2.0之後,語言引入了一個新的特性叫作 Class Extensions, 它能夠看作是一類特殊的 category,能夠給原有類增長新的屬性和方法。

經過http://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html介紹咱們能夠看出,若是 categories 是爲類增長外部方法的話,那麼 extensions 就是用作類的內部拓展。

Class extensions 的外觀很簡單,就是一個 Category 後面括號內的名字爲空:

 

 

接下來,你就能夠給你的類裏添加屬性,方法了:

 

 

Class extensions 經常使用來定義類的私有變量和方法。

總上所屬,若是你開發時候遇到不管如何都須要爲類添加變量的狀況,最好的選擇就是subclass。相反若是你只但願增長一些函數簇。Categories是最好的選擇。而類內部須要用到的私有變量和方法則最好寫在 Class extensions 裏。

Categories關注的重心是代碼設計,把不一樣功能的方法分離開。在Objc裏由於Categories是runtimes級別的特性,因此這種分離不只體如今源碼結構上,同時體如今運行時過程當中。這意味着一個category裏的方法在程序運行中若是沒有被調用,那麼它就不會被加載到內存中。因此合理的使用categories會減小你的程序內存消耗。

因此我我的給你們的建議是,每一個Cocoa程序員都應該收集整理本身的一套NS類函數的Categories擴展庫。這對你從此程序開發效率和掌控狀況都有很大提升。

 

9. Drawing Issues

你們知道,MacOS 是一個很是注重UI的系統。因此在 MacOS 編程裏繪製是一個很是重要的部分。第9部分,我會介紹 MacOS 下繪製編程。

從繪製技術分類上看,Cocoa程序員能接觸的幾種繪製技術列表以下:

  1. Cocoa Drawing(NS-prefix)
  2. Core Graphics(CG-prefix, called Quazrtz 2D)
  3. Core Animation
  4. Core Image
  5. OpenGL

在這裏我不打算給你們介紹如何繪製具體的按鈕或者表格。只是介紹一下,它們的代碼風格,優點和限制。

Cocoa Drawing

Cocoa Drawing應該是學習Cocoa程序開發最早接觸的繪製技術。也是目前大多數MacOS程序所使用的繪製技術,其底層使用Quazrtz 2D(Core Graphics)。蘋果對應文檔爲 Cocoa Drawing Guide。Cocoa Drawing並無統一的繪製函數,全部繪製函數分散在幾個主要的NS類的下面。例如, NSImage, NSBezierPath, NSString, NSAttributedString, NSColor, NSShadow,NSGradient …

因此很簡單,當你看到以下代碼就能夠判斷,使用的是Cocoa Drawing方法

 

 

這種代碼多出如今NSView的drawRect函數內。Cocoa Drawing 的渲染上下文是 NSGraphicsContext,我不斷的看到不少新手把 NSGraphicsContext 和 CoreGraphics 的 CGContextRef 搞混。雖然它們很像而且也確實是有關係的,不過若是你不瞭解當繪製時候的 render context 不少時候將獲得一個空白頁面的結果。

Core Graphics

Core Graphics 是 Cocoa Drawing layer 的底層技術,在 iOS 開發中很是廣泛,由於 iOS 系統中並不存在 Cocoa layer 因此網上能夠找到的可能是 Core Graphics 繪製代碼段子,這給那些不瞭解 Mac 開發的新手來講形成了很大困擾。Cocoa 是 Mac OS 下的 application framework 而 iOS 下的 application framework 則是 UIKit.framework又叫 Cocoa Touch,它們分享部分代碼基礎但又不徹底同樣。例如,Cocoa Touch 下的 UIView 的渲染上下文會使用 UIGraphicsGetCurrentContext() 取得,它獲得的是一個 CGContextRef 指針,而在 NSView 裏多用 [NSGraphicsContext currentContext] 取得渲染上下文。它獲得的是一個 NSGraphicsContext 對象。固然 NSView 裏也能夠經過 CGContextRef ctx = [[NSGraphicsContext currentContext] graphicsPort]; 來取得一個 Core Graphics 渲染上下文。 可見 Mac OS 下的開發更爲靈活一些。由於 iOS 中的 UIKit 開發初期就瞄準了顯卡硬件加速,全部 UIView 都是默認 layer-backed 的。iOS 開發者必須使用 Core Graphics 和 Core Animation 這幾個相對底層的繪製技術。

請看下面等價代碼,做用是繪製一個白色矩形。可是分別使用 Core Graphics 和 Cocoa Drawing:

 

 

能夠看出,這是2種風格徹底不一樣的繪製技術。Cocoa Drawing 是分散式的繪製函數,而 Core Graphics 是傳統的相似 OpenGL 的集成式的繪製方式。其實 Cocoa Drawing 下層是 Core Graphics, Core Graphics 的下層是 OpenGL。

在 OSX 下 NSGraphicsContext 和 CGContextRef 大部分時候是能夠相互轉換的。

NSGraphicsContext 到 CGContextRef:

 

 

CGContextRef 到 NSGraphicsContext:

 

 

大部分時候使用 Cocoa Drawing 能夠繪製出須要的效果,可是某些特殊時候須要 Core Graphic 繪製,例如一些特殊的陰影,clip效果,自定義pattern phase,blending style等等。

Core Animation

若是說 Core Graphics 和 Cocoa Drawing 是通用的 UI 繪製框架的話,那麼 CA 顯然是界面動畫繪製的高級技術。 Core Animation 的對應 Cocoa Animation 部分應該是 NSAnimation 和 NSViewAnimation,但這2個差距比較大。NSAnimation 出現與 OS X 10.4,Core Animation 是 10.5 後出現的。NSViewAnimation 功能和使用相對簡單。

簡單來講,Core Animation 的做用對象是 CALayer, NSAnimation 的做用對象是 NSView。瞭解你的程序界面是在處理那種對象很重要。

Core Image

對於這個繪製技術,這篇文章給了我不少啓示你們也能夠看看。Notes on Rendering 2D Graphics on a Mac 雖然是一篇 note 可是,記錄了不少實際應用中的經歷,能夠對個各類繪製技術有一個比較全面的解析。

根據此文的介紹。Core Image 適合處理小量大圖,而很是不適合處理大量小圖。由於 CI 利用 GPU 運算,而數據到 GPU 的round-trip 時間數量級在 millisecond。這就意味着,1000 張小圖分別再 GPU 運算,時間至少再 1000*1 ms。此文做者嘗試繪製3000張小圖片,利用 Cocoa Drawing 本來耗時 750ms,可是改用CI後耗時猛增到3秒。

因此,這就是CI在osx繪製技術裏所處的宏觀角色:單圖作實時處理。

openGL

待寫…

 

10. design pattern

待寫…

1:這裏其實頗有意思,爲什麼我用「更高層次思考」,而不是「更底層次」。做爲一個編譯器和語言開發人員,面對的問題確實更底層沒錯,可是他們思考的維度更高,更抽象,這樣子。一個不算恰當的比方就好像一個三維世界的人處理二維世界的一條線的問題。

2:Posting技術在10.5之後deprecated,而且64bit run-time也再也不支持

from http://lianxu.me/2012/11/10-cocoa-objc-newbie-problems/

相關文章
相關標籤/搜索