一、场景描述以及所存在的问题
随着业务的发展,业务都有一些运营业务,当用户触发某一个操作时展示业务弹窗,随之而来的会碰到如下场景:
场景一
- 多个触发时机都需触发业务弹窗
场景导致问题:
- 弹窗逻辑分散在各处,查找逻辑成本高
- 后端下发数据形式各种各样,分不清各个场景的使用规则
场景二
一个触发时机会根据不同的情况展示不同的业务弹窗
场景导致问题:
- 当用户触发操作,没有优先级管理,
场景二
- 用户触发操作和弹窗展示有时间间隔,在间隔内触发了其他弹窗造成弹窗层叠出现
场景导致问题:
没有弹窗状态,无法知道当前是否已经出现弹窗,出现弹窗层叠出现,影响用户体验: eg:电话后10s弹窗,这时候用户触发了其他操作展示的弹窗,这时候出现层叠情况
根据场景再进行抽象我们要解决的问题
- 对弹窗业务逻辑进行集中管理
- 规范化后端下发数据的格式
- 感知弹窗状态
- 提供优先级管理机制
二、针对以上问题设计弹窗模块
1.针对每个场景类型进行抽象,定义枚举区分
ini
示例
enum TanChuangType: String {
case north = "North"
case south = "South"
case east = "East"
case west = "West"
}
2.抽象协议方法,业务弹窗必须要实现的方法
go
示例
protocol iTanChuangTypeService {
func show(type:TanChuangType)
fucc canTanchuang(type:TanChuangType)
}
3.设计模块管理TanChuangManager
- 建立场景枚举和弹窗实现的映射关系
- 对于提供接受后端数据的接口
- 对于提供使用方的调用接口
- 记录当前弹窗的状态,检查当前页面是否已经具有了弹窗,有则不弹窗防止层叠的出现
4.数据格式定义
bash
[
{
弹窗场景
type:North,
优先级
priority:1
业务数据
business:{
}
}
]
三、对于存量问题的改造
常规方案:对触发时机的代码都加上逻辑判断, 调用弹窗处理模块能够弹窗类似
scss
示例
if(TanChuangManager.canTanchuang(.north)) {
TanChuangManager.show(.north)
} else {
原有逻辑
}
常规方案的问题:
- 整个业务都需要改动
- 触发方法中都要加入原有的额外逻辑
- 后期对原有逻辑修改,会影响弹窗逻辑,造成不要的问题点
hook改进方案:
1.常规hook方案:
通常的hook方案是对现有特定的方法进行hook,比如对viewWillApper等方法这样对所有对象都能生效,但对于我们的场景用户触发操作执行都是开发者自定义的,没有一个统一的方法直接hook行不通
2.改进hook方案:
对于用户的操作在开发者体现就是就是能够响应事件的UI控件,比如UIButton或者手势事件,比如UIGesture 所以就他们添加事件的方法进行hook
-
hook添加事件的方法
-
在hook自定义事件中构造NSProxy代理对象并设置成响应事件的target对象
objectivec
构造的NSProxy代理对象
@interface Proxy : NSProxy
@property (nonatomic, strong) id target;
@property (nonatomic, assign) TanChuangType type;
@property (nonatomic, strong) SEL action;
@end
以UIButton举例hook方法处理
ini
构造的NSProxy代理对象
- (void)addTarget:(nullable id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents {
Proxy *proxy = [[proxy alloc] init];
proxy.target = target;
proxy.action = action;
proxy.type = self.type; 分类的方式给UIButton添加的枚举属性
替换target对象
[self addTarget:proxy action:action forControlEvents:controlEvents];
}
这样用户点击的时候是NSProxy代理对象接收事件
- NSProxy代理对象中事件处理
lua
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
if(self.type && [TanChuangManager canTanchuang:self.type]){
[TanChuangManager show:self.type];
} else {
处理原有逻辑
return [self.target methodSignatureForSelector:sel];
}
}
三、最后的用法
1.在创建手势和UI控件设置定义好的枚举
ini
button.type = .north
2.开发弹窗逻辑并配置到TanChuangManager即可
四、效果
1.动态行:后端通过数据动态通知弹窗展示场景,弹窗优先级
2.开发:新增业务只需关注弹窗业务开发符合单一原则
五、面试场景下,怎么回答能够体现了你在(架构抽象 、模块化设计 、系统性思考)方面的能力
1.背景与问题归纳 (体现你的场景理解力和总结抽象能力)
随着业务的发展,出现了大量基于用户触发的运营弹窗需求。但在实践中遇到了几个典型问题:
- 弹窗逻辑分散在各处,增加维护成本
- 后端下发数据格式不统一,难以规范接入
- 弹窗之间无优先级管理,弹窗状态不可感知,易出现层叠等问题
因此,我设计了一个统一的弹窗管理模块,从架构上解决弹窗业务的发展性和复杂性问题。
2. 设计目标归纳 (体现你的系统性设计思维)
设计目标明确四点:
-
弹窗业务逻辑集中管理,降低接入和维护成本
-
统一后端数据格式,提升数据驱动能力
-
感知并管理弹窗状态,保障体验一致性
-
引入优先级调度机制,确保多弹窗竞争时体验合理
3. 整体架构设计 (体现你的抽象设计与模块划分能力)
为此,我做了如下抽象和设计:
- 场景类型枚举化:通过TanChuangType枚举区分不同业务弹窗场景,方便统一管理
- 弹窗协议化:定义iTanChuangTypeService协议,每个业务弹窗都需遵循,实现规范
- 集中式管理器:引入TanChuangManager,统一处理弹窗注册、状态感知、优先级调度
- 标准化数据定义:后端下发的数据统一遵循 [场景 + 优先级 + 业务数据] 格式
- 弹窗状态管理:模块内部感知弹窗展示与否,防止无序堆叠
4. ### 兼容存量业务的演进策略 ### (体现你的实际落地与渐进式改造能力)
面对已有大量存量业务,有两种接入策略:
-
常规方案:在触发时机调用TanChuangManager进行弹窗判断和展示
- 整个业务都需要改动
- 触发方法中都要加入原有的额外逻辑
- 后期对原有逻辑修改,会影响弹窗逻辑,造成不要的问题点
-
Hook改进方案:
- 针对UIButton等UI控件的addTarget:action:forControlEvents:方法进行方法交换
- 通过NSProxy代理方式,保证原业务逻辑不受影响
这样可以最小侵入、动态增强存量业务,极大降低改造成本。
(这里体现你的工程实践能力和对平滑演进的思考)
5.**技术细节亮点(体现你在细节层面有深度)
- 为什么使用NSProxy
- 弹窗状态怎么保证线程安全
(这里顺带带出你对架构原则、线程安全等关注)