需求前提
1. app内轻量级的视频播放功能,故不希望引入“过度开发、过度封装”的第三方控件组,使用原生的AVPlayerViewController
2. 工具栏有新增控件需求,如下载按钮 等
3. 希望自定义的控件组可以伴随 系统原生控件组一起出现或隐藏
需要的第三方库
aop框架组
pod 'Aspects'
实施步骤
1.新增两个属性,记录要操作的view和HOOK的对象信息
//记录音量控制的父控件,控制它隐藏显示的 view @property (nonatomic, weak)UIView *volumeSuperView; //记录我们 hook 的对象信息 @property (nonatomic, strong)id<AspectToken>hookAVPlaySingleTap;
2.在视频播放器响应手势事件时进行HOOK
Class UIGestureRecognizerTarget = NSClassFromString(@"UIGestureRecognizerTarget"); _hookAVPlaySingleTap = [UIGestureRecognizerTarget aspect_hookSelector:@selector(_sendActionWithGestureRecognizer:) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo>info,UIGestureRecognizer *gest){ if (gest.numberOfTouches == 1) { //AVVolumeButtonControl if (!self.volumeSuperView) { UIView *view = [gest.view findViewByClassName:@"AVVolumeButtonControl"]; if (view) { while (view.superview) { view = view.superview; if ([view isKindOfClass:[NSClassFromString(@"AVTouchIgnoringView") class]]) { self.volumeSuperView = view; [view HF_addObserverForKeyPath:@"hidden" block:^(__weak id object, id oldValue, id newValue) { NSLog(@"newValue ==%@",newValue); BOOL isHidden = [(NSNumber *)newValue boolValue]; dispatch_async(dispatch_get_main_queue(), ^{ //做同步显隐操作 }); }]; break; } } } } } } error:nil];
3. 出控制器时,应移除HOOK
- (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [self.hookAVPlaySingleTap remove]; }
部分category工具方法代码
- (UIView *)findViewByClassName:(NSString *)className { UIView *view; if ([NSStringFromClass(self.class) isEqualToString:className]) { return self; } else { for (UIView *child in self.subviews) { view = [child findViewByClassName:className]; if (view != nil) break; } } return view; }
#import "NSObject+BlockObserver.h" #import <objc/message.h> @interface HFDefaultObserver : NSObject @property (nonatomic, copy) HFKVOblock kvoBlock; @property (nonatomic, copy) HFNotificationBlock notificationBlock; @end @implementation HFDefaultObserver - (instancetype)initWithKVOBlock:(HFKVOblock)kvoBlock { if (self = [super init]) { _kvoBlock = kvoBlock; } return self; } - (instancetype)initWithNotificationBlock:(HFNotificationBlock)notificationBlock { if (self = [super init]) { _notificationBlock = notificationBlock; } return self; } //实现监听方法 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { if (!self.kvoBlock) { return; } BOOL isPrior = [[change objectForKey:NSKeyValueChangeNotificationIsPriorKey] boolValue]; if (isPrior) { return; } NSKeyValueChange changeKind = [[change objectForKey:NSKeyValueChangeKindKey] integerValue]; if (changeKind != NSKeyValueChangeSetting) { return; } id oldValue = [change objectForKey:NSKeyValueChangeOldKey]; if (oldValue == [NSNull null]) { oldValue = nil; } id newValue = [change objectForKey:NSKeyValueChangeNewKey]; if (newValue == [NSNull null]) { newValue = nil; } if (oldValue != newValue) { self.kvoBlock(object, oldValue, newValue); } } - (void)handleNotification:(NSNotification *)notification { !self.notificationBlock ?: self.notificationBlock(notification); } @end @implementation NSObject (BlockObserver) static NSString * const KHFObserverKey = @"KHFObserverKey"; static NSString * const KHFNotificationObserversKey = @"KHFNotificationObserversKey"; // 替换dealloc方法,自动注销observer + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Method originalDealloc = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc")); Method newDealloc = class_getInstanceMethod(self, @selector(autoRemoveObserverDealloc)); method_exchangeImplementations(originalDealloc, newDealloc); }); } - (void)autoRemoveObserverDealloc { if (objc_getAssociatedObject(self, (__bridge const void *)KHFObserverKey) || objc_getAssociatedObject(self, (__bridge const void *)KHFNotificationObserversKey)) { [self HF_removeAllObserverBlocks]; [self HF_removeAllNotificationBlocks]; } //这句相当于直接调用dealloc [self autoRemoveObserverDealloc]; } - (void)HF_addObserverForKeyPath:(NSString *)keyPath block:(HFKVOblock)block { if (keyPath.length == 0 || !block) { return; } NSMutableDictionary *observersDict = objc_getAssociatedObject(self, (__bridge const void *)KHFObserverKey); if (!observersDict) { observersDict = [NSMutableDictionary dictionary]; objc_setAssociatedObject(self, (__bridge const void *)KHFObserverKey, observersDict, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } NSMutableArray * observers = [observersDict objectForKey:keyPath]; if (!observers) { observers = [NSMutableArray array]; [observersDict setObject:observers forKey:keyPath]; } HFDefaultObserver *observer = [[HFDefaultObserver alloc] initWithKVOBlock:block]; [self addObserver:observer forKeyPath:keyPath options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL]; [observers addObject:observer]; } - (void)HF_removeObserverBlocksForKeyPath:(NSString *)keyPath { if (keyPath.length == 0) { return; } NSMutableDictionary *observersDict = objc_getAssociatedObject(self, (__bridge const void *)KHFObserverKey); if (observersDict) { NSMutableArray *observers = [observersDict objectForKey:keyPath]; [observers enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [self removeObserver:obj forKeyPath:keyPath]; }]; [observersDict removeObjectForKey:keyPath]; } } - (void)HF_removeAllObserverBlocks { NSMutableDictionary *observersDict = objc_getAssociatedObject(self, (__bridge const void *)KHFObserverKey); if (observersDict) { [observersDict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSMutableArray *obsevers, BOOL * _Nonnull stop) { [obsevers enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [self removeObserver:obj forKeyPath:key]; }]; }]; [observersDict removeAllObjects]; } } - (void)HF_addNotificationForName:(NSString *)name block:(HFNotificationBlock)block { if (name.length == 0 || !block) { return; } NSMutableDictionary *observersDict = objc_getAssociatedObject(self, (__bridge const void *)KHFNotificationObserversKey); if (!observersDict) { observersDict = [NSMutableDictionary dictionary]; objc_setAssociatedObject(self, (__bridge const void *)KHFNotificationObserversKey, observersDict, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } NSMutableArray *observers = [observersDict objectForKey:name]; if (!observers) { observers = [NSMutableArray array]; [observersDict setObject:observers forKey:name]; } HFDefaultObserver *observer = [[HFDefaultObserver alloc] initWithNotificationBlock:block]; [[NSNotificationCenter defaultCenter] addObserver:observer selector:@selector(handleNotification:) name:name object:nil]; [observers addObject:observer]; } - (void)HF_removeNotificationBlocksForName:(NSString *)name { if (name.length == 0) { return; } NSMutableDictionary *observersDict = objc_getAssociatedObject(self, (__bridge const void *)KHFNotificationObserversKey); if (observersDict) { NSMutableArray *observers = [observersDict objectForKey:name]; [observers enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [[NSNotificationCenter defaultCenter] removeObserver:obj name:name object:nil]; }]; [observersDict removeObjectForKey:name]; } } - (void)HF_removeAllNotificationBlocks { NSMutableDictionary *observersDict = objc_getAssociatedObject(self, (__bridge const void *)KHFNotificationObserversKey); [observersDict enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSMutableArray *observers, BOOL * _Nonnull stop) { [observers enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [[NSNotificationCenter defaultCenter] removeObserver:obj name:key object:nil]; }]; }]; [observersDict removeAllObjects]; } @end
#import <Foundation/Foundation.h> typedef void(^HFKVOblock)(__weak id object, id oldValue, id newValue); typedef void(^HFNotificationBlock)(NSNotification *notification); @interface NSObject (BlockObserver) - (void)HF_addObserverForKeyPath:(NSString *)keyPath block:(HFKVOblock)block; - (void)HF_removeObserverBlocksForKeyPath:(NSString *)keyPath; - (void)HF_removeAllObserverBlocks; - (void)HF_addNotificationForName:(NSString *)name block:(HFNotificationBlock)block; - (void)HF_removeNotificationBlocksForName:(NSString *)name; - (void)HF_removeAllNotificationBlocks;
@end