承接上節
在本節中,將對之前的故事版進行改進
改進故事版I
先大致介紹一下協議
在大多數應用程序中,都會存在一些需要與其他對象中的觸發行爲者進行交換的數據,這導致互相關聯、不獨立的代碼難以維護和複用。相應的解決辦法,就是使用協議(protocol)。
協議增加了代碼的可維護性、複用性和靈活性。可以認爲協議是請求者和供應者之間的合同。供應者常被叫做代理(delegate),統一實現一些方法,從而請求者可以調用。每個方法通常用於如下三個目的之一
- 從代理請求數據,如Car對象
- 在改變時通知代理,以允許存儲、取消或再次顯示數據
- 指向特定的行爲,如呼叫某人前來取車
協議的主要優點在於複用性,並且每個實現了所規定的方法的類,都可以成爲代理。相似的是,任何類都可以使用協議方法,請求信息或發起行爲。
要了解更多關於協議的信息,查看Programming in Objective-C 2.0,第二版,Stephen G Kochan著,有Addison We是可以出版,翻看該書中第231頁關於協議的內容。也可以閱讀蘋果公司Objective-C文檔中關於協議的章節。
使用協議交換數據
協議的常見用法是交換數據。編輯者或請求者需要信息,由代理提供並且或許由代理更新。
在這種情況下,汽車編輯者需要知道汽車對象和拍照。Add/View場景或代理,在汽車對象有改變時需要被通知到,從而可以更新顯示內容。這要求協議有三個方法:
- carToEdit返回汽車對象以進行編輯
- carNumber返回被編輯汽車的拍照(不是索引數)
- editedCarUpdated告訴代理,編輯操作已經完成
現在創建新的Objective-C協議,命名爲CarEditViewControllerProtocol,並將它添加到項目中。
需要注意的是,需要選擇Objective-C File中,File Type 選擇protocol
代碼如下:
#import <Foundation/Foundation.h>//在協議中個導入用到的類的所有頭文件。Foundation.h定義了許多Cocoa類型,包括NSInteger類型
@class Car;
@protocol CarEditViewControllerProtocol <NSObject>//@protocol是聲明協議的指令。接下來的部分是協議的名稱,接着是協議要包含的內容。在這種情況下,CarEditViewControllerProtocol有權限訪問任何在NSObject協議中聲明的方法,如self、class和description等
- (Car*)carToEdit;//指定方法聲明的方式如同公共類方法
- (NSInteger)carNumber;
- (void)editedCarUpdated;
@end
在定義好協議後,改變要使用該協議的請求者。在CarValet應用中,從改變導入文件開始,進行修改。在CarEditViewController.h文件中,移除carNumber屬性,然後按下列代碼進行更改
#import <UIKit/UIKit.h>
#import "CarEditViewControllerProtocol.h"//修改
@class Car;
@interface CarEditViewController : UIViewController
//@property (nonatomic) NSInteger carNumber;//修改
@property (weak, nonatomic) id <CarEditViewControllerProtocol> delegate;//修改
@property (strong,nonatomic) Car *currentCar;
@property (weak, nonatomic) IBOutlet UILabel *carNumberLabel;
@property (weak, nonatomic) IBOutlet UITextField *makeField;
@property (weak, nonatomic) IBOutlet UITextField *modelField;
@property (weak, nonatomic) IBOutlet UITextField *yearField;
@property (weak, nonatomic) IBOutlet UITextField *fuelField;
@end
最後一個修改是協議中十分重要的一部分。id是佔位符,能代表任意類的對象。這意味着任意類,包含哪些我們還沒有想到過的類,都可以成爲該協議的代理。雖然Edit場景要求Car對象擁有某些特定屬性,但我們依然擁有設置該代理的靈活性。可以在其他的項目中重用到Edit場景。另一處要注意的地方是,在聲明id變量類型時並不適用星號。id變量的聲明應該是delegate
而非*delegate
。
在類型之後,用尖括號括起的CarEditViewControllerProtocol,這表明無論delegate所屬的類是什麼,他都必須遵從協議。也就是說,它必須實現協議要求的所有方法。
現在更新CarEditViewController.m中的viewDidLoad方法,代碼如下
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSString *carNumberText;
carNumberText = [NSString stringWithFormat:@"Car Number:%d",[self.delegate carNumber]];//修改 啓辰的拍照,現在從代理返回
self.carNumberLabel.text = carNumberText;
self.currentCar = [self.delegate carToEdit];//修改 因已編輯過Car對象,故而通知代理。
self.makeField.text = self.currentCar.make;
self.modelField.text = self.currentCar.model;
self.yearField.text = [NSString stringWithFormat:@"%d",self.currentCar.year];
self.fuelField.text = [NSString stringWithFormat:@"%0.2f",self.currentCar.fuelAmount];
最後一處修改是通知Add/View場景進行自我更新。在viewWillDisappear:的結尾處添加對[self.delegate editedCarUpdated]
的調用
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
[self.delegate editedCarUpdated];
}
現在已經完成請求者部分,是時候更新代理部分了。
修改ViewController.h文件頂部的代碼
#import <UIKit/UIKit.h>
#import "CarEditViewControllerProtocol.h"//修改
@interface ViewController : UIViewController
<CarEditViewControllerProtocol>//修改
@property (weak, nonatomic) IBOutlet UILabel *totalCarsLabel;
@property (weak, nonatomic) IBOutlet UILabel *CarNumberLabel;
@property (weak, nonatomic) IBOutlet UILabel *CarInfoLabel;
- (IBAction)newCar:(id)sender;
- (IBAction)previousCar:(id)sender;
- (IBAction)nextCar:(id)sender;
@end
<CarEditViewControllerProtocol>
的意思是ViewController支持——也即是說,遵從——我們的協議。當然,我們還未實現任何支持。此處,Xcode展現了它幫助你檢查代碼完整的方法之一。查看工具欄中間部分的狀態欄,可以看到右邊有黃色警告三角形。表明在編譯代碼時已導致警告。單擊警告可以找到更多細節。因爲協議的方法部分不存在,該警告表明存在不完整的實現。
在newCar:方法的上方添加協議方法:
- (Car*)carToEdit {
return arrayOfCars[displayedCarIndex];
}
- (NSInteger)carNumber {
return displayedCarIndex + 1;
}
- (void)editedCarUpdated {
[self displayCurrentCarInfo];
}
最後還有一個錯誤提示需要解決
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:@"EditSegue"]){
CarEditViewController *nextCOntroller;
nextCOntroller = segue.destinationViewController;
nextCOntroller.delegate = self;//修改
Car *currentCar = arrayOfCars[displayedCarIndex];
nextCOntroller.currentCar = currentCar;
}
}
然後就是成功運行了。
改進故事版2
IOS 6 引入了一種更好的方法來將數據返回到激發segue的視圖控制器:簡單地將一個特殊類型的IBAction添加到視圖控制器中即可。同城,動作方法有個看似爲(id)sender的參數,或者根本沒有參數。通過將參數修改爲(UIStoryboard*)segue,可以創建特殊類型的動作——一種能接受segue對象的動作。
僅當一個場景激發另一個場景的切換時纔會發送prepareForSegue:sender:。它是向前segue。在IOS 6中,存在另一種類型的segue,其動作是相反的:在一個changing想要返回到之前的場景時發送prepareForSeguesender:。它會回退(unwinds)首次打開場景的segue,並且在打開之前的場景時不會創建新場景。
要查看這個回退動作,修改如下代碼:
在ViewController.m底部添加
- (IBAction)editingDone:(UIStoryboardSegue*)segue {
[self displayCurrentCarInfo];
}
現在需要激發這個回退動作,方法如同Edit的修改
添加按鈕(這裏換了個樣式)
Ctrl將Done拉到下圖位置,選擇editingDone
在CarEditViewController.m中修改如下方法
- (IBAction)editingDone:(UIStoryboardSegue*)segue {
NSLog(@"\neditedCarUpdated called!\n");
[self displayCurrentCarInfo];
}
每次Done之後,會在控制檯看到
運行的時候發現,還是沒能立即刷新,然而你對同一個再次編輯,再次返回,會發現他保存的是上一個的變化。
講道理,我們要的應該是點返回只有立即更新纔對,那麼做如下修改
在CarEditViewController.m中添加方法,這個跟之前跳轉到類似,我就不多解釋了
添加segue標籤
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:@"EditDoneSegue"]){
self.currentCar.make = self.makeField.text;
self.currentCar.model = self.modelField.text;
self.currentCar.year = [self.yearField.text integerValue];
self.currentCar.fuelAmount = [self.fuelField.text floatValue];
}
}
現在運行之後,一旦更更久能立即顯示啦
今天的介紹就到這裏咯
我的另一個博客站點:Arnold-你們好啊