多界面传值
多界面传值
- 前言
- 属性传值
- 协议传值
- block传值
- 通知传值
- KVO传值
- 总结
前言
在先前的的仿写中多次使用到了不同界面的传值,这里作一个总结。
属性传值
属性传值是最常用、最简单的一种控制器间数据传递方式,是通过定义属性并设置值来传递数据的。
以在3gshare关注状态的仿写的内容为例,将要跳转进的视图控制器定义为属性,在每次push时,判断该属性是否已经被初始化,如果没有,则初始化,否则不重复操作初始化,使得push进的界面的状态被保留,而不是反复刷新。
@property(nonatomic, strong) FollowViewController *followVC;
if (!self.followVC) {self.followVC = [[FollowViewController alloc] init];self.followVC.title = @"新关注的";self.followVC.followStatus = self.followStatus;
} else {self.followVC.followStatus = self.followStatus;
}
[self.navigationController pushViewController:self.followVC animated:YES];
-(void)pressBtn:(UIButton*)button {NSInteger index = button.tag; BOOL selected = ![self.followStatus[index] boolValue];self.followStatus[index] = @(selected);button.selected = selected;
}
效果不再重复展示。
协议传值
协议传值是通过定义协议和代理方法的方式进行多界面传值,常用于反向传值,也就是从第二个页面把数据回传到第一个页面。实现方法是第二页定义一个协议,第一页遵守并实现协议方法,然后第二页通过代理对象调用协议方法,把数据回去。
这里我们依然以3gshare的仿写中注册界面将账号信息传回给登录界面为例:
- 定义协议和声明代理属性(一般定义在被传值的界面,也就是发送数据的一方)
@protocol InformationDelegate <NSObject>-(void)setupInformation:(NSMutableDictionary*)dictionary;@end@interface RegisterViewControl : UIViewController@property(nonatomic, weak) id<InformationDelegate> dictDelegate;@end
- 触发代理方法:也就是调用代理方法,一般在被传值的界面。
NSString *UserName = self.UserNameText.text;
NSString *PassWord = self.PassWordText.text;
NSDictionary *dict = [NSDictionary dictionaryWithObject:PassWord forKey:UserName];
[self.dictionary addEntriesFromDictionary:dict];
//self.dictionary交给代理对象self.dictDelegate执行方法setupInformation
[self.dictDelegate setupInformation:self.dictionary];
[self.navigationController popViewControllerAnimated:YES];
- 实现代理方法(一般在传值的界面):也就是谁想接受传值的数据,就实现这个方法。
#import "LoginViewControl.h"
#import "RegisterViewControl.h"@interface LoginViewControl ()<UITextFieldDelegate, UITabBarDelegate, InformationDelegate>@end@implementation LoginViewControl-(void)setupInformation:(NSMutableDictionary*)dictionary {[self.dict addEntriesFromDictionary:dictionary];
}
- 设置代理:这一步最重要也最容易被漏掉,它一般写在传值方,也就是接受数据的页面。
-(void)pressRegister {RegisterViewControl *registerVC = [[RegisterViewControl alloc] init];registerVC.dictDelegate = self;[self.navigationController pushViewController:registerVC animated:YES];
}
具体的效果展示也不再重复。
block传值
block传值跟协议代理传值的思路相似,也用于后面向前面的传值,但它使用代码块进行传值。
- 定义block类型和属性
#import <UIKit/UIKit.h>typedef void(^SendBlock)(NSString *text);@interface BViewController : UIViewController@property(nonatomic, copy) SendBlock send;@end
typedef void(^SendBlock)(NSString *text)
:
- typedof:定义类型别名
- void(^SendBlock)(NSString *text):无返回值,带NSString参数的block类型
- SendBlock:block名字
这里值得注意的是:属性为什么要用 copy
- 首先我们要知道block的存储位置,block 本质上是一个带有上下文的函数对象。根据创建场景不同,block 的存储位置不同:
- 栈上:默认block分配在栈上,随着作用域结束就会被销毁。
- 堆上:如果使用copy关键字,那么系统会将block从栈复制到堆上,这样即使超出作用域,block仍然继续存在。
- 全局区:不捕获外部变量的 block,也叫全局 block,会直接存放在全局区,不需要 copy 就能安全使用。
- 然后我们再来考虑为什么属性要用copy而不是strong。
- strong:赋值时,block可能还是栈上的block,当方法调用完后,栈内存释放,block也随之销毁,但strong修饰的属性还会持有一个悬空指针,访问就会崩溃。
- copy:copy会把 block 拷贝到堆上,生命周期就不依赖原作用域,直到对象释放才销毁,是安全的。
这里我们再用一个代码来看一下区别:
#import <Foundation/Foundation.h>typedef void(^TestBlock)(void);@interface MyClass : NSObject@property(nonatomic, strong) TestBlock strongBlock;
@property(nonatomic, copy) TestBlock copyBlock;@end#import <Foundation/Foundation.h>
#import "MyClass.h"void test(void) {MyClass *obj = [[MyClass alloc] init];int value = 42;obj.strongBlock = ^{NSLog(@"strongBlock: %d", value);};obj.copyBlock = ^{NSLog(@"copyBlock: %d", value);};NSLog(@"strongBlock class: %@", [obj.strongBlock class]);NSLog(@"copyBlock class: %@", [obj.copyBlock class]);
}int main(int argc, const char * argv[]) {@autoreleasepool {test();}return 0;
}
运行结果:
我们发现运行结果和我们预想的不一样,无论strong还是copy,block都在堆上。我查阅了一下资料,发现这是编译器ARC模式下,系统自动帮我们做了一次copy操作,因此我们会发现block存储位置是一样的。然而在MRC模式下的输出结果是我们预想的那样。
这里有些超出我现学的知识,后续了解学习后来完善补充这里的内容。
- 触发block:将要回传的数据作为block的参数传入并执行block。
- (void)backAndSend {if (self.send) {self.send(self.textField.text);}[self dismissViewControllerAnimated:YES completion:nil];
}
将输入框输入的内容作为参数传入block时,前一个视图控制器接收的信息就会是我传进去的参数。
- 接收数据:在需要接受的地方将传回的值进行使用。
- (void)goToB {BViewController *bVC = [[BViewController alloc] init];bVC.send = ^(NSString *text) {self.label.text = [NSString stringWithFormat:@"收到: %@", text];};[self presentViewController:bVC animated:YES completion:nil];
}
展示一下效果:
通知传值
通知传值是一种适合一对多传值的传值方式,它使用了通知中心来实现观察者模式,允许一个对象在发生改变时通知其他观察者对象。
首先我们确定我们的目的,从A跳转到B,并且点击B中按钮将我们字典中存入的内容返回给A,也就是在B界面修改并传值给A界面。
- 发送通知:
将B界面的值传回给A界面,因此在B界面发送通知。
[[NSNotificationCenter defaultCenter] postNotificationName:@"MyNotification"object:niluserInfo:info];
- postNotificationName:通知的名称,通过该名称区分不同的通知,以便接收时,监视相应名称的通知。
- object:通知的发送者,表示是哪个对象发送了这个通知。通常情况下,我们不需要传递发送者,可以传入nil。
- userInfo:通知的附加信息,可以通过字典传递一些额外的信息给接收通知的对象。通常情况下,我们在发送通知时,将一些需要传递的数据放入这个字典中。
- 注册监听,注册观察者:
B界面将值传回给A界面,因此在A界面注册监听,也就是A界面愿意接受B界面传回的值。
[[NSNotificationCenter defaultCenter] addObserver:selfselector:@selector(receiveNotice:)name:@"MyNotification"object:nil];
- addObserver:要注册的观察者对象,通常当前对象作为参数接受通知。
- selector:观察者对象用于处理通知的方法,这个方法必须带有一个参数,通常是 NSNotification 对象,用于接收传递的通知信息。
- name:要观察的通知名称,与通知名称匹配。
- object:通知发送者的对象。如果设置为 nil,则会接收任何发送给指定名称的通知。如果设置为特定对象,只有该对象发送的与指定名称匹配的通知才会被发送给观察者。
- 接收通知:
在A界面接收到传回来的值,也就是这个字典,并修改label
- (void)receiveNotice:(NSNotification *)notification {NSDictionary *info = notification.userInfo;NSString *msg = info[@"message"];self.label.text = msg;
}
- 移除通知:
最重要且容易遗漏的一步,整个通知传值结束后,一定要移除通知,避免内存泄露
- (void)dealloc {[[NSNotificationCenter defaultCenter] removeObserver:self];
}
效果展示:
KVO传值
笔者此时还没有学习这种学习方式,今后学习后会来补充完善这里的内容。
总结
在仿写项目的过程中,多界面传值是很重要的部分,笔者会在学习后继续补充完善。