一、簡述 在iOS開發過程中,頁面跳轉時在頁面之間進行數據傳遞是很常見的事情,我們稱這個過程為頁面傳值。頁面跳轉過程中,從主頁面跳轉到子頁面的數據傳遞稱之為正向傳值;反之,從子頁面返回主頁面時的數據傳遞稱之為反向傳值。 目前我所瞭解和掌握的傳值方式有: 二、頁面傳值的詳解 2.0 準備工作 為了實現 ...
一、簡述
在iOS開發過程中,頁面跳轉時在頁面之間進行數據傳遞是很常見的事情,我們稱這個過程為頁面傳值。頁面跳轉過程中,從主頁面跳轉到子頁面的數據傳遞稱之為正向傳值;反之,從子頁面返回主頁面時的數據傳遞稱之為反向傳值。
目前我所瞭解和掌握的傳值方式有:
- 屬性傳值
- 單例傳值
- NSUserDefaults傳值
- 代理傳值
- block傳值
- 通知傳值
- KVO/KVC iOS----KVC和KVO 詳解
二、頁面傳值的詳解
2.0 準備工作
為了實現頁面之間傳值,我們需要準備兩個頁面,代碼結構如下圖所示。其中,KLMainViewController為主主頁面,KLSubViewController為子頁面,頁面之間的跳轉使用UINavigationController來實現。每個頁面中都有一個文本編輯框,我們需要將其中一個頁面文本框中的內容傳遞到另一個頁面中。
1 #import "KLMainViewController.h" 2 #import "KLSubViewController.h" 3 4 @interface KLMainViewController () 5 6 @property (strong, nonatomic) UITextField *textField; 7 @property (strong, nonatomic) UIButton *button; 8 9 @end 10 11 @implementation KLMainViewController 12 13 - (void)viewDidLoad { 14 [super viewDidLoad]; 15 self.title = @"主界面"; 16 17 _textField = [[UITextField alloc] init]; 18 _textField.textColor = [UIColor redColor]; 19 _textField.textAlignment = NSTextAlignmentCenter; 20 _textField.backgroundColor = kBgColor; 21 _textField.text = @"主界面的label信息"; 22 [self.view addSubview:_textField]; 23 WEAKSELF 24 [_textField mas_makeConstraints:^(MASConstraintMaker *make) { 25 make.center.mas_equalTo(weakSelf.view).mas_offset(0.0f); 26 make.left.mas_equalTo(weakSelf.view).mas_offset(15.0f); 27 make.right.mas_equalTo(weakSelf.view).mas_offset(-15.0f); 28 }]; 29 30 _button = [UIButton buttonWithType:UIButtonTypeCustom]; 31 [_button setTitle:@"跳轉到子界面" forState:UIControlStateNormal]; 32 [_button setTitleColor:[UIColor blueColor] forState:UIControlStateNormal]; 33 [_button addTarget:self action:@selector(btnClicked:) forControlEvents:UIControlEventTouchUpInside]; 34 [self.view addSubview:_button]; 35 [_button mas_makeConstraints:^(MASConstraintMaker *make) { 36 make.centerX.mas_equalTo(weakSelf.view).mas_offset(0.0f); 37 make.top.mas_equalTo(weakSelf.textField.mas_bottom).mas_offset(40.0f); 38 }]; 39 40 } 41 42 - (void) btnClicked:(UIButton *)btn { 43 KLSubViewController *subVC = [[KLSubViewController alloc] init]; 44 [self.navigationController pushViewController:subVC animated:YES]; 45 } 46 47 @endKLMainViewController.m
1 //KLSubViewController.h 2 #import <UIKit/UIKit.h> 3 4 NS_ASSUME_NONNULL_BEGIN 5 6 @interface KLSubViewController : UIViewController 7 8 @property (strong, nonatomic) UITextField *textField; 9 @property (strong, nonatomic) UIButton *button; 10 11 @property (strong, nonatomic) NSString *content; 12 13 @end 14 15 NS_ASSUME_NONNULL_END 16 17 //KLSubViewController.m 18 #import "KLSubViewController.h" 19 20 @interface KLSubViewController () 21 22 @end 23 24 @implementation KLSubViewController 25 26 - (void)viewDidLoad { 27 [super viewDidLoad]; 28 self.view.backgroundColor = [UIColor whiteColor]; 29 self.title = @"子界面"; 30 31 _textField = [[UITextField alloc] init]; 32 _textField.textColor = [UIColor redColor]; 33 _textField.textAlignment = NSTextAlignmentCenter; 34 _textField.backgroundColor = kBgColor; 35 _textField.text = @"子界面的label信息"; 36 [self.view addSubview:_textField]; 37 WEAKSELF 38 [_textField mas_makeConstraints:^(MASConstraintMaker *make) { 39 make.center.mas_equalTo(weakSelf.view).mas_offset(0.0f); 40 make.left.mas_equalTo(weakSelf.view).mas_offset(15.0f); 41 make.right.mas_equalTo(weakSelf.view).mas_offset(-15.0f); 42 }]; 43 44 _button = [UIButton buttonWithType:UIButtonTypeCustom]; 45 [_button setTitle:@"返回主界面" forState:UIControlStateNormal]; 46 [_button setTitleColor:[UIColor blueColor] forState:UIControlStateNormal]; 47 [_button addTarget:self action:@selector(btnClicked:) forControlEvents:UIControlEventTouchUpInside]; 48 [self.view addSubview:_button]; 49 [_button mas_makeConstraints:^(MASConstraintMaker *make) { 50 make.centerX.mas_equalTo(weakSelf.view).mas_offset(0.0f); 51 make.top.mas_equalTo(weakSelf.textField.mas_bottom).mas_offset(40.0f); 52 }]; 53 } 54 55 - (void) btnClicked:(UIButton *)btn { 56 57 [self.navigationController popViewControllerAnimated:YES]; 58 } 59 60 @endKLSubViewController
2.1 屬性傳值
方法描述:在從當前頁面跳轉到下主頁面之前,提前創建下主頁面,通過賦值的方式將當前頁面的數據賦予下主頁面的屬性。
適用場景:當從主頁面push到子頁面時,子頁面需要使用到主頁面的數據,我們需要使用到正向傳值。
傳遞方式:正向傳值。
使用步驟:
- 子頁面的.h文件中定義屬性來保留要傳遞過來的數據
//子頁面KLSubViewController.h的屬性定義 @interface KLSubViewController : UIViewController @property (strong, nonatomic) UITextField *textField; @property (strong, nonatomic) UIButton *button; @property (strong, nonatomic) NSString *content;//屬性接收數據 @end
- 主頁面在跳轉的時候將數據賦值給子頁面對應的屬性
//主界面跳轉時將數據賦值給對應的屬性 @interface KLMainViewController () @property (strong, nonatomic) UITextField *textField; @property (strong, nonatomic) UIButton *button; @end @implementation KLMainViewController - (void)viewDidLoad { [super viewDidLoad]; self.title = @"主界面"; //佈局代碼省略 ...... } //跳轉 - (void) btnClicked:(UIButton *)btn { KLSubViewController *subVC = [[KLSubViewController alloc] init]; subVC.content = @"來自主界面的數據"; // subVC.textField.text = @"來自主界面的數據"; //這樣傳遞是有問題的,因為子頁面中的textfield是在viewDidLoad中進行初始化和佈局的,在這時候textfield還沒有初始化,為nil,所以賦值是失效的 [self.navigationController pushViewController:subVC animated:YES]; } @end
2.2 代理傳值
方法描述:首先在子頁面的頭文件中添加一個代理(協議)的定義,定義一個傳遞數據的方法,並且在子頁面的類中添加一個代理屬性;然後,在子頁面返回主頁面之前調用代理中定義的數據傳遞方法(方法參數就是要傳遞的數據);最後,在主頁面中遵從該代理,並實現代理中定義的方法,在方法的實現代碼中將參數傳遞給主頁面的屬性。
適用場景:已經通過push的方式進入到子頁面,在從子頁面返回主頁面的時候(子頁面會釋放掉記憶體),需要在主頁面中使用子頁面中的數據,這是就可以利用代理反向傳值。
傳遞方式:反向傳值。
使用步驟:
- 在子頁面中添加一個代理協議,在協議中定義一個傳遞數據的方法
- 在子頁面.h文件中添加一個代理屬性
//子頁面的.h文件,定義代理以及代理屬性 // 聲明代理 @protocol BToADelegate <NSObject> // 代理方法 - (void)transferString:(NSString *)string; @end @interface KLSubViewController : UIViewController @property (strong, nonatomic) UITextField *textField; @property (strong, nonatomic) UIButton *button; @property (nonatomic, weak) id<BToADelegate> delegate;//代理屬性 @end
- 在子頁面返回主頁面之前掉好用代理中定義數據傳遞方法,方法參數就是要傳遞的數據
//子頁面返回時調用代理方法 - (void) btnClicked:(UIButton *)btn { //如果當前的代理存在,並且實現了代理方法,則調用代理方法進行傳遞數據 if (self.delegate && [self.delegate respondsToSelector:@selector(transferString:)]) { [self.delegate transferString:@"子頁面回傳的數據"]; } [self.navigationController popViewControllerAnimated:YES]; }
- 在主頁面中遵從該代理,並實現代理中定義的方法,在方法的實現代碼中將參數傳遞給主頁面的屬性
//要實現BToADelegate @interface KLMainViewController () <BToADelegate> @property (strong, nonatomic) UITextField *textField; @property (strong, nonatomic) UIButton *button; @end @implementation KLMainViewController - (void)viewDidLoad { [super viewDidLoad]; self.title = @"主界面"; //佈局代碼省略 ... } - (void) btnClicked:(UIButton *)btn { KLSubViewController *subVC = [[KLSubViewController alloc] init]; subVC.delegate = self; //申明子頁面的代理是主頁面自身self [self.navigationController pushViewController:subVC animated:YES]; } #pragma mark BToADelegate 代理方法,子頁面調用的時候會回調該方法 - (void)transferString:(NSString *)string { self.textField.text = string; } @end
2.3 Block傳值
方法描述:在子頁面中添加一個塊語句屬性,在子頁面返回主頁面之前調用該塊語句。在主頁面跳轉子頁面之前,設置子頁面中的塊語句屬性將要執行的動作(回調函數)。這樣,在子頁面返回主頁面時就會調用該回調函數來傳遞數據。
適用場景:已經通過push的方式進入到子頁面,在從子頁面返回主頁面的時候(子頁面會釋放掉記憶體),需要在主頁面中使用子頁面中的數據,這是就可以利用代理反向傳值。
傳遞方式:反向傳遞。
使用步驟:整個步驟和代理差不多
- 在子頁面中添加一個塊語句屬性
//定義block的類型 typedef void (^TransDataBlock)(NSString *content); @interface KLSubViewController : UIViewController @property (strong, nonatomic) UITextField *textField; @property (strong, nonatomic) UIButton *button; @property (copy, nonatomic) TransDataBlock transDataBlock;//定義一個block屬性,用於回傳數據 @end
- 在子頁面返回主頁面之前調用該塊語句
#import "KLSubViewController.h" @interface KLSubViewController () @end @implementation KLSubViewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; self.title = @"子界面"; //佈局代碼省略 ...... } - (void) btnClicked:(UIButton *)btn { //如果回傳block存在 則調用該block進行回傳數據 if (self.transDataBlock) { self.transDataBlock(@"子頁面回傳的數據"); } [self.navigationController popViewControllerAnimated:YES]; } @end
- 在主頁面跳轉子頁面之前,設置子頁面中的塊語句屬性將要執行的動作(回調函數)
#import "KLMainViewController.h" #import "KLSubViewController.h" @interface KLMainViewController () @property (strong, nonatomic) UITextField *textField; @property (strong, nonatomic) UIButton *button; @end @implementation KLMainViewController - (void)viewDidLoad { [super viewDidLoad]; self.title = @"主界面"; //佈局代碼省略 ...... } - (void) btnClicked:(UIButton *)btn { KLSubViewController *subVC = [[KLSubViewController alloc] init]; //通過子頁面的block回傳拿到數據後進行處理,賦值給當前頁面的textfield subVC.transDataBlock = ^(NSString *content) { self.textField.text = content; }; [self.navigationController pushViewController:subVC animated:YES]; } @end
2.4 通知傳值
方法描述:在通知接收方需要註冊通知,並指定接收到通知後進行的操作;而在通知發送方則在需要傳遞數據時發送通知就OK了。通知的操作都是通過NSNotificationCenter來完成的。
但是要註意的兩點是:
- 要想能夠接收到通知進行處理,必須先註冊通知。
- 在註冊通知的頁面消毀時一定要移除已經註冊的通知,否則會造成記憶體泄漏
-
註冊的接收通知的名稱必須和發送通知的名稱保持一致才能接收到,否則無法接收到發出的通知
適用場景:
- 一般用於已經通過push的方式進入到子頁面,在從子頁面返回主頁面的時候(子頁面會釋放掉記憶體),需要在主頁面中使用子頁面中的數據,這是就可以利用通知反向傳值。
- 但是也可以用於通過push進入子頁面時向子頁面傳遞數據,這時就可以用通知進行正向傳值。
傳遞方式:正向傳遞(很少這樣用)、反向傳遞(更常用)。
使用步驟:
- 反向傳遞:
- 在子頁面返回的時候發送通知,註冊的接收通知的名稱必須和發送通知的名稱保持一致才能接收到,否則無法接收到發出的通知
@interface KLSubViewController () @end @implementation KLSubViewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; self.title = @"子界面"; //佈局代碼省略 ....... } - (void) btnClicked:(UIButton *)btn { //發送通知回傳數據,回傳的數據格式自定義,這裡定義為dictionary類型 [[NSNotificationCenter defaultCenter] postNotificationName:@"TransDataNoti" object:nil userInfo:@{@"content":@"子頁面回傳的數據"}]; [self.navigationController popViewControllerAnimated:YES]; }
- 在主頁面註冊通知,並制定接收到通知後執行的操作方法。需要註意的是,在註冊通知的頁面消毀時一定要移除已經註冊的通知,否則會造成記憶體泄漏。
@interface KLMainViewController () @property (strong, nonatomic) UITextField *textField; @property (strong, nonatomic) UIButton *button; @end @implementation KLMainViewController - (void)dealloc { //移除所有通知 [[NSNotificationCenter defaultCenter] removeObserver:self]; // 移除某個 // [[NSNotificationCenter defaultCenter] removeObserver:self name:@"TransDataNoti" object:nil]; } - (void)viewDidLoad { [super viewDidLoad]; self.title = @"主界面"; //佈局代碼省略 ...... //註冊通知,用於接收通知,接收通知的名稱必須和發送通知的名稱保持一致才能接收到,否則無法接收到發出的通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notiReceived:) name:@"TransDataNoti" object:nil]; } //接收通知,解析內容進行處理 - (void)notiReceived:(NSNotification *)sender { self.textField.text = sender.userInfo[@"content"]; } - (void) btnClicked:(UIButton *)btn { KLSubViewController *subVC = [[KLSubViewController alloc] init]; [self.navigationController pushViewController:subVC animated:YES]; } @end
- 正向傳遞:和反向傳遞的不走基本就是反過來就OK了,但是有一點需要註意的是正向傳遞時從主界面push到子界面時發送通知,這時候要確保子界面已經註冊了通知,否則會收不到通知的,所以正向傳遞時,子界面通知的註冊應該在子界面的初始化init方法中進行。
- 在主頁面返回的時候發送通知,註冊的接收通知的名稱必須和發送通知的名稱保持一致才能接收到,否則無法接收到發出的通知。
@interface KLMainViewController () @property (strong, nonatomic) UITextField *textField; @property (strong, nonatomic) UIButton *button; @end @implementation KLMainViewController - (void)viewDidLoad { [super viewDidLoad]; self.title = @"主界面"; //佈局代碼省略 ...... } - (void) btnClicked:(UIButton *)btn { KLSubViewController *subVC = [[KLSubViewController alloc] init]; //發送通知回傳數據,回傳的數據格式自定義,這裡定義為dictionary類型 [[NSNotificationCenter defaultCenter] postNotificationName:@"TransDataNoti" object:nil userInfo:@{@"content":@"主頁面傳遞的數據"}]; [self.navigationController pushViewController:subVC animated:YES]; } @end
- 在子頁面註冊通知,並制定接收到通知後執行的操作方法。正向傳遞時註冊通知、基本佈局不能放在viewDidLoad中,要放在初始化函數init中。
@interface KLSubViewController () @end @implementation KLSubViewController - (void)dealloc { //移除所有通知 [[NSNotificationCenter defaultCenter] removeObserver:self]; // 移除某個 // [[NSNotificationCenter defaultCenter] removeObserver:self name:@"TransDataNoti" object:nil]; } - (instancetype)init { self = [super init]; //初始化代碼省略 ...... //註冊通知,用於接收通知,接收通知的名稱必須和發送通知的名稱保持一致才能接收到,否則無法接收到發出的通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notiReceived:) name:@"TransDataNoti" object:nil]; return self; } - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; self.title = @"子界面"; } //接收通知,解析內容進行處理 - (void)notiReceived:(NSNotification *)sender { self.textField.text = sender.userInfo[@"content"]; } - (void) btnClicked:(UIButton *)btn { [self.navigationController popViewControllerAnimated:YES]; } @end
2.5 NSUserDefaults傳值
方法描述:NSUserDefaults傳值是將所要傳的值寫在沙盒目錄裡面,需要獲取值的時候直接訪問沙盒,獲取這個值就可以了,這種傳值方法一般用在需要將數據本地存儲的時候,比如:用戶名之類,當用戶下次登錄或者使用app的時候,可以直接從本地讀取。
適用場景:任何需要數據傳遞的場景都適用,但是傳遞數據的類型僅限於基本數據類型,不能用於自定義的對象類型。
傳遞方式:正向傳值、反向傳值。
使用步驟:
- 需要傳值時將數據通過NSUserDefaults保存到沙盒目錄裡面
- (void) btnClicked:(UIButton *)btn { /* setObject:後面寫的就是所需要傳遞的值 forKey:要具有唯一性、一致性; 唯一性是指:當代碼中用到多個NSUserDefaults方法時,要保證不同的key不一樣,否則就是覆蓋值 一致性:這裡傳遞一個值,當需要用到的時候,要用valueForkey的方法,這個key和傳值時候寫的key要一樣,寫錯了就找不到值了。 */ [[NSUserDefaults standardUserDefaults] setObject:@"NSUserDefaults傳值" forKey:@"NSUserDefaults"]; [[NSUserDefaults standardUserDefaults] synchronize]; [self.navigationController popViewControllerAnimated:YES]; }
- 需要使用值時通過NSUserDefaults從沙盒目錄裡面取值進行處理
_label.text = [[NSUserDefaults standardUserDefaults] valueForKey:@"NSUserDefaults"];
2.6 單例傳值
方法描述:單例傳值的性質和NSUserDefaults傳值的性質類似,只是單例傳值是將數據保存在單例對象中,需要的時候同樣從單例對象中去獲取數據使用就ok。
適用場景:任何需要數據傳遞的場景都適用,傳遞的數據可以是任何類型的數據。
傳遞方式:正向傳值、反向傳值均OK。
使用步驟:
- 創建一個類,擁有一些屬性用於保存數據,並實現單例方法
@interface KLDanliObj : NSObject @property (nonatomic, copy) NSString *content; //保存數據數據的屬性 + (instancetype) shardDanLiObj; //單例對象獲取方法 @end
#import "KLDanliObj.h" static DanLi *danli = nil; @implementation KLDanliObj + (instancetype) shardDanLiObj { //實現方法,判斷是否為空,是就創建一個全局實例給它 if (danli == nil) { danli = [[KLDanliObj alloc] init]; } return danli; } @end
- 需要傳遞數據時使用單例類將數據保存到單例的屬性中
[KLDanliObj shardDanLiObj].content = @"主界面傳遞的數據";
- 需要使用值時通過單例的屬性獲取數據進行使用和處理
self.textField.text = [KLDanliObj shardDanLiObj].content;
2.7 KVC傳值
方法描述:KVC(Key-value coding)鍵值編碼,單看這個名字可能不太好理解。其實翻譯一下就很簡單了,就是指iOS的開發中,可以允許開發者通過Key名直接訪問對象的屬性,或者給對象的屬性賦值,而不需要調用明確的存取方法,這樣就可以在運行時動態地訪問和修改對象的屬性。這其實和屬性傳值比較類似。
適用場景:當從主頁面push到子頁面時,子頁面需要使用到主頁面的數據,我們需要使用到正向傳值。
傳遞方式:正向傳值
使用步驟:
- 在需要傳值時使用KVC給子頁面的屬性進行賦值就ok了
- (void) btnClicked:(UIButton *)btn { KLSubViewController *subVC = [[KLSubViewController alloc] init]; //給子頁面subVC的屬性content賦值 和subVC.content = @"主頁面傳遞的數據";效果一樣 [subVC setValue:@"主頁面傳遞的數據" forKey:@"content"]; [self.navigationController pushViewController:subVC animated:YES]; }
2.8 KVO傳值
方法描述:KVO(Key-Value-Observing,鍵值觀察),即觀察關鍵字的值的變化。首先在子頁面中聲明一個待觀察的屬性,在返回主頁面之前修改該屬性的值。在主頁面中提前分配並初始化子頁面,並且註冊對子頁面中對應屬性的觀察者。在從子頁面返回上主之前,通過修改觀察者屬性的值,在主頁面中就能自動檢測到這個改變,從而讀取子頁面的數據。
適用場景:已經通過push的方式進入到子頁面,在從子頁面返回主頁面的時候(子頁面會釋放掉記憶體),需要在主頁面中使用子頁面中的數據,這是就可以利用代理反向傳值。
傳遞方式:反向傳遞。
使用步驟:
- 在主頁面註冊觀察者,實現KVO的回調方法,併在主頁面銷毀時移除觀察者
@interface KLMainViewController () @property (strong, nonatomic) UITextField *textField; @property (strong, nonatomic) UIButton *button; @property (strong, nonatomic) KLSubViewController *subVC; @end @implementation KLMainViewController - (void)dealloc { //移除觀察者 [self.subVC removeObserver:self forKeyPath:@"content"]; } - (void)viewDidLoad { [super viewDidLoad]; self.title = @"主界面"; //佈局代碼省略 ....... } - (void) btnClicked:(UIButton *)btn { if (!_subVC) { _subVC = [[KLSubViewController alloc] init]; //註冊觀察者 [_subVC addObserver:self forKeyPath:@"content" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:nil]; } [self.navigationController pushViewController:_subVC animated:YES]; } // KVO的回調,當觀察者中的數據有變化時會回調該方法 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ if ([keyPath isEqualToString:@"content"]){ self.textField.text = self.subVC.content; } } @end
- 子頁面在返回主頁面時修改對應屬性的內容,則會回調主頁面的回調方法
- (void) btnClicked:(UIButton *)btn { self.content = @"子頁面回傳數據";//修改屬性的內容 [self.navigationController popViewControllerAnimated:YES]; }