iOS --NStimer

手动目录

  • NSTimer 打破强持有的方法
    方法一: 在 viewWillDisappear 中释放NSTimer
    方法二:在didMoveToParentViewController中释放
    方法三:消息转发
    方法四:中介者模式
    ___普通方式
    ___runtimer方式
  • 为什么weakSelf不能打破循环引用

iOS开发中,遇到定时器的时候很多,大多数人会选择用NSTimer。

NSTimer 有一个新的API不用考虑循环引用
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
但是它支持的版本是10.12之后。

我们用的多的还是这个API:
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
这个API就会 有一个问题:循环引用造成VC释放不掉。
为什么会释放不掉? 我们在API中看说明:

target
The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated.

target 是一个强持有。

NSTimer 打破强持有的方法

方法一: 在 viewWillDisappear 中释放NSTimer

@property (nonatomic, strong) NSTimer       *timer;

self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];

- (void)fireHome {
}

- (void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    [self.timer invalidate];
    self.timer = nil;
}

这种方法很显然,局限性很大,大多数情况下,这种当时都是不友好的,因为大多数情况下,都会进行push或者present。

方法二:在didMoveToParentViewController中释放

- (void)didMoveToParentViewController:(UIViewController *)parent{
    // 无论push 进来 还是 pop 出去 正常跑
    if (parent == nil) {
       [self.timer invalidate];
        self.timer = nil;
        NSLog(@"timer 走了");
    }
}

方法三:消息转发

这种方式 需要借助虚基类来实现。

思路: 创建一个虚基类,指定Timer的target为这个虚基类。然后让虚基类把消息在发送给原来的target

// 虚基类  LGProxy
@interface LGProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end

@interface LGProxy()
@property (nonatomic, weak) id object;
@end

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

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

// 用这种方式创建NSTimer  --- 指定target 为 LGProxy.object
self.proxy = [LGProxy proxyWithTransformObject:self];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];

- (void)fireHome {
    NSLog(@"hello word - %d",num);
}

方法四:中介者模式

普通方式

用一个类保存Timer的target、SEL等信息

// .h
@interface JEWeakTimer : NSObject
+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      target:(id)aTarget
                                    selector:(SEL)aSelector
                                    userInfo:(id)userInfo
                                     repeats:(BOOL)repeats;
@end


// .m
@interface JEWeakTimerTarget : NSObject

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

@end

@implementation JEWeakTimerTarget

- (void) fire:(NSTimer *)timer {
    if(self.target) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self.target performSelector:self.selector withObject:timer.userInfo afterDelay:0.0f];
#pragma clang diagnostic pop
    } else {
        [self.timer invalidate];
    }
}

@end

@implementation JEWeakTimer

+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      target:(id)aTarget
                                    selector:(SEL)aSelector
                                    userInfo:(id)userInfo
                                     repeats:(BOOL)repeats {
    JEWeakTimerTarget* timerTarget = [[JEWeakTimerTarget alloc] init];
    timerTarget.target = aTarget;
    timerTarget.selector = aSelector;
    timerTarget.timer = [NSTimer scheduledTimerWithTimeInterval:interval
                                                         target:timerTarget
                                                       selector:@selector(fire:)
                                                       userInfo:userInfo
                                                        repeats:repeats];
    return timerTarget.timer;
}
@end
runtime 方式

思路: 通过runtime 动态添加 动态向中介者中添加一个方法实现(SEL(timer的SEL) - > IMP(自定义的IMP ),在自定义的IMP中,通过runtime 向原来的类中发送消息

// .h
@interface LGTimerWapper : NSObject

- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

@end

// .m
#import 

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

@end

@implementation LGTimerWapper

- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
    if (self == [super init]) {
        self.target     = aTarget; // vc
        self.aSelector  = aSelector; // 方法 -- vc 释放
        
        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)fireHomeWapper, type);
            
            self.timer      = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo];
        }
    }
    return self;
}

void fireHomeWapper(JETimerWapper *warpper){
    
    if (warpper.target) {
        void (*lg_msgSend)(void *,SEL, id) = (void *)objc_msgSend;
         lg_msgSend((__bridge void *)(warpper.target), warpper.aSelector,warpper.timer);
    }
}
@end

为什么weakSelf不能打破循环引用

上面说的各种解决NSTimer不释放的问题,都没有提到weakSelf。为什么block可以解决循环引用,而NSTimer不可以?
其实把Block本质了解之后,再来看这个问题就很好处理了。
在iOS-- block中提到一个地方,block_assign (第二层拷贝) 中是对对象的指针进行持有。

void _Block_object_assign(void *destArg, const void *object, const int flags) {
  const void **dest = (const void **)destArg;
  switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {

    case BLOCK_FIELD_IS_OBJECT:
      _Block_retain_object(object);
      *dest = object;                //  ⬅️     持有的是指针地址,而不是对象本身
      break;

self和weakSelf虽然都是指向同一个对象,但他们是两个不同的地址,weakSelf不强持有对象,也就是不操作引用计数。
block在copy的时候,会强持有临时变量的指针地址,而不是指针指向的对象,所以weakSelf可以解决block循环引用问题,而NSTimer强持有的是对象。

你可能感兴趣的:(iOS --NStimer)