文章目錄
2,從Hello,World!開始
本系列講座有著很強的前後相關性,如果你是第一次閱讀本篇文章,為了更好的理解本章內容,筆者建議你最好從本系列講座的第1章開始閱讀,請點擊這裡。
現在筆者假設大家已經有了開發的環境。好了,我們開始構築我們的第一個程式。
在開始第一個程式之前,筆者需要提醒大家一下,如果手裡面有開發環境的話並且是第一次親密接觸Xcode的話,為了可以熟悉開發環境,強烈建議按照筆者的步驟一步一步的操作下去。儘管如此,筆者還是為大家準備了已經做好的代碼,點擊這裡下載。
2.1,構築Hello, World
第一步,啟動Xcode。初次啟動的時候,也許會彈出一個“Welcome to Xcode”的一個對話方塊。這個對話方塊和我們的主題沒有關係,我們可以把它關掉。
第二步,選擇螢幕上部菜單的“File->New Project”,出現了一個讓你選擇項目種類的對話方塊。你需要在對話方塊的左邊選擇“Command Line Utility” ,然後在右邊選擇“Foundation Tool”,然後選擇“Choose...”按鈕。2.1所示。
圖2-1,建立項目
注意
也許有人會問,你不是要講解iPhone的開發,那麼為什麼不選擇“iPhone OS”下面的“Application”呢?
是這樣的,在這個系列當中,筆者主要側重於Objective-C的文法的講解,為了使得講解簡單易懂,清除掉所有和要講解的內容無關的東西,所以筆者在這裡只是使用最簡單的命令列。
第三步,Xcode會提問你項目的名字,在“Save As”裡面輸入“02-Hello World”,然後選擇“Save”。2-2所示
圖2-2,輸入項目的名字
第四步,得到一個2-3所示的一個畫面。嘗試一下用滑鼠分別點擊左側視窗欄裡面的“02-Hello World”,“Source”.“Documentation”,“External Frameworks and Libraries”,“Products”,然後觀察一下右邊的視窗都出現了什麼東西。一般來說,“02-Hello World”就是項目的名字下面是項目所有的檔案的列表。項目下面的子目錄分別是和這個項目相關的一些虛擬或者實際上的目錄。為什麼我說是虛擬呢?大家可以通過Finder開啟你的工程檔案的目錄,你會發現你的所有檔案居然都在根目錄下,根本就不存在什麼“Source”之類的目錄。
圖2-3,項目瀏覽視窗
第五步,選擇螢幕上方菜單的“Run”然後選擇“Console”,出現了2-4所示的畫面,用滑鼠點擊視窗中間的“Build and Go”按鈕。
圖2-4,運行結果畫面
如果不出什麼意外的話,大家應該看到我們熟悉得不能再熟悉的“Hello Wolrd!” 。由於我們沒有寫任何的代碼,所以從理論上來說,這部分代碼不應該出現編譯錯誤。好的,從下面開始,筆者要開始對這個Hello World裡面的一些新鮮的東西進行講解。
2.2,標頭檔匯入
在Java或者C/C++裡面,當我們的程式需要引用外部的類或者方法的時候,需要在程式源檔案中包含外部的類以及方法的包(java裡面的jar package)或者標頭檔(C/C++的.h), 在Objective-C裡面也有相類似的機制。筆者在這一節裡面將要向大家介紹在Objective-C裡面,標頭檔是怎樣被包含進來的。
請同學們到Xcode開發環境的左側視窗裡面,點擊Source檔案夾,然後就在右側部分看到了代碼源檔案的列表,找到02-Hello World.m之後單擊會在Xcode的視窗裡面出現,雙擊滑鼠代碼會在一個新視窗出現,請同學們按照這種方法開啟"02-Hello World.m"。
對於Java程式來說,來源程式的尾碼為.java,對於C/C++代碼來說,尾碼為c/cpp,現在我們遇到了.m。當Xcode看到了.m檔案之後,就會把這個檔案當作Objective-C檔案來編譯。同學們也許會猜到,當Xcode遇到c/cpp,或者java的時候也會對應到相應的語言的。
好的,我們順便提了一下Xcode對.m檔案的約定,現在我們開始從第一行代碼講起,請參看下列代碼:
1 #import <Foundation/Foundation.h>
2
3 int main (int argc, const char * argv[]) {
4 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
5
6 // insert code here
7 NSLog(@"Hello, World!");
8 [pool drain];
9 return 0;
10 }
11
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// insert code here
NSLog(@"Hello, World!");
[pool drain];
return 0;
}
有過C/C++經驗的同學看到第一行,也許會覺得有些親切;有過Java經驗的同學看到第一行也許也會有一種似曾相識的感覺。同學們也許猜到了這是幹什麼用的,沒錯,這個正是標頭檔。不過,在C/C++裡面是#include,在java裡面是import,這裡是#import。
在C/C++裡面會有#include互相包含的問題,這個時候需要#ifdef來進行編譯的導向,在Xcode裡面,同學們可以"放心的"包含各種東西,這個沒有關係,因為我們的編譯器有足夠的“聰明”,因為同一個標頭檔只是被匯入一次。除了#import變得聰明了一點之外,和#include的功能是完全一樣的。
我們再來看看我們的另外一個新的朋友---Foundation.h。這個是系統架構Foundation framework的標頭檔,有了它你可以免費的擷取系統或者說蘋果公司為你精心準備的一系列方便你使用的系統功能,比如說字串操作等等。Foundation架構從屬於Cocoa框架組,Cocoa的另外一個架構為Application Kit,或者是UIKit,其中前者的應用對象為MAC OS,後者的應用對象為iPhone OS。本系列入門指南將只是使用Foundation,因為筆者需要向同學們介紹Objective-C的基本使用方法,為了避免過多的新鮮東西給同學們造成閱讀上的困難,所以命令列就已經足夠了。
說到這裡,筆者需要澄清一點,其實MAC OS的Cocoa和iPhone的Cocoa是不一樣的,可以說,其中iPhone是MAC OS的一個子集。
2.3,main函數
有過C/C++或者java經驗的同學們對第3行代碼應該很熟悉了,是的大家都一樣主程式的入口都是main。這個main和C/C++語言裡面的main是完全一樣的,和java語言在本質上也是完全一樣的。因為Objective-C完全的繼承了C語言的特性。確切的說,不是說Objective-C和C語言很相似,而是Objective-C和C語言是完全相容的。
關於main函數是幹什麼用的,筆者就不在這裡羅嗦了,有興趣的同學可以找一本C語言的書看看。
2.4,關於NSAutoreleasePool
自己動手,豐衣足食---
在第4行,我們遇到了另外一個新鮮的東西,這就是NSAutoreleasePool。
讓我把這個單詞分為三部分,NS,Autorelease和Pool。
當我們看到NS的時候,也許不知道是代表著什麼東西。NS其實只是一個首碼,為了避免命名上的衝突。NS來自於NeXTStep的一個軟體,NeXT Software的縮寫,NeXT Software是Cocoa的前身,一開始使用的是NS,為了保持相容性所以NS一直得以保留。在多人開發的時候,為了避免命名上的衝突,開發組的成員最好事先定義好各自的首碼。但是,最好不要有同學使用NS首碼,這樣會讓其他人產生誤解。
略微有些遺憾的是,Objective-C不支援namespace關鍵字,不知道後續的版本是否會支援。
下面我們討論一下Autorelease和Pool。
程式在執行的時候,需要向系統申請記憶體空間的,當記憶體空間不再被使用的時候,毫無疑問記憶體需要被釋放,否則有限的記憶體空間會很快被佔用光光,後面的程式將無法得到執行的有效記憶體空間。從電腦技術誕生以來,無數的程式員,我們的無數先輩都在為管理記憶體進行努力的工作,發展到現在,管理記憶體的工作已經得到了非常大的完善。
在Objective-C或者說Cocoa裡面,有三種記憶體的管理方式。
第一種,叫做“Garbage Collection”。這種方式和java類似,在你的程式的執行過程中,始終有一個高人在背後準確地幫你收拾垃圾,你不用考慮它什麼時候開始工作,怎樣工作。你只需要明白,我申請了一段記憶體空間,當我不再使用從而這段記憶體成為垃圾的時候,我就徹底的把它忘記掉,反正那個高人會幫我收拾垃圾。遺憾的是,那個高人需要消耗一定的資源,在攜帶裝置裡面,資源是緊俏商品所以iPhone不支援這個功能。所以“Garbage Collection”不是本入門指南的範圍,對“Garbage Collection”內部機制感興趣的同學可以參考一些其他的資料,不過說老實話“Garbage Collection”不大適合適初學者研究。
第二種,叫做“Reference Counted”。就是說,從一段記憶體被申請之後,就存在一個變數用於儲存這段記憶體被使用的次數,我們暫時把它稱為計數器,當計數器變為0的時候,那麼就是釋放這段記憶體的時候。比如說,當在程式A裡面一段記憶體被成功申請完成之後,那麼這個計數器就從0變成1(我們把這個過程叫做alloc),然後程式B也需要使用這個記憶體,那麼計數器就從1變成了2(我們把這個過程叫做retain)。緊接著程式A不再需要這段記憶體了,那麼程式A就把這個計數器減1(我們把這個過程叫做release);程式B也不再需要這段記憶體的時候,那麼也把計數器減1(這個過程還是release)。當系統(也就是Foundation)發現這個計數器變成了0,那麼就會調用記憶體回收程式把這段記憶體回收(我們把這個過程叫做dealloc)。順便提一句,如果沒有Foundation,那麼維護計數器,釋放記憶體等等工作需要你手工來完成。
這樣做,有一個明顯的好處就是,當我們不知道是A先不使用這段記憶體,還是B先不使用這段記憶體的時候,我們也可以非常簡單的控制記憶體。否則,當我們在程式A裡面釋放記憶體的時候,還需要看看程式B是否還在使用這段記憶體,否則我們在程式A裡面釋放了記憶體之後,可憐的程式B將無法使用這段記憶體了。這種方式,尤其是在多線程的程式裡面很重要,如果多個線程同時使用某一段記憶體的時候,安全的控制這些記憶體成為很多天才的程式員的夢魘。
如果有同學搞過COM的話,那麼應該對Release/AddRef很熟悉了,其實Obejctive-C和他們的機制是一樣的。
接下來,我需要解釋一下Autorelease方式。上述的alloc->retain->release->dealloc過程看起來比較令人滿意,但是有的時候不是很方便,我們代碼看起來會比較羅嗦,這個時候就需要Autorelease。Autorelease的意思是,不是立即把計數器減1而是把這個過程放線上程裡面加以維護。當線程開始的時候,需要通知線程(NSAutoreleasePool),線程結束之後,才把這段記憶體釋放(drain)。Cocoa把這個維護所有申請的記憶體的計數器的集合叫做pool,當不再需要pool(水池)的時候就要drain(放水)。
筆者想要說的是,雖然iPhone支援Autorelease但是我們最好不要使用。因為Autorelease方式從本質上來說是一種延遲釋放記憶體的機制,手機的空間容量有限,我們必須節約記憶體,確定不需要的記憶體應該趕快釋放掉,否則當你的程式使用很多記憶體的情況下也許會發生溢出。這一個習慣最好從剛剛開始學習使用Objective-C的時候就養成,否則長時間使用Autorelease會讓你變得“懶散”,萬一遇到問題的時候,解決起來會非常耗費時間的。所以,還是關於記憶體管理,我們還是自己動手,豐衣足食。當然筆者不是說絕對不可以使用,而是當使用Autorelease可以明顯減低程式複雜度和易讀性的時候,還是考慮使用一下換一下口味。
說到這裡,可能有的同學已經開始發暈了,認為這個東西比較難以理解。是的,筆者在這裡只是介紹一個大概的東西,在這裡只要瞭解計數器的概念就可以了,筆者將在隨後的章節裡面對這個功能加以詳細論述,請同學們放心,這個東西和Hello World一樣簡單。
關於Pool
在使用Pool的時候,也要記住系統給你的Pool的容量不是無限大的,從這一點來說和在現實世界的信用卡比較相似。
你可以在一定程度透支,但是如果“忘記掉”信用卡的額度的話,會造成很大的系統風險。
第三種,就是傳統而又原始的C語言的方式,筆者就不在這裡敘述了。除非你在Objective-C裡面使用C代碼,否則不要使用C的方式來申請和釋放記憶體,這樣會增加程式的複雜度。
線程是什麼東西?
線程指的是進程中一個單一順序的控制流程。它是系統獨立調度和指派的基本單位。同一進程中的多個線程將共用該進程中的全部系統資源,比如檔案描述符和訊號處理等等。 一個進程可以有很多線程,每個線程並存執行不同的任務。
2.5,關於[[NSAutoreleasePool alloc] init];
關於程式第4行等號右邊出現的括弧以及括弧裡面的內容,筆者將在後續的章節裡面介紹。在這裡,同學們可以理解為,通過告訴Objective-C編譯器[[NSAutoreleasePool alloc] init],編譯器就會成功的編譯產生NSAutoreleasePoo對象的代碼就可以了。
2.6,Objective-C裡面的注釋
同學們在第6行看到了//的注釋,這個和C++以及Java是一樣的,表示這一行的內容是注釋,編譯器將會忽略這一行的內容。筆者在上面說過Objective-C完全相容C語言,所以C語言裡面傳統的/**/在Objective-C裡面也是有效。
2.7,命令列輸出
第7行,我們看到了NSLog這個函數。NS上面已經講過了,我們都知道Log是什麼意思,那麼這段代碼的意思就是輸出一個字串,Xcode的代碼產生器自己把字串定義為“Hello, World!”。NSLog相當於C語言裡面的printf,由於我們是在使用Objective-C所以筆者將會和同學們一起,在這裡暫時忘記掉我們過去曾經熟悉的printf。
有眼光銳利的同學會發現在字串的前面多了一個@符號,這是一個什麼東西呢?
如前所述,Objective-C和C是完全相容的,但是NSLog這個函數需要的參數是NSString,這樣就產生了一個問題,如果使用C的字串方式的話,為了保持和C的相容性編譯器將會把字串理解為C的字串。為了和C的字串劃清界限,在C的字串前面加上@符號,Objective-C的編譯器會認為這是一個NSString,是一個NSLog喜歡的參數。
為什麼NSLog或者Cocoa喜歡使用NSString? 因為NSString封裝了一系列的字串的方法比如字串比較,字串和數字相互轉換等等的方法,使用起來要比C的字串方便的多。
2.8,本章總結
非常感謝同學們耐心的看到這裡!
通過理解本章的內容,同學們應該可以使用Xcode建立一個命令列的工程,理解.m檔案的基本要素,理解記憶體的管理方法的思路,還有Objective-C的注釋的寫法,以及命令列的輸出方法。
是不是很簡單又很有樂趣呢?筆者將會盡最大努力把看起來複雜的東西講解的簡單一些,並且真心的希望大家可以從中找到樂趣。
下一章我們要講解一個同學們已經很熟悉的一個概念,Class也就是類。