iOS开发基础21-深入理解通知、代理、KVO和Block在iOS开发中的应用

在iOS开发中,不同对象之间的通信是非常常见的需求。主要有四种常用的方式来处理这种通信需求:通知(Notification)、代理(Delegate)、键值观察(KVO)和Block。本文将详细介绍这四种方式,分析其适用场景、使用方法、优缺点。

一、通知(Notification)

1. 通知中心(NSNotificationCenter)

通知中心是iOS系统提供的一种机制,它允许不同的对象通过发布和接收通知来进行通信。每个应用程序都有一个唯一的通知中心实例。任何对象都可以向通知中心发布通知,其他对象则可以注册为监听者来接收这些通知。

2. 通知(NSNotification)的结构

一个通知通常包含三个属性:

  • name: 通知的名称。
  • object: 通知发布者。
  • userInfo: 附带的额外信息,是一个字典类型。

初始化一个通知对象

NSNotification *notification = [NSNotification notificationWithName:@"testNotification" object:self userInfo:@{@"key": @"value"}];

3. 发布通知

发布通知有以下几种方式:

// 示例1:发布一个自定义通知对象
[[NSNotificationCenter defaultCenter] postNotification:notification];

// 示例2:发布一个名称和发布者的通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"testNotification" object:self];

// 示例3:发布一个包含名称、发布者和额外信息的通知
[[NSNotificationCenter defaultCenter] postNotificationName:@"testNotification" object:self userInfo:@{@"key": @"value"}];

4. 注册通知监听器

要监听某个通知,需要在通知中心注册一个监听器:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"testNotification" object:nil];

也可以使用block的方式:

[[NSNotificationCenter defaultCenter] addObserverForName:@"testNotification" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
    NSLog(@"Received notification with info: %@", note.userInfo);
}];

5. 取消注册通知监听器

监听器在不再需要时应该取消注册,以避免应用程序崩溃:

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

6. UIDevice通知

UIDevice提供了一些常用的设备通知,例如设备旋转、电池状态等:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationChanged:) name:UIDeviceOrientationDidChangeNotification object:nil];

7. 键盘通知

键盘状态改变时,系统会发送相应的通知,可以用来执行一些特定操作:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];

小结

通知机制适用于一个对象要向多个对象传递消息的场景,但需要注意通知名称的一致性。

二、代理(Delegate)

1. 代理模式的定义

代理是一种设计模式,可以将一个类中的某些使命交由另一个类来处理。代理模式在iOS开发中非常常见,例如UITableViewDelegate

2. 使用代理模式的步骤

  1. 定义协议:声明委托的方法。
  2. 实现协议:代理类实现协议方法。
  3. 设置代理:设置代理对象。

定义协议和方法

@protocol XMGWineCellDelegate <NSObject>
@optional
- (void)wineCellDidClickPlusButton:(UITableViewCell *)cell;
- (void)wineCellDidClickMinusButton:(UITableViewCell *)cell;
@end

在Cell内部调用代理方法

@interface XMGWineCell : UITableViewCell
@property (nonatomic, weak) id<XMGWineCellDelegate> delegate;
@end

@implementation XMGWineCell

- (void)plusButtonClicked {
    if ([self.delegate respondsToSelector:@selector(wineCellDidClickPlusButton:)]) {
        [self.delegate wineCellDidClickPlusButton:self];
    }
}

- (void)minusButtonClicked {
    if ([self.delegate respondsToSelector:@selector(wineCellDidClickMinusButton:)]) {
        [self.delegate wineCellDidClickMinusButton:self];
    }
}

@end

在控制器中实现代理方法

@interface ViewController () <XMGWineCellDelegate>
@end

@implementation ViewController

- (void)wineCellDidClickPlusButton:(XMGWineCell *)wineCell {
    // 处理加号点击事件
}

- (void)wineCellDidClickMinusButton:(XMGWineCell *)wineCell {
    // 处理减号点击事件
}

@end

小结

代理模式适用于一个对象委托另一个对象处理任务的场景。比通知机制更规范,适合一对一通信。

三、键值观察(KVO)

1. KVO的定义

KVO,即键值观察,是指观察某个对象的属性值变化。

2. 添加KVO监听

[obj addObserver:self forKeyPath:@"property" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

3. 实现观察方法

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"property"]) {
        // 处理属性变化
    }
}

4. 移除监听

在对象销毁前移除监听:

- (void)dealloc {
    [obj removeObserver:self forKeyPath:@"property"];
}

小结

KVO非常适合用来监听对象属性值的变化,但其灵活度不如通知和代理。

四、Block

1. Block的定义

Block是一种封装了代码和数据的对象,能够在需要时执行这些代码。它类似于C语言中的函数指针,但比函数指针更强大。

2. Block的声明和使用

声明一个Block

typedef void(^ExampleBlock)(NSString *message);

使用Block

ExampleBlock block = ^(NSString *message) {
    NSLog(@"Message: %@", message);
};

block(@"Hello, Block!");

3. Block在回调中的应用

Block在回调中非常有用。例如,可以定义一个带有Block回调的自定义类:

@interface CustomClass : NSObject
@property (nonatomic, copy) void (^completionHandler)(BOOL success);

- (void)performActionWithCompletion:(void (^)(BOOL success))completion;
@end

@implementation CustomClass

- (void)performActionWithCompletion:(void (^)(BOOL success))completion {
    // 保存Block
    self.completionHandler = completion;

    // 执行一些操作
    // 操作完成后,调用Block
    if (self.completionHandler) {
        self.completionHandler(YES);
    }
}

@end

在使用时:

CustomClass *customObject = [[CustomClass alloc] init];
[customObject performActionWithCompletion:^(BOOL success) {
    if (success) {
        NSLog(@"Action completed successfully!");
    } else {
        NSLog(@"Action failed.");
    }
}];

小结

Block适用于需要回调的场景,代码更简洁和易维护。Block还可以捕获和保存其所处作用域中的变量。

五、案例:购物车

1. 通知在购物车中的应用

在购物车案例中,Cell内部发布通知,控制器中监听通知:

Cell 内部发布通知

[[NSNotificationCenter defaultCenter] postNotificationName:@"plusClickNotification" object:self];

控制器中注册和处理通知

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(plusClick:) name:@"plusClickNotification" object:nil];

- (void)plusClick:(NSNotification *)note {
    XMGWineCell *cell = note.object;
    int totalPrice = self.totalPriceLabel.text.intValue + cell.wine.money.intValue;
    self.totalPriceLabel.text = [NSString stringWithFormat:@"%d", totalPrice];
}

2. 使用代理模式

Cell 内部调用代理方法

if ([self.delegate respondsToSelector:@selector(wineCellDidClickPlusButton:)]) {
    [self.delegate wineCellDidClickPlusButton:self];
}

控制器实现代理方法

- (void)wineCellDidClickPlusButton:(XMGWineCell *)wineCell {
    int totalPrice = self.totalPriceLabel.text.intValue + wineCell.wine.money.intValue;
    self.totalPriceLabel.text = [NSString stringWithFormat:@"%d", totalPrice];
}

3. 使用KVO

添加监听

[wine addObserver:self forKeyPath:@"count" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

实现观察方法

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(XMGWine *)wine change:(NSDictionary *)change context:(void *)context {
    if (new > old) {
        int totalPrice = self.totalPriceLabel.text.intValue + wine.money.intValue;
    } else {
        int totalPrice = self.totalPriceLabel.text.intValue - wine.money.intValue;
    }
    self.totalPriceLabel.text = [NSString stringWithFormat:@"%d", totalPrice];
}

4. 使用Block

在购物车案例中,可以通过Block实现加号按钮点击后的总价计算。

在Cell中声明Block属性

typedef void(^WineCellActionBlock)(XMGWineCell *cell);

@interface XMGWineCell : UITableViewCell
@property (nonatomic, copy) WineCellActionBlock plusActionBlock;
@property (nonatomic, copy) WineCellActionBlock minusActionBlock;
@end

在Cell中调用Block

- (void)plusButtonClicked {
    if (self.plusActionBlock) {
        self.plusActionBlock(self);
    }
}

- (void)minusButtonClicked {
    if (self.minusActionBlock) {
        self.minusActionBlock(self);
    }
}

在控制器中设置Block回调

XMGWineCell *cell = ...
[cell setPlusActionBlock:^(XMGWineCell * _Nonnull cell) {
    int totalPrice = self.totalPriceLabel.text.intValue + cell.wine.money.intValue;
    self.totalPriceLabel.text = [NSString stringWithFormat:@"%d", totalPrice];
}];

[cell setMinusActionBlock:^(XMGWineCell * _Nonnull cell) {
    int totalPrice = self.totalPriceLabel.text.intValue - cell.wine.money.intValue;
    self.totalPriceLabel.text = [NSString stringWithFormat:@"%d", totalPrice];
}];

六、对比分析

1. 通知

  • 优点:适用于多对多的通信,解耦性好。
  • 缺点:通知名称重名时容易导致冲突,管理不便。

2. 代理

  • 优点:适用于一对一的通信,严格的接口规范。
  • 缺点:耦合度高,代理方法较多时,代码维护复杂。

3. KVO

  • 优点:适用于对象属性值的变化观察,简洁。
  • 缺点:灵活度低,容易添加和移除监听时发生错误。

4. Block

  • 优点:代码简洁,适用于回调场景,能捕获变量。
  • 缺点:循环引用问题需要特别注意,处理不当容易造成内存泄漏。

结论

通知、代理、KVO和Block各有优缺点,适用于不同的场景。在实际开发中,开发者应根据具体需求选择合适的机制,以实现高效、可靠的对象间通信。

posted @ 2015-07-26 01:25  Mr.陳  阅读(503)  评论(0编辑  收藏  举报