# 04.Timer优化

### 前言

NSTimer 是 iOS 开发中老生常谈的知识点了，今天再来唠叨一番。

### 一、 NSTimer 常规使用

通过下面简单的 Demo 看看现象。

```
#import "NSTimerViewController.h"

@interface NSTimerViewController ()
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, assign) NSInteger count;
@end

@implementation NSTimerViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self selfTargetTimer];
}

#pragma mark - 不同 NSTimer 使用方式
/// 常规使用 self 作为 Target 的 NSTimer
- (void)selfTargetTimer {
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(countAction) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}

#pragma mark - Action
- (void)countAction {
    self.count += 1;
    NSLog(@"%@ - %ld",self, self.count);
}

- (void)dealloc {
    [self.timer invalidate];
    self.timer = nil;
    NSLog(@"NSTimerViewController dealloc");
}

@end
```

我们反复 Push 到这个 ViewController ，出现了典型的内存泄露。

![1](/files/vPwmZq1G6Lq2BRsxxwqu)

通过内存图我们能够发现，根本原因是 `RunLoop` -> `Timer` <-> `Self` 这样的一个持有关系。

![2](/files/rmgfQsMuXq123bkWvRmA)

而在该API的文档中也是有说明的。

![3](/files/QqQRfE1WgW8WG1EY2hCd)

为了解决内存泄露，我们需要打破这个循环引用。

#### 1.1 weakSelf

我们用 weakself 替代 Self 是否有用呢？

```
__weak typeof(self)weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(countAction) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
```

![4](/files/h7ryhO3ChFwKmw3S94F8)

还是一样，这里是 `RunLoop` -> `Timer` <-> `weakSelf` -> `Self` 依旧没有打破。

#### 1.2 block

还有这样一个 API

```
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval 
                                    repeats:(BOOL)repeats 
                                      block:(void (^)(NSTimer *timer))block;
```

我们试试

```
__weak typeof(self)weakSelf = self;
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        [weakSelf countAction];
    }];
```

![5](/files/R89ZEBmx1PwXMnsFWpDR)

这个 `API` 就可以很好的避免循环引用了。这里的关系是 `RunLoop` -> `Timer` -> `block` 。这里只需要注意不要让 `block` 产生内存泄露就ok了。

### 二、 自定义 Timer

#### 2.1 加一层间接

通过封装，解决 `Timer` 和 `Self` 的强引用关系： `RunLoop` -> `Timer` -> `??` \~\~ `Self`

```
#import <objc/message.h>

@interface RYTimer ()

@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL aSelector;
@property (nonatomic, strong) NSTimer *timer;

@end

@implementation RYTimer

- (instancetype)initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo {
    if (self == [super init]) {
        self.target = aTarget;
        self.aSelector = aSelector;
        
        if ([self.target respondsToSelector:self.aSelector]) {
            Method method = class_getInstanceMethod([self.target class], aSelector);
            const char *type = method_getTypeEncoding(method);
            class_addMethod([self class], aSelector, (IMP)fireIMP, type);
            
            self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo];
        }
    }
    return self;
}

void fireIMP(RYTimer *timer){
    if (timer.target) {
        void (*ry_msgSend)(void *,SEL, id) = (void *)objc_msgSend;
        ry_msgSend((__bridge void *)(timer.target), timer.aSelector,timer.timer);
    }else{
        [timer invalidate];
    }
}

- (void)invalidate{
    [self.timer invalidate];
    self.timer = nil;
}

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

@end
```

![6](/files/1sd3KNwDoPwUjaT7WAij)

#### 2.2 消息转发机制

我们同样可以利用消息转发机制来对 Timer 进行优化，这里的思想和上面一样，都是使用了一层间接，但是具体实现方式不同。这里使用了 NSProxy 结合消息转发实现。

```
@interface RYProxy : NSProxy

+ (instancetype)proxyWithTransformObject:(id)object;

@end


@interface RYProxy ()

@property (nonatomic, weak) id object;

@end

@implementation RYProxy

+ (instancetype)proxyWithTransformObject:(id)object {
    RYProxy *proxy = [RYProxy alloc];
    proxy.object = object;
    return proxy;
}

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

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

}

- (void)forwardInvocation:(NSInvocation *)invocation{
    if (self.object) {
        [invocation invokeWithTarget:self.object];
    }
}

@end
```

虽然 `Target` 并不是 `Self` 但经过消息转发，最终处理消息的还是消息的还是 `Self` 。

```
RYProxy *timerProxy = [RYProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:timerProxy selector:@selector(countAction) userInfo:nil repeats:YES];
```

![7](/files/ZT6nRhPmTRLZ2XfAtd49)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ryukiedev.gitbook.io/wiki/ios/nei-cun-guan-li/04.timer-you-hua.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
