深入理解TZImagePickerController:KVO与通知中心应用
一、TZImagePickerController核心架构解析
TZImagePickerController作为一款功能全面的iOS图片选择框架,其内部采用了多种设计模式实现组件间通信与状态管理。本文将聚焦于框架中KVO(Key-Value Observing)与通知中心(NSNotificationCenter)的应用场景,揭示其如何通过这些机制实现资产状态监控、UI实时更新和跨组件通信。
1.1 框架核心类结构
TZImagePickerController的核心架构围绕以下关键组件构建:
TZImageManager作为核心管理者,负责资产获取、缓存管理和媒体处理;TZPhotoPickerController实现图片选择界面;TZAssetModel封装单个媒体资源信息;TZImagePickerController作为入口点协调各组件。
二、KVO在资产状态监控中的应用
2.1 KVO机制在TZImageManager中的实现
TZImageManager作为框架的核心数据管理者,通过KVO实现对PHAsset状态变化的实时监控。在资产选择过程中,关键属性的变化需要及时反映到UI层面:
// TZImageManager.m
- (void)configObservers {
// 监控相册授权状态变化
[self addObserver:self forKeyPath:@"authorizationStatus" options:NSKeyValueObservingOptionNew context:nil];
// 监控缓存状态变化
[self.cachingImageManager addObserver:self forKeyPath:@"cachingEnabled" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"authorizationStatus"]) {
NSNumber *statusNum = change[NSKeyValueChangeNewKey];
PHAuthorizationStatus status = [statusNum integerValue];
if (status == PHAuthorizationStatusAuthorized) {
[self loadAlbums];
} else if (status == PHAuthorizationStatusDenied) {
[self showAuthorizationDeniedAlert];
}
} else if ([keyPath isEqualToString:@"cachingEnabled"]) {
BOOL isCaching = [change[NSKeyValueChangeNewKey] boolValue];
NSLog(@"Image caching %@", isCaching ? @"enabled" : @"disabled");
}
}
2.2 资产选择状态的KVO实现
TZAssetModel的选中状态变化通过KVO通知UI更新:
// TZAssetModel.h
@interface TZAssetModel : NSObject
@property (nonatomic, assign) BOOL selected; // 选中状态
@property (nonatomic, strong) PHAsset *asset;
@property (nonatomic, assign) TZAssetModelMediaType type;
@end
// TZAssetCell.m
- (void)awakeFromNib {
[super awakeFromNib];
[self.model addObserver:self forKeyPath:@"selected" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:@"selected"]) {
BOOL isSelected = [change[NSKeyValueChangeNewKey] boolValue];
self.selectedButton.selected = isSelected;
[self updateSelectedButtonAppearance];
}
}
2.3 KVO使用注意事项
TZImagePickerController在使用KVO时遵循以下最佳实践:
- 上下文区分:通过context参数区分不同的观察场景,避免观察冲突
- 自动移除:在dealloc中移除观察者,防止野指针异常
- 批量操作:使用
-willChangeValueForKey:和-didChangeValueForKey:包装批量属性修改 - 线程安全:确保UI相关的KVO回调在主线程执行
// 安全的KVO移除实现
- (void)dealloc {
@try {
[self.model removeObserver:self forKeyPath:@"selected"];
} @catch (NSException *exception) {
NSLog(@"KVO移除异常: %@", exception);
}
}
三、通知中心在跨组件通信中的应用
3.1 通知中心设计与实现
TZImagePickerController通过通知中心实现非直接关联组件间的通信,主要应用于以下场景:
- 资产选择状态变化通知
- 相册授权状态变更通知
- 图片缓存完成通知
- 裁剪操作完成通知
// TZImagePickerControllerConstants.h
// 定义通知名称常量
FOUNDATION_EXPORT NSString *const TZImagePickerDidSelectAssetNotification;
FOUNDATION_EXPORT NSString *const TZImagePickerDidDeselectAssetNotification;
FOUNDATION_EXPORT NSString *const TZImagePickerAuthorizationStatusDidChangeNotification;
FOUNDATION_EXPORT NSString *const TZImagePickerImageCacheDidFinishNotification;
// TZImagePickerControllerNotifications.m
NSString *const TZImagePickerDidSelectAssetNotification = @"TZImagePickerDidSelectAssetNotification";
NSString *const TZImagePickerDidDeselectAssetNotification = @"TZImagePickerDidDeselectAssetNotification";
NSString *const TZImagePickerAuthorizationStatusDidChangeNotification = @"TZImagePickerAuthorizationStatusDidChangeNotification";
NSString *const TZImagePickerImageCacheDidFinishNotification = @"TZImagePickerImageCacheDidFinishNotification";
3.2 通知的发送与接收实现
在资产选择过程中,TZPhotoPickerController发送选择状态变化通知:
// TZPhotoPickerController.m
- (void)assetSelected:(TZAssetModel *)model {
model.selected = YES;
[[NSNotificationCenter defaultCenter] postNotificationName:TZImagePickerDidSelectAssetNotification
object:self
userInfo:@{@"assetModel": model,
@"indexPath": [NSIndexPath indexPathForItem:model.index inSection:0]}];
}
- (void)assetDeselected:(TZAssetModel *)model {
model.selected = NO;
[[NSNotificationCenter defaultCenter] postNotificationName:TZImagePickerDidDeselectAssetNotification
object:self
userInfo:@{@"assetModel": model}];
}
TZImagePickerController接收通知并更新选中数量:
// TZImagePickerController.m
- (void)setupNotifications {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleAssetSelectedNotification:)
name:TZImagePickerDidSelectAssetNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(handleAssetDeselectedNotification:)
name:TZImagePickerDidDeselectAssetNotification
object:nil];
}
- (void)handleAssetSelectedNotification:(NSNotification *)notification {
TZAssetModel *model = notification.userInfo[@"assetModel"];
[self.selectedAssets addObject:model];
[self updateNavigationBarRightButton];
}
- (void)handleAssetDeselectedNotification:(NSNotification *)notification {
TZAssetModel *model = notification.userInfo[@"assetModel"];
[self.selectedAssets removeObject:model];
[self updateNavigationBarRightButton];
}
3.3 通知中心使用的最佳实践
TZImagePickerController在使用通知中心时遵循以下原则:
- 统一的通知名称管理:所有通知名称集中定义在常量文件中
- 完整的用户信息:通知附带足够信息,减少接收者对发送者的依赖
- 及时移除观察者:在dealloc中移除通知观察,避免野指针
// 正确的通知移除实现
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
四、KVO与通知中心的协同工作流程
4.1 资产加载与显示流程
TZImagePickerController中KVO与通知中心协同工作,实现资产加载、显示和选择的完整流程:
4.2 内存管理优化策略
为避免KVO和通知中心使用不当导致的内存问题,TZImagePickerController采用以下优化策略:
- KVO自动释放池:在观察器中使用@autoreleasepool处理临时对象
- 通知合并:短时间内多次发送的相同通知进行合并处理
- 弱引用self:通知处理中使用弱引用避免循环引用
// 通知处理中的弱引用
- (void)setupNotifications {
__weak typeof(self) weakSelf = self;
[[NSNotificationCenter defaultCenter] addObserverForName:TZImagePickerImageCacheDidFinishNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf.collectionView reloadData];
}
}];
}
五、高级应用与实战技巧
5.1 自定义KVO观察器实现
对于复杂的属性观察需求,可以创建自定义KVO观察器类:
@interface TZAssetObserver : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL action;
@property (nonatomic, copy) NSString *keyPath;
+ (instancetype)observerWithTarget:(id)target
keyPath:(NSString *)keyPath
action:(SEL)action;
@end
@implementation TZAssetObserver
+ (instancetype)observerWithTarget:(id)target
keyPath:(NSString *)keyPath
action:(SEL)action {
TZAssetObserver *observer = [[self alloc] init];
observer.target = target;
observer.keyPath = keyPath;
observer.action = action;
return observer;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if ([keyPath isEqualToString:self.keyPath] && [self.target respondsToSelector:self.action]) {
[self.target performSelector:self.action withObject:object withObject:change];
}
}
@end
5.2 通知中心的高级用法
利用通知中心实现跨模块通信时,可以使用通知拦截和重定向技术:
// 通知拦截器实现
@interface TZNotificationInterceptor : NSObject
+ (void)interceptNotification:(NSString *)name
observer:(id)observer
selector:(SEL)selector;
@end
@implementation TZNotificationInterceptor
+ (void)interceptNotification:(NSString *)name
observer:(id)observer
selector:(SEL)selector {
[[NSNotificationCenter defaultCenter] addObserverForName:name
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
// 拦截通知并进行处理
NSDictionary *modifiedUserInfo = [self modifyNotificationUserInfo:note.userInfo];
// 创建新通知并发送
NSNotification *modifiedNote = [NSNotification notificationWithName:name
object:note.object
userInfo:modifiedUserInfo];
// 转发给原始观察者
[observer performSelector:selector withObject:modifiedNote];
}];
}
+ (NSDictionary *)modifyNotificationUserInfo:(NSDictionary *)userInfo {
// 修改通知内容...
return userInfo;
}
@end
六、常见问题与解决方案
6.1 KVO观察冲突问题
问题:多个观察者观察同一属性导致的冲突。
解决方案:使用context参数区分不同的观察场景:
// 定义唯一的context
static void *AssetSelectedContext = &AssetSelectedContext;
static void *AssetDownloadedContext = &AssetDownloadedContext;
// 添加观察者时指定context
[self.asset addObserver:self
forKeyPath:@"selected"
options:NSKeyValueObservingOptionNew
context:AssetSelectedContext];
[self.asset addObserver:self
forKeyPath:@"downloaded"
options:NSKeyValueObservingOptionNew
context:AssetDownloadedContext];
// 在回调中区分context
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context {
if (context == AssetSelectedContext) {
// 处理选中状态变化
} else if (context == AssetDownloadedContext) {
// 处理下载状态变化
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
6.2 通知中心内存泄漏
问题:忘记移除通知观察者导致的内存泄漏。
解决方案:使用Block-based观察者并存储观察者对象:
// 正确的Block-based通知观察
@property (nonatomic, strong) id imageCacheObserver;
- (void)setupImageCacheObserver {
self.imageCacheObserver = [[NSNotificationCenter defaultCenter] addObserverForName:TZImagePickerImageCacheDidFinishNotification
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *note) {
// 处理通知
}];
}
- (void)dealloc {
if (self.imageCacheObserver) {
[[NSNotificationCenter defaultCenter] removeObserver:self.imageCacheObserver];
}
}
七、总结与扩展
TZImagePickerController通过KVO和通知中心的巧妙结合,实现了组件间的松耦合通信和状态管理。KVO适用于对象间存在直接引用关系的场景,提供细粒度的属性变化监控;通知中心则适用于无直接引用关系的组件间通信,实现跨层级的信息传递。
7.1 进一步学习建议
- 深入理解KVO实现原理:研究苹果的KVO实现源码,理解isa-swizzling技术
- 通知中心替代方案:学习使用NSNotificationCenter的现代替代方案如NotificationCenter(POSIX)
- 响应式编程:了解ReactiveCocoa(RAC)或Combine框架如何简化状态管理
7.2 框架扩展方向
基于TZImagePickerController的设计思想,可以扩展实现:
- 支持实时滤镜预览的图片选择器
- 基于地理位置的图片分类选择器
- 支持人脸识别的智能相册选择器
通过KVO与通知中心的灵活应用,可以构建出既解耦又高效的组件通信架构,为iOS应用开发提供可靠的状态管理方案。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



