开头
数据界面间传递
实际用处
- 在实际开发中,经常会有一个需求,在获取数据后,要在界面显示,或者当数据更新后,要进行界面刷新,这就引出了一个问题,如果
将数据获取工作和界面刷新工作绑定在一起,也就是说如何让一个员工(A类)通知另一个员工(B类)工作
- 在我经手的项目和仿写的项目中,常用的方法有
通知、代理协议、block,下面会依次举例
通知
实际例子
A是一个商家,需要10个鸡腿,所以A关注了一个叫做需要鸡腿的社区
- 而
B正好手上多出了10个鸡腿,就在需要鸡腿的社区,发布了10个鸡腿的帖子
- 那么,
A和B达成了合作,A拿到了所需要的10个鸡腿,进行下一步的制作
核心函数
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleNotification:)
name:@"NeedFood"
object:@10];
[[NSNotificationCenter defaultCenter] postNotificationName:@"NeedFood"
object:@10
userInfo:dic];
基础用法
[[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];
}
- (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];
}
注意
[[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去实现
基础用法
@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];
}
}
- (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);
- (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);
}
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);
typedef NSString* _Nullable (^testBlock)(NSString* _Nullable str, NSNumber* _Nullable count) ;
@property (nonatomic, copy) testBlock myBlock;
NSString *returnStr = self.myBlock(@"TestViewController", self.count);
NSLog(@"%@", returnStr);
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解决
- 当然对一些直白的数据的话,直接用属性赋值就可以解决了,上面的处理方式,针对事有一定先后顺序的任务