
【OC】多界面传值总结文章目录【OC】多界面传值总结属性传值(A-B)代理传值(B-A)Block传值B-A通知传值任意KVO传值任意单例传值NSUserDefaults 传值总结与 Trade-off 对比快速对照表详细 Trade-off 分析在学习iOS 开发的知识过程中把几种常见的多界面传值方式梳理了一下记录在这里方便以后回顾属性传值(A-B)这是一种正向传值也是最简单的一种就是从 A 界面跳转到 B 界面的时候把数据直接塞给 B思路在 B 控制器里定义一个属性跳转之前给它赋值B 拿到之后用就行了接收方B// SecondViewController.h#importUIKit/UIKit.hinterfaceSecondViewController:UIViewControllerproperty(nonatomic,copy)NSString*receiveStr;end// SecondViewController.m-(void)viewDidLoad{[superviewDidLoad];// 直接用 receiveStr 就行NSLog(%,self.receiveStr);self.titleLabel.textself.receiveStr;}发送方A// FirstViewController.m-(void)jumpToSecond{// 创建第二个视图并为其赋值来达到跨界面传值的效果SecondViewController*secondVC[[SecondViewController alloc]init];secondVC.receiveStr我是从第一个界面传来的;[self.navigationController pushViewController:secondVC animated:YES];}这个最简单正向传没啥问题但是反向传B → A就不行了代理传值(B-A)这是一种反向传值比如从 B 把数据传回给 A思路B 里面定义一个协议A 来遵守这个协议B 在需要传值的时候调用代理方法A 实现这个方法拿数据接收方A// FirstViewController.m// 遵守协议interfaceFirstViewController()SecondVCDelegateend// 跳转的时候设置代理-(void)jumpToSecond{SecondViewController*secondVC[[SecondViewController alloc]init];secondVC.delegateself;// secondVC的代理是我自己所以实现代码也在我这里写更改的UI也是我[self.navigationController pushViewController:secondVC animated:YES];}// 实现代理方法-(void)sendData:(NSString*)data{NSLog(B 传回来的数据%,data);self.label.textdata;// secondVC调用的协议方法在我这里实现的所以我可以修改我的界面数据而使用的数据是secondVC传回来的}发送方B// SecondViewController.h#importUIKit/UIKit.h// 定义协议protocolSecondVCDelegateNSObject-(void)sendData:(NSString*)data;endinterfaceSecondViewController:UIViewController// 代理属性用 weak 防止循环引用property(nonatomic,weak)idSecondVCDelegatedelegate;end// SecondViewController.m// 点击按钮传值回去-(void)sendBtnClick{if([self.delegate respondsToSelector:selector(sendData:)]){[self.delegate sendData:这是从 B 传回来的数据];// 协议的实现细节由A决定我只用使用协议方法就好}[self.navigationController popViewControllerAnimated:YES];}Block传值B-ABlock 传值和代理传值效果差不多也是反向传但是写起来比代理简洁很多思路B 里面定义一个 block 属性A 跳转到 B 之前给这个 block 赋值实现具体逻辑B 在合适的时候调用这个 block 把数据传出去发送方B// SecondViewController.h#importUIKit/UIKit.hinterfaceSecondViewController:UIViewController// 这里的block其实和属性传值有点相像都是由A进行赋值// 但是由于block的捕获特性在 A 处写好代码捕获 A 的变量// 存到 B 的属性里等 B 需要时调用代码却在 A 的上下文里执行从而修改 A 的数据property(nonatomic,copy)void(^SendDataBlock)(NSString*data);end// SecondViewController.m-(void)sendBtnClick{// 调用 block 传数据if(self.SendDataBlock){// 先判断是否为nilself.SendDataBlock(这是 Block 传回来的数据);}[self.navigationController popViewControllerAnimated:YES];}接收方A// FirstViewController.m-(void)jumpToSecond{SecondViewController*secondVC[[SecondViewController alloc]init];// 给 block 赋值在这里写好回调逻辑secondVC.SendDataBlock^(NSString*data){NSLog(Block 收到数据%,data);self.label.textdata;};[self.navigationController pushViewController:secondVC animated:YES];}⚠️注意循环引用block 里面用了self如果secondVC也强引用了 A就会形成循环引用所以最好通过弱引用打破循环引用__weaktypeof(self)weakSelfself;secondVC.sendBlock^(NSString*data){NSLog(Block 收到数据%,data);weakSelf.label.textdata;};通知传值任意通知这个机制比较灵活不只是两个界面之间一对多都可以适合那种不好直接建立联系的两个模块之间通信思路发通知的地方 post监听的地方 addObserver然后处理就行接收方比如 A先注册监听-(void)viewDidLoad{[superviewDidLoad];// 一对多的关键就在于任何一个对象都可以注册监听通过对应的通知名称来获取信息// 注册通知[[NSNotificationCenter defaultCenter]addObserver:selfselector:selector(receiveNotification:)name:SendDataNotification// 通知名称通过该id来接受object:nil];// object可以指定接受某一种对象的通知}// 接收到通知的处理方法-(void)receiveNotification:(NSNotification*)notification{// 携带的数据NSString*datanotification.userInfo[data];// 发送者谁发的id sendernotification.object;// 通知名称NSString*namenotification.name;// 更新UIself.label.textdata;}// 记得移除不然会出问题-(void)dealloc{[[NSNotificationCenter defaultCenter]removeObserver:self];}发送方B发通知// 携带的数据NSDictionary*userInfo{avatar:selectedImage,message:头像已更新};// 发送通知[[NSNotificationCenter defaultCenter]postNotificationName:SendDataNotificationobject:self// 可以传输一个对象自定义类userInfo:userInfo// 可以传输一个字典];接收方C[[NSNotificationCenter defaultCenter]addObserver:selfselector:selector(onUserLogin:)name:SendDataNotificationobject:nil];-(void)onUserLogin:(NSNotification*)notification{NSLog(TabBar更新用户相关的 tab 图标);[selfupdateUserTabIcon];}‼️dealloc里面一定要removeObserver不然注册了但控制器销毁了再收到通知会崩通知滥用的话代码逻辑会很乱不好追踪所以能用代理就用代理通知用来处理那种远距离的通信KVO传值任意KVOKey-Value Observing严格来说不算传值更像是观察某个对象属性的变化当被观察的属性值改变时观察者会自动收到通知比喻你订阅了一个人的微博他每次发新动态系统自动推送给你的手机思路A 观察 B 的某个属性B 的属性一变A 就知道了被观察的对象B 里有一个属性// SecondViewController.hproperty(nonatomic,copy)NSString*observeStr;A 添加观察-(void)viewDidLoad{[superviewDidLoad];// 假设 self.secondVC 是持有的 B 对象[self.secondVC addObserver:selfforKeyPath:observeStroptions:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld// 告诉系统你要什么信息// NSKeyValueObservingOptionNew 提供变化后的新值// NSKeyValueObservingOptionOld 提供变化前的旧值// NSKeyValueObservingOptionInitial 注册时立即调用一次回调// NSKeyValueObservingOptionPrior 变化前和变化后各调用一次context:nil];}// 属性变化时会调这个方法-(void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context{if([keyPath isEqualToString:observeStr]){NSString*newValuechange[NSKeyValueChangeNewKey];// change[NSKeyValueChangeOldKey]旧值// change[NSKeyValueChangeNewKey]新值// change[NSKeyValueChangeKindKey]变化类型设置/插入/删除NSLog(observeStr 变了新值是%,newValue);}}// 移除观察不然会崩-(void)dealloc{[self.secondVC removeObserver:selfforKeyPath:observeStr];}B 那边改属性值// 直接改KVO 会自动触发self.observeStr新的数据;KVO 感觉平时用的不多一般在需要监听 model 属性变化的时候会用界面传值用这个稍微有点绕‼️记得remove只要你能拿到一个对象的引用你就可以监听它的属性变化属性是用property声明的property自动生成了会触发 KVO 的 setter 方法特性代理/Block/通知KVO触发方式主动调用属性变化自动触发需要写调用代码吗需要[self.delegate xxx]不需要属性赋值自动触发被监听对象知道有监听者吗知道delegate属性不知道完全解耦单例传值单例严格来说也不算传值更像是搞了一个全局共享的数据容器谁都能读谁都能写适合那种多个界面都需要访问同一份数据的场景比如用户信息、购物车、全局配置之类的思路创建一个单例类把需要共享的数据放进去任何界面都通过这个单例来存取数据不用在界面之间互相传DataManager.h单例类#importFoundation/Foundation.hinterfaceDataManager:NSObjectproperty(nonatomic,copy)NSString*shareData;property(nonatomic,strong)NSMutableArray*dataList;// 获取单例的类方法(instancetype)sharedManager;endDataManager.m#importDataManager.himplementationDataManager(instancetype)sharedManager{staticDataManager*instancenil;staticdispatch_once_t onceToken;// dispatch_once 保证这段代码只执行一次线程安全dispatch_once(onceToken,^{instance[[DataManager alloc]init];instance.dataList[NSMutableArray array];});returninstance;}enddispatch_once是关键保证多线程情况下也只会创建一个实例不会出现创建多个的情况。A 界面写入数据-(void)saveData{[DataManager sharedManager].shareData存进单例的数据;[[DataManager sharedManager].dataList addObject:item1];}B 界面读取数据不需要 A 传直接自己取-(void)viewDidLoad{[superviewDidLoad];NSString*data[DataManager sharedManager].shareData;NSLog(从单例拿到的数据%,data);self.label.textdata;}单例的优点是用起来特别方便任何地方都能直接取不用关心调用链但缺点也很明显数据是全局的随便哪里都能改出了问题很难排查是谁改的另外单例的生命周期是整个 App数据不用了也不会自动释放要注意内存NSUserDefaults 传值UserDefaults 是系统提供的一个轻量级持久化方案数据写进去之后即使 App 重启也还在严格来说这也不是传值更像是存值但多界面共享数据的时候也会用到思路把数据写到 UserDefaults任何界面任何时候都能读出来而且数据是持久化的写入数据A 界面-(void)saveToUserDefaults{NSUserDefaults*defaults[NSUserDefaults standardUserDefaults];// 支持的基础类型[defaults setObject:存进去的字符串forKey:myDataKey];[defaults setInteger:18forKey:ageKey];[defaults setBool:YES forKey:isLoginKey];// 写完最好同步一下保证立刻写入磁盘// iOS 7 之后其实会自动同步但手动调一下更保险[defaults synchronize];}读取数据B 界面甚至 App 重启后也能读到-(void)loadFromUserDefaults{NSUserDefaults*defaults[NSUserDefaults standardUserDefaults];NSString*data[defaults objectForKey:myDataKey];NSInteger age[defaults integerForKey:ageKey];BOOL isLogin[defaults boolForKey:isLoginKey];NSLog(data%, age%ld, isLogin%d,data,(long)age,isLogin);}存复杂对象比如自定义 modelUserDefaults 不能直接存自定义对象要先序列化成 NSData// NSData 就是把任何数据图片、文件、字符串等转换成二进制数据装进一个盒子里// 存需要 model 遵守 NSCoding 协议MyModel*model[[MyModel alloc]init];model.nametest;NSData*data[NSKeyedArchiver archivedDataWithRootObject:model];[[NSUserDefaults standardUserDefaults]setObject:data forKey:modelKey];// 取NSData*savedData[[NSUserDefaults standardUserDefaults]objectForKey:modelKey];MyModel*savedModel[NSKeyedUnarchiver unarchiveObjectWithData:savedData];删除数据[[NSUserDefaults standardUserDefaults]removeObjectForKey:myDataKey];UserDefaults 适合存用户偏好设置、登录状态、上次选择的选项这类小数据‼️不适合存大数据UserDefaults 本质上是个 plist 文件太大会影响性能也不适合存敏感信息不加密直接明文要存密码之类的用 Keychain启动时全量加载UserDefaults 对应的 plist 文件在 App 启动时会整个读进内存不是按需读取所以文件越大启动越慢特性单例NSUserDefaults数据存储位置内存硬盘磁盘App 关闭后数据❌ 消失✅ 还在使用场景临时全局数据需要保存的用户设置能存什么任何对象基础类型自定义对象和UIImage要转换成NSData速度快内存慢一点读写硬盘总结与 Trade-off 对比快速对照表传值方式传值方向是否持久化适用场景属性传值A → B 正向否相邻界面正向传简单数据代理传值B → A 反向否反向传回调逻辑多时Block 传值B → A 反向否反向传代码简洁通知传值任意方向否跨模块一对多广播KVO 传值观察属性变化否监听 model 层数据变化变化自动调用单例传值任意方向否内存全局共享数据多界面共用UserDefaults任意方向是用户偏好、登录态等小数据详细 Trade-off 分析属性传值✅ 最简单直观代码少一看就懂❌ 只能正向反向传不了❌ 强依赖A 需要 import BA 和 B 耦合在一起代理传值✅ 反向传值的经典方案UIKit 里大量使用是 Apple 推荐的模式✅ 协议定义清晰接口明确可以有多个方法❌ 写起来最繁琐要定义协议、声明属性、遵守协议、实现方法步骤多❌ 只能一对一一个 delegate 只有一个对象接收Block 传值✅ 写起来比代理简洁很多回调逻辑就写在跳转旁边上下文清晰✅ 也是反向传适合一次性的回调场景❌ 如果回调逻辑很复杂block 里嵌套一堆东西可读性会变差block 地狱❌ 也是一对一没法多个地方同时接收通知传值✅ 完全解耦发送方和接收方互相不知道对方存在✅ 支持一对多可以跨任意层级传值不受界面层级限制❌ 太灵活了反而是缺点滥用之后代码逻辑很难追踪不知道谁发的谁在收❌ 通知名字是字符串写错了不报错运行时才发现收不到KVO 传值✅ 不需要被观察方主动发数据属性一变自动触发✅ 适合做数据绑定model 变了 view 自动更新❌ keyPath 是字符串写错了也是运行时才发现用于界面传值很绕❌ 忘记 removeObserver 必崩而且错误信息不友好单例传值✅ 用起来极方便任何地方直接[DataManager sharedManager].xxx拿数据✅ 适合真正全局共享的数据比如用户信息、购物车❌ 数据是全局的谁都能改出了 bug 很难定位是哪里改的❌ 生命周期等于 App 生命周期数据不会自动释放有内存压力UserDefaults 传值✅ 数据持久化App 重启还在这是其他方式没有的✅ 用起来也很简单API 直观❌ 数据明文存储不安全不能存密码、token 这类敏感信息用 Keychain❌ 不适合频繁读写每次都是 IO 操作存大数据会拖慢启动