开头
- 这是分篇章,整篇请看零基础iOS开发学习日记
数据界面间传递
实际用处
- 在实际开发中,经常会有一个需求,在获取数据后,要在界面显示,或者当数据更新后,要进行界面刷新,这就引出了一个问题,如果
将数据获取工作和界面刷新工作绑定在一起
,也就是说如何让一个员工(A类)通知另一个员工(B类)工作
- 在我经手的项目和仿写的项目中,常用的方法有
通知
、代理协议
、block
,下面会依次举例
通知
实际例子
A
是一个商家,需要10个鸡腿
,所以A关注
了一个叫做需要鸡腿
的社区- 而
B
正好手上多出了10个鸡腿
,就在需要鸡腿
的社区,发布了10个鸡腿
的帖子 - 那么,
A
和B
达成了合作,A
拿到了所需要的10个鸡腿
,进行下一步的制作
核心函数
- 添加观察,类比上面例子中
A
的工作
[[NSNotificationCenter defaultCenter] addObserver:self //观察者,需要的人
selector:@selector(handleNotification:) //下一步的行为
name:@"NeedFood" //通知名,社区名
object:@10]; //要求,类比10个鸡腿,可以为nil
复制代码
- 发布通知,类比上面例子中
B
的工作
[[NSNotificationCenter defaultCenter] postNotificationName:@"NeedFood"
object:@10 //要求,拥有的是个鸡腿
userInfo:dic]; //附带的消息,以字典的形式传递
复制代码
基础用法
- A类中,观察通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"NeedFood" object:@10];
复制代码
- 处理通知
- (void)handleNotification:(NSNotification *)notification {
//获取传过来的信息
NSDictionary *dic = [notification userInfo];
NSLog(@"i receive foods from %@", dic[@"name"]);
}
复制代码
- 移除观察,iOS9后不用手动添加,但是历史遗留习惯,会在析构中写上
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
复制代码
- B类中,发布通知,每次点击后数量+1
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.count = [NSNumber numberWithInt:(self.count.intValue +1)];
NSLog(@"TestViewController - %d", self.count.intValue);
NSDictionary *dic = @{@"name" : @"TestViewController"};
[[NSNotificationCenter defaultCenter] postNotificationName:@"NeedFood" object:self.count userInfo:dic];
}
复制代码
注意
- 当观察者通知名为nil时,会观察所有通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:nil object:@10];
[[NSNotificationCenter defaultCenter] postNotificationName:@"NeedFood" object:self.count userInfo:dic];
复制代码
- 但是,当通知者,通知名为nil时,发送的通知不会被响应,系统也会报警告
[[NSNotificationCenter defaultCenter] postNotificationName:nil object:self.count userInfo:dic];
复制代码
- 当观察者object有参数,也就是有要求;通知者object参数为nil时,通知不会被响应
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"NeedFood" object:@10];
[[NSNotificationCenter defaultCenter] postNotificationName:@"NeedFood" object:nil userInfo:dic];
复制代码
- 但是,当观察者object参数为nil,无要求;通知者object有参数,只要通知名对的上都会被响应
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"NeedFood" object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:@"NeedFood" object:@10 userInfo:dic];
复制代码
- 所以,在实际开发中,一般object为nil,而通知名的设置要注重可读性,需要传递的值放在数据字典中即可
代理协议
实际例子
A
有一台电脑
,可以用来写论文B
有创新点和数据
,但是没有电脑,不能写论文- 所以,
B委托A
,让A
帮B
把论文写出来 - 这里的
一台电脑
其实是一种能力,指的是A有、B需要但是B没有的能力
,类比到程序中,就像是在ViewController
上添加一个tableView
,但是仅仅是tableView
是无法在控制器上显示的,所以就需要委托ViewControll(遵守代理协议)
去实现必要的方法(代理方法)
,对应上面的例子,也就是B
把论文要求和内容告诉A
,让A
去实现
基础用法
- B类,建立协议,包含需要
A
来做的工作
@protocol TestViewControllerDelegate <NSObject>
//必须要做的
@required
- (void)writePapper:(NSNumber *)count;
//选择要做的
@optional
- (void)check;
@end
复制代码
- 设置代理对象
@property (nonatomic, weak) id<TestViewControllerDelegate> delegate;
复制代码
- 在需要的地方执行代理方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.count = [NSNumber numberWithInt:(self.count.intValue +1)];
if ([self.delegate respondsToSelector:@selector(writePapper:)]) {
[self.delegate writePapper:self.count];
}
if ([self.delegate respondsToSelector:@selector(check)]) {
[self.delegate check];
}
}
复制代码
- A类中,遵守代理协议,实现代理方法
- (void)writePapper:(NSNumber *)count {
NSLog(@"ViewController - writePapper - %d", count.intValue);
}
- (void)check {
NSLog(@"ViewController - check");
}
复制代码
小结
- 在我的日常开发中,代理常用来进行数据刷新,自定义控件的点击方式,传值等工作
- 在写代理前,一定要明确
需要另一个控制器做当前控制器的什么工作,需要用到什么数据
Block
实际例子
- block的感觉,其实和代理是完全一样的;本质上就是,在当前控制器运行一段代码块,而这个代码块,是要在另一个控制器去准备的;也就是说,另一个控制器,帮当前控制器做一些处理
- 这里也正好把各种情况下的
block
的使用形式整理一下
作为对象属性
- 经常用做传值工作
- 格式
@property (nonatomic, copy) returnType (^blockName)(parameterTypes);
//不带参数、无返回值
@property (nonatomic, copy) void (^testBlock)(void);
//带参数,有返回值
@property (nonatomic, copy) NSString* (^testStringBlock)(NSString* str, NSNumber* count);
复制代码
- B类,在需要的地方直接调用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.count = [NSNumber numberWithInt:(self.count.intValue +1)];
self.testBlock();
NSString *returnStr = self.testStringBlock(@"TestViewController", self.count);
NSLog(@"%@", returnStr);
}
复制代码
- A类,给B类的
block
赋值
vc.testBlock = ^{
NSLog(@"ViewController - testBlock");
};
vc.testStringBlock = ^NSString * _Nonnull(NSString * _Nonnull str, NSNumber * _Nonnull count) {
str = [str stringByAppendingFormat:@" - ViewController - %d", count.intValue];
return str;
};
复制代码
作为函数参数
- 经常用做自定义处理数据,让开发者有权限处理当前函数内的数据
- 格式
- (void)handleWithBlock:(returnType (^nullability)(parameterTypes))blockName;
//无参数,无返回值
- (void)handleWithBlock:(void (^)(void)) blockName;
//有参数
- (void)handleWithBlock:(void (^)(NSNumber* count)) blockName;
//有参数、有返回值
- (void)handleWithBlock:(NSString* (^)(NSNumber* count)) blockName;
复制代码
- 函数实现
//无参数,无返回值
- (void)handleWithBlock:(void (^)(void)) blockName {
blockName();
}
//有参数
- (void)handleWithBlock:(void (^)(NSNumber* count)) blockName {
blockName(@10);
}
//有参数、有返回值
- (void)handleWithBlock:(NSString* (^)(NSNumber* count)) blockName {
NSString* str = blockName(@10);
NSLog(@"handleWithBlock - %@", str);
}
复制代码
- 函数调用
//无参数,无返回值
[self handleWithBlock:^{
NSLog(@"handleWithBlock %d", self.count.intValue);
}];
//有参数
[self handleWithBlock:^(NSNumber *count) {
NSLog(@"handleWithBlock - %d", count.intValue);
}];
//有参数、有返回值
[self handleWithBlock:^NSString *(NSNumber *count) {
NSLog(@"%@", [NSString stringWithFormat:@"handleWithBlock -%d", count.intValue]);
return [NSString stringWithFormat:@"handleWithBlock -%d", count.intValue];
}];
复制代码
做一个数据类型
- 格式
typedef returnType (^TypeName)(parameterTypes);
//_Nullable值得是可以返回nil,对应的也有_Nonnull,类型为testBlock
typedef NSString* _Nullable (^testBlock)(NSString* _Nullable str, NSNumber* _Nullable count) ;
复制代码
- 定义block对象
@property (nonatomic, copy) testBlock myBlock;
复制代码
- B类中使用
NSString *returnStr = self.myBlock(@"TestViewController", self.count);
NSLog(@"%@", returnStr);
复制代码
- A类中赋值
vc.myBlock = ^NSString * _Nullable(NSString * _Nullable str, NSNumber * _Nullable count) {
NSLog(@"%@ - %d", str, count.intValue);
return @"ViewController - myBlock";
};
复制代码
小结
- block常在网络方法中使用,让用户自定义错误和正确的处理
- 随着iOS的迭代,不少原来用协议的方法,也集合到了用block调用,这也说明了block和代理协议的使用目标是一致的,当然,代理协议能处理的事是更多的
block的修饰问题
- 栈中的对象随时会被销毁,再次调用空对象,会导致崩溃
- block是被存储在栈中的,经过ARC处理后,存储到堆上的,所以在MRC的情况下,block作为属性时要用copy修饰,复制粘贴到堆区,也就是进行了深拷贝,赋值到了一个新的地址,拥有了block的所有权,防止生成僵尸对象
总结
- 我感觉这三种调用方式,选择哪个还是要根据代码整体设计来决定,增加可读性
- 如果两个任务关联性较强,则使用通知,例如蓝牙控制器收到信息,通知界面进行页面更新
- 如果是自定义控件,需要调用控制器的方法,则用代理协议来实现
- 如果是一些函数中的一些数据处理和错误处理,则用block解决
- 当然对一些直白的数据的话,直接用属性赋值就可以解决了,上面的处理方式,针对事有一定先后顺序的任务
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END