「OC」内存管理——定时器内存泄漏

「OC」内存管理——定时器内存泄漏

前言

在我们写项目的过程之中,我们总是会使用NSTimer来帮助我们进行滚动视图的翻页等行为,稍不注意就会导致严重的内存泄漏问题。这篇文章就来简单探究一下如何处理定时器的内存泄漏

定时器的使用

[NSTimer scheduledTimerWithTimeInterval:1.0
                                 target:self   // 关键点:强引用 target
                               selector:@selector(tick)
                               userInfo:nil
                                repeats:YES];

image-20250708192431182

我们如果在控制器之中添加定时器,然后再通过对VC进行dismiss或者pop操作,我们的控制器并不会进行dealloc,这是因为在使用定时器的过程之中出现了循环引用

img

内存泄漏的处理

在dimiss和pop的其他方法之中销毁timer

我们在didMoveToParentViewController添加以下内容

- (void)didMoveToParentViewController:(UIViewController *)parent{
    if (parent == nil) {
       [self.timer invalidate];
        self.timer = nil;
        NSLog(@"timer 走了");
    }
}

我们在离开这个页面之后将计时器置空

image-20250709104315363

中介者模式

在我们使用计时器的时候,其实我们主要是的让计时器能够定时执行对应的方法,对应的target其实没有那么重要,那我们可以使用一个类实现方法,以达到同样的目的,这里我使用runtime的API临时给NSObject添加了一个方法


image-20250709110016254

可以看到我们解决了控制器和定时器的循环引用,但是没有解决对中介者的回收,这种方式还是有一定缺陷的。

我们可以在控制器的dealloc函数里面,将定时器置空,就可以解决这个问题

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

自定义Timer

根据第二个思路,我们可以将timer直接分装为一个类

/*********** JCTimerWapper.m 文件 ***********/
#import "JCTimerWapper.h"
#import <objc/message.h>

@interface JCTimerWapper ()

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

@end

@implementation JCTimerWapper

- (instancetype)jc_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 && [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)jc_fireHandler, 
                            type);
            
            // 创建定时器,目标设为封装器自身
            self.timer = [NSTimer scheduledTimerWithTimeInterval:ti 
                                                         target:self 
                                                       selector:aSelector 
                                                       userInfo:userInfo 
                                                        repeats:yesOrNo];
        }
    }
    return self;
}

// 动态添加的定时器事件处理方法
void jc_fireHandler(JCTimerWapper *wapper, SEL _cmd, id timerParam) {
    // 1. 检查原始目标是否还存活
    if (wapper.target) {
        // 2. 构造原始objc_msgSend函数指针
        void (*jc_msgSend)(void *, SEL, id) = (void *)objc_msgSend;
        
        // 3. 转发消息到原始目标对象
        jc_msgSend((__bridge void *)(wapper.target), 
                   wapper.aSelector,
                   timerParam);
    } 
    else {
        // 4. 目标对象已释放,清理定时器资源
        [wapper.timer invalidate];
        wapper.timer = nil;
    }
}

// 销毁定时器
- (void)jc_invalidate {
    [self.timer invalidate];
    self.timer = nil;
}

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

@end

封装完之后我们就可以直接进行使用,当我们alloc的时候就自动创建计时器

//定义
self.timerWapper = [[JCTimerWapper alloc] cjl_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES];

//释放
- (void)dealloc{
     [self.timerWapper jc_invalidate];
}

使用NSProxy虚拟类

我们还是使用runtime的API

//************NSProxy子类************
@interface JCProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end

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

@implementation JCProxy
+ (instancetype)proxyWithTransformObject:(id)object{
    JCProxy *proxy = [JCProxy alloc];
    proxy.object = object;
    return proxy;
}
-(id)forwardingTargetForSelector:(SEL)aSelector {
    return self.object;
}

//************解决timer强持有问题************
self.proxy = [JCProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];

//在dealloc中将timer正常释放
- (void)dealloc{
    [self.timer invalidate];
    self.timer = nil;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值