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
相关推荐
可曾去过倒悬山7 小时前
mac操作笔记
macos
可曾去过倒悬山7 小时前
Mac上优雅简单地使用Git:从入门到高效工作流
git·elasticsearch·macos
wahkim9 小时前
iOS高级开发工程师面试——其他
ios
山楂树の11 小时前
模型优化——在MacOS 上使用 Python 脚本批量大幅度精简 GLB 模型(通过 Blender 处理)
python·macos·3d·图形渲染·blender
光头才能变强14 小时前
Mac安装Navicat教程Navicat Premium for Mac v17.1.9 Mac安装navicat【亲测】
macos
我现在不喜欢coding14 小时前
混乱的scheme、.xcconfig、build Settings梳理的清清楚楚
ios·xcode
Frilled Lizard17 小时前
解决mac下git pull、push需要输入密码
git·macos
是小崔啊19 小时前
Mac下的Homebrew
macos
ls_qq_267081347020 小时前
cocos打包web - ios设备息屏及前后台切换音频播放问题
前端·ios·音视频·cocos-creator
不爱说话郭德纲1 天前
别再花冤枉钱!手把手教你免费生成iOS证书(.p12) + 打包IPA(超详细)
前端·ios·app