文章目录
前言
在写网易云音乐以及3GShare包括后面的学生管理系统时,用到许多界面传值方法,特撰写博客记录目前学过的几种多界面传值方法
一、属性传值
属性传值是通过定义属性并设置值来实现传递数据的方式,多用于前一个页面向后一个页面传值
假设有两个视图控制器:ViewControllerA 和 ViewControllerB。
在 ViewControllerA.h 文件中定义一个属性:
objectivec
#import <UIKit/UIKit.h>
@interface ViewControllerA : UIViewController
@property (nonatomic, strong) NSString *dataToPass;
@end
在 ViewControllerA.m 文件中设置一个按钮点击事件,当点击按钮时,跳转到 ViewControllerB 并传递数据:
objectivec
#import "ViewControllerA.h"
#import "ViewControllerB.h"
@implementation ViewControllerA
- (void)buttonTapped {
ViewControllerB *viewControllerB = [[ViewControllerB alloc] init];
viewControllerB.receivedData = self.dataToPass;
[self.navigationController pushViewController:viewControllerB animated:YES];
}
@end
在 ViewControllerB.h 文件中定义一个属性来接收从 ViewControllerA 传递过来的数据:
objectivec
#import <UIKit/UIKit.h>
@interface ViewControllerB : UIViewController
@property (nonatomic, strong) NSString *receivedData;
@end
在 ViewControllerB.m 文件中可以使用接收到的数据进行相应的操作:
objectivec
> #import "ViewControllerB.h"
>
> @implementation ViewControllerB
>
> - (void)viewDidLoad {
> [super viewDidLoad];
>
> // 使用接收到的数据
> NSLog(@"Received data: %@", self.receivedData); }
>
> @end
这样,当在 ViewControllerA 视图控制器中点击按钮,切换到 ViewControllerB 视图控制器时,就可以将 dataToPass 的值传递给 receivedData,并在 ViewControllerB 中使用接收到的数据。
二、协议传值
协议传值是通过定义协议和代理方法,在不同的视图控制器之间传递数据的方式。多用于后一个页面将数据回传给前一个页面
这里以3GShare注册界面回传账号密码给登录界面的协议传值作为示例并讲解其步骤:
-
定义协议:
在发送数据的视图控制器(通常是源视图控制器)的头文件中定义一个协议,其中包含需要传递的数据的代理方法。
这步需要我们定义协议的名称后再去定义回传数据的方法
-
声明代理属性:
在发送数据的视图控制器的头文件中声明一个代理属性 ,用于保存代理对象。
-
触发代理方法:
在发送数据的视图控制器中,在适当的时机,触发代理方法,并将需要传递的数据作为参数传递给代理方法。
-
实现代理方法:
在接收数据的视图控制器,也就是前一个控制器(通常是目标视图控制器)中,遵循协议并实现代理方法,以接收传递过来的数据。
-
设置代理:(这是最重要的一步,很多人会忘了这步)
在发送数据的视图控制器中,在跳转到接收数据的视图控制器之前,设置目标视图控制器的代理为当前视图控制器。
通俗的理解就是将后面的视图控制器的代理对象设为前一个视图控制器,让后一个为前一个代理,也就是手下替老板做事,然后将结果告诉老板。
三、block传值
block传值同样也用于后面向前面传值,与协议传值有些相似的地方,但它使用代码块进行传值
- 定义 Block:
在发送方(当前视图控制器)中定义一个 Block 属性,用于接收传递的数据。Block 的类型取决于你要传递的数据类型。
objectivec
typedef void(^testblock)(NSString *);
@property(nonatomic, copy)testblock send;
typedef: 这是一个关键字,用于定义一个数据类型的别名,将后面的复杂类型定义简化为一个简洁的名字。
void(^testblock): 这部分是 Block 的类型定义,其中 testblock 是这个 Block 的别名,也就是我们定义的数据类型名。
(NSString *): 这是 Block 的参数列表,用括号括起来,表示 Block 接受一个 NSString 类型的参数。
这里需要注意的是:
typedef void(^testblock)(NSString *);:这是一个 Block 类型的定义,其中 testblock 是我们给这个 Block 类型起的别名,类似于自定义的数据类型。这个 Block 接受一个 NSString 类型的参数,并且没有返回值。
- 发送方设置 Block:
在发送方视图控制器中设置 Block,将需要传递的数据作为 Block 的参数传入,并执行 Block。
在这段代码中,我将self.textField.text作为参数传入我的代码块,前一个视图控制器接收到的信息就是我传进去的参数 - 接收数据:
在需要接受的地方将传回来的值进行使用,这里的(NSString *sendtext)中的sendtext就是回传回来的值
这里给出block传值的实现动画
四、KVO传值
KVO(Key-Value Observing)是一种iOS编程中的一种观察者模式,用于监听对象属性值的变化。通过KVO,一个对象可以监视另一个对象的属性,当被监视的属性值发生变化时,会收到通知并执行相应的操作。
KVO实现步骤:
- 注册观察者:
在需要监听属性变化的对象(被观察者)中,调用
addObserver:forKeyPath:options:context:
方法来注册观察者。这个方法告诉系统哪个对象要观察哪个属性,以及观察的选项和上下文。
假设我们有一个 Person 类,其中有一个属性 age,我们希望在另一个视图控制器中监听 age 的变化。
objectivec
self.person = [[Person alloc] init];
self.person.age = 3;
// 注册观察者
[self.person addObserver:self
forKeyPath:@"age"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:NULL];
observer: 这是观察者对象,即要接收属性变化通知的对象。通常是当前视图控制器或其他感兴趣的对象。
keyPath: 要监听的属性的名称,以字符串表示。当该属性的值发生变化时,KVO 就会通知观察者。
options: 一个枚举值,用于指定监听的选项。这个参数可以设置为多个选项的组合,使用按位或(|)进行连接。常见的选项有:
NSKeyValueObservingOptionNew: 当属性的值发生变化时,提供新的属性值作为通知的参数。
NSKeyValueObservingOptionOld: 当属性的值发生变化时,提供旧的属性值作为通知的参数。
NSKeyValueObservingOptionInitial: 在添加观察者时,立即发送一次通知,提供当前属性的值作为通知的参数。
NSKeyValueObservingOptionPrior: 在属性值发生实际变化之前,先发送一次通知,提供旧的属性值作为通知的参数。
context: 这是一个指针类型的参数,用于传递额外的上下文信息。通常情况下可以传入 NULL,表示不需要传递上下文信息。如果你需要在观察者中处理一些额外的信息,可以使用自定义的指针类型来传递数据。
- 实现监听方法:
在观察者对象中,实现一个监听方法- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
,监听方法会在被观察的属性发生变化时被调用。
objectivec
// 实现监听方法,当 age 属性变化时会调用该方法
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context {
if ([keyPath isEqualToString:@"age"]) {
// 获取旧值和新值
NSNumber *oldAge = [change valueForKey:NSKeyValueChangeOldKey];
NSNumber *newAge = [change valueForKey:NSKeyValueChangeNewKey];
NSLog(@"old age: %@ --- new age: %@", oldAge, newAge);
}
}
我们常用[change valueForKey:NSKeyValueChangeOldKey]
与[change valueForKey:NSKeyValueChangeNewKey]
来获得我们的新值与旧值
- 移除观察者:
在观察者对象不再需要监听属性变化时,记得调用 removeObserver:forKeyPath: 方法来移除观察者,避免潜在的内存泄漏。
实现效果
五、KVO的自动触发与手动触发
一般来说,我们使用KVO来监听对象的属性,都是针对对象的单个属性,但如果我们想监听一个集合,上面的方法就不适用了。这是因为KVO的触发类型分为自动与手动,我们上面讲述的仅仅只是KVO的自动触发
- 自动触发KVO通常适用于以下情况:
监听对象属性的变化:对于简单的对象属性,例如字符串、数字等,可以使用自动触发KVO。就像我们上面设置了一个age,这就是简单的对象属性,只需要在被监听属性声明前加上@property关键字,并使用nonatomic修饰符即可。当属性值发生变化时,KVO会自动发送通知给观察者。
使用@synthesize合成属性:在使用@synthesize合成属性时,如果指定了观察者,则合成的属性会自动触发KVO通知。
- 手动触发KVO通常适用于以下情况:
监听集合属性的变化:集合属性不支持自动触发KVO通知,所以需要使用手动触发KVO来监听集合属性的变化。在修改集合属性之前调用willChangeValueForKey:方法,在修改完成后调用didChangeValueForKey:方法。
监听非对象类型属性:对于非对象类型的属性,例如C语言基本数据类型,由于它们不是对象,无法自动触发KVO。这时需要使用手动触发KVO通知来监听属性的变化。
自定义KVO通知:有时候我们可能需要在一些特定的场景下自定义KVO通知,这时可以使用手动触发KVO来实现。
这样一来我们知道了如果对象的设置的属性是集合的话,我们需要手动来触发我们的KVO,接下来讲一下原因:
当我们监听一个集合属性时(如NSMutableArray类型属性),KVO默认只会监听这个集合属性的变化,但不会监听集合中的元素的变化。也就是说,如果我们直接修改集合中的元素(比如使用addObject:方法),而没有通过集合属性的setter方法进行修改,KVO是无法察觉到集合中元素的变化的。
通俗的讲,我们的KVO的自动触发只适用于用setter方法修改的属性,也就是用点语法修改的属性都会自动触发我们的KVO,但是集合无法用点语法进行修改,那么这个时候就需要用到我们的手动触发
为了解决这个问题,我们需要在修改集合属性之前,调用willChangeValueForKey :方法,在修改之后,调用didChangeValueForKey:方法。这样做可以手动触发KVO通知,告诉KVO机制集合属性发生了变化,使得KVO能够正确地监听到集合中元素的变化。
- 我们在原来的对象中添加一个集合属性
- 我们将简单的对象属性添加到我们的集合中,同时将我们的监听对象改为我们的集合
此时我还没有去手动触发我们的KVO,我们试着运行一下程序:
我们的内容并没有变化,这也说明了我们并没有触发我们的KVO的监听方法 - 接下来我们将实现手动监听
willChangeValueForKey::在对象属性发生变化之前调用,用于通知KVO即将开始监听属性的变化。
didChangeValueForKey::在对象属性发生变化后调用,用于通知KVO属性的变化已经完成。
willChangeValueForKey 和 didChangeValueForKey 方法通过配对,这样可以通知观察者(监听者)该属性值即将发生变化和已经发生变化。这对于手动触发 KVO 监听非常重要,需要注意的是,这两种方法一定是配对出现的
自此,我们实现了我们KVO对集合类型的监听
六、通知传值
通知传值是一种在不同对象之间进行信息传递的方式,它使用了通知中心来实现观察者模式,允许一个对象在发生改变时通知其他观察者对象。其可以用于跨多个界面传值
其步骤简单分为四步
- 创建并发送通知:
首先,在发送者对象中创建一个通知,并指定通知的名称(通常使用字符串来表示)。可以通过NSNotification类或NSNotificationName宏来创建通知。需要传递信息给其他对象时,可以通过NSNotificationCenter的postNotificationName:object:userInfo:方法来发送通知。在发送通知时,可以附带一些额外的信息(如字典)作为通知的userInfo参数。
我们接下来详细看一下创建与发送通知的方法:
objectivec
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
aName: 通知的名称,是一个字符串类型的参数。通过这个名称来标识不同的通知。发送通知时,需要指定要发送的通知的名称,接收通知时,也需要监听相应名称的通知。
anObject: 通知的发送者,是一个可选参数。通知可以有一个发送者,表示是哪个对象发送了这个通知。通常情况下,我们不需要传递发送者,可以传入nil。
aUserInfo: 通知的附加信息,是一个可选参数。可以通过这个字典传递一些额外的信息给接收通知的对象。通常情况下,我们在发送通知时,可以将一些需要传递的数据放入这个字典中。
这里需要注意的是我们的userInfo:后跟的参数是一个字典类型,我们通过传回一个字典,并通过查找字典的key来获取我们需要的数据
- 注册观察者:
在接收者对象中,需要注册对某个通知感兴趣的观察者。这样,当通知被发送时,观察者就能接收到通知并做出相应的响应。使用NSNotificationCenter的addObserver:selector:name:object:方法来注册观察者。
objectivec
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
observer: 要注册的观察者对象。观察者对象将接收到与指定通知名称匹配的通知。通常,这个参数是当前对象,即要接收通知的对象。
selector: 观察者对象中用于处理通知的方法(函数)的选择器。这个方法必须带有一个参数,通常是 NSNotification 对象,用于接收传递的通知信息。该方法的声明通常形如 -(void)methodName:(NSNotification *)notification;。
name: 要观察的通知名称。这是一个字符串,用于标识要监听的通知。当发送通知时,只有与这个名称匹配的通知才会被发送给观察者。
object: 通知发送者的对象。如果设置为 nil,则会接收任何发送给指定名称的通知。如果设置为特定对象,只有该对象发送的与指定名称匹配的通知才会被发送给观察者。
这里有一点需要特别注意的就是要观察的通知名称必须于创造的通知的名称相同,否则无法接受来自通知的数据
-
接收通知:
接收者对象需要实现一个方法,用于处理接收到的通知。这个方法是在观察者注册时通过selector参数指定的。当通知被发送时,通知中心会调用这个方法,并传递相关的信息给观察者。
以我们上面的方法为例,receiveNotice:就是我们处理通知的方法
-
移除观察者:
在接收者对象被销毁之前,需要将其从通知中心中移除,避免出现潜在的内存泄漏。可以使用NSNotificationCenter的removeObserver:系列方法来移除观察者。
objectivec
- (void)dealloc {
// 移除通知观察者
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
接下来让我们看一下效果:
这样一来,我们就实现了通知传值
总结
多界面传值是iOS中十分重要的知识,笔者还有很多知识还没学到例如KVC等,以后学到了会加以补充