當學完本章時,你將得到CarValet應用程序的初始版本號。以及足夠完畢本書學習的Objective-C知識。
2.1 使用模板建立Hello World 應用程序
建立Hello World演示樣例應用程序的最簡單方法,就是使用Xcode的預設模板。在接下來的步驟中,你將建立一個新的項目。而後改動它,使之能打印「Hello World」,並能在iOS模擬器上執行。在建立第一個Xcode項目時,你將學到關於Xcode這一關鍵工具的總體介紹,以及怎樣建立和操縱項目。
2.1.1 建立Hello World 項目
啓動Xcode。你將看到如圖2-1所看到的的Xcode歡迎頁面(假設不當心關閉了這個頁面。可經過選擇Window | Welcome to Xcode(歡迎使用Xcode)或按下Cmd+Shift+1組合鍵來又一次顯示)。html
單擊Create a New Project。git
也可以在Xcode中選擇File | New | Project(或按下Cmd+Shift+N組合鍵)。以後將會出現如圖2-2所看到的的模板選擇窗體。算法
默認狀況下,模板選擇窗體被嵌套在一個新的、名爲工做區(workspace)的大窗體中。這個單獨的工做區窗體中包括Xcode全部的編輯器和檢查器(inspector)特性。
編程
在圖2-2所看到的的模板選擇窗體中,選擇左邊的iOS菜單下的Application,而後選擇SingleView Application(單視圖應用程序),單擊Nextbutton。api
在這個窗體中,請遵循下列步驟:
(1) 在Product Name中輸入HelloWorld。
(2) 將Company Identifier(公司標識符)設置爲你所在的公司,使用反向域名命名法。好比,Maurice的公司標識符是com.mauricesharp或com.klmapps,Rod的公司標識符是com.strougo,而Erica的公司標識符是com.sadun。數組
假設沒有公司標識符。蘋果公司建議使用edu.self。
緩存
(3) 單擊Nextbutton。安全
但假設指定前綴爲「MS」。那麼Xcode會建立名爲MSSlideMenuController的文件和類,從而避免潛在的衝突。微信
在本書中。爲了讓命名更簡短。並未使用前綴,但是你應當依據本身的開發人員名稱或公司名稱設置前綴。
專家提示:前綴格式
最典型的類前綴是一小串大寫字母(蘋果公司推薦使用3 個字母),通常是你或你公司名稱的單詞首字母。網絡
在公司內部項目或開源項目中,這樣的格式非常常見。但可讀性並不是最好。下面是一個滑動菜單類的各類不一樣的名稱。這並不是我實際編寫過的一個類。是我想出來的。但最好仍是試着觀察一下列表中的這些類名條目,依照你瀏覽代碼時的方式,看看哪個更easy高速識別:
SlideMenuViewController
MSSlideMenuViewController
MS_SlideMenuViewController
msSlideMenuViewController
ms_slideMenuViewController
極其可能的是,全部大寫且沒有分隔符的版本號最難識別。小寫的稍稍easy點。但難以識別的程度僅次於前者。更easy識別的是那些帶有分隔符的版本號。你應當選擇能與符號名稱簡易搭配使用的類前綴。
長遠來看,你將節省大量的開發與調試時間。
• Devices(設備)——應用程序的目標設備平臺。可以是iPhone、iPad 或Universal(通用應用程序)——可以同一時候執行在這兩種平臺上的應用程序。
上一個Xcode窗體面板詢問你Hello World項目的存儲位置。
假設願意的話,Xcode還可以爲項目建立本地的git倉庫。針對當前這個Hello World演示樣例。將Source Control(源代碼控制)複選框保持爲未選中狀態。
選擇一個目錄並單擊建立。如圖2-4所看到的。
在單擊Createbutton以後,你應該能看到在Xcode中,本身的Hello World新項目已被打開。它帶有一些Xcode本身主動生成的文件。至此,你獲得一個看似不怎麼有趣,但是功能完備的應用程序。假設立馬單擊Run(執行)button,你的app會顯示一個頂部帶有狀態條的白色屏幕。
開發應用程序時,你將在Xcode中花費大量時間。圖2-5顯示了展開項目文件並選擇ViewController.m文件後的Xcode界面。
圖2-5 Xcode 項目界面的組成部分
如下是對Xcode界面的高速導航。
接下來的幾章將探討Xcode界面中不一樣組件的不少其它細節知識。如下的數字與圖2-5中顯示的數字一一相應:
(1) 單擊Runbutton(在左邊),編譯應用程序,並在模擬器或設備中啓動應用程序。單擊並保持(長按)Runbutton。你會看到額外的選項,比方用於測試或分析項目的選項。
右側的button可用於中止正在執行的應用程序、正在執行的構建或已發起的其它動做。
(2) 在這個分爲兩段的彈出式菜單button中,左段用於選擇和編輯方案(Scheme),也就是關於你想執行什麼以及怎樣執行該應用程序。右段用於選擇在哪兒執行應用程序:是在某種模擬器中仍是在某個已鏈接的設備上。
(3) Status(狀態)區域向你展現上一動做的執行結果或當前動做的進度。
動做包含構建應用程序、執行應用程序或是下載應用程序到設備上。
(4) Editor(編輯器)button用於配置編輯器(圖2-5中標爲7的區域)中顯示的內容。
左側button在圖中已被選中,僅僅顯示一件東西。即主編輯區。中間的Assistant(輔助編輯)button將編輯區劃分爲兩塊,在右側會顯示與主編輯器相關的文件。在右側一般狀況下會顯示頭文件。
最後一個button用於顯示源碼差別,並且可以查看文件的不一樣版本號。
這一點在使用源碼倉庫跟蹤代碼變更時很實用。
注意,不能同一時候顯示輔助編輯和源碼視圖。
(5) 這個區域控制Xcode中工做區視圖的總體佈局。第一個button顯示或隱藏Navigation(導航器,圖2-5中的區域6)。第二個button控制Debugger(調試器。區域10)。
最後一個button顯示Utilities(區域8和9)。
(6) Navigator(導航器)可以顯示項目的不一樣視圖。頂部的圖標控制視圖的類型。在圖2-5中,被選中的是目錄圖標,所以這個區域顯示的是文件以及基於組(group)的項目導航器。其它圖標是:項目符號導航器。用於在全項目範圍內進行搜索的放大鏡圖標,用於查看構建問題的警告三角形圖標。還有單元格測試導航器。調試導航器,斷點列表。最後是日誌視圖。easy混淆的一點是,文件和組導航器中顯示的文件結構和Finder中的文件結構。這二者可以是不一樣的。在導航器中,組被顯示爲目錄圖標。但這些與Finder中的目錄是不一樣的。假設想讓組和Finder目錄保持一致。就需要在Finder中加入這些目錄,並將源文件保存在正確的位置。
(7) Editor(編輯器)是你將在Xcode中花費大多數時間的地方。眼下,它顯示的是源代碼編輯器。
它還可以顯示Interface Builder(界面生成器)、數據模型編輯器或執行時調試測量儀器。
(8) 工具區域顯示了不一樣種類的工具,包含文件信息、高速幫助(當前被選中的內容)、數據模型細節查看器、視圖屬性(比方顏色、button標題以及尺寸,包含屏幕大小和約束)。
(9) 工具區域的底部爲庫區域。
在此處你能夠找到Interface Builder中構造應用程序所需的可拖曳組件、代碼片斷,甚至包含圖片和其它媒體文件。注意在工具區域的底部可能僅僅看到含有庫選擇圖標的一行。要展開庫。請單擊並向上拖動選擇器工具欄。
(10) 調試區域包括三個主要部分。
頂部工具欄的左邊包括用於在執行中暫停以及單步執行的控件。右側包括當前選中的文件。底部被切割成兩個主要區域,一個用於檢查變量,另一個用於打開控制檯。在整本書中,你將學習到有關的不少其它詳細知識,特別是在第14章「Instruments和調試」中。現在你對Xcode已有必定了解,該建立第一個應用程序了。
2.1.3 加入Hello World 標籤
默認狀況下。Xcode建立設置爲執行在iPhone或iPad上的項目;當建立一個項目時。下一個項目使用前一次的設置。新項目一開始就包括你建立本身的應用程序所需的所有類和支持文件。
當執行一個新項目時。它會顯示一個空白的白色屏幕。
在屏幕上看到Hello World的最快方法是往項目里加入標籤。保持項目打開,運行下面步驟(見圖2-6):
(1) 經過觀察方案彈出菜單的右側(圖2-5中的區域2),確認項目被設置爲執行在模擬器中。假設右側沒有顯示「iPhone Retina(4-inch)」,單擊方案彈出菜單的對應一邊並且選擇這一條。注意下拉菜單包括兩部分:左側是方案彈窗,右側贊成你選擇應用程序在哪裏執行。你應僅僅將Hello World標籤加入到iPhone故事板中。因而。應用程序在iPhone而不是iPad上執行是很重要的。
(2) 在Xcode項目導航器中單擊並選擇Main_iPhone.Storyboard文件。
Interface Builder會在編輯區域打開。在很罕見的狀況下,Interface Builder並無打開。這時你要檢查並肯定沒有單擊顯示源碼差別的第三個button(參見圖2-5中的區域4)。假設源碼控制button未選中,嘗試選擇一個.m或.h文件。而後再次選擇故事板文件。假設仍是不管用,那麼退出Xcode並從新啓動。
(3) 在右下方的查找面板中。輸入label並將這個標籤對象拖曳到故事板的畫布中。
(4) 在Xcode工做區左側在Utility區域中,在Attributes檢查器的文本框內輸入Hello World,將這個標籤調整得好看一些。
圖2-6顯示這個標籤在視圖控制器(view controller)的頂部水平居中。
(5) 單擊Runbutton,編譯並執行Hello World應用程序。
你甚至不需要編寫不論什麼代碼。在下一節。你將學習一些Objective-C基礎知識,回到這個Hello World應用程序,並加入一些代碼以練習Objective-C。
圖2-7 所看到的的模擬器是全尺寸的、4 英寸Retina 顯示屏,並且可能很大,特別是當在筆記本電腦屏幕上工做時。
可以使用Simulator Window | Scale 菜單更改顯示屏的顯示大小。
要成爲熟練的iOS開發人員,你需要學習Objective-C。它是iOS和Mac的主要編程語言。Objective-C是一種強大的面向對象編程語言,贊成你利用蘋果公司的Cocoa和Cocoa Touch框架,構建應用程序。在這一章,你將學習主要的Objective-C技巧,開始iOS編程。你將學到接口、方法以及不少其它其它知識。要想完畢這些學習。必須往Hello World應用程序中加入一些自定義的類,練習所學的知識。
然而。Objective-C 有太多的內容。咱們無法在這麼小的一節裏邊全部涵蓋,而這些沒有涵蓋的內容中,有一些對於編寫產品級質量的應用程序是很重要的。
學習Objective-C的一份重要材料是Learning Objective-C 2.0:A Hands-on Guide to Objective-C for Mac and iOS Developers,2nd edition。做者是Robert Clair。
還可以使用Objective-C Programming:The Big Nerd
Ranch Guide,做者是Aaron Hillegass。這本書涵蓋了更高級的知識。
它將C語言的組成部分與Smalltalk-80中產生的概念加以混合。
Smalltalk是最老和最有名的面向對象編程語言之中的一個。是由Xerox PARC開發的一種動態類型的交互式語言。Cox將Smalltalk的對象和消息傳遞系統層疊到標準C語言之上,從而構建了一種新的語言。這樣的方法贊成應用程序猿在繼續使用熟悉的C語言進行開發的同一時候。在那種語言內部使用基於對象的特性。在20世紀80年代後期,Objective-C被史蒂夫·喬布斯的計算機創業公司Next的NeXTStep操做系統選做主要開發語言。NeXTStep成爲OS X直到iOS的精神和字面意義上的先驅。
Objective-C 2.0在2007年10月隨着OS X Leopard一塊兒公佈,它引入了不少新特性,如屬性和高速迭代。
在2010年,蘋果公司更新了Objective-C語言,添加了Block這個C語言擴展。它提供了匿名函數。並贊成開發人員將Block做爲對象進行處理(你將在第13章中學習不少其它內容)。在2011年夏,蘋果公司引入了本身主動引用計數(Automatic Reference Counting,ARC),這個擴展大大簡化了開發,它贊成應用程序猿將注意力集中在應用程序語義上,而不用操心內存管理(準確地說,ARC是編譯時擴展而非語言擴展)。近期,Objective-C被擴展爲支持字面量(定義靜態對象的方式)以及索引(訪問數組和字典中元素的方式)。蘋果公司持續改進Objective-C語
言。因此要注意新的iOS和Xcode更新版本號的公佈說明。
面向對象編程使用了ANSI C沒有的表特性。
對象是一種數據結構。它關聯了一組公開聲明的函數調用。Objective-C中的每個對象包括一些實例變量(instance variable)。也就是這樣的數據結構的數據域(或字段)。還包括一些方法(method)。也就是該對象所能運行的函數調用。面向對象的代碼使用這些對象、變量和方法來引入一些編程抽象來添加代碼的可讀性和可靠性。你有時候可能會看到實例變量被縮寫爲iVar,方法被稱做消息(message)。
對象使用類(class)進行定義。
可以將類以爲是決定對象終於長什麼樣的模板:怎樣查看狀態(實例變量),以及支持什麼行爲(消息)。
類自己一般不作太多事情。它們的主要用途是建立功能完整的對象。
對象被稱做實例(instance),也就是基於類所提供模板的起做用的實體。之因此命名爲「實例變量」。是因爲它們僅僅存在於類的實例中。而不是類自身的內部。當往第4步的文本框中輸入「Hello World」時。實際上也就設置了一個UILabel對象的text實例變量的值。
UILabel類自己並無text變量可被設置。
所有這些事情,以及建立標籤實例的代碼已經本身主動完畢了。
面向對象編程讓你構建可重用的代碼並從面向過程開發的正常控制流中解耦。
面向對象的應用程序環繞着對象和它們的方法所提供的本身定義數據結構來開發。iOS的Cocoa Touch以及Mac OS X的Cocoa提供了一個包括大量這樣的本身定義對象的倉庫。
Objective-C解鎖了那個倉庫,並贊成你用最少的努力和代碼。基於蘋果公司的工具箱建立有效而強大的應用程序。
iOS 的Cocoa Touch 中以NS 開頭的類名。好比NSString 和NSArray,可追溯到NeXT公司。NS 表明NeXTStep,執行在NeXT 計算機之上的操做系統。蘋果公司於1996 年收購了NeXT。
讓大多數Objective-C的學習者感到困惑的一點,在於消息傳遞的語法——用於調用或執行類實例所實現的方法。不像函數調用時使用的「函數名(參數列表)」語法。傳遞消息給對象時要使用方括號。
一條消息讓一個對象運行一個方法。
實現這種方法。產生一個結果,是這個對象的職責。方括號裏的第一個元素是消息的接收者,也就是實現這種方法的對象;第二個元素是方法名稱以及可能會有的傳給那個方法的一些參數。它們一塊兒定義了你想要發送的消息。在C語言中。你可能會這麼寫:
<span style="font-size:14px;">printCarInfo(); // This function prints out the info on the default car</span>
但是在Objective-C裏,你這麼寫:
<span style="font-size:14px;">[self printCarInfo]; // This method prints out the info on the default car</span>
在某些語言中,你可能看到this.printCarInfo()。
在Objective-C中,self表明當前對象,大致上與this相似。
在其它語言中,你可能會使用相似someOtherObject.printCarInfo()的代碼。在還有一個對象上調用方法,若是someOtherObject擁printCarInfo()函數。在Objective-C中,可以使用下面代碼:
<span style="font-size:14px;">[someOtherObject printCarInfo]; // This method prints out the info on the default car</span>
除了Objective-C的類型以外,方法中的類型可以使用標準C語言中相同的類型。
不像函數調用。Objective-C限制了可以實現和調用方法的主體。方法屬於類。並且類的接口定義了哪些方法是公開的。或者說。是面向外部世界的聲明。
當函數包括一個或多個參數時,代碼就開始看起來不同了。假定必須將汽車對象myCar 傳遞給printCarInfo()函數。
在C語言中,你會這麼寫:
printCarInfo(myCar); // Print the info from the myCar object
在Objective-C中,你會這麼寫:
<span style="font-size:14px;">[self printCarInfo:myCar]; // Objective-C equivalent, but with poor method name</span>
<span style="font-size:14px;">[self printCarInfoWithCar:myCar]; // More clear as to which car it will print out</span>
在C語言中,你會這麼寫:
<span style="font-size:14px;">printCarInfo(myCar,10); // Print the info using a font size of 10</span>
<span style="font-size:14px;">[self printCarInfoWithCar:myCar withFontSize:10]; // Print using a font size of 10</span>
讓咱們再深刻一步。現在假定你有三個參數:汽車對象,信息的字號。還有表示文字是否需要粗體顯示的布爾值。
在C語言中,你會使用例如如下代碼:
<span style="font-size:14px;">printCarInfo(myCar, 10, 1); // Using 1 to represent the value of true in C</span>
<span style="font-size:14px;">[self printCarInfoWithCar:myCar withFontSize:10 shouldBoldText:YES];</span>
方法名與參數依次交錯放置,能有效地讓Objective-C 的消息傳遞變得easy閱讀和理解。在C 語言和其它語言中,你不得不時常參考函數定義以肯定每個參數是什麼以及參數的順序。在Objective-C 中。這些都很是清晰。就在你面前。在使用一些含有5 個或不少其它個參數的UIKit 方法調用時,你會特別明顯地體會到這一點。
在C語言中。你會使用下面代碼:
<span style="font-size:14px;">float mySpeed = calculateSpeed(100,10); // returns the speed based on distance / time</span>
<span style="font-size:14px;">float mySpeed = [self calculateSpeedWithDistance:100 time:10];</span>
要看到不少其它信息,
請看https://developer.apple.com/library/iOS/#referencelibrary/GettingStarted/RoadMapiOS/Languages/
WritingObjective-CCode/WriteObjective-CCode/WriteObjective-CCode/html。
方法可以訪問類中定義的所有東西。換句話說,可以訪問實例變量以及隨意類實例中實現的方法。
在這樣的意義上。方法如何執行對於調用者對象是透明的。
某個特定方法的實現代碼甚至是整個類可以全然改變而不需要將別的不論什麼地方改動。
這在升級或替換應用程序中的特性時是很實用的:多是讓它們更有效地更新新的硬件特性,甚至完全替換通訊的處理方式。
2.2.2 類和對象
對象是面向對象編程的核心。
可以經過構建類定義對象。類即爲建立對象的模板。
在Objective-C中。類定義描寫敘述了怎樣構建屬於這個類的新對象。好比,要建立汽車對象。需要定義Car類。並且在需要時用這個類建立新的對象。
與C語言相似。在Objective-C中實現類需要分兩處進行:頭文件和實現文件。頭文件規定了外部世界怎樣與這個類交互:實例變量及其類型。方法及其參數和返回值類型。就像契約,頭文件承諾類的實例怎樣與別的對象對接。
實現文件的內容即爲類怎樣提供實例變量的值,以及方法被調用時怎樣響應。除了頭文件裏定義的公有變量和方法。在實現文件裏也可以定義變量與方法,並且實現文件通常會包括私有的變量和方法。
每個類使用標準的C.h約定。在頭文件裏列出實例變量和方法。好比。你可能會像代碼清單2-1那樣定義SimpleCar對象。此處所看到的的Car.h頭文件包括聲明SimpleCar對象結構的接口。
代碼清單2-1 聲明SimpleCar 接口(SimpleCar.h)
<span style="font-size:14px;">#import <Foundation/Foundation.h> @interface SimpleCar : NSObject { NSString *_make; NSString *_model; int _year; } @property float fuelAmount; - (void)configureCarWithMake:(NSString*)make model:(NSString*)model year:(int)year; - (void)printCarInfo; - (int)year; - (NSString*)make; - (NSString*)model; @end</span>
在Objective-C中。你會使用identifiersLikeThis而不是identifiers_like_this。
類名首字母大寫,而其它的名稱首字母小寫。
可以在代碼清單2-1中看到。類名SimpleCar以大寫首字母開頭。實例變量fuelAmount採用駝峯式命名法。但是以小寫字母開頭。
示比例如如下:
- (void) printCarInfo
在Objective-C中。@符號用在特定的一些keyword中。此處展現的兩個元素(@interface和@end)劃分了類接口定義的開頭與結尾。類定義描寫敘述了一個含有5個方法與4個實例變量的對象。
在這4個變量中,僅僅有fuelAmount是公有的(public),意思是它在SimpleCar類的外部可以使用。
花括號裏的其它三個變量僅僅能被SimpleCar及其子類使用。這三個變量也可以定義在.m實現文件裏,但那樣的話它們將僅僅對SimpleCar類可見。假設想讓子類(比方ElectricCar)共享這些變量的話,這就成問題了。
私有變量中有兩個(_make和_model)是字符串類型。Objective-C一般使用基於NSString對象的類,而不是基於字節(byte)的用char *聲明類型的C字符串。正如在這本書中處處可見的。NSString提供的功能遠遠多於C字符串。對於這個類。可以找出字符串的長度。查找和替換子字符串,顛倒字符串。提取文件擴展名。以及不少其它。這些特性都被編寫到iOS(和Mac OS)的對象庫裏。
私有的_year變量和公有的fuelAmount變量都屬於簡單類型。前者是int類型,後者是float類型。
使用前置的下劃線字符(_)是Objective-C中區分實例變量和getter方法的通常作法。使用x=_year是直接從實例變量得到值,而x=[self year]是調用getter方法-(int)year。可以作一些必要的計算或者在返回以前暫時計算值。setter方法相似getter,僅僅只是是用於設置實例變量的值。
反覆一下,使用setter可以運行一些額外的工做,比方更新屏幕上的計數器。
你將在下面內容中以及全書中學習建立與使用getter和setter。
第一個公有方法例如如下所看到的:
<span style="font-size:14px;">configureCarWithMake:model:year:</span>
<span style="font-size:14px;">[myCar configureWithMake:c1 model:c2 year:i];</span>
每個方法都有返回參數。printCarInfo返回void,year返回int。而make和model都返回NSString*類型。與C同樣。這些表明方法返回的數據類型。
void表明這種方法不返回不論什麼東西。
在C語言中。與printCarInfo和year方法等價的函數聲明是void printCarInfo()和int year()。
使用Objective-C的「方法名分散在參數中」的方式。對新應用程序猿來講可能看起來很是奇怪,但是很是快就會變成你很是喜好的特性。
當方法名告訴你什麼什麼參數該在哪兒時,就不需要推測該傳遞什麼參數。
你會在iOS編程中屢次見到這個。特別是當使用respondsToSelector:這種方法調用時,這種方法讓你在執行時檢查對象可否響應特定的消息。
注意代碼清單2-1中的頭文件使用#import載入頭文件,而不是#include。當導入(import)頭文件時,Objective-C本身主動跳過已經被加入了的文件。
所以可以往各類各樣的頭文件里加入@import指令,而不會有不論什麼損失。
1. 定義實現文件
.h文件告訴外界怎樣與類的對象交互。.m文件或實現文件包括了賦予對象生命與力量的代碼。代碼清單2-2展現了SimpleCar類的某種實現。
代碼清單2-2 SimpleCar 類的實現文件(SimpleCar.m)
<span style="font-size:14px;">#import "SimpleCar.h" @implementation SimpleCar - (void)configureCarWithMake:(NSString*)make model:(NSString*)model year:(int)year { _make = [make copy]; _model = [model copy]; _year = year; } - (void)printCarInfo { NSLog(@"--SimpleCar-- Make: %@ - Model: %@ - Year: %d - Fuel: %0.2f", _make, _model, _year, [self fuelAmount]); } - (int)year { return _year; } - (NSString*)make { return [_make copy]; } - (NSString*)model { return [_model copy]; } @end</span>
configureCarWithMake:model:year爲每個私有實例變量設置值。
除了fuelAmount以外,不能爲當前的汽車對象單獨設置某個值。
使用訪問器方法(access method)讀取不論什麼單個元素的值是可行的。好比代碼清單2-2底部定義的-(int)year。
因爲頭文件爲fuelAmount使用了@property,因此setter和getter方法,以及下劃線版本號的變量已經爲你建立好了。
你將在本章後邊的「2.3.2節「屬性」中看到相關不少其它內容。
第一個方法設置三個非公有實例變量的值。printCarInfo將所有實例變量的當前值打印到日誌中。最後三個方法是私有實例變量的getter方法。
你可能注意到的一點是。配置方法和getter方法都與字符串的副本打交道。這是一種廣泛的防護性實踐。避免代碼意外地改動字符串的值。但當你意識到每個變量是一個指向NSString對象的指針時,就會理解了。假設將這個指針賦值給字符串參數。那麼它將與這個字符串的擁有者指向一樣的內存位置。假設原始擁有者改變了字符串的值。汽車對象會獲得新的值,因爲它指向一樣的內存地址。
賦值並返回字符串的副本,會獲得不一樣內存區域的新對象。
這些副本可以被改動。而不會改變當前汽車對象的make和model。注意。你惟一需要注意的是,這僅僅適用於長期存在的實例變量。暫時字符串和對象不需要被拷貝。
2. 建立對象
你已經學到,類定義一個或不少其它個對象。
類在執行時怎樣變成對象?要建立對象。你需要讓類爲新對象分配足夠的內存,並且返回一個指向這塊內存的指針。
而後讓新對象初始化本身。你經過調用alloc方法處理內存分配,並且初始化發生在調用init時。假設正在建立SimpleCar對象。那麼可以使用例如如下兩行代碼:
<span style="font-size:14px;">SimpleCar *myCar = [SimpleCar alloc]; [myCar init];</span>
一組嵌套消息的返回值來自最後一條消息。
在以前的代碼中,第一行爲汽車對象分配內存並且返回一個指向那個對象的指針。第二行拿到了分配好的汽車對象並且加以初始化。init方法返回初始化以後的對象。
因爲myCar已經指向正確的對象,而第二行不需要使用這個返回值。使用嵌套可以將這兩行縮短爲一行:
<span style="font-size:14px;">SimpleCar *myCar = [[SimpleCar alloc] init];</span>
你在此處看到的「分配後緊跟init」的模式是實例化對象的最多見方式。SimpleCar類指向alloc方法。
它分配足夠存儲類定義中所有實例變量的新內存塊,將所有實例變量清理爲0或nil,並返回指向這個內存塊開始位置的指針。
新分配的塊是實例,表明內存中單獨的對象。
某些類,比方視圖。使用本身定義的初始化方法,好比initWithFrame:。如你在本章後邊將看到的,可以編寫本身定義的初始化方法。比方initWithMake:model:year:fuelAmount:。這種緊隨內存分配進行初始化的模式普遍存在。
你在內存中建立這個對象。而後預設所有關鍵的實例變量。
3. 繼承方法
對象在繼承實例變量的同一時候會繼承方法實現。SimpleCar是一種NSObject,所以所有NSObject能夠響應的消息Simple Car也能夠響應。這就是myCar可使用alloc和init進行初始化的緣由。
這兩個方法是由NSObject定義的,可用於建立和初始化不論什麼SimpleCar實例。因爲它繼承自NSObject類。Objective-C中的所有類都終於繼承自NSObject,NSObject處於它們繼承樹的頂端。
提示:繼承的方法
假設查看Hello World 項目的AppDelegate 或ViewController 類的.h 文件。就會看到AppDelegate 繼承自UIResponder,並且ViewController 繼承自UIViewController。而UIViewController 接下來繼承自UIResponder。
假設選擇並右擊UIResponder,選擇Jump to Definition(跳到定義),那麼Xcode 會爲你顯示UIResponder 的聲明。在那裏可以看到,它也繼承自NSObject。
做爲還有一個演示樣例,當應用程序中有數組時,你有可能會使用NSArray或NSMutableArray—— 一種贊成你增刪元素的NSArray。所有數組方法都可以被可改動的數組以及它們的子類使用。可以統計數組中元素的個數。依據索引數字取出對象等。
警告:
有些類對「子類化」(subclassing)並不友好。
它們是做爲類簇(class cluster)來實現的。也就是,類自身會依據一些標準,建立一些其它類的對象。NSArray 和NSString 都是類簇的演示樣例。它們以最能有效利用內存的方式。使用不一樣的類建立對象。所有這些類都在文檔中作了清晰標記。在子類化系統類以前,要細緻檢查一下其是否爲類簇。
子類可以實現與超類具備一樣選擇器的方法。
在子類對象上調用此方法將運行新的方法。這取決於這種方法怎樣實現,要麼特殊化。要麼覆蓋超類行爲。
特殊化(Specializing)的意思是(運行新邏輯的同一時候)還讓超類方法運行,方法是將消息發送到super對象,super是表明超類的特殊標識符。覆蓋(Overriding)的意思是並不將消息發送到超類,超類的行爲從不運行。一個不錯的演示樣例就是初始化方法。需要確保繼承鏈中的每一個類都有計劃初始化自身。
只是方法僅僅需要記得調用自身的超類。
初始化方法老是包括下面形式的一行:
<span style="font-size:14px;">self = [super init];</span>
4. 指向對象
你已經瞭解到,使用類建立對象是很easy的事情。當擁有一個新建立(分配)和初始化的對象時。下一步就是引用並使用這個新對象。在Objective-C中,你使用*字符表示變量是指向對象的指針,這在代碼清單2-2中的_make和_model變量聲明處可以看到。_make和_model變量都指向對象(NSString)並且變量名前邊必須有*。
其它變量屬於原始類型,不是對象。
變量自身,或更準確說內存地址,保存的是值而不是對象的地址。_year變量是原始類型(int)的演示樣例,所以不需要*字符。
當向對象發送消息時。要去掉*字符。在下面代碼片斷中。myCar對象被建立爲SimpleCar類的實例,並且printCarInfo方法被調用:
<span style="font-size:14px;">SimpleCar *myCar = [[SimpleCar alloc] init]; [myCar printCarInfo];</span>
<span style="font-size:14px;">SimpleCar *sameCar = myCar;</span>
<span style="font-size:14px;">id sameCar = myCar;</span>
使用與建立HelloWorld項目時一樣的步驟:
(1) 在Xcode中,選擇File | New | Project(或按下Cmd+Shift+N組合鍵)。
(2) 選擇iOS Single View Application模板,與你建立HelloWorld項目時選中的模板一樣。
(3) 在下一個面板中,在應用程序的名稱文本框中輸入CarValet。
確保Devices被設置爲Universal。Organization和Company Identifier文本框中應該已經填入你在建立HelloWorld項目時填寫的內容。
假設需要的話可以改動這些內容,而後單擊Nextbutton。
(4) 保存項目,Xcode就會在新窗體中打開這個項目。假設已經打開一個項目。那麼在Save面板的底部可能會有Add To(加入到)選項。假設是這種話。確保選中的是相似「Don’t Add to Any Project or Workspace」(不要加入到不論什麼項目或工做區)的選項。
注意:
這個演示樣例應用程序中的代碼——以及本章中其它演示樣例的代碼——都可以在本書的演示樣例代碼中找到。參見前言。瞭解從GitHub 下載本書演示樣例代碼的具體方法。
與HelloWrold 全然同樣。 CarValet 應用程序已經帶有Xcode 模板提供的兩個類:
AppDelegate和ViewController。現在添加Car類以表示簡單的汽車:
(1) 首先,右擊Navigation窗體中的CarValet應用程序目錄,並選擇New File。也可以在菜單中選擇File | New | File或按下Cmd+N快捷鍵。
(2) 在新文件對話框中,選擇iOS下的Cocoa Touch。而後選擇Objective-C class。如圖2-9所看到的,而後單擊Nextbutton。
(4) 保存面板的底部有一塊區域可用於指定Target成員。此時,重要的是確保CarValet被選中。如圖2-11所看到的。在確認該複選框已被選中後,單擊Create,Xcode會建立Car類。並且將它放到你的項目中。
編輯Car.h頭文件,使它與代碼清單2-3保持一致。
代碼清單2-3 Car.h 頭文件 // Car.h // CarValet #import <Foundation/Foundation.h> // 1 @interface Car : NSObject { // 2 int _year; // 3 NSString *_make; // 4 NSString *_model; // 5 float _fuelAmount; // 6 } - (id)initWithMake:(NSString *)make // 7 model:(NSString *)model year:(int)year fuelAmount:(float)fuelAmount; - (void)printCarInfo; // 8 - (float)fuelAmount; // 9 - (void)setFuelAmount:(float)fuelAmount; - (int)year; // 10 - (NSString*)make; - (NSString*)model; @end
雙斜槓可用於單行或行內凝視。可以使用斜槓和星號的組合來包圍凝視塊——也就是多行凝視:
// this is a one line comment // and so is this, even though it follows the last one [MyObject doSomething]; // and this is an end of line comment /* And finally a lot of comments started by a forward-slash and asterisk that can include lots of lines and ends with an asterisk then forward-slash. */
第一個非凝視行導入了Foundation框架。這個iOS和Mac OS家中的耕田老牛。在Foundation框架中。可以找到各類東西,從數組和日期到謂詞,從URL網絡鏈接到JSON處理,還有最最重要的對象NSObject。
接下來是Car類的@interface聲明。
經過:NSObject標記,可以瞭解到Car類繼承自NSObject類。
@interface和@end之間的語句對Car類進行了定義。在@interface聲明的花括號裏,可以看到4個實例變量。用於保存汽車對象所需要的信息。這些方法定義了怎樣給汽車對象發送消息。
如下描寫敘述了代碼清單2-3中帶數字凝視的代碼行中所發生的事情:
(2) 定義Car對象(NSObject的子類)的接口。
(3) _year是汽車的生產年份,存爲一個整體對象,這是一種非對象的原始類型。
(4) _make是汽車的品牌,存爲一個NSString對象。
(5) _model是汽車的型號。存爲還有一個NSString對象。
(6) _fuel是汽車油箱裏的燃料,存爲浮點值。
(7) initWithMake:model:year:fuelAmount:方法初始化新分配的對象。並設置汽車的品牌、型號和年份,以及油箱中的燃料。
這就是前面所說的本身定義init方法。
(8) printCarInfo方法向調試控制檯打印出汽車的信息。
(9) fuelAmount和setFuelAmount這一對方法,爲_fuelAmount實例變量的getter和setter方法。
(10) 剩下的三個方法是其它私有實例變量的getter方法。
initWithMake:model:year:fuelAmount:方法是關於方法名稱和參數怎樣交替放置的清晰示例。這4個參數分別接收NSString *、NSString *、int和float類型的值。
注意這兩個方法前面的連字符,它代表這些方法由對象實例進行實現。好比,要調用[myCar printCarInfo]而不是[CarprintCarInfo]。
後者會將消息發送到Car類而不是實際的Car對象。你會在本書後面看到類方法和實例方法的對照差異(類方法由「+」而不是「-」表示),只是,更加完整的討論超出了本書範圍。
方法調用可以很是長。好比,如下的方法調用初始化iOS的UIAlert對象:
initWithTitle:message:delegate:cancelButtonTitle:otherButtonTitles:
使用託付對象是iOS中的還有一常見模式,它提供了一種方法。經過讓兩個對象使用定義好的一組消息(稱爲protocal(協議)來進行通訊。這些消息是協議提供者和託付對象之間的一種契約。本質上,託付對象承諾實現這些消息,而提供者承諾正確地使用它們。協議與類是分開定義的;它們是不一樣的事物。不論什麼類可以選擇以託付對象或實現者的身份使用協議。UIAlert採用這樣的模式以通知託付對象。用戶單擊了哪一個button。當閱讀這本書時,你會看到系統對象中更加複雜地使用託付模式的演示樣例。你還會在本身構建的一些對象裏實現這樣的模式——也就是建立協議並向對象加入托付對象代碼。
在Xcode 中,組合鍵Ctrl+Cmd+向上箭頭可以讓你移到下一個配對文件。而Ctrl+Cmd+向下箭頭可以讓你移到前一個配對文件。
這對組合鍵使得在頭文件和實現文件之間切換變得很easy。
實現的源碼一般包括在.m文件裏(m可以表明implementation或method)。顧名思義,類的方法文件提供的是方法的實現。以及這個類怎樣運行它的功能。
在更大的類中。除了會在.h文件裏定義方法外,你可能會實現其它的獲得支持的非公有方法。
不像公有方法。不需要在定義私有方法前聲明它們。編譯器足夠聰明,甚至在使用這些方法的代碼的後邊才實現這些方法的狀況下。編譯器也可以識別私有方法在哪裏。
在通讀本書的過程當中。你會看到關於這一點的不少其它內容。
此外。可以聲明僅對這個對象可見的局部實例變量。可以在@implement語句如下的花括號中實現這一點。
好比。可以加入局部的isAL emon標記變量:
@implementation Car { BOOL isALemon; }
代碼清單2-4 Car.m 實現文件 // Car.m // CarVale #import "Car.h" @implementation Car - (id)init { self = [super init]; // 1 if(self != nil) { // 2 _year = 1900; // 3 _fuelAmount = 0.0f; // 4 } return self; // 5 }
這保證了NSObject要求的不論什麼初始化邏輯。在特定於Car類的初始化邏輯以前運行完成。
(2) 檢查一下。確保self實際已經初始化。假設這種話,這個對象的剩餘部分將被建立。
(3) _year實例變量默認被設置爲1900。
(4) _fuelAmount默認被設置爲0.0f。雖然不是嚴格必要的,但在數字末尾包括f可以告訴編譯器這是float值。而不是其它類型的浮點值。
(5) self的值被返回。注意返回的內容依賴於第2步的檢查。假設超類返回nil,此處返回nil,不然會返回現已初始化的Car對象。
到此爲止,Car對象已經被初始化,但仍然不能響應以initWithMake:開頭的本身定義初始化方法和printCarInfo方法,或者不論什麼其它方法調用。假設試圖調用那些方法,你將遇到執行時應用程序崩潰,同一時候會在Debug區域的Console框中顯示一條消息「unrecognized selector sentto instance」。
向nil 發送一條消息,併發送一條未識別的消息(選擇器)
在Objective-C 中。發送消息和執行選擇器這兩個術語在本質上是同一個東西:在對象上調用方法。
雖然向nil 發送不論什麼消息都是全然安全的。但向一個未實現這條消息的對象發送一條消息會致使執行時應用程序崩潰。這是Objective-C 剛開始學習的人常犯的錯誤。並且正如你將在本書後邊看到的,存在一些方法,能夠在發送消息以前檢查對象或類可否響應這條消息(或這個選擇器)。此外還要
注意,消息可以在繼承樹的不論什麼地方實現。
也就是說,這條消息可以在特定的類中定義。也可以在這個對象繼承的不論什麼類中定義。
經過加入代碼清單2-5的內容。添加下列兩個方法:本身定義初始化方法以及printCarInfo方法。代碼清單2-5 Car.m 實現initWithMake:model:year:fuelAmount:和printCarInfo 方法 - (id)initWithMake:(NSString *)make // 1 model:(NSString *)model year:(int)year fuelAmount:(float)fuelAmount { self = [super init]; // 2 if(self != nil) { // 3 _make = [make copy]; // 4 _model = [model copy]; _year = year; _fuelAmount = fuelAmount; } return self; // 5 } - (void)printCarInfo { if(!_make) return; // 6 if(!_model) return; NSLog(@"Car Make: %@", _make); // 7 NSLog(@"Car Model: %@", _model); NSLog(@"Car Year: %d", _year); NSLog(@"Number of Gallons in Tank: %0.2f", _fuelAmount); }
(3) 檢查超類是否能夠初始化這個對象。並且假設成功的話,初始化對象的剩餘部分。
假設失敗的話,self的值會是nil。
(4) 現在爲Car對象設置所有實例變量。
(5) 到此爲止,self要麼是nil(假設超類初始化失敗的話),要麼是已經初始化的對象。
注意當初始化失敗時,返回nil是正確的作法。
(6) 僅當Car定義了make和model時纔會信息打印。
(7) 用NSLog在控制檯打印值。
真實的左花括號使用慣例本書的代碼清單讓方法的左花括號({)緊隨方法名。
這是爲了節省空間,而不是典型的代碼慣例。
依照慣例。花括號單獨佔一行。可以在Xcode 本身主動生成的代碼中看到,如
ViewController.m。
爲避免反覆勞動,可以將第一個方法簡化爲例如如下代碼:
- (id)init { return [self initWithMake:nil model:nil year:1900 fuelAmount:0.0f]; }
不論什麼其它的初始化方法都可以調用這個完整的初始化方法。這是Objective-C的還有一常見模式。
initWithMake:model:year:fuelAmount:被稱做基本初始化方法(base initializer),因爲它是不論什麼其它本身定義初始化方法都可以調用的最主要的一個。你將在整本書中看到這個模式的使用。
2. 訪問器
.h文件裏聲明的最後5個方法用於訪問汽車對象的信息。在這個演示樣例中。也就是實例變量。在Car.m文件的底部加入代碼清單2-6中的代碼。
代碼清單2-6 Car.m 文件裏。訪問器方法的實現 - (float)fuelAmount { return _fuelAmount; // 1 } - (void)setFuelAmount:(float)fuelAmount{ _fuelAmount = fuelAmount; // 2 } - (int)year { // 3 return _year; } - (NSString*)make { return [_make copy]; } - (NSString*)model { return [_model copy]; }
(2) 將_fuelAmount實例變量的值設置爲fuelAmount參數的值。
(3) 定義剩下的實例變量的getter方法。
每個getter方法都會返回相關的實例變量的值。一般。每個公有的實例變量都可以用getter和setter隱藏起來。變量本身可以在.m文件裏聲明,這樣就僅僅有它們的汽車對象可以直接訪問這些變量。即便在這個簡單的類裏。這也意味着需要聲明和定義8個額外的方法,以及大量的反覆代碼。
幸運的是。有一種更好的方法。
2.3.2 屬性
屬性讓你定義實例變量,並讓編譯器建立訪問器方法——也就是說,可以訪問(get或set)變量或信息的方法。
編譯器還可以生成下劃線版本號的變量。聲明屬性是很是easy的:
@property float fuelAmount;
float _fuelAmount; - (float)fuelAmount; - (void)setFuelAmount:(float)fuelAmount;
不論什麼非汽車對象都必須使用getter和setter方法。
變量和方法的實現是在編譯時被加入的。而假設需要作一些特殊的事情,那麼可以在.m文件裏,實現特定的訪問器方法。這樣,編譯器就會使用你的方法替代。
遵循下面步驟更新Car對象以使用屬性:
(1) 在編輯器中打開Car.m文件,並移除fuelAmount、setFuelAmount、year、make和model這些方法的實現。
(2) 打開Car.h並移除你在第1步中刪除的那些方法的聲明。
(3) 改動頭文件裏定義這些實例變量的部分,與下面內容同樣(新代碼以粗體顯示,確保刪掉下劃線):
@interface Car : NSObject
@property int year;
@property NSString *make;
@property NSString *model;
@property float fuelAmount;
- (id)initWithMake:(NSString *)make
使用屬性可能看起來是多餘的。
畢竟代碼清單2-3中的類定義代碼定義了訪問器,並且下劃線實例變量是私有的。
那麼爲何使用屬性?原來,除了節省空間以外,使用屬性比使用公開聲明的方法還有不少其它優勢。特別是封裝和點表示法。
1. 封裝
封裝贊成在應用程序中的其它部分。包含不論什麼使用對象的client,隱藏實現細節。對象的內部表示(實例變量)和行爲(方法等)與對象向外界聲明自身的方式隔離。僅僅要公開聲明的細節保持不變,就可以自由地完全改動內部實現。屬性提供的是,以一種結構良好並且受限地暴露對象狀態和其它信息的方式。
然而,屬性並不限於用做公有變量。它們在類的定義中也可發揮重要做用。屬性贊成向類定義的其它部分加入時尚的前瞻性開發技術,包含延遲初始化(lazy loading)和緩存。
這就是類既可以做爲屬性的client,也可以做爲屬性提供者的緣由。
除了隱藏細節,封裝還贊成在其它項目中重用一樣代碼。設計良好的Car 類不限於CarValet 應用程序。還可以在汽車收藏應用程序、零售庫存跟蹤應用程序,甚至在遊戲中使用。隨着你開發不少其它應用程序。細導致用封裝可以收穫一組可以縮短開發週期的即插即用的類。
類不只限於表示數據;它們還可以實現接口行爲、本身定義視圖,甚至實現server通訊。
2. 點表示法
點表示法贊成不用方括號。就可訪問對象信息。可以使用myCar.year取代[myCar year]調用,讀取year實例變量的值。雖然這可能看起來像是直接訪問year實例變量。但實際上並非如此。
屬性老是調用方法,而這些方法會訪問對象數據。由於屬性依賴於方法將數據帶到對象外部。所以並無破壞對象的封裝。
使用my.year會調用[myCar year]。
經過使用屬性,編譯器會本身主動生成必需的訪問器方法。
假設需要作一些特殊的事情,好比檢查遠程Webserver。就在.m文件裏定義year訪問器方法。而後編譯器就會用你寫的方法取代本身主動生成的方法。由於方法隱藏,屬性簡化了代碼的顯示和佈局。好比,可以經過訪問屬性,設置表的單元格文字。代碼例如如下:
myTableViewCell.textLabel.text = @"Hello World";
[[myTableViewCell textLabel] setText:@"Hello World"];
對那些使用點訪問結構的應用程序猿來講,記住點訪問結構是在調用方法而不是遍歷對象層次結構是很重要的。
要練習使用點表示法。建議將printCarInfo的實現替換爲代碼清單2-7中的代碼。
代碼清單2-7 Car.m 中更新後的printCarInfo 實現 - (void)printCarInfo { if(self.make && self.model) { // 1 NSLog(@"Car Make: %@", self.make); // 2 NSLog(@"Car Model: %@", self.model); NSLog(@"Car Year: %d", self.year); NSLog(@"Number of Gallons in Tank: %0.2f", self.fuelAmount); } else { // 3 NSLog(@"Car undefined: no make or model specified."); } }
(2) 使用點標記法,將每個變量的值打印到日誌。
(3) 假設沒有make和model。就更新日誌。
現在代碼清單2-7中的代碼更易於閱讀,更不用說。此處還在汽車對象沒有全然定義的情況下加入了一些打印日誌的代碼。
而且變量是對象級的元素而不是局部變量。這樣顯得更清晰。雖然對於這麼短的方法,這樣作彷佛可有可無。但可以想象。在需要通讀更長代碼的情況下,這樣作的益處。
也可以對初始化方法作相似改動,雖然這是有風險的,尤爲是你會使用本身定義訪問器。在本身定義訪問器中。使用點表示法多是最最危急的——參閱如下的旁註。「爲什麼使用下劃線:不用點。不用訪問器。」爲什麼使用下劃線:不用點。不用訪問器一種常見的錯誤來源。就是在屬性的本身定義訪問器或可能調用本身定義訪問器的方法中使用點表示法。
舉一個簡單的演示樣例:
- (void) setMake:(NSString*)newMake { if(![newMake isEqualToString:self.make) { self.make = newMake; } }
它檢查新的make 值是否與舊的make 值一樣,假設不一樣就,將汽車對象的make 屬性設爲新值。但是此處有一些隱藏的問題。
self.make = newMake 可以解釋爲:
[self setMake:newMake];
這是一個無限循環——固然,更準確說。在應用程序崩潰以前是無限循環。
正確的作法是,在setter 方法中使用下劃線版本號的變量。此處賦值變爲:
_make = newMake;
代碼清單2-8 ViewController.m 文件裏的viewWillAppear:方法 - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; Car *myCar = [[Car alloc] init]; // 1 [myCar printCarInfo]; // 2 myCar.make = @"Ford"; // 3 myCar.model = @"Escape"; myCar.year = 2014; myCar.fuelAmount = 10.0f; [myCar printCarInfo]; // 4 Car *otherCar = [[Car alloc] initWithMake:@"Honda" // 5 model:@"Accord" year:2010 fuelAmount:12.5f]; [otherCar printCarInfo]; // 6 }
假設返回看看代碼清單2-7,在printCarInfo方法中。你會看到檢查make和model不爲nil的if語句以及當它們爲nil時的結果消息。
(3) make、model、year和fuelAmount被一一設置。
NSString值的雙引號前都有個@前綴,浮點值的末尾有個f。
(4) printCarInfo第二次被調用,這一次make和model已設置。所以有關福特汽車的信息會被打印。
(5) 一個新的汽車對象被建立。並使用本身定義初始化方法設置值。
(6) 不像第(2)步中在簡單init後調用printCarInfo的情形,此次調用會打印出本田汽車的信息,因爲make和model都已被定義。
當執行這段代碼時,會在控制檯看到例如如下內容:
2013-07-02 08:35:44.267 CarValet[3820:a0b] Car undefined: no make or model specified.
2013-07-02 08:35:44.269 CarValet[3820:a0b] Car Make: Ford
2013-07-02 08:35:44.269 CarValet[3820:a0b] Car Model: Escape
2013-07-02 08:35:44.270 CarValet[3820:a0b] Car Year: 2014
2013-07-02 08:35:44.270 CarValet[3820:a0b] Number of Gallons in Tank: 10.00
2013-07-02 08:35:44.270 CarValet[3820:a0b] Car Make: Honda
2013-07-02 08:35:44.271 CarValet[3820:a0b] Car Model: Accord
2013-07-02 08:35:44.271 CarValet[3820:a0b] Car Year: 2010
2013-07-02 08:35:44.272 CarValet[3820:a0b] Number of Gallons in Tank: 12.50
2.4 屬性:另外兩個特性
當前版本號的CarValet應用程序是下一章需要的。
本節內容涵蓋CarValet項目的兩種不一樣的變化形式。在本章的演示樣例代碼中也包括了這兩種變化形式。
讀者中有人可能會懷疑,在汽車對象建立後改動make、model和year是不是個好主意。
到眼下爲止,你已經依照屬性默認的讀寫配置使用它們。然而,將屬性設置爲僅僅讀是很是easy的。
所有需要作的就是在聲明屬性時添加一點額外內容。屬性聲明的通常形式例如如下:
@property <(qualifier1, qualifier2, ...)> <type> <property_name>;
當中。限定符能讓你改動很是多東西。包含將屬性設置爲僅僅讀。併爲內存管理指定不一樣級別的對象所有權,甚至改動默認getter和setter方法的名稱。
所以,假設想要不一樣的行爲,僅僅需要改動默認值就能夠。
故而。建立僅僅讀屬性很easy,僅僅需要包括readonly限定符。
想將make、model和year設置爲僅僅讀,僅僅需要改動.h文件裏的定義:
@property (readonly) int year;
@property (readonly) NSString *make;
@property (readonly) NSString *model;
改動成功以後,試着構建這個項目。你會在ViewController.m中獲得三個錯誤,提示你在嘗試設置僅僅讀屬性的值。現在。刪除或凝視掉ViewController中設置僅僅讀值的這些代碼。
但是,假如你想實現這些屬性在外部僅僅讀。而在內部可以讀寫。也很是easy,因爲可以在.m文件裏又一次聲明屬性。可以經過在Car.m文件的@implementation語句以前添加例如如下代碼。添加或覆蓋類的接口定義:
@interface Car() @property (readwrite) int year; @property NSString *make; @property NSString *model; @end
- (void)shoutMake;
- (void)shoutMake { self.make = [self.make uppercaseString]; }
... qfuelAmount:12.5f]; [otherCar printCarInfo]; [otherCar shoutMake]; [otherCar printCarInfo]; }
本身定義getter 和setter
在還有一版本號中,有時你並不想使用編譯器生成的默認getter和setter方法。好比。若是你想讓self.fuelAmount返回值爲公升而不是加侖,但僅當新屬性showLiters值爲YES時纔會如此。
於是,你還會想要經過使用isShowLiters。以訪問這個新屬性。
加入這個屬性僅僅需要一行代碼。在已有屬性的下邊加入下面代碼:
@property (getter = isShowingLiters) BOOL showLiters;
你沒有使用aCar.showLiters檢查這個變量的值,而是使用aCar.isShowingLiters—— 一個更具描寫敘述性的名稱。設置這個值仍然使用
aCar.showLiters: if(aCar.isShowingLiters) { aCar.showLiters = NO; }
@property (setter = setTheFuelAmountTo:) float fuelAmount;
需要發送一條消息以調用本身定義setter方法。下列語句能運行:
[aCar setTheFuelAmountTo:20.0f];
aCar.setTheFuelAmountTo = 20.f;
- (float)fuelAmount { if(self.isShowingLiters) { return (_fuelAmount * 3.7854) ; } return _fuelAmount; }
在加入這種方法以後,你會注意到一個黃色的警告三角形。內容是「writable atomicproperty…」。這是什麼?在此處,這個警告是全然正確的。
還有一個屬性限定符是變量的原子性。
也就是說,是否在不論什麼時刻僅有一個對象能訪問這個變量(原子或同步訪問),仍是多個對象可以同一時候在多個線程中訪問這個對象(非原子或非同步訪問)?
當在多線程環境中進行開發時,可使用atomic屬性。確保賦值依照預期運行。當將一個對象設置爲atomic時。編譯器會在它被訪問或改動以前,加入本身主動爲對象加鎖的代碼,並且在以後加入解鎖代碼。這就確保了不管是否有併發線程,設置或讀取對象的值均可以被完整地運行。
然而,設置原子性(atomic)的成本高得離譜,並且有無限等待解鎖的危急。
所有屬性默認都是原子的,但是可以使用nonatomic屬性限定符。這是一般更加安全且性能更好的替代者:
@property (nonatomic) NSString *make;
原子屬性。用加鎖/解鎖行爲。可確保對象從開始到結束獲得完整更新,以後纔會運行興許的讀取或變動行爲,但是原子屬性應當僅在需要時才使用。
有人提出,訪問器一般並不是加鎖的合適地點。並不能確保線程安全。即便所有屬性都是原子的。對象也可能會被設置爲無效狀態。
在實踐中,大多數屬性會被標記爲nonatomic。處理可能的線程安全訪問的問題會用到其他機制。
更深刻的討論可參見Learning Objective C 2.0,第2版,Robert Clair著。
改動Car.h中的fuelAmount屬性聲明,糾正錯誤(當改動時,可以順手爲所有屬性添加nonatomic限定符,包含showLiters):
@property (nonatomic) float fuelAmount;
otherCar.showLiters = YES; [otherCar printCarInfo];
但是,這是本章末尾挑戰題3的內容。
2.5 子類化和繼承:挑戰一下
隨着汽車廠商持續改進使用燃料的方式,客戶要求你能夠表示一輛混合動力汽車。你的任務是建立繼承自Car類的HibridCar類,並且加入一個方法,以返回直到汽車的電池電量和燃料耗盡汽車所跑的里程數。
能夠將這種方法命名爲-(float)milesUntilEmpty。
線索:
不要忘記跟蹤每加侖英里數(Miles Per Gallon,MPG),這樣可以計算汽車失去動力前的距離。好比。2013 款豐田普銳斯混合動力汽車可以達到42MPG。假設油箱裏剩下10 加侖,那麼理論上這輛車在油箱耗盡以前可以行駛402 英里。
思考幾分鐘。
想到解決方法了嗎?
閱讀如下的內容。看看怎樣作。
繼承和子類化
在Objective-C中。每個新類都派生自已有的類。代碼清單2-3到2-7中描寫敘述的Car類繼承自NSObject——Objective-C類樹的根類。每個子類加入或改動從父類(也稱爲超類)繼承來的狀態和行爲。
Car類向它所繼承的NSObject類中加入了一些實例變量和方法。
HibridCar類繼承自Car類並添加了一些功能。根據混合動力汽車能夠達到的MPG。計算汽車在燃料耗盡前能夠行駛的距離。代碼清單2-9和2-10展現了實現HibridCar類的一種可能方法。
本章的演示樣例代碼在目錄「CarValet HybridCar」中包括代碼清單2-9到2-11的項目。
代碼清單2-9 HybridCar.h 頭文件 // HybridCar.h // CarValet #import "Car.h" @interface HybridCar : Car @property (nonatomic) float milesPerGallon; - (float)milesUntilEmpty; - (id)initWithMake:(NSString *)make model:(NSString *)model year:(int)year fuelAmount :(float)fuelAmount MPG:(float)MPG; @end
這個屬性存儲了這輛混合動力汽車所能達到的每加侖英里數。milesUntilEmpty返回這輛汽車使用油箱當前含量(fuelAmount)可以行駛的千米數,自定義初始化方法添加了一個MPG參數以設置milesPerGallon。
代碼清單2-10顯示了HybridCar類可能的實現文件。 代碼清單2-10 HybridCar.m 實現文件 // HybridCar.m // CarValet #import "HybridCar.h" @implementation HybridCar - (id)init { self = [super init] ; if (self != nil) { _milesPerGallon = 0.0f; } return self; } - (id)initWithMake:(NSString *)make model:(NSString *)model year:(int)year fuelAmount:(float)fuelAmount MPG:(float)MPG { self = [super initWithMake:make model:model year:year fuelAmount:fuelAmount]; if(self != nil) { _milesPerGallon = MPG; } return self; } - (void)printCarInfo { [super printCarInfo]; NSLog(@"Miles Per Gallon: %0.2f", self.milesPerGallon); if(self.milesPerGallon > 0.0f) { NSLog(@"Miles until empty: %0.2f", [self milesUntilEmpty]); } } - (float)milesUntilEmpty { return (self.fuelAmount * self.milesPerGallon); } @end
方法主體看上去會是這樣:
return [self initWithMake:nil model:nil year:1900 fuelAmount:0.0f MPG:0.0f];
然而,這樣作會產生隱藏的bug和/或維護成本。若是Car有若干子類,可能有混合動力型、電動型以及柴油型。甚至可能有子類的子類。如GasElectricHybrid、DieselElectricHybrid等。
將生產年份的默認值設置爲0,從而能夠很是easy地檢測到是否存在忘記設置的值。假設每個子類的init方法都使用對應類的本身定義初始化方法。那就不得不改動每個子類中的值。忘記值的改動就會引入bug。
然而,假設init方法使用[super init],而後設置特定於子類的默認值,那麼僅僅需要在一個地方進行改動就能夠。
此處有個不錯的演示樣例,使用的initWithMake:model:year:fuelAmount:MPG:是子類本身定義初始化方法繼承超類方法,並添加額外功能——詳細地設置了milesPerGallon。首先,調用超類的initWithMake:model:year:fuelAmount:方法,初始化Car對象的屬性。而後初始化HybridCar對象詳細的值。詳細化(specialization)是繼承中很是強大的一部分,贊成每個類僅僅作它需要作的事情。當詳細化與封裝結合時,可以讓一個類的開發人員僅僅聚焦於那個類,利用繼承鏈上方的公有方法和屬性以加速開發過程。
milesUntilEmpty方法用於計算在油箱耗盡前這輛汽車還能再跑多少英里。它使用一個簡單的公式,將MPG乘以油箱中燃料的加侖數。
在真實的混合動力汽車中,算法將很是可能複雜得多。
最後一步是往CarValet應用程序的ViewController中添加一個HybridCar類的實例。你需要在ViewController.m文件的頂部加入#import "HybridCar.h"語句。而後將代碼清單2-11中的內容加入到viewWillAppear:方法中。
代碼清單2-11 加入一輛混合動力汽車到ViewController.m 文件裏 HybridCar *myHybrid = [[HybridCar alloc] initWithMake:@"Toyota" model:@"Prius" year:2012 fuelAmount:8.3f MPG:42.0f]; [myHybrid printCarInfo];
假設執行CarValet應用程序,就將在調試控制檯看到例如如下信息:
2013-07-03 08:39:45.458 CarValet[9186:a0b] Car Make: Toyota
2013-07-03 08:39:45.458 CarValet[9186:a0b] Car Model: Prius
2013-07-03 08:39:45.459 CarValet[9186:a0b] Car Year: 2012
2013-07-03 08:39:45.459 CarValet[9186:a0b] Number of Gallons in Tank: 8.30
2013-07-03 08:44:39.419 CarValet[9346:a0b] Miles Per Gallon: 42.00
2013-07-03 08:44:39.419 CarValet[9346:a0b] Miles until empty: 348.60
HybridCar類可以用多種不一樣的方式進行定義和實現。花點時間建立一些變化形式。關鍵是要開始適應Objective-C的語法。可以經過完畢本章結尾的挑戰題,進行不少其它練習。
當繼續閱讀本書,並且繼續編寫本身的應用程序時。Objective-C的語法和模式會變成你的次日性。
2.6 小結
本章提供無刪節的、大信息量的對Xcode、Objective-C語法、對象、類、屬性和繼承的介紹,此外還讓你使用Xcode練習建立項目,以及Objective-C概念。
本章仍是在你通讀本書時,可以返回查看的一章。要想得到最大的價值。應該試着直接在Xcode中試驗本章中討論的所有內容。花時間擺弄演示樣例應用程序。親本身主動手得到的經驗是獲取iOS開發關鍵技能的最好方法。
學習Objective-C需要的不不過一章。假設要認真學習iOS編程。並且這些概念對你來講還很是生疏。那麼請考慮搜尋專門爲這個平臺的新手介紹這些技術的單一主題書籍。考慮Learning Objective-C 2.0:A Hands-on Guide to Objectiv-C for Mac and iOS Developers。第2版,Robert Clair著;或者Programming in Objective-C。第5版,Stephen G. Kochan著。對於Xcode,查找Xcode 4 Unleashed,第2版。Fritz F. Anderson著;或者Xcode 4 Developer Reference。Richard Went著,Richard Went爲Xcode 5提供了更新的版本號(雖然Xcode 4版本號也是實用的)。
蘋果公司也提供很好的 Objective-C 2.0 簡單介紹。 位於http ://developer.apple.com/Mac/library/do cumentation/Cocoa/Conceptual/ObjectiveC/Introduction/introObjectiveC.html。
在本章。你建立了CarValet應用程序。在下一章,你將在這個項目的基礎上構建並學習故事板,這是一種圖形化地建立應用程序中所有屏幕的方式。並且將它們組裝在一塊兒。
你還將學習不少其它關於Objective-C和iOS編程中一些重要技術的知識。
2.7 挑戰題
1. 更新printCarInfo方法。當僅僅有make是nil時打印「Car undefined:no make specified」。當僅僅有model爲nil打印「Car undefined:no model specified」。假設二者都爲nil。那麼仍然打印「Car undefined:no make or model specified」。
可以經過調用initWithMake:model:year:fuelAmount:的變種,建立汽車測試對象以檢查代碼。
2. 建立Car類的子類ElectricCar。花點時間設計該類:實例變量,對已有方法的改動,以及不論什麼獨有的方法。當作完這些後,要麼開始實現,要麼繼續閱讀以瞭解一些可能的方式。設計ElectricCar類有若干方式。
一部分選項取決於你想從Car類繼承多少東西。在描寫敘述汽車的實例變量中。惟獨fuel多是個問題。
電動汽車使用充電器。可以重用fuel並且僞裝它就是charge(充電器)。那麼你需要作的不過改動printCarInfo並且添加一個方法。用於打印剩餘電量。還可以添加一個實例變量用於表示每千瓦時的行駛距離,並且使用那個值計算這輛汽車剩餘的可行駛里程。
3. 在2.4節的「本身定義getter和setter」部分。可以看到怎樣依據fuelAmount返回美國加侖或公升數。
但是printCarInfo老是打印加侖數。改動printCarInfo。使得在isShowingLiters爲NO時打印加侖數,爲YES時打印公升數。當printCarInfo使用公升時。改動Car類,使得可以用英國加侖、美國加侖和公升打印結果。
你需要找到一種方法,設置燃料用哪一種單位進行顯示。假設使用BOOL類型,注意有可能多個BOOL變量同一時候被設置爲YES。
《iOS開發全然上手——使用iOS 7和Xcode 5開發移動與平板應用》試讀電子書免費提供,有需要的留下郵箱,一有空即發送給你們。
別忘啦頂哦!