零基础iOS开发学习日记-功能篇-数据界面间传递

开头

数据界面间传递

实际用处

  • 在实际开发中,经常会有一个需求,在获取数据后,要在界面显示,或者当数据更新后,要进行界面刷新,这就引出了一个问题,如果将数据获取工作和界面刷新工作绑定在一起,也就是说如何让一个员工(A类)通知另一个员工(B类)工作
  • 在我经手的项目和仿写的项目中,常用的方法有通知代理协议block,下面会依次举例

通知

实际例子

  • A是一个商家,需要10个鸡腿,所以A关注了一个叫做需要鸡腿的社区
  • B正好手上多出了10个鸡腿,就在需要鸡腿的社区,发布了10个鸡腿的帖子
  • 那么,AB达成了合作,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,让AB把论文写出来
  • 这里的一台电脑其实是一种能力,指的是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
喜欢就支持一下吧
点赞0 分享