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
相关推荐
芳草萋萋鹦鹉洲哦5 小时前
【vue3+tauri+rust】如何实现下载文件mac+windows
windows·macos·rust
恋猫de小郭12 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
点金石游戏出海17 小时前
每周资讯 | Krafton斥资750亿日元收购日本动画公司ADK;《崩坏:星穹铁道》新版本首日登顶iOS畅销榜
游戏·ios·业界资讯·apple·崩坏星穹铁道
旷世奇才李先生19 小时前
Swift 安装使用教程
开发语言·ios·swift
90后的晨仔20 小时前
Xcode16报错: SDK does not contain 'libarclite' at the path '/Applicati
ios
finger2448020 小时前
谈一谈iOS线程管理
ios·objective-c
Digitally21 小时前
如何将大型视频文件从 iPhone 传输到 PC
ios·iphone
梅名智21 小时前
IOS 蓝牙连接
macos·ios·cocoa
美狐美颜sdk1 天前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭1 天前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin