「OC」内存管理——定时器内存泄漏
前言
在我们写项目的过程之中,我们总是会使用NSTimer来帮助我们进行滚动视图的翻页等行为,稍不注意就会导致严重的内存泄漏问题。这篇文章就来简单探究一下如何处理定时器的内存泄漏
定时器的使用
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self // 关键点:强引用 target
selector:@selector(tick)
userInfo:nil
repeats:YES];
我们如果在控制器之中添加定时器,然后再通过对VC进行dismiss或者pop操作,我们的控制器并不会进行dealloc,这是因为在使用定时器的过程之中出现了循环引用
内存泄漏的处理
在dimiss和pop的其他方法之中销毁timer
我们在didMoveToParentViewController
添加以下内容
- (void)didMoveToParentViewController:(UIViewController *)parent{
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer 走了");
}
}
我们在离开这个页面之后将计时器置空
中介者模式
在我们使用计时器的时候,其实我们主要是的让计时器能够定时执行对应的方法,对应的target其实没有那么重要,那我们可以使用一个类实现方法,以达到同样的目的,这里我使用runtime的API临时给NSObject添加了一个方法
可以看到我们解决了控制器和定时器的循环引用,但是没有解决对中介者的回收,这种方式还是有一定缺陷的。
我们可以在控制器的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;
}