iOS學習——頁面的傳值方式iOS----KVC和KVO 詳解

  • 2019 年 10 月 8 日
  • 筆記

一、簡述

在iOS開發過程中,頁面跳轉時在頁面之間進行數據傳遞是很常見的事情,我們稱這個過程為頁面傳值。頁面跳轉過程中,從主頁面跳轉到子頁面的數據傳遞稱之為正向傳值;反之,從子頁面返回主頁面時的數據傳遞稱之為反向傳值。

  目前我所了解和掌握的傳值方式有:

  1. 屬性傳值
  2. 單例傳值
  3. NSUserDefaults傳值
  4. 代理傳值
  5. block傳值
  6. 通知傳值
  7. 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 @end
 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 @end

2.1 屬性傳值

方法描述:在從當前頁面跳轉到下主頁面之前,提前創建下主頁面,通過賦值的方式將當前頁面的數據賦予下主頁面的屬性。

適用場景:當從主頁面push到子頁面時,子頁面需要使用到主頁面的數據,我們需要使用到正向傳值。

傳遞方式:正向傳值。

使用步驟:

  1. 子頁面的.h文件中定義屬性來保留要傳遞過來的數據
//子頁面KLSubViewController.h的屬性定義  @interface KLSubViewController : UIViewController    @property (strong, nonatomic) UITextField *textField;  @property (strong, nonatomic) UIButton *button;    @property (strong, nonatomic) NSString *content;//屬性接收數據    @end
  1. 主頁面在跳轉的時候將數據賦值給子頁面對應的屬性
//主介面跳轉時將數據賦值給對應的屬性  @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的方式進入到子頁面,在從子頁面返回主頁面的時候(子頁面會釋放掉記憶體),需要在主頁面中使用子頁面中的數據,這是就可以利用代理反向傳值。

傳遞方式:反向傳值。

使用步驟:

  1. 在子頁面中添加一個代理協議,在協議中定義一個傳遞數據的方法
  2. 在子頁面.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
  1. 在子頁面返回主頁面之前掉好用代理中定義數據傳遞方法,方法參數就是要傳遞的數據
//子頁面返回時調用代理方法  - (void) btnClicked:(UIButton *)btn {      //如果當前的代理存在,並且實現了代理方法,則調用代理方法進行傳遞數據      if (self.delegate &&          [self.delegate respondsToSelector:@selector(transferString:)]) {          [self.delegate transferString:@"子頁面回傳的數據"];      }      [self.navigationController popViewControllerAnimated:YES];  }
  1. 在主頁面中遵從該代理,並實現代理中定義的方法,在方法的實現程式碼中將參數傳遞給主頁面的屬性
//要實現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的方式進入到子頁面,在從子頁面返回主頁面的時候(子頁面會釋放掉記憶體),需要在主頁面中使用子頁面中的數據,這是就可以利用代理反向傳值。

傳遞方式:反向傳遞。

使用步驟:整個步驟和代理差不多

  1. 在子頁面中添加一個塊語句屬性
//定義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
  1. 在子頁面返回主頁面之前調用該塊語句
#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
  1. 在主頁面跳轉子頁面之前,設置子頁面中的塊語句屬性將要執行的動作(回調函數)
#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進入子頁面時向子頁面傳遞數據,這時就可以用通知進行正向傳值。

傳遞方式:正向傳遞(很少這樣用)、反向傳遞(更常用)。

使用步驟:

  • 反向傳遞:
  1. 在子頁面返回的時候發送通知,註冊的接收通知的名稱必須和發送通知的名稱保持一致才能接收到,否則無法接收到發出的通知
@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];  }
  1. 在主頁面註冊通知,並制定接收到通知後執行的操作方法。需要注意的是,在註冊通知的頁面消毀時一定要移除已經註冊的通知,否則會造成記憶體泄漏。
@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方法中進行。
  1. 在主頁面返回的時候發送通知,註冊的接收通知的名稱必須和發送通知的名稱保持一致才能接收到,否則無法接收到發出的通知。
@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
  1. 在子頁面註冊通知,並制定接收到通知後執行的操作方法。正向傳遞時註冊通知、基本布局不能放在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的時候,可以直接從本地讀取。

適用場景:任何需要數據傳遞的場景都適用,但是傳遞數據的類型僅限於基本數據類型,不能用於自定義的對象類型。

傳遞方式:正向傳值、反向傳值。

使用步驟:

  1. 需要傳值時將數據通過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];  }
  1. 需要使用值時通過NSUserDefaults從沙盒目錄裡面取值進行處理
_label.text = [[NSUserDefaults standardUserDefaults] valueForKey:@"NSUserDefaults"];

2.6 單例傳值

方法描述:單例傳值的性質和NSUserDefaults傳值的性質類似,只是單例傳值是將數據保存在單例對象中,需要的時候同樣從單例對象中去獲取數據使用就ok。

適用場景:任何需要數據傳遞的場景都適用,傳遞的數據可以是任何類型的數據。

傳遞方式:正向傳值、反向傳值均OK。

使用步驟:

  1. 創建一個類,擁有一些屬性用於保存數據,並實現單例方法 #import "KLDanliObj.h" static DanLi *danli = nil; @implementation KLDanliObj + (instancetype) shardDanLiObj { //實現方法,判斷是否為空,是就創建一個全局實例給它 if (danli == nil) { danli = [[KLDanliObj alloc] init]; } return danli; } @end
@interface KLDanliObj : NSObject    @property (nonatomic, copy) NSString *content; //保存數據數據的屬性    + (instancetype) shardDanLiObj; //單例對象獲取方法    @end
  1. 需要傳遞數據時使用單例類將數據保存到單例的屬性中
[KLDanliObj shardDanLiObj].content = @"主介面傳遞的數據"; 
  1. 需要使用值時通過單例的屬性獲取數據進行使用和處理
self.textField.text = [KLDanliObj shardDanLiObj].content; 

2.7 KVC傳值

方法描述:KVC(Key-value coding)鍵值編碼,單看這個名字可能不太好理解。其實翻譯一下就很簡單了,就是指iOS的開發中,可以允許開發者通過Key名直接訪問對象的屬性,或者給對象的屬性賦值,而不需要調用明確的存取方法,這樣就可以在運行時動態地訪問和修改對象的屬性。這其實和屬性傳值比較類似。

適用場景:當從主頁面push到子頁面時,子頁面需要使用到主頁面的數據,我們需要使用到正向傳值。

傳遞方式:正向傳值

使用步驟:

  1. 在需要傳值時使用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的方式進入到子頁面,在從子頁面返回主頁面的時候(子頁面會釋放掉記憶體),需要在主頁面中使用子頁面中的數據,這是就可以利用代理反向傳值。

傳遞方式:反向傳遞。

使用步驟:

  1. 在主頁面註冊觀察者,實現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
  1. 子頁面在返回主頁面時修改對應屬性的內容,則會回調主頁面的回調方法
- (void) btnClicked:(UIButton *)btn {      self.content = @"子頁面回傳數據";//修改屬性的內容      [self.navigationController popViewControllerAnimated:YES];  }

Tags: