iOS 使用消息转发机制实现多代理功能
在iOS开发中,我们有时候会用到多代理功能,比如我们列表的埋点事件,需要我们在列表的某个特定的时机进行埋点上报,我们当然可以用最常见的做法,就是设置代理实现代理方法,然后在对应的代理方法里面进行上报,但是这样做有个问题,就是会做大量重复的工作,我们想要到达的效果是,我们只需要实现业务逻辑,而埋点操作,只需需要我们配置一下数据,就会自动进行,这样就为我们减少了大量的重复性工作。
下面介绍一下我们实现列表的自定化埋点的思路
我们自定义一个列表类,继承于系统类,然后该类有一个代理中心,
该代理中心类负责消息转发,他引用了真正的原始代理,和一个代理对象,该代理对象也实现了列表的代理方法,里面的实现只进行埋点操作。 我们重写自定义列表类的setDelegate方法,在里面创建代理对象,并将列表的代理设置为代理中心,在代理中心中将消息转发给代理对象和原始代理, 通过这样的方式,实现了自动化埋点
代码
代理中心
//
// LBDelegateCenter.m
// TEXT
//
// Created by mac on 2025/3/2.
// Copyright © 2025 刘博. All rights reserved.
//
#import "LBDelegateCenter.h"
@implementation LBDelegateCenter
- (instancetype)initWithTarget:(id)target proxy:(id)proxy
{
if (self = [super init]) {
_target = target ? target : [NSNull null];
_proxy = proxy ? proxy : [NSNull null];
}
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
NSMethodSignature *targetSign = [_target methodSignatureForSelector:sel];
if (targetSign) {
return targetSign;
}
NSMethodSignature *proxySign = [_proxy methodSignatureForSelector:sel];
if (proxySign) {
return proxySign;
}
return [super methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
//AUKLogInfo(@"New proxy = %@, selector=%@", [self.proxy class], NSStringFromSelector([anInvocation selector]));
BOOL hit = NO;
if ([_target respondsToSelector:[anInvocation selector]]) {
hit = YES;
[anInvocation invokeWithTarget:_target];
}
if ([_proxy respondsToSelector:[anInvocation selector]]) {
//AUKLogInfo(@"New proxy handle");
hit = YES;
[anInvocation invokeWithTarget:_proxy];
}
if (!hit && [super respondsToSelector:[anInvocation selector]]) {
[super forwardInvocation:anInvocation];
}
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
if ([_target respondsToSelector:aSelector]) {
return YES;
}
if ([_proxy respondsToSelector:aSelector]) {
return YES;
}
return [super respondsToSelector:aSelector];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol
{
if ([_target conformsToProtocol:aProtocol]) {
return YES;
}
if ([_proxy conformsToProtocol:aProtocol]) {
return YES;
}
return [super conformsToProtocol:aProtocol];
}
- (BOOL)isKindOfClass:(Class)aClass
{
if ([_target isKindOfClass:aClass]) {
return YES;
}
if ([_proxy isKindOfClass:aClass]) {
return YES;
}
return [super isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass
{
if ([_target isMemberOfClass:aClass]) {
return YES;
}
if ([_proxy isMemberOfClass:aClass]) {
return YES;
}
return [super isMemberOfClass:aClass];
}
@end
代理对象
//
// LBScrollViewDelegate.m
// TEXT
//
// Created by mac on 2025/3/2.
// Copyright © 2025 刘博. All rights reserved.
//
#import "LBScrollViewDelegate.h"
#import <UIKit/UIKit.h>
#import "UITableViewCell+Event.h"
#import "UICollectionViewCell+Event.h"
@implementation LBScrollViewDelegate
// 自动轮播的开始,但是需要重点关注是否可能存在触发scrollViewDidScroll
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
//执行滚动
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
//开始拖动,执行曝光埋点
}
//
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView
{
//开始减速, 停止滚动
}
//
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
//停止减速
}
//
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (!decelerate) {
//停止滚动
}
}
//
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
//
}
// 10.3.86 切换使用新方法代替点击捕获
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
[cell setMonitorSelected:YES];
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView cellForItemAtIndexPath:indexPath];
[cell setMonitorSelected:YES];
}
// 结束显示周期是准确的,但开始显示可能只显示一次,可能显示并不完全,所以暂只开了开始。
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self needCheckCellIn:tableView isStart:YES]) {
}
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
if ([self needCheckCellIn:collectionView isStart:YES]) {
}
}
- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath*)indexPath
{
if ([self needCheckCellIn:tableView isStart:NO]) {
}
}
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath
{
if ([self needCheckCellIn:collectionView isStart:NO]) {
}
}
- (BOOL)needCheckCellIn:(UIView *)view isStart:(BOOL)start
{
return YES;
}
@end
自定义列表类
//
// LBCollectionView.m
// TEXT
//
// Created by mac on 2025/3/2.
// Copyright © 2025 刘博. All rights reserved.
//
#import "LBEventCollectionView.h"
#import "LBScrollViewDelegate.h"
#import "LBDelegateCenter.h"
@implementation LBEventCollectionView
- (void)didMoveToWindow
{
[super didMoveToWindow];
//执行cell 曝光埋点
}
- (void)reloadData
{
[super reloadData];
[self checkNeedReportLog_auk];
}
- (void)checkNeedReportLog_auk
{
if (!self.window || !self.superview) {
return;
}
// 上报当前visiblecell及其子view的所有埋点,放到下一个runloop,等到cell渲染完成
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(performCommitScroll) object:nil];
[self performSelector:@selector(performCommitScroll) withObject:nil afterDelay:0.5];
}
- (void)performCommitScroll
{
//执行曝光埋点
}
- (LBScrollViewDelegate *)collectionDelegate_auk
{
if (!_collectionDelegate_auk) {
_collectionDelegate_auk = [[LBScrollViewDelegate alloc] init];
}
return _collectionDelegate_auk;
}
- (void)setDelegate:(id<UICollectionViewDelegate>)delegate
{
if (delegate == nil) {
// self.delegateProxy_auk = nil;
[super setDelegate:nil];
return;
}
self.delegateProxy_auk = [[LBDelegateCenter alloc] initWithTarget:self.collectionDelegate_auk proxy:delegate];
self.delegateProxy_auk.scrollView = self;
[super setDelegate:(id)self.delegateProxy_auk];
}
- (NSMutableDictionary *)visibleCellInfos_auk
{
if (!_visibleCellInfos_auk) {
_visibleCellInfos_auk = [[NSMutableDictionary alloc] init];
}
return _visibleCellInfos_auk;
}
- (NSMutableDictionary *)lastVisibleInfos_auk
{
if (!_lastVisibleInfos_auk) {
_lastVisibleInfos_auk = [[NSMutableDictionary alloc] init];
}
return _lastVisibleInfos_auk;
}
- (NSHashTable *)validViews_auk
{
if (!_validViews_auk) {
_validViews_auk = [NSHashTable hashTableWithOptions:NSHashTableWeakMemory];
}
return _validViews_auk;
}
- (void)dealloc
{
// self.delegate = nil;
_delegateProxy_auk = nil;
_collectionDelegate_auk = nil;
}
- (BOOL)supportAspectExposure
{
return NO;
}
@end