iOS NSTimer 内存泄漏问题

文章探讨了NSTimer导致内存泄漏的现象,分析了原因在于runloop对timer的强引用和timer对target的强引用。为了解决这个问题,提出创建一个中间对象LBTimerTarget,使timer弱引用target,避免内存泄漏。提供了实现代码展示了如何创建安全的定时器,确保正确释放资源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内存泄漏的表现

如图, 在vc中,将计时器timer 属性特性设置为weak,创建一个自动添加到runloop中的计时器
请添加图片描述
///计时器方法
请添加图片描述

/// vc的dealloc
请添加图片描述
然而当VC退出的时候,并没有执行dealloc

请添加图片描述

由此看到,虽然timer和VC之间没有循环引用(timer 是weak的),但是VC并没有释放

NSTimer 内存泄漏的原因

由于timer是添加到runloop 中的,由此可推测,将timer 添加到runloop的过程中,
runloop 强持有了timer, 又因为timer强持有target,由于runloop是
一直存在的(只要该线程一直存在),就造成了target被一直持有而不释放,从而造成内存泄漏
如图
请添加图片描述

解决方案

添加一个中间实例对象a,该对象a对真实的target b弱引用,将timer 直接的target设置为该中间实例,从而解除timer对真实的target b 强持有,避免内存泄漏
逻辑如图
请添加图片描述

#实现代码
.h

//
//  NSTimer+lbTimer.h
//  LIUBO
//
//  Created by liubo on 2018/8/2.
//  Copyright © 2018年 iOS. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface LBTimerTarget : NSObject

@end

@interface NSTimer (LBTimer)

/**
 普通定时器,自动加入Default类型的runloop中
 */
+ (NSTimer *)lb_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                        target:(id)target
                                        repeat:(BOOL)repeat
                                         block:(void(^)(NSTimer *timer))block;

/**
 普通定时器,需要手动添加至runloop中
 */
+ (NSTimer *)lb_timerWithTimeInterval:(NSTimeInterval)interval
                               target:(id)target
                               repeat:(BOOL)repeat
                                block:(void(^)(NSTimer *timer))block;

/**
 普通定时器,需要手动添加至runloop中,可以进行暂停,重启操作
 */
+ (NSTimer *)lb_timerWithTimeInterval:(NSTimeInterval)interval
                               target:(id)target
                             selector:(SEL)selector
                             userInfo:(nullable id)userInfo
                              repeats:(BOOL)repeat;

@end

NS_ASSUME_NONNULL_END

.m

//
//  NSTimer+lbTimer.m
//  LIUBO
//
//  Created by liubo on 2018/8/2.
//  Copyright © 2018年 iOS. All rights reserved.
//

#import "NSTimer+LBTimer.h"

@interface LBTimerTarget ()

@property (nonatomic,weak) id sourceTarget;
@property (nonatomic,copy) void(^actionBlock)(NSTimer *timer);
@property (nonatomic, assign) SEL actionSelector;

@end

@implementation LBTimerTarget

- (instancetype)initWithSelector:(SEL)aSelector  sourceTarget:(id)sourceTarget{
    self = [super init];
    if (self) {
        self.actionBlock = nil;
        self.sourceTarget = sourceTarget;
        self.actionSelector = aSelector;
    }
    return self;
}

- (void)dealloc
{
    
}

- (instancetype)initWithBlock:(void(^)(NSTimer *timer))block sourceTarget:(id)sourceTarget{
    self = [super init];
    if (self) {
        self.actionBlock = block;
        self.sourceTarget =sourceTarget;
    }
    return self;
}

- (void)timerAction:(NSTimer *)timer{
    if (self.sourceTarget == nil) {
        [timer invalidate];
        timer = nil;
    }else{
        if (self.actionBlock) {
            self.actionBlock(timer);
        }else{
            IMP imp = [self.sourceTarget methodForSelector:self.actionSelector];
            void (*func)(id, SEL,NSTimer *) = (void *)imp;
            func(self.sourceTarget, self.actionSelector,timer);
        }
    }
}

@end


#pragma mark - NSTimer Category

@implementation NSTimer (LBTimer)

+ (NSTimer *)lb_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                        target:(id)target
                                        repeat:(BOOL)repeat
                                         block:(void(^)(NSTimer *timer))block
{
    LBTimerTarget *timerTarget = [[LBTimerTarget alloc] initWithBlock:block sourceTarget:target];
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:interval target:timerTarget selector:@selector(timerAction:) userInfo:nil repeats:YES];
    return timer;
}

+ (NSTimer *)lb_timerWithTimeInterval:(NSTimeInterval)interval
                               target:(id)target
                               repeat:(BOOL)repeat
                                block:(void(^)(NSTimer *timer))block
{
    LBTimerTarget *timerTarget = [[LBTimerTarget alloc] initWithBlock:block sourceTarget:target];
    NSTimer *timer = [NSTimer timerWithTimeInterval:interval target:timerTarget selector:@selector(timerAction:) userInfo:nil repeats:YES];
    return timer;
}

+ (NSTimer *)lb_timerWithTimeInterval:(NSTimeInterval)interval
                               target:(id)target
                             selector:(SEL)selector
                             userInfo:(nullable id)userInfo
                              repeats:(BOOL)repeat
{
    LBTimerTarget *timerTarget = [[LBTimerTarget alloc] initWithSelector:selector sourceTarget:target];
    NSTimer *timer = [NSTimer timerWithTimeInterval:interval target:timerTarget selector:@selector(timerAction:) userInfo:userInfo repeats:repeat];
    return timer;
}

@end

测试结果,正常释放
请添加图片描述
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值