开发技术文章

优雅的解决NSTimer循环引用

2021-12-01  本文已影响0人  再好一点点

一. 使用NSProxy解决NSTimer、CADisplayLink等循环引用

如下使用NSTimer如果不做任何处理会导致内存泄露。为了解决selftimer互相强引用问题,可以再某个时间点调用 [self.timer invalidate];self.timer = nil;,这样是可以解决内存泄漏的,但是不太灵活。

 self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];

使用下面这种方式可以很灵活的解决循环引用问题。

1. ViewController.m类
@interface ViewController ()
@property (strong, nonatomic) NSTimer *timer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[YHProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
}

- (void)timerTest
{
    NSLog(@"%s", __func__);
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    [self.timer invalidate];
}

@end
2. YHProxy类
#import <Foundation/Foundation.h>

@interface YHProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end

#import "YHProxy.h"

@implementation YHProxy

+ (instancetype)proxyWithTarget:(id)target
{
    // NSProxy对象不需要调用init,因为它本来就没有init方法
    YHProxy *proxy = [YHProxy alloc];
    proxy.target = target;
    return proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
    [invocation invokeWithTarget:self.target];
}
@end

NSProxy这个类很简单,他不会进行方法查找,也不会进行动态消息解析,而是直接调用methodSignatureForSelectorforwardInvocation,所以不存在效率低的问题。

二. NSProxy对象调用isKindOfClass

1. YHProxy1类
#import <Foundation/Foundation.h>

@interface YHProxy1 : NSObject
+ (instancetype)proxyWithTarget:(id)target;
@property (weak, nonatomic) id target;
@end

#import "YHProxy1.h"

@implementation YHProxy1

+ (instancetype)proxyWithTarget:(id)target
{
    YHProxy1 *proxy = [[YHProxy1 alloc] init];
    proxy.target = target;
    return proxy;
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return self.target;
}

@end
2. main函数
int main(int argc, char * argv[]) {
    @autoreleasepool {
        ViewController *vc = [[ViewController alloc] init];
        YHProxy *proxy = [YHProxy proxyWithTarget:vc];
        YHProxy1 *proxy1 = [YHProxy1 proxyWithTarget:vc];
        NSLog(@"%d %d",
              [proxy isKindOfClass:[ViewController class]],
              [proxy1 isKindOfClass:[ViewController class]]);
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
3. 结果:
1 0
4. 分析

YHProxy1继承自NSObjectYHProxy1的所有父类和[ViewController class]都不是一种类型,所以结果为0
[proxy isKindOfClass:[ViewController class]]为什么为1呢?因为NSProxyisKindOfClass内部直接调用了- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel、- (void)forwardInvocation:(NSInvocation *)invocation,修改了target导致的,相当于是vc调用了isKindOfClass,所以[proxy isKindOfClass:[ViewController class]]等于[vc isKindOfClass:[ViewController class]]结果为1。
通过GNUStep可以看到伪代码。

上一篇 下一篇

猜你喜欢

热点阅读