OC的数据传递-Block,代理,通知,KVO

本文探讨了Objective-C中的数据传递技术,包括Block、代理、通知和KVO。Block作为代码块,实现了双向数据传递;代理通过@protocol实现一对一的消息传递;通知则是一对多的单向通信;KVO则允许对象监听其他对象属性的变化。文中通过实例代码展示了每种技术的使用和特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在开始之前,先贴一张图片,表明为什么需要将Block,代理,通知和KVO放在一起。

OC的数据传递

由上图可知,Block,代理,通知和KVO都能实现数据传递的功能,可以根据不同的情景选择合适的方式

Block

声明
// 使用typedef为block变量定义别名
// 解释:将一个返回值为double,参数为int的block取一个Rename的别名
typedef double (^Rename)(int);
// Block作为属性的写法
@property (nonatomic,copy) int (^sumOfTwoNumber)(int,int);
// 使用typedef后的block声明变量
@property (nonatomic,copy) Rename divideByTwo;

赋值
// 直接为block赋值
self.sumOfTwoNumber = ^(int a,int b){
    return a + b;
};
// 调用block
NSLog(@"%zd",self.sumOfTwoNumber(10,10)); 

参数
/**
  *  block作为方法参数的的写法
  *  @param before 只是为了说明如果不只有一个block参数,那该怎么写
  *  @param aBlock block参数
  *
  *  @return block返回的整数值
  */
- (int) blockParam:(NSString *)before Block:(int (^)(int,int)) aBlock {
    // 模拟数据的处理
    if (arc4random_uniform((unsigned int)before.length) % 2 == 0) {
        // 执行block
        return aBlock(2,(int)before.length);
    }
    return aBlock(1,(int)before.length);
}
/// 不使用block实现相同功能
- (int) nonBlockParam:(NSString *)before {
    // 模拟数据的处理
    if (arc4random_uniform((unsigned int)before.length) % 2 == 0) {
        return 2 + (int)before.length;
    }
    return 1 + (int)before.length;
}

返回值
NSLog(@"%f",self.divideByTwo(5));
// MARK: - 懒加载block
- (Rename)divideByTwo {
    if (_divideByTwo == nil) {
        _divideByTwo = ^(int param){
            return param / 2.0;
        };
    }
    return _divideByTwo;
}

价值(个人观点)

block是一段预先准备的代码块,它和函数非常的类似,

// 调用方法来传递block,并返回
int result = [self blockParam:@"Jus for test" Block:self.sumOfTwoNumber];

- (int) blockParam:(NSString *)before Block:(int (^)(int,int)) aBlock {

使用了代码块作为参数,真正执行的时是根据预先定义的

return a + b

来完成的

接收者是预先定义的sumOfTwoNumber 发送者是- (int) blockParam:(NSString *)before Block:(int (^)(int,int)) aBlock,接收者做为参数传递而发送者又回调了接收者,所以实现了数据的双向传递

代码块的好处是什么呢? 如果只从上面的- (int) nonBlockParam:(NSString *)before 来进行对比,代码块似乎没有什么用,但是我们可以换个角度去思考,如果nonBlockParam:
是系统或第三方框架提供的方法,我们一般修改它的代码,现在要求在处理完之后,不是要a + b,而是a - b,那就稍微不太妙了,当然我们也可以将需要改变的代码抽取出来,然后写个方法,那也可以实现需求,但是两种方式的实现,block感觉更漂亮一些。


更多

关于block还有很多的内容,内存相关的,还有block引起的循环引用….

代理

代理在iOS中是处处可见的,很多视图都提供了代理属性,比如UITableView,UICollectionView,代理的是通过@protocol(协议)这个语言特性来体现的。

被代理方
  1. 创建一个协议,一般是类名+delegate,声明代理方法
  2. 定义代理属性一般就叫delegate (一般是id<协议>) 即遵守协议的任意NSObject对象
  3. 在适当的时候调用代理属性的代理方法

代理方
  1. 遵守代理协议
  2. 成为被代理方的代理对象 (被代理对象.delegate = self)
  3. 实现代理方法

下面通过代码来实现一个代理的例子

// 被代理对象中:
// 1.创建协议
@protocol DelegateViewControllerDelegate <NSObject>

@optional
- (void) delegateViewControllerDidChange:(UIViewController *) delegateVC;

@end

// 2.代理属性
@property (nonatomic,weak) id<DelegateViewControllerDelegate> delegate;

- (IBAction)changeImage {
    if ([self.delegate respondsToSelector:@selector(delegateViewControllerDidChange:)]) {
        // 3.在合适的时机调用
        [self.delegate delegateViewControllerDidChange:self];
    }
}

// 在代理对象中
// 1.遵守代理协议
@interface ViewController () <DelegateViewControllerDelegate>

/// storyboard方式跳转前会调用的方法
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
    // 如果是从代理对象跳到被代理对象
    if ([segue.identifier isEqualToString:@"delegate2ed"]) {
        // 取出目标控制器
        DelegateViewController *delegateVC = segue.destinationViewController;
        // 2.当前控制器成为代理对象
        delegateVC.delegate = self;
    }
}

/// 实现代理方法
- (void)delegateViewControllerDidChange:(UIViewController *)delegateVC{
    self.imgView.image = [UIImage imageNamed:@"02"];
}

// 实现dealloc方法,看是否有循环引用
- (void)dealloc {
    NSLog(@"abc");
}

效果图

分析:从运行的结果看,并没有循环引用,而要实现代理要做的步骤非常清晰,代理是一对一的,消息不是直接响应的,然后发送者知道接收者是谁,比如在这里,发送者有delegate属性delegateVC.delegate = self;后,就知道谁是接收者了。

通知

代理是一对一的,而通知是一对多,代理或block都可以双向通讯,但是通知只能是单向的,注册通知的对象就是接收者,iOS实现通知主要分两步:

发布通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"ChangeImageNotification" object:self];
注册通知
// 注册通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeImageNoti:) name:@"ChangeImageNotification" object:nil];

两者都是依靠通知中心这个单例 对象

下面就来用代码实现一个简单的通知

  • 自定义一个UIImageView的子类
  • 设置它的touchBegin事件,发布通知
  • 然后在ViewController的viewDidLoad中设置初始的tag,让UIImageView可以与用户交互,注册通知
  • 创建接收到通知的响应事件

相关代码:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    self.tag = self.tag == 1 ? -1 : 1;
    // 创建一个异步任务去发送通知,测试通知是同步的还是异步的
    // 点击图片的时候发布一个通知并把自己做为参数进行传递
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"----%@",[NSThread currentThread]);
        [[NSNotificationCenter defaultCenter] postNotificationName:@"ChangeImageNotification" object:self];
    });
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // 让imageView可以与用户交互
    self.imagView.userInteractionEnabled = YES;
    // 给imageView设置一个tag值
    self.imagView.tag = 1;
    // 注册通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeImageNoti:) name:@"ChangeImageNotification" object:nil];
}
// 接收到指定通知的响应方法
- (void) changeImageNoti:(NSNotification *) noti {
    NSLog(@"当前线程:%@",[NSThread currentThread]);
    // Just for log
    NSLog(@"%@",noti);
    if (self.imagView.tag == -1) {
        self.imagView.image = [UIImage imageNamed:@"02"];
    } else {
        self.imagView.image = [UIImage imageNamed:@"01"];
    }
}

// 切记通知需要被取消通知 不然有可能会有野指针错误
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
// 记得:要在storyboard中绑定对应的视图

效果图

补充一点:通知是同步的

KVO

KVO的全称是Key-Value Observing,提供了一种机制,为一个对象的属性添加一个观察者,当该属性发生变化时,这个观察者就会得到消息。

以下是KVO的一个例子

- (void)viewDidLoad {
    [super viewDidLoad];
    // 添加KVO通知 观察frame的新值
    [self.imgView addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    self.imgView.frame = CGRectOffset(self.imgView.frame, 0, -40);
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    NSLog(@"%@",change);
}

最初时isa指针指向

添加KVC后

发现isa指针指向的类不同了,查阅有关资料,得知KVO实现过程中,会创建一个当前观察对象类的子类,然后在其中调用observeValueForPath:ofObject:change:context:方法

源代码

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值