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相关文章:
iOS 使用消息转发机制实现多代理功能
在iOS开发中,我们有时候会用到多代理功能,比如我们列表的埋点事件,需要我们在列表的某个特定的时机进行埋点上报,我们当然可以用最常见的做法,就是设置代理实现代理方法,然后在对应的代理方法里面进行上报&…...
16.3 LangChain Runnable 协议精要:构建高效大模型应用的核心基石
LangChain Runnable 协议精要:构建高效大模型应用的核心基石 关键词:LCEL Runnable 协议、LangChain 链式开发、自定义组件集成、流式处理优化、生产级应用设计 1. Runnable 协议设计哲学与核心接口 1.1 协议定义与类结构 #mermaid-svg-PlmvpSDrEUrUGv2p {font-family:&quo…...
Starrocks入门(二)
1、背景:考虑到Starrocks入门这篇文章,安装的是3.0.1版本的SR,参考:Starrocks入门-CSDN博客 但是官网的文档,没有对应3.0.x版本的资料,却有3.2或者3.3或者3.4或者3.1或者2.5版本的资料,不要用较…...
【北京迅为】itop-3568 开发板openharmony鸿蒙烧写及测试-第1章 体验OpenHarmony—烧写镜像
瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工…...
Electron一小时快速上手
1. 什么是 Electron? Electron 是一个跨平台桌面应用开发框架,开发者可以使用 HTML、CSS、JavaScript 等 Web 技术来构建桌面应用程序。它的本质是结合了 Chromium 和 Node.js,现在广泛用于桌面应用程序开发。例如,以下桌面应用都使用了 El…...
算法004——盛最多水的容器
力扣——盛最多水的容器点击即可跳转 当我们选择1号线和8号线时,下标为 1 和 8 形成容器的容积的高度是由 较矮的决定的,即下标为 8 的位置; 而宽度则是 1到8 之间的距离,为 8-17,此时容器的容积为 7 * 7 49。 当我…...
Java Web-Filter
Filter 在 Java Web 开发中,Filter(过滤器)是 Servlet 规范中的一个重要组件,它可以对客户端与服务器之间的请求和响应进行预处理和后处理。以下从多个方面详细介绍 Java Web 中的 Filter: 一、概念和作用 概念&…...
LeetCode 热题100 438. 找到字符串中所有字母异位词
LeetCode 热题100 | 438. 找到字符串中所有字母异位词 大家好,今天我们来解决一道经典的算法题——找到字符串中所有字母异位词。这道题在 LeetCode 上被标记为中等难度,要求我们在字符串 s 中找到所有是 p 的异位词的子串,并返回这些子串的…...
DeepSeek-R1训练时采用的GRPO算法数学原理及算法过程浅析
先来简单看下PPO和GRPO的区别: PPO:通过奖励和一个“评判者”模型(critic 模型)评估每个行为的“好坏”(价值),然后小步调整策略,确保改进稳定。 GRPO:通过让模型自己生…...
Qt基于信号量QSemaphore实现的生产者消费者模型
在 Qt 中,信号量(QSemaphore)是一种用于控制对共享资源访问的同步工具。它允许一定数量的线程同时访问共享资源,适合用于生产者-消费者模型。 代码实现 #include <QCoreApplication> #include <QThread> #include &…...
七星棋牌 6 端 200 子游戏全开源修复版源码(乐豆 + 防沉迷 + 比赛场 + 控制)
七星棋牌源码 是一款运营级的棋牌产品,覆盖 湖南、湖北、山西、江苏、贵州 等 6 大省区,支持 安卓、iOS 双端,并且 全开源。这个版本是 修复优化后的二开版本,新增了 乐豆系统、比赛场模式、防沉迷机制、AI 智能控制 等功能&#…...
CSDN博客导出设置介绍
在CSDN编辑博客时,如果想导出保存到本地,可以选择导出为Markdown或者HTML格式。其中导出为HTML时有这几种选项:jekyll site,plain html,plain text,styled html,styled html with toc。分别是什…...
_ 为什么在python中可以当变量名
在 Python 中,_(下划线)是一个有效的变量名,这主要源于 Python 的命名规则和一些特殊的使用场景。以下是为什么 _ 可以作为变量名的原因和常见用途: --- ### 1. **Python 的命名规则** Python 允许使用字母ÿ…...
使用haproxy实现MySQL服务器负载均衡
一、环境准备 主机名IP地址备注openEuler-1192.168.121.11mysql-server-1openEuler-2192.168.121.12mysql-server-2openEuler-3192.168.121.13clientRocky-1192.168.121.51haproxy 二、mysql-server配置 [rootopenEuler-1 ~]# yum install -y mariadb-server [rootopenEuler…...
音视频-WAV格式
1. WAV格式说明: 2. 格式说明: chunkId:通常是 “RIFF” 四个字节,用于标识文件类型。(wav文件格式表示)chunkSize:表示整个文件除了chunkId和chunkSize这 8 个字节外的其余部分的大小。Forma…...
apload-lab打靶场
1.提示显示所以关闭js 上传<?php phpinfo(); ?>的png形式 抓包,将png改为php 然后放包上传成功 2.提示说检查数据类型 抓包 将数据类型改成 image/jpeg 上传成功 3.提示 可以用phtml,php5,php3 4.先上传.htaccess文件࿰…...
通用查询类接口数据更新的另类实现
文章目录 一、简要概述二、java工程实现1. 定义main方法2. 测试运行3. 源码放送 一、简要概述 我们在通用查询类接口开发的另类思路中,关于接口数据的更新,提出了两种方案: 文件监听 #mermaid-svg-oJQjD6jQ8T19XlHA {font-family:"tre…...
sentinel详细使用教学
sentinel源码地址: https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D sentinel官方文档: https://sentinelguard.io/zh-cn/docs/introduction.html Sprong Cloud alibaba Sentinel文档【小例子】 : https://github.com/alibaba/spring-cl…...
python django
官网地址 https://www.djangoproject.com/ 安装 控制台输入命令 pip install django 或者可以指定版本号 pip install django3.2.4 创建项目 在控制台找个目录存放生成好的项目,输入命令 django-admin startproject demo_django 然后用pycharm打开项目可以…...
SuperMap iClient3D for WebGL 影像数据可视范围控制
在共享同一影像底图的服务场景中,如何基于用户权限体系实现差异化的数据可视范围控制?SuperMap iClient3D for WebGL提供了自定义区域影像裁剪的方法。让我们一起看看吧! 一、数据制作 对于上述视频中的地图制作,此处不做讲述&am…...
HTML元素,标签到底指的哪块部分?单双标签何时使用?
1. 标签(Tag) vs 元素(Element) 标签(Tag) 标签是 HTML 中用于定义元素的符号,用尖括号 < > 包裹。例如 <img> 是标签。元素(Element) 元素是由 标签 内容…...
OpenHarmony4.1-轻量与小型系统ubuntu开发环境
因OpenHarmony官网提供包含轻量、小型与标准系统的全量代码非常宠大,解包后大概需要70G以上硬盘空间,如要编译标准系统则需要140G以上空间。 如硬盘空间有限与只使用轻量/小型OpenHarmony系统,则可以下载并直接使用本人裁剪源码过的ubuntu硬盘…...
秒杀系统的常用架构是什么?怎么设计?
架构 秒杀系统需要单独部署,如果说放在订单服务里面,秒杀的系统压力太大了就会影响正常的用户下单。 常用架构: Redis 数据倾斜问题 第一步扣减库存时 假设现在有 10 个商品需要秒杀,正常情况下,这 10 个商品应该均…...
LabVIEW中三种PSD分析VI的区别与应用
在LabVIEW的声音与振动分析工具包中,SVFA Power Spectral Density VI、SVFA Power Spectral Density Subset VI 和 SVFA Zoom Power Spectral Density VI 均用于信号频域分析,但它们在功能、适用场景和操作逻辑上存在显著差异。以下从区别、应用场合、注…...
Python 如何实现 Markdown 记账记录转 Excel 存储
文章精选推荐 1 JetBrains Ai assistant 编程工具让你的工作效率翻倍 2 Extra Icons:JetBrains IDE的图标增强神器 3 IDEA插件推荐-SequenceDiagram,自动生成时序图 4 BashSupport Pro 这个ides插件主要是用来干嘛的 ? 5 IDEA必装的插件&…...
蓝桥杯备考:动态规划入门题目之下楼梯问题
按照动态规划解题顺序,首先,我们要定义状态表示,这里根据题意f[i]就应该表示有i个台阶方案总数 第二步就是 确认状态转移方程,画图分析 所以实际上f[i] 也就是说i个台阶的方案数实际上就是第i-1个格子的方案数第i-2个格子的方案数…...
【树莓派学习】树莓派3B+的安装和环境配置
【树莓派学习】树莓派3B的安装和环境配置 文章目录 【树莓派学习】树莓派3B的安装和环境配置一、搭建Raspberry Pi树莓派运行环境1、下载树莓派镜像下载器2、配置wifi及ssh3、SSH访问树莓派1)命令行登录2)远程桌面登录3)VNC登录(推…...
算法题(83):寄包柜
审题: 需要我们对模拟柜子的数组进行插入数据和打印数据的操作 思路: 首先我们观察题目,发现可以用一个数组表示一个柜子,而数组中每个索引的位置可以看成是一个个格子。但是柜子的数据量是1e5,且格子的数据量是1e5.如…...
深入浅出MySQL:概述与体系结构解析
目录 1. 初识MySQL 1.1. 数据库 1.1.1. OLTP(联机事务处理)1.1.2. OLAP(联机分析处理) 2. SQL 2.1. 定义2.2. DQL(数据查询语言)2.3. DML(数据操纵语言)2.4. DDL(数据定…...
tin这个单词怎么记
英语单词 tin,一般用作名词,意为“罐头;锡”: tin n.锡;罐头;罐;罐头盒;(盛涂料、胶水等的)马口铁罐,白铁桶;罐装物;金属食品盒;烘焙…...
