NSProxy 核心原理、消息机制、多继承、AOP、Timer 解耦、快速转发全解

25 阅读5分钟

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

NSProxy
  ↳ 根类,不继承任何类
  ↳ 遵守 NSObject 协议

NSObject
  ↳ 根类,继承自...(本身就是根类)
  ↳ 遵守 NSObject 协议

结论:

  • NSProxy ≠ 继承自 NSObject
  • 两者是平级的两个根类
  • 但都遵守 <NSObject> 协议

2. 内存结构(结构体差异)

NSObject 结构体(经典 objc_object)

c

运行

struct NSObject {
    Class isa;
    // 可添加成员变量、属性
    // 有完整的内存布局
}
  • 有内存
  • 能存属性
  • 能写 init
  • 能添加成员变量

NSProxy 结构体(纯转发,无内存)

c

运行

struct NSProxy {
    Class isa;
    // 没有额外存储空间
    // 不能添加成员变量
}

NSProxy 设计上就是:

  • 不存储数据
  • 不持有属性
  • 不负责业务
  • 只负责转发消息

3. 消息发送机制差异(最关键)

iOS 方法调用 = objc_msgSend

NSObject 消息路线(完整 4 步)

  1. 找自身方法
  2. 动态解析 resolveInstanceMethod
  3. 快速转发 forwardingTargetForSelector
  4. 慢速转发 methodSignatureForSelector + forwardInvocation

NSProxy 消息路线(直接跳过前 3 步)

  1. 直接进入转发
  2. 不查找自身方法
  3. 不执行动态解析
  4. 直接调用 methodSignatureForSelector

这就是 NSProxy 性能极高的原因!

4. 核心能力总结表

表格

维度NSObjectNSProxy
继承关系根类根类(不继承 NSObject)
内存存储支持属性、成员变量不支持存储(纯转发)
init 方法可以写可以写,但不推荐
消息查找完整查找流程直接进入转发
性能正常极高(无查找损耗)
用途实体对象代理、转发、AOP、多继承
能否解决 Timer 循环引用非常简单
能否实现多继承不能完美实现
能否做 AOP可以(Method Swizzle)天然支持(无侵入)

三、Runtime 消息机制深度讲解(为 NSProxy 铺路)

1. 消息发送完整流程

当调用 [obj doSomething]

NSObject 流程:

  1. 查 isa 找类
  2. 查方法缓存
  3. 查方法列表
  4. 父类查找
  5. 动态解析
  6. 快速转发
  7. 慢速转发
  8. 报错 unrecognized selector

NSProxy 流程:

  1. 直接进入 慢速转发

2. 快速转发 vs 慢速转发

快速转发

plaintext

- (id)forwardingTargetForSelector:(SEL)aSelector
  • 性能高
  • 只能转发给一个对象
  • 无法修改方法、参数

慢速转发(NSProxy 专用)

plaintext

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
- (void)forwardInvocation:(NSInvocation *)invocation
  • 能修改 SEL、参数、返回值
  • 能转发给多个对象
  • 能实现 AOP、多继承、埋点
  • NSProxy 完全依赖它

四、NSProxy 核心用法(四大实战场景)

场景 1:实现【多继承】(OC 不支持多继承,NSProxy 完美模拟)

OC 只支持单继承,但业务中经常需要:

  • 一个类拥有 A、B 两个类的能力

NSProxy 可以做到!

实现代码

swift

@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

A *a = [A new];
B *b = [B new];
id proxy = [MultiProxy alloc] initWithTargets:@[a,b];

[proxy run]; // 自动找 ab 执行

真正的多继承能力!


场景 2:解决 NSTimer / CADisplayLink 循环引用(最优解)

问题

NSTimer 会强持有 target,ViewController 强持有 Timer → 循环引用,无法释放

传统方案

  • __weak
  • 中介者
  • 封装(不够优雅)

NSProxy 最优解

plaintext

@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

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

@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

id obj = [AOPProxy proxyWithTarget:realObj];
[obj doSomething];

自动输出:

plaintext

before call: doSomething
after call: doSomething

无侵入、无耦合、高性能、可插拔


场景 4:方法快速转发、方法交换、统一拦截

NSProxy 可以:

  • 转发任意方法
  • 修改参数
  • 修改返回值
  • 替换方法实现
  • 统一异常捕获

plaintext

- (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 核心结论

  1. NSProxy 不继承自 NSObject,是平级根类
  2. NSProxy 无内存、无属性、只负责消息转发
  3. NSProxy 直接进入转发流程,性能极高
  4. NSProxy 能实现多继承、解耦 Timer、AOP、埋点、方法转发
  5. NSProxy 是 iOS 最优雅的切面、代理、中介方案

使用场景(必须记住)

  • 多继承模拟
  • NSTimer 解耦
  • AOP 切片、埋点、日志
  • 方法转发、拦截、修改
  • 架构解耦、中介模式