Objective-C 中的观察者模式运用与场景
观察者模式基础概念
在软件系统中,经常会出现这样一种场景:当一个对象的状态发生变化时,需要自动通知其他多个对象,让它们做出相应的反应。例如,在一个新闻发布系统中,当有新的新闻发布时,所有订阅该类新闻的用户都应该收到通知。观察者模式(Observer Pattern)就是为了解决这类问题而诞生的一种设计模式。
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当这个主题对象的状态发生变化时,会自动通知所有的观察者对象,使它们能够自动更新自己的状态。在这种模式中,主题(Subject)是被观察的对象,而观察者(Observer)则是依赖于主题的对象。
Objective-C 中实现观察者模式的基础方式
在Objective-C中,实现观察者模式可以通过使用通知中心(NSNotificationCenter)或者KVO(Key - Value Observing,键值观察)。
使用NSNotificationCenter
NSNotificationCenter是一个基于通知的观察者模式实现。它允许对象注册为某个特定通知的观察者,当该通知被发布时,观察者会收到通知并执行相应的处理代码。
首先,我们创建一个主题类Subject
,当主题状态变化时,它会发布通知:
#import <Foundation/Foundation.h>
@interface Subject : NSObject
@property (nonatomic, assign) NSInteger state;
- (void)changeState;
@end
@implementation Subject
- (void)changeState {
self.state = arc4random_uniform(100);
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName:@"SubjectStateChanged" object:self userInfo:@{@"newState": @(self.state)}];
}
@end
然后,创建一个观察者类Observer
,它会注册为SubjectStateChanged
通知的观察者:
#import <Foundation/Foundation.h>
@interface Observer : NSObject
- (void)handleNotification:(NSNotification *)notification;
@end
@implementation Observer
- (void)handleNotification:(NSNotification *)notification {
NSLog(@"Observer received notification. Subject's new state: %@", notification.userInfo[@"newState"]);
}
@end
在使用时,我们在main
函数中进行如下操作:
#import <Foundation/Foundation.h>
#import "Subject.h"
#import "Observer.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Subject *subject = [[Subject alloc] init];
Observer *observer = [[Observer alloc] init];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:observer selector:@selector(handleNotification:) name:@"SubjectStateChanged" object:subject];
[subject changeState];
[center removeObserver:observer name:@"SubjectStateChanged" object:subject];
}
return 0;
}
在上述代码中,Subject
类通过NSNotificationCenter
发布了一个名为SubjectStateChanged
的通知,Observer
类通过addObserver:selector:name:object:
方法注册为该通知的观察者。当Subject
的changeState
方法被调用时,通知被发布,Observer
的handleNotification:
方法被执行。最后,通过removeObserver:name:object:
方法移除观察者,避免内存泄漏。
使用KVO
KVO是一种基于观察者模式的机制,它允许开发者监听对象属性值的变化。
首先,创建一个被观察的类Person
:
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@end
@implementation Person
@end
然后,创建一个观察者类Watcher
:
#import <Foundation/Foundation.h>
@interface Watcher : NSObject
@end
@implementation Watcher
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"name"] && object != nil) {
NSLog(@"The name of the person has changed to: %@", change[NSKeyValueChangeNewKey]);
}
}
@end
在main
函数中使用KVO:
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Watcher.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
Watcher *watcher = [[Watcher alloc] init];
[person addObserver:watcher forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
person.name = @"John";
[person removeObserver:watcher forKeyPath:@"name"];
}
return 0;
}
在上述代码中,Watcher
类通过addObserver:forKeyPath:options:context:
方法注册为Person
类name
属性的观察者。当person
的name
属性值发生变化时,Watcher
的observeValueForKeyPath:ofObject:change:context:
方法会被调用。最后,通过removeObserver:forKeyPath:
方法移除观察者。
观察者模式在iOS开发中的常见场景
界面更新场景
在iOS应用开发中,界面更新是一个非常常见的场景。例如,当用户在设置界面中更改了应用的主题颜色,应用的各个界面都需要相应地更新以显示新的主题颜色。
假设我们有一个ThemeManager
类作为主题状态的主题对象:
#import <Foundation/Foundation.h>
@interface ThemeManager : NSObject
@property (nonatomic, strong) UIColor *themeColor;
+ (instancetype)sharedManager;
- (void)changeThemeColor;
@end
@implementation ThemeManager
+ (instancetype)sharedManager {
static ThemeManager *sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[ThemeManager alloc] init];
});
return sharedManager;
}
- (void)changeThemeColor {
self.themeColor = [UIColor redColor];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName:@"ThemeColorChanged" object:self userInfo:@{@"newColor": self.themeColor}];
}
@end
然后,有一个ViewController
类作为观察者,监听主题颜色变化并更新界面:
#import "ViewController.h"
#import "ThemeManager.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(updateTheme) name:@"ThemeColorChanged" object:[ThemeManager sharedManager]];
}
- (void)updateTheme {
ThemeManager *manager = [ThemeManager sharedManager];
self.view.backgroundColor = manager.themeColor;
}
- (void)dealloc {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self name:@"ThemeColorChanged" object:[ThemeManager sharedManager]];
}
@end
在上述代码中,ThemeManager
类管理应用的主题颜色,当主题颜色变化时,它发布ThemeColorChanged
通知。ViewController
类注册为该通知的观察者,当收到通知时,它会更新视图的背景颜色。
数据模型变化通知场景
在一个具有数据模型的应用中,例如一个待办事项应用,当待办事项的数据模型发生变化时,如新增、删除或修改待办事项,相关的视图需要及时更新。
假设我们有一个TodoList
类作为数据模型主题对象:
#import <Foundation/Foundation.h>
@interface Todo : NSObject
@property (nonatomic, strong) NSString *title;
@property (nonatomic, assign) BOOL completed;
@end
@implementation Todo
@end
@interface TodoList : NSObject
@property (nonatomic, strong) NSMutableArray<Todo *> *todos;
+ (instancetype)sharedList;
- (void)addTodo:(Todo *)todo;
- (void)removeTodoAtIndex:(NSUInteger)index;
@end
@implementation TodoList
+ (instancetype)sharedList {
static TodoList *sharedList = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedList = [[TodoList alloc] init];
sharedList.todos = [NSMutableArray array];
});
return sharedList;
}
- (void)addTodo:(Todo *)todo {
[self.todos addObject:todo];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName:@"TodoListChanged" object:self userInfo:@{@"changeType": @"add", @"newTodo": todo}];
}
- (void)removeTodoAtIndex:(NSUInteger)index {
if (index < self.todos.count) {
Todo *todo = self.todos[index];
[self.todos removeObjectAtIndex:index];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName:@"TodoListChanged" object:self userInfo:@{@"changeType": @"remove", @"removedTodo": todo}];
}
}
@end
然后,有一个TodoViewController
类作为观察者,监听待办事项列表变化并更新界面:
#import "TodoViewController.h"
#import "TodoList.h"
#import "Todo.h"
@interface TodoViewController ()
@property (nonatomic, strong) UITableView *tableView;
@end
@implementation TodoViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
[self.view addSubview:self.tableView];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(updateTableView) name:@"TodoListChanged" object:[TodoList sharedList]];
}
- (void)updateTableView {
[self.tableView reloadData];
}
- (void)dealloc {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self name:@"TodoListChanged" object:[TodoList sharedList]];
}
@end
在上述代码中,TodoList
类管理待办事项列表,当列表发生新增或删除操作时,它发布TodoListChanged
通知。TodoViewController
类注册为该通知的观察者,当收到通知时,它会重新加载表格视图以显示最新的待办事项列表。
网络请求状态通知场景
在应用进行网络请求时,需要实时通知用户请求的状态,如开始请求、请求成功、请求失败等。
假设我们有一个NetworkManager
类作为主题对象:
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, NetworkRequestStatus) {
NetworkRequestStatusStarted,
NetworkRequestStatusSuccess,
NetworkRequestStatusFailed
};
@interface NetworkManager : NSObject
+ (instancetype)sharedManager;
- (void)startRequest;
@end
@implementation NetworkManager
+ (instancetype)sharedManager {
static NetworkManager *sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[NetworkManager alloc] init];
});
return sharedManager;
}
- (void)startRequest {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName:@"NetworkRequestStatusChanged" object:self userInfo:@{@"status": @(NetworkRequestStatusStarted)}];
// 模拟网络请求
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
BOOL success = arc4random_uniform(2);
if (success) {
[center postNotificationName:@"NetworkRequestStatusChanged" object:self userInfo:@{@"status": @(NetworkRequestStatusSuccess)}];
} else {
[center postNotificationName:@"NetworkRequestStatusChanged" object:self userInfo:@{@"status": @(NetworkRequestStatusFailed)}];
}
});
}
@end
然后,有一个ViewController
类作为观察者,监听网络请求状态变化并更新界面:
#import "ViewController.h"
#import "NetworkManager.h"
@interface ViewController ()
@property (nonatomic, strong) UILabel *statusLabel;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 50)];
[self.view addSubview:self.statusLabel];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:@selector(updateStatus:) name:@"NetworkRequestStatusChanged" object:[NetworkManager sharedManager]];
[[NetworkManager sharedManager] startRequest];
}
- (void)updateStatus:(NSNotification *)notification {
NSNumber *statusNumber = notification.userInfo[@"status"];
NetworkRequestStatus status = (NetworkRequestStatus)[statusNumber integerValue];
switch (status) {
case NetworkRequestStatusStarted:
self.statusLabel.text = @"Request started";
break;
case NetworkRequestStatusSuccess:
self.statusLabel.text = @"Request success";
break;
case NetworkRequestStatusFailed:
self.statusLabel.text = @"Request failed";
break;
default:
break;
}
}
- (void)dealloc {
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center removeObserver:self name:@"NetworkRequestStatusChanged" object:[NetworkManager sharedManager]];
}
@end
在上述代码中,NetworkManager
类管理网络请求,并在请求不同阶段发布NetworkRequestStatusChanged
通知。ViewController
类注册为该通知的观察者,根据不同的状态更新界面上的状态标签。
深入理解观察者模式在Objective-C中的优缺点
优点
- 解耦主题与观察者:主题和观察者之间通过通知或KVO机制进行通信,它们不需要直接引用对方,降低了对象之间的耦合度。例如在待办事项应用中,
TodoList
类不需要知道具体有哪些ViewController
会关注它的变化,只需要发布通知即可。 - 易于扩展:当有新的观察者需要监听主题的变化时,只需要注册为观察者即可,不需要修改主题的代码。比如在主题颜色更新场景中,新添加一个
SettingsViewController
也需要监听主题颜色变化,只需要在SettingsViewController
中注册为ThemeColorChanged
通知的观察者即可,ThemeManager
类无需改动。 - 支持广播通信:一个主题状态变化可以通知多个观察者,实现一对多的关系。就像在网络请求状态通知场景中,多个视图控制器都可以监听
NetworkManager
的网络请求状态变化通知。
缺点
- 内存管理问题:如果不及时移除观察者,可能会导致内存泄漏。例如在使用
NSNotificationCenter
时,如果在ViewController
销毁时没有移除对特定通知的观察,ViewController
虽然被释放,但通知中心仍然持有对它的引用,导致内存无法回收。 - 调试困难:由于观察者模式采用了异步通信的方式,特别是在复杂的应用中,很难追踪通知的传递路径和处理逻辑。比如在一个大型项目中,多个地方都发布了相同名称的通知,很难快速定位是哪个地方发布的通知导致了某个观察者的异常行为。
- 性能问题:当有大量的观察者时,发布通知或KVO通知触发可能会带来性能开销。因为需要遍历所有的观察者并执行相应的处理方法,这在性能敏感的场景下可能会影响应用的响应速度。
优化与最佳实践
- 及时移除观察者:在对象销毁时,一定要记得移除对相关通知或KVO观察的注册。可以在
dealloc
方法中统一进行移除操作,例如在前面的各个示例中,我们都在dealloc
方法中通过NSNotificationCenter
的removeObserver:name:object:
方法或者KVO的removeObserver:forKeyPath:
方法移除了观察者。 - 使用唯一的通知名称:为了避免调试困难,在应用中尽量使用唯一的通知名称。可以采用命名空间的方式,例如在应用名称后加上具体的功能模块和通知类型,如
com.example.app.TodoListChanged
,这样可以减少通知名称冲突的可能性。 - 批量处理通知:在性能敏感的场景下,可以考虑批量处理通知。例如,在短时间内可能会多次触发某个主题的状态变化通知,我们可以设置一个定时器,在定时器触发时统一处理这些通知,而不是每次通知触发都立即处理,这样可以减少不必要的性能开销。
通过深入理解和合理运用观察者模式,结合Objective-C提供的NSNotificationCenter
和KVO机制,开发者可以构建出更加灵活、可扩展且易于维护的iOS应用。在实际开发中,根据不同的场景和需求,选择合适的实现方式,并遵循最佳实践原则,能够有效地提高代码质量和开发效率。