> For the complete documentation index, see [llms.txt](https://ryukiedev.gitbook.io/wiki/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ryukiedev.gitbook.io/wiki/ios/nei-cun-guan-li/04.timer-you-hua.md).

# 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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-ed653d70920114575e145be5d4f5d64a1d1c0ad2%2F04-01.gif?alt=media)

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

![2](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-f5c992c206b62de647050821489be82db8dff6e0%2F04-02.png?alt=media)

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

![3](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-d46180787265190b52de37abc8411683b1d21840%2F04-03.png?alt=media)

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

#### 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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-ffc73ddf1ff1f82c2e1bb6868cbdc0cf1d687b59%2F04-04.gif?alt=media)

还是一样，这里是 `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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-279c2ad64bd42bd842b3c40688b96ef566531164%2F04-05.gif?alt=media)

这个 `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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-9a10d20f61a8007e02736ca1baa74c86951c3742%2F04-06.gif?alt=media)

#### 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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MI8JgbGh3U6X_oedqkm%2Fuploads%2Fgit-blob-05aae50d056ed9c6ce982de7030744208fa4fe61%2F04-07.gif?alt=media)
