在 本教程的第一部分中,你學會了面向對象設計的基本概念:對象,繼承以及模型-視圖-控制器(MVC)模式。你初步完成了一個叫作 Vehicles 的程序,它幫助你更好的理解所學的這些概念。
在這第二部分中,你將學習多態性以及其它一些面向對象編程的關鍵概念:類工廠方法和單例。
若是你已經完成了本教程的前半部分,那就太好了!你能夠在本教程中繼續使用以前您所使用的工程。然而,若是你剛剛開始閱讀本篇教程,你也能夠從這裏下載咱們爲你準備的第一部分完整工程。html
關於多態的普通定義來源於它的希臘詞根 – 「Poly」 表示不少, 「Morph」 表示形式。
在計算機科學中,這個詞有特別的定義,依據 Free Online Dictionary of Computing網站的解釋:
一個變量,它在可能指向一個對象,這個對象的類在編譯時還未知,可是會在運行時根據實際指向的類執行相應的反饋。
這些定義最終能夠歸結爲「一個對象同時能夠成爲不一樣的事物」。
Objective-C 中的多態性有一些子類型,可是其中最主要的兩種類型,也是最多見的兩種就是修飾模式和適配器模式。ios
蘋果公司的基礎文檔 Cocoa 設計模式中有這樣的解釋:
修飾設計模式將額外的職責動態的附加給一個對象。修飾模式爲用於拓展功能性的子類化提供了靈活的選擇。就像子類化同樣,修飾模式能讓你不用修改原來的代碼就能合併添加新的功能。 修飾類包含了一個被拓展行爲類的對象。
在Objective-C中,一個修飾模式的最典型例子就是類別的使用。
類別是iOS中一種特別的類,它能讓你在不繼承一個類,也不須要修改這個類的源代碼的狀況下爲這個類添加額外你所須要的方法。它主要被用來拓展iOS自帶的UIKit組件。
類別與子類之間的區別很是的簡單:類別能讓你爲一個存在的類添加新的方法,可是你不能修改已經存在的方法。你不能爲一個類別添加新的特性或者實例變量 – 你只能使用那些原本就存在的。若是你想添加新的特性或者實例變量,那你就要考慮使用繼承來建立一個子類,並添加你想要添加的特性和方法了。
可是若是你不須要這樣作呢?假如你只是須要將你常用的一些方法封裝進UIKit對象呢?在這種狀況下,類別就是你的最佳解決方案。
在你的練習應用中,你將爲UIAlertView添加一個簡便方法,以免爲一個簡單的警告界面重複的書寫 分配-初始化-顯示
這些步驟。編程
打開 FileNewFileCocoa Touch, 並選擇 Objective-C Category:
將 Convenience 做爲類別名填入第一欄,在第二欄選擇添加一個UIAlertView的類別:
一旦你建立完成這些文件,你就會發現 Xcode 給類別不一樣的文件名以區分這是一個類別文件,就像下面顯示的這樣:
這種 [原類名]+[類別名] 的形式同時指明瞭被修飾的類名和類別自己的名字。你甚至能夠在同一個應用中爲同一個類添加各類不一樣的類別;這樣還能使這個類別在其它的應用中更容易被使用。
在類別中建立一個方法,就像爲一個普通的類建立方法同樣。由於你將要建立一個UIAlertView 的新的實例,而不是使用已經存在的實例,打開 UIAlertView+Convenience.h 文件, @interface
行以後添加以下方法聲明:設計模式
// Shows a UIAlertView with the given title and message, and an OK button to dismiss it. + (void)showSimpleAlertWithTitle:(NSString *)title andMessage:(NSString *)message; |
接着,打開 UIAlertView+Convenience.m 文件,添加以下方法實現:數組
+ (void)showSimpleAlertWithTitle:(NSString *)title andMessage:(NSString *)message { UIAlertView *simpleAlert = [[UIAlertView alloc] initWithTitle:title message:message delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; [simpleAlert show]; } |
這裏所作事很是簡單 — 你只是集成了一些你要重複使用的代碼,它產生一個簡單的警告窗口,上面帶一個可讓窗口消失的取消按鈕。
接着,打開 VehicleDetailViewController.m 文件,並添加以下導入語句:安全
#import "UIAlertView+Convenience.h" |
在這個文件的底部,你會發現一些 IBAction
方法,方法體中僅僅有 TODO
註釋。像下面這樣更新goForward
, goBackward
, stopMoving
, 和 makeNoise
方法以使用你的新類別:多線程
-(IBAction)goForward { [UIAlertView showSimpleAlertWithTitle:@"Go Forward" andMessage:[self.detailVehicle goForward]]; } -(IBAction)goBackward { [UIAlertView showSimpleAlertWithTitle:@"Go Backward" andMessage:[self.detailVehicle goBackward]]; } -(IBAction)stopMoving { [UIAlertView showSimpleAlertWithTitle:@"Stop Moving" andMessage:[self.detailVehicle stopMoving]]; } -(IBAction)makeNoise { [UIAlertView showSimpleAlertWithTitle:@"Make Some Noise!" andMessage:[self.detailVehicle makeNoise]]; } |
編譯並運行你的應用;選擇一輛車之後,隨意點擊一個除了 「Turn…」 以外的按鈕,你會看到根據不一樣車所顯示的對應的消息。好比,若是你在不一樣車中都點擊「Make Some Noise!」 按鈕,你看到的將會是下面這樣:
可是若是你要作一些更復雜的事情呢 – 好比你須要從你顯示的 UIAlertView 中獲取一些信息呢?這個時候,適配器模式和委託就要派上用場了。app
再看蘋果文檔中的解釋 Cocoa Fundamentals Guide:
適配器設計模式將一個類的接口轉變爲另一種用戶指望的接口。適配器讓那些由於接口不適配而衝突的類可以一塊兒工做。對目標對象的類實現瞭解耦。
協議是 Objective-C 中適配器的最主要的例子。它能夠指定一些能被任何類所實現的方法。它們一般被用於 DataSource
和 Delegate
方法,可是也能夠用於幫助兩個徹底不相關類之間的通訊。
這種模式的優點在於只須要某個類聲明它聽從這個協議,不管這個類是個模型或者視圖又或是控制器都不要緊。它只想知道在另一個類裏所發生的事,併爲此實現全部須要的方法。
爲了知道用戶但願車所應轉彎的角度,你就須要利用 UIAlertViewDelegate 協議以獲取用戶輸入UIAlertView的數據。ide
打開 VehicleDetailViewController.h 並將它聲明爲聽從 UIAlertViewDelegate 協議,像下面這樣將協議名加入一對括號中:函數
@interface VehicleDetailViewController : UIViewController <UIAlertViewDelegate> |
若是要將上面這行語句翻譯爲中文,將會是:「這是一個VehicleDetailViewController 它是UIViewController 的子類,並聽從於 UIAlertViewDelegate 協議」。若是一個類聽從於多個協議,你只須要將它們一塊兒列在括號中並用逗號分開就好了。
你將用這些來實現一個獲取用戶指望轉彎度數的機制。
打開 VehicleDetailViewController.m 將 turn
方法替換爲以下的實現:
-(IBAction)turn { //Create an alert view with a single text input to capture the number of degrees //to turn your vehicle. Set this class as the delegate so one of the delegate methods //can retrieve what the user entered. UIAlertView *turnEntryAlertView = [[UIAlertView alloc] initWithTitle:@"Turn" message:@"Enter number of degrees to turn:" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Go!", nil]; turnEntryAlertView.alertViewStyle = UIAlertViewStylePlainTextInput; [[turnEntryAlertView textFieldAtIndex:0] setKeyboardType:UIKeyboardTypeNumberPad]; [turnEntryAlertView show]; } |
該方法建立了一個帶輸入框的 UIAlertView ,用來提示用戶輸入一個數字。
接下來,你須要爲 UIAlertView 實例添加一個委託方法做爲用戶輸入一個數字之後的回調函數。添加以下的方法:
#pragma mark - UIAlertViewDelegate method -(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { //Note: Only one alert view will actually declare this class its delegate, so we can // proceed without double-checking the alert view instance. If you have more than // one alert view using the same class as its delegate, make sure you check which // UIAlertView object is calling this delegate method. if (buttonIndex != alertView.cancelButtonIndex) { //Get the text the user input in the text field NSString *degrees = [[alertView textFieldAtIndex:0] text]; //Convert it from a string to an integer NSInteger degreesInt = [degrees integerValue]; //Use the simple alert view to display the information for turning. [UIAlertView showSimpleAlertWithTitle:@"Turn" andMessage:[self.detailVehicle turn:degreesInt]]; } //else the user has cancelled, and we don't need to do anything. } |
以上的代碼實現了一個選定的 UIAlertViewDelegate 中的一個方法,它用於監聽UIAlertView中某個按鈕被按下的事件。編譯並運行你的程序;從列表裏選擇一輛車,點擊 Turn 按鈕,並輸入一個須要轉彎的角度,就像這樣:
若是你點擊了Cancel,將不會發生任何事,由於你在委託的實現中忽略了它。然而,若是你點擊 Go!,前面那個 UIAlertView 將會消失,下面這個 UIAlertView 將會出現:
你的應用如今在功能上已經完整了。然而,你總會但願將你的代碼寫的更加優雅,更易於管理和擴展。是時候介紹另外兩個面向對象的設計模式了,它們將大大簡化你的編程工做。
儘管在面向對象編程中你能夠運用大量的模式(事實上, Eli Ganem 已經發表了一篇相關的 教程),其中有兩種對你的‘車’應用來講顯得特別的有用:類工廠方法 和 單例。
它們都被普遍的運用於 iOS 開發,理解它們將有效的幫助你理解將來你做爲一個iOS開發者所要接觸到的大部分代碼。
類工廠的主要理念在於產生並返回一個特定類的實例對象,並在產生對象時儘量的預填充數據。相比調用 alloc/init
再設置特性,使用類工廠的一個顯而易見的好處就是,你的代碼將會縮短不少。
這樣,你就能使用一個方法來建立一個對象並初始化它的全部特性,而不須要用一個方法來建立對象,再用不少行代碼來設置各類特性。與此同時,這種技術還有兩個不太明顯的好處。
其一,它強制了每一個使用你的類的人必須提供你所須要的每一項數據,以建立一個功能完整的實例對象。鑑於你在本教程的前面部分所建立的那些對象,你可能也發現了,每每很容易就忘記了一個或兩個特性的初始化。有了類工廠方法, 你將被強制關注你建立對象所須要的每一項特性。
其二,公認的減小了使用了 ARC 的代碼將產生的問題,在 iOS 5以後類工廠方法將返回自動釋放池對象,釋放調用者而沒必要在之後再釋放。你可能不須要擔憂這個問題,除非你須要兼容老的代碼,可是這依舊是一個值得注意的問題。
打開Vehicle.h 文件並聲明以下類工廠方法,該方法的參數表明了一個車的全部基本參數:
//Factory Method + (instancetype)vehicleWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource wheels:(NSInteger)numberOfWheels; |
instancetype
類型是一個稍微安全版本的 id
。一個 id
類型的參數或者返回值,將會接受任何 NSObject 的子類,instancetype
做爲一個方法簽名,它告訴你在這個實例被初始化後,你將收到的必定是這個類或者它的子類的實例。
注意: 更多關於instancetype
的介紹,請閱讀 NSHipster
另一件須要關注的事是有關於類工廠方法和繼承:由於類工廠方法返回一個徹底初始化的對象,因此在父類中你要當心的使用它們,它返回一個特殊類的對象。
打開 Vehicle.m 文件,添加以下類工廠方法的實現:
#pragma mark - Factory method + (instancetype)vehicleWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource wheels:(NSInteger)numberOfWheels; { //Use self in the superclass to ensure you're getting the proper return type for each of the subclasses. Vehicle *newVehicle = [[self alloc] init]; //Set the provided values to the appropriate instance variables. newVehicle.brandName = brandName; newVehicle.modelName = modelName; newVehicle.modelYear = modelYear; newVehicle.powerSource = powerSource; newVehicle.numberOfWheels = numberOfWheels; //Return the newly created instance. return newVehicle; } |
這裏的類工廠方法初始化了對象並設置了特性。由於車將會有子類,因此請確保你使用的是 [[self alloc] init]
而不是 [[Vehicle alloc] init]
。這樣, 像 Car 這樣的子類也可使用這個繼承的類工廠方法來獲取一個Car對象,而不是 Vehicle 對象。
注意: Quality Coding 網站有一篇深刻分析此話題的文章 How to Botch Your Objective-C Factory Method。
打開 Car.h 文件並聲明以下的類工廠方法:
//Factory Method +(Car *)carWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource numberOfDoors:(NSInteger)numberOfDoors convertible:(BOOL)isConvertible hatchback:(BOOL)isHatchback sunroof:(BOOL)hasSunroof; |
由於你須要的是有關於 Vehicle 類的全部信息,而不只僅是輪子的個數,你將全部有關Vehicle 類的特性和帶有Car-特色的特性做爲方法的參數。
打開 Car.m 文件,將 init
方法替換爲以下的類工廠方法實現:
#pragma mark - Factory Method +(Car *)carWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource numberOfDoors:(NSInteger)numberOfDoors convertible:(BOOL)isConvertible hatchback:(BOOL)isHatchback sunroof:(BOOL)hasSunroof { //Create the car object using the superclass factory method. Car *newCar = [Car vehicleWithBrandName:brandName modelName:modelName modelYear:modelYear powerSource:powerSource wheels:4]; //Set the car-specific properties using the passed-in variables. newCar.numberOfDoors = numberOfDoors; newCar.isConvertible = isConvertible; newCar.isHatchback = isHatchback; newCar.hasSunroof = hasSunroof; //Return the fully instantiated Car object. return newCar; } |
注意依據以往的經驗法則來講,你並不必定必須從 init
方法和類工廠方法中二選一;然而在本例中你將不會再直接使用 init
方法,取而代之的類工廠方法將負責全部init
方法曾經所作的事。這種管理舊代碼的方法是合理的,由於你將不會再須要它們。
接下來,打開 VehicleListTableViewController.m 並更新 setupVehicleArray
方法以對你建立的每一個汽車類使用新的類工廠方法,就像下面這樣:
//Create a car. Car *mustang = [Car carWithBrandName:@"Ford" modelName:@"Mustang" modelYear:1968 powerSource:@"gas engine" numberOfDoors:2 convertible:YES hatchback:NO sunroof:NO]; //Add it to the array [self.vehicles addObject:mustang]; //Create another car. Car *outback = [Car carWithBrandName:@"Subaru" modelName:@"Outback" modelYear:1999 powerSource:@"gas engine" numberOfDoors:5 convertible:NO hatchback:YES sunroof:NO]; //Add it to the array. [self.vehicles addObject:outback]; //Create another car Car *prius = [Car carWithBrandName:@"Toyota" modelName:@"Prius" modelYear:2007 powerSource:@"hybrid engine" numberOfDoors:5 convertible:YES hatchback:YES sunroof:YES]; //Add it to the array. [self.vehicles addObject:prius]; |
編譯並運行你的應用;一切看起來就像往常同樣,可是你知道實際上在建立你的 Vehicle 數組的時候,你的代碼量大大減小了。如今你能夠將這種模式應用到 Motorcycle 和 Truck 類上了。
在 Motorcycle.h 文件中,添加以下新的類工廠方法聲明:
//Factory Method +(Motorcycle *)motorcycleWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear engineNoise:(NSString *)engineNoise; |
在此狀況下,你將添加一些特定的參數以建立 Motorcycles的實例。
如今打開 Motorcycle.m 並將 init
方法替換爲你的類工廠方法的實現,就像下面這樣:
#pragma mark - Factory Method +(Motorcycle *)motorcycleWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear engineNoise:(NSString *)engineNoise { //Create a new instance of the motorcycle with the basic properties by calling the Factory //method on the superclass. Motorcycle *newMotorcycle = [Motorcycle vehicleWithBrandName:brandName modelName:modelName modelYear:modelYear powerSource:@"gas engine" wheels:2]; //Set the Motorcycle-specific properties. newMotorcycle.engineNoise = engineNoise; return newMotorcycle; } |
打開 Truck.h 並添加以下類工廠方法聲明:
//Factory Method +(Truck *)truckWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource wheels:(NSInteger)numberOfWheels cargoCapacityCubicFeet:(NSInteger)cargoCapacityCubicFeet; |
就像以前同樣,你將帶入一些特定於你新的 Vehicle 實例的參數 — 在此狀況下, 卡車。
打開 Truck.m, 添加以下類工廠方法的實現 (本例中沒有能夠替代的 init
方法):
#pragma mark - Factory Method +(Truck *)truckWithBrandName:(NSString *)brandName modelName:(NSString *)modelName modelYear:(NSInteger)modelYear powerSource:(NSString *)powerSource wheels:(NSInteger)numberOfWheels cargoCapacityCubicFeet:(NSInteger)cargoCapacityCubicFeet { //Create a new instance using the superclass's factory method. Truck *newTruck = [Truck vehicleWithBrandName:brandName modelName:modelName modelYear:modelYear powerSource:powerSource wheels:numberOfWheels]; newTruck.cargoCapacityCubicFeet = cargoCapacityCubicFeet; //Return the newly created truck instance. return newTruck; } |
如今你已經爲 Motorcycle 和 Truck 建立了你的類工廠方法,回到 VehicleDetailsViewController.m 並更新你的代碼以利用新的類工廠方法,就像下面這樣:
//Add a motorcycle Motorcycle *harley = [Motorcycle motorcycleWithBrandName:@"Harley-Davidson" modelName:@"Softail" modelYear:1979 engineNoise:@"Vrrrrrrrroooooooooom!"]; //Add it to the array. [self.vehicles addObject:harley]; //Add another motorcycle Motorcycle *kawasaki = [Motorcycle motorcycleWithBrandName:@"Kawasaki" modelName:@"Ninja" modelYear:2005 engineNoise:@"Neeeeeeeeeeeeeeeeow!"]; //Add it to the array [self.vehicles addObject:kawasaki]; //Create a truck Truck *silverado = [Truck truckWithBrandName:@"Chevrolet" modelName:@"Silverado" modelYear:2011 powerSource:@"gas engine" wheels:4 cargoCapacityCubicFeet:53]; [self.vehicles addObject:silverado]; //Create another truck Truck *eighteenWheeler = [Truck truckWithBrandName:@"Peterbilt" modelName:@"579" modelYear:2013 powerSource:@"diesel engine" wheels:18 cargoCapacityCubicFeet:408]; [self.vehicles addObject:eighteenWheeler]; |
編譯並運行你的應用;一切仍是像往常同樣工做。然而,你縮短並簡化了你的代碼,將你常常須要重複使用的代碼轉移到能夠重用的類工廠方法中去。
類工廠方法不只使用方便,還大大下降了再不經意間漏掉一個特性的可能性。它們可能很常見,像 NSString 的 stringWithFormat:
或 UIButton 的buttonWithType:
– 如今你已經將它們添加到你本身的車類及其它的子類中!
類工廠方法中一種頗有用也很特別的方法就是 單例。它確保了一個類的一個特定實例永遠只被初始化一次。
這對於那些只須要一個實例的東西來講是很棒的 — 好比, UIApplication 單例 sharedApplication
— 或者那些初始化開銷很大的類,又或者雖然所存的數據很小可是你的應用從始至終都須要獲取並更新它。
在 Vehicles 應用中,你會發現有一個數據可能咱們從始至終都須要接觸或者更新的:車的列表。這個列表也違反了 MVC 原則,由於它讓 VehicleListTableViewController 來管理它的建立和生存。經過將車的列表移交到它本身的單例類中去,你的代碼在未來會更具可拓展性。
打開 FileNewFileObjective-C Class 並建立一個名爲VehicleList 的 NSObject 的子類。打開VehicleList.h 並添加以下類方法聲明和一個用於存儲車的數組特性:
//The list of vehicles. @property (nonatomic, strong) NSArray *vehicles; //Singleton Instance + (VehicleList *)sharedInstance; |
接着,打開 VehicleList.m 並添加以下單例類工廠方法的實現:
+ (VehicleList *)sharedInstance { //Declare a static instance variable static VehicleList *_vehicleList = nil; //Create a token that facilitates only creating this item once. static dispatch_once_t onceToken; //Use Grand Central Dispatch to create a single instance and do any initial setup only once. dispatch_once(&onceToken, ^{ //These are only invoked the onceToken has never been used before. _vehicleList = [[VehicleList alloc] init]; _vehicleList.vehicles = [VehicleList initialVehicleList]; }); //Returns the shared instance variable. return _vehicleList; } |
注意你將 _vehicleList
實例變量和 onceToken
GCD 標記聲明爲 static
變量。這意味着這個變量將存在於這個應用的整個聲明週期中。這將從兩個方面有助於單例的建立:
_vehicleList
實例變量是否爲空, GCD 能更快的檢測 onceToken
是否被執行過了,以相應的決定是否須要建立 _vehicleList
實例。使用 GCD 來執行這個檢測操做同時也是線程安全的,由於 dispatch_once
確保了當它被從多線程調用的時候,下一個對象只有在當前線程結束以後纔會被容許建立實例。_vehicleList
實例不會被意外的覆蓋掉,由於靜態變量只能被初始化一次。若是任何人在你的_vehicleList
變量被初始化後,不經意間另外調用了一次[[VehicleList alloc] init]
,將不會對你現有的 VehicleList 對象有任何效果。接下來,你須要將車的建立工做從 VehicleListTableViewController 轉移到 VehicleList 類中。
首先,在VehicleList.m文件的頭部導入 Car, Motorcycle, 和 Truck 類:
#import "Car.h" #import "Motorcycle.h" #import "Truck.h" |
接着,在 VehicleList.m文件中添加以下方法:
+ (NSArray *)initialVehicleList { //Initialize mutable array. NSMutableArray *vehicles = [NSMutableArray array]; //Create a car. Car *mustang = [Car carWithBrandName:@"Ford" modelName:@"Mustang" modelYear:1968 powerSource:@"gas engine" numberOfDoors:2 convertible:YES hatchback:NO sunroof:NO]; //Add it to the array [vehicles addObject:mustang]; //Create another car. Car *outback = [Car carWithBrandName:@"Subaru" modelName:@"Outback" modelYear:1999 powerSource:@"gas engine" numberOfDoors:5 convertible:NO hatchback:YES sunroof:NO]; //Add it to the array. [vehicles addObject:outback]; //Create another car Car *prius = [Car carWithBrandName:@"Toyota" modelName:@"Prius" modelYear:2007 powerSource:@"hybrid engine" numberOfDoors:5 convertible:YES hatchback:YES sunroof:YES]; //Add it to the array. [vehicles addObject:prius]; //Add a motorcycle Motorcycle *harley = [Motorcycle motorcycleWithBrandName:@"Harley-Davidson" modelName:@"Softail" modelYear:1979 engineNoise:@"Vrrrrrrrroooooooooom!"]; //Add it to the array. [vehicles addObject:harley]; //Add another motorcycle Motorcycle *kawasaki = [Motorcycle motorcycleWithBrandName:@"Kawasaki" modelName:@"Ninja" modelYear:2005 engineNoise:@"Neeeeeeeeeeeeeeeeow!"]; //Add it to the array [vehicles addObject:kawasaki]; //Create a truck Truck *silverado = [Truck truckWithBrandName:@"Chevrolet" modelName:@"Silverado" modelYear:2011 powerSource:@"gas engine" wheels:4 cargoCapacityCubicFeet:53]; [vehicles addObject:silverado]; //Create another truck Truck *eighteenWheeler = [Truck truckWithBrandName:@"Peterbilt" modelName:@"579" modelYear:2013 powerSource:@"diesel engine" wheels:18 cargoCapacityCubicFeet:408]; [vehicles addObject:eighteenWheeler]; //Sort the array by the model year NSSortDescriptor *modelYear = [NSSortDescriptor sortDescriptorWithKey:@"modelYear" ascending:YES]; [vehicles sortUsingDescriptors:@[modelYear]]; return vehicles; } |
以上方法建立或者重置車的列表,並能在任什麼時候候被調用。
你會發現大部分代碼都是從VehicleListTableViewController轉移過來的,可是如今全部的車都被添加到新建立的一個局部變量 vehicles
數組,而不是 VehicleListTableViewController的 self.vehicles。
如今你能夠回到 VehicleListTableViewController.m 並移除三樣咱們再也不須要的內容:
setupVehiclesArray
方法,並刪除其在awakeFromNib
中的調用。vehicles
實例變量並刪除其在 awakeFromNib
中的初始化調用。Car.h
, Motorcycle.h
, 和 Truck.h
的#imports
語句。你的VehicleListTableViewController的私有接口以及 awakeFromNib
的實現如今看起來應該是這樣的:
@interface VehicleListTableViewController () @end @implementation VehicleListTableViewController #pragma mark - View Lifecycle - (void)awakeFromNib { [super awakeFromNib]; //Set the title of the View Controller, which will display in the Navigation bar. self.title = @"Vehicles"; } |
你將注意到Xcode 提示你有三個錯誤,由於你有三個地方使用了 vehicles 特性以填充 UITableViewDataSource 和 segue 處理方法。你須要用你新的單例取代它們。
首先,在 VehicleListTableViewController.m 文件頭部導入 VehicleList 類,以便你可使用單例:
#import "VehicleList.h" |
接着,找到三個 Xcode 提示出錯的地方,並將代碼更新爲使用 VehicleList 單例的vehicles
數組,就像下面這樣:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [[VehicleList sharedInstance] vehicles].count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath]; Vehicle *vehicle = [[VehicleList sharedInstance] vehicles][indexPath.row]; cell.textLabel.text = [vehicle vehicleTitleString]; return cell; } #pragma mark - Segue handling - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([[segue identifier] isEqualToString:@"showDetail"]) { NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow]; Vehicle *selectedVehicle = [[VehicleList sharedInstance] vehicles][indexPath.row]; [[segue destinationViewController] setDetailVehicle:selectedVehicle]; } } |
編譯並運行你的應用;你會看到和以前同樣的列表,可是你會所以而睡得更安穩,由於你知道應用背後的代碼更加乾淨,簡潔也更易於擴展了。
基於以上這些改變,你將可以輕易的爲這個列表添加新的Vehicles。好比,你想添加一個新的 UIViewController 來容許用戶添加他們本身的 Vehicle, 你就只須要將它添加到單例的 Vehicles 數組。
又或者,你想讓用戶可以編輯Vehicle對象,你能確保全部的數據都被存儲並返回,而不須要爲VehicleListViewController實現一個 delegate。
有關於單例有一件事特別須要注意:它們將在你的應用的整個生命週期中生存着,所以你不該該給它們加載太多數據。它們在輕量級的數據存儲以及使對象們對整個應用範圍內可見是很棒的。
若是你須要存儲大量的數據,那麼你可能須要尋找一些更加健壯的工具來幫助存儲和檢索數據,就像 Core Data 。
在一個單獨的應用中,你運用基本對象,繼承,MVC模式,多態性還有單例和類工廠方法建立了一個乾淨的,面向對象的應用程序。你能夠經過 這裏回顧本工程的完成版代碼。
有關於單例的更多介紹,你能夠閱讀 Mike Ash的一篇極棒的文章 the Care and Feeding of Singletons。
若是你須要瞭解更多有關於面向對象模式的信息,Eli Ganem 寫了一篇很好的教程,叫作 iOS Design Patterns , 它回顧了一些本教程中提到的內容,接着它介紹了一些更加高級的設計模式,你能夠用來構建更加優雅的代碼。若是你有任何問題,歡迎在下面的評論中留言!