NSProxy 与 NSObject 本质区别、结构体差异、NSProxy 四大实战场景(多继承、Timer 解耦、AOP 切片、方法快速转发) 。全程深度原理 + 可直接复制的实战代码,适合高级 iOS 开发者进阶、面试、架构优化。
一、开篇:为什么 NSProxy 是 iOS 最被低估的类?
在 iOS 开发中,NSProxy 是一个虚类、代理基类、不继承自 NSObject 的特殊存在。
它的地位:
- iOS 中唯一不继承自 NSObject 的根类
- 消息转发机制的终极形态
- 实现多继承、AOP、解耦、埋点、方法交换的神器
- 解决 NSTimer / CADisplayLink 循环引用 的最优解
而 NSObject 是我们日常 99% 类的基类,拥有完整的生命周期、属性、方法、内存管理。
两者的设计目标完全不同:
- NSObject:实体对象,负责存储、生命周期、功能实现
- NSProxy:代理对象,负责消息转发、中转、拦截,不存储数据
二、底层核心对比:NSProxy vs NSObject(深度)
1. 继承结构(最本质区别)
plaintext
objectivec
NSProxy
↳ 根类,不继承任何类
↳ 遵守 NSObject 协议
NSObject
↳ 根类,继承自...(本身就是根类)
↳ 遵守 NSObject 协议
结论:
- NSProxy ≠ 继承自 NSObject
- 两者是平级的两个根类
- 但都遵守
<NSObject>协议
2. 内存结构(结构体差异)
NSObject 结构体(经典 objc_object)
c
运行
objectivec
struct NSObject {
Class isa;
// 可添加成员变量、属性
// 有完整的内存布局
}
- 有内存
- 能存属性
- 能写
init - 能添加成员变量
NSProxy 结构体(纯转发,无内存)
c
运行
objectivec
struct NSProxy {
Class isa;
// 没有额外存储空间
// 不能添加成员变量
}
NSProxy 设计上就是:
- 不存储数据
- 不持有属性
- 不负责业务
- 只负责转发消息
3. 消息发送机制差异(最关键)
iOS 方法调用 = objc_msgSend
NSObject 消息路线(完整 4 步)
- 找自身方法
- 动态解析
resolveInstanceMethod - 快速转发
forwardingTargetForSelector - 慢速转发
methodSignatureForSelector+forwardInvocation
NSProxy 消息路线(直接跳过前 3 步)
- 直接进入转发
- 不查找自身方法
- 不执行动态解析
- 直接调用 methodSignatureForSelector
这就是 NSProxy 性能极高的原因!
4. 核心能力总结表
表格
| 维度 | NSObject | NSProxy |
|---|---|---|
| 继承关系 | 根类 | 根类(不继承 NSObject) |
| 内存存储 | 支持属性、成员变量 | 不支持存储(纯转发) |
| init 方法 | 可以写 | 可以写,但不推荐 |
| 消息查找 | 完整查找流程 | 直接进入转发 |
| 性能 | 正常 | 极高(无查找损耗) |
| 用途 | 实体对象 | 代理、转发、AOP、多继承 |
| 能否解决 Timer 循环引用 | 难 | 非常简单 |
| 能否实现多继承 | 不能 | 完美实现 |
| 能否做 AOP | 可以(Method Swizzle) | 天然支持(无侵入) |
三、Runtime 消息机制深度讲解(为 NSProxy 铺路)
1. 消息发送完整流程
当调用 [obj doSomething]
NSObject 流程:
- 查 isa 找类
- 查方法缓存
- 查方法列表
- 父类查找
- 动态解析
- 快速转发
- 慢速转发
- 报错
unrecognized selector
NSProxy 流程:
- 直接进入 慢速转发
2. 快速转发 vs 慢速转发
快速转发
plaintext
erlang
- (id)forwardingTargetForSelector:(SEL)aSelector
- 性能高
- 只能转发给一个对象
- 无法修改方法、参数
慢速转发(NSProxy 专用)
plaintext
erlang
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
- (void)forwardInvocation:(NSInvocation *)invocation
- 能修改 SEL、参数、返回值
- 能转发给多个对象
- 能实现 AOP、多继承、埋点
- NSProxy 完全依赖它
四、NSProxy 核心用法(四大实战场景)
场景 1:实现【多继承】(OC 不支持多继承,NSProxy 完美模拟)
OC 只支持单继承,但业务中经常需要:
- 一个类拥有 A、B 两个类的能力
NSProxy 可以做到!
实现代码
swift
objectivec
@interface MultiProxy : NSProxy
- (instancetype)initWithTargets:(NSArray *)targets;
@end
@implementation MultiProxy {
NSArray *_targets;
}
- (instancetype)initWithTargets:(NSArray *)targets {
_targets = targets;
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
for (id target in _targets) {
NSMethodSignature *sig = [target methodSignatureForSelector:sel];
if (sig) return sig;
}
return [super methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
for (id target in _targets) {
if ([target respondsToSelector:invocation.selector]) {
[invocation invokeWithTarget:target];
break;
}
}
}
@end
使用
plaintext
css
A *a = [A new];
B *b = [B new];
id proxy = [MultiProxy alloc] initWithTargets:@[a,b];
[proxy run]; // 自动找 a 或 b 执行
真正的多继承能力!
场景 2:解决 NSTimer / CADisplayLink 循环引用(最优解)
问题
NSTimer 会强持有 target,ViewController 强持有 Timer → 循环引用,无法释放
传统方案
- __weak
- 中介者
- 封装(不够优雅)
NSProxy 最优解
plaintext
less
@interface TimerProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation TimerProxy {
id _target;
}
+ (instancetype)proxyWithTarget:(id)target {
TimerProxy *proxy = [TimerProxy alloc];
proxy->_target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [_target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation invokeWithTarget:_target];
}
@end
使用
plaintext
objectivec
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1
target:[TimerProxy proxyWithTarget:self]
selector:@selector(timerEvent)
userInfo:nil
repeats:YES];
完美解耦!
- VC 释放
- Timer 自动释放
- 无循环引用
- 代码极简
场景 3:AOP 切片编程、埋点、日志、性能监控(NSProxy 最强能力)
AOP:不侵入原有代码,统一添加逻辑
例如:
- 所有点击埋点
- 所有网络请求日志
- 所有页面性能统计
- 方法执行耗时
NSProxy 实现 AOP
plaintext
less
@interface AOPProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation AOPProxy {
id _target;
}
+ (instancetype)proxyWithTarget:(id)target {
AOPProxy *proxy = [AOPProxy alloc];
proxy->_target = target;
return proxy;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [_target methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
// 【切片前】埋点/日志
NSLog(@"before call: %@", NSStringFromSelector(invocation.selector));
// 转发
[invocation invokeWithTarget:_target];
// 【切片后】统计
NSLog(@"after call: %@", NSStringFromSelector(invocation.selector));
}
@end
使用
plaintext
ini
id obj = [AOPProxy proxyWithTarget:realObj];
[obj doSomething];
自动输出:
plaintext
r
before call: doSomething
after call: doSomething
无侵入、无耦合、高性能、可插拔
场景 4:方法快速转发、方法交换、统一拦截
NSProxy 可以:
- 转发任意方法
- 修改参数
- 修改返回值
- 替换方法实现
- 统一异常捕获
plaintext
ini
- (void)forwardInvocation:(NSInvocation *)invocation {
// 修改参数
int arg;
[invocation getArgument:&arg atIndex:2];
arg = 100;
[invocation setArgument:&arg atIndex:2];
// 执行
[invocation invokeWithTarget:_target];
// 修改返回值
int ret = 200;
[invocation setReturnValue:&ret];
}
五、NSProxy 与 Method Swizzle 的区别
表格
| 方式 | 侵入性 | 性能 | 适用 |
|---|---|---|---|
| Method Swizzle | 高(全局替换) | 中 | 全局方法替换 |
| NSProxy | 无(局部代理) | 极高 | 多继承、AOP、解耦、转发 |
结论:
- Swizzle 是暴力替换
- NSProxy 是优雅转发
- 大型架构一定优先 NSProxy
六、总结
NSProxy 核心结论
- NSProxy 不继承自 NSObject,是平级根类
- NSProxy 无内存、无属性、只负责消息转发
- NSProxy 直接进入转发流程,性能极高
- NSProxy 能实现多继承、解耦 Timer、AOP、埋点、方法转发
- NSProxy 是 iOS 最优雅的切面、代理、中介方案
使用场景(必须记住)
- 多继承模拟
- NSTimer 解耦
- AOP 切片、埋点、日志
- 方法转发、拦截、修改
- 架构解耦、中介模式