深入理解TZImagePickerController:KVO与通知中心应用

深入理解TZImagePickerController:KVO与通知中心应用

【免费下载链接】TZImagePickerController 一个支持多选、选原图和视频的图片选择器,同时有预览、裁剪功能,支持iOS6+。 A clone of UIImagePickerController, support picking multiple photos、original photo、video, also allow preview photo and video, support iOS6+ 【免费下载链接】TZImagePickerController 项目地址: https://gitcode.com/gh_mirrors/tz/TZImagePickerController

一、TZImagePickerController核心架构解析

TZImagePickerController作为一款功能全面的iOS图片选择框架,其内部采用了多种设计模式实现组件间通信与状态管理。本文将聚焦于框架中KVO(Key-Value Observing)与通知中心(NSNotificationCenter)的应用场景,揭示其如何通过这些机制实现资产状态监控、UI实时更新和跨组件通信。

1.1 框架核心类结构

TZImagePickerController的核心架构围绕以下关键组件构建:

mermaid

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时遵循以下最佳实践:

  1. 上下文区分:通过context参数区分不同的观察场景,避免观察冲突
  2. 自动移除:在dealloc中移除观察者,防止野指针异常
  3. 批量操作:使用-willChangeValueForKey:-didChangeValueForKey:包装批量属性修改
  4. 线程安全:确保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在使用通知中心时遵循以下原则:

  1. 统一的通知名称管理:所有通知名称集中定义在常量文件中
  2. 完整的用户信息:通知附带足够信息,减少接收者对发送者的依赖
  3. 及时移除观察者:在dealloc中移除通知观察,避免野指针
// 正确的通知移除实现
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

四、KVO与通知中心的协同工作流程

4.1 资产加载与显示流程

TZImagePickerController中KVO与通知中心协同工作,实现资产加载、显示和选择的完整流程:

mermaid

4.2 内存管理优化策略

为避免KVO和通知中心使用不当导致的内存问题,TZImagePickerController采用以下优化策略:

  1. KVO自动释放池:在观察器中使用@autoreleasepool处理临时对象
  2. 通知合并:短时间内多次发送的相同通知进行合并处理
  3. 弱引用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 进一步学习建议

  1. 深入理解KVO实现原理:研究苹果的KVO实现源码,理解isa-swizzling技术
  2. 通知中心替代方案:学习使用NSNotificationCenter的现代替代方案如NotificationCenter(POSIX)
  3. 响应式编程:了解ReactiveCocoa(RAC)或Combine框架如何简化状态管理

7.2 框架扩展方向

基于TZImagePickerController的设计思想,可以扩展实现:

  • 支持实时滤镜预览的图片选择器
  • 基于地理位置的图片分类选择器
  • 支持人脸识别的智能相册选择器

通过KVO与通知中心的灵活应用,可以构建出既解耦又高效的组件通信架构,为iOS应用开发提供可靠的状态管理方案。

【免费下载链接】TZImagePickerController 一个支持多选、选原图和视频的图片选择器,同时有预览、裁剪功能,支持iOS6+。 A clone of UIImagePickerController, support picking multiple photos、original photo、video, also allow preview photo and video, support iOS6+ 【免费下载链接】TZImagePickerController 项目地址: https://gitcode.com/gh_mirrors/tz/TZImagePickerController

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值