NSProxy 与定时器
来源:互联网 发布:sql 不等于空字符串 编辑:程序博客网 时间:2024/05/17 16:12
前言
今天看别人的代码, 发现用到了NSProxy这个类, 就查了一下, 然后就发现, 自己用了这么久的定时器NSTimer
, 居然大部分都会有内存问题, 就觉得必须记录一下, 如果你也像我一样用的NSTimer
, 那你可能就要注意了, 请看如下问题代码:
@property (nonatomic, weak) NSTimer *timer;
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. // 定时器 重不重复没影响 self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerRunning) userInfo:nil repeats:YES]; // 这句话 是为了让滑动scrollView的时候定时器不会停止, 加不加对今天的问题没影响 [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];}
- (void)timerRunning{ NSLog(@"1");}- (void)dealloc{ NSLog(@"FirstViewController --dealloc"); [self.timer invalidate];}
上面的用法是有问题的, NSTimer必须要调用invalidate
方法, 才能释放, 然而上面的- dealloc
方法就不会走, 所以定时器也不会释放。(不信的可以亲自试试, 一定要用target-action
的模式, 用block
是不会出现这个问题的, 想知道为什么, 继续往下看)
无法释放的原因如下原因如下:
其实产生循环引用
的根本就是引用计数
, 然而上面的情况并不仅仅是循环引用
, 如果不用属性保存NSTimer
, pop
控制器后, 依然会造成定时器
在后台打印。 我们用引用计数
来解释原因:
- 控制器push或者present过来, 控制器的引用计数+1
- 添加控制器相当于在
Runloop
中注册timer
,Runloop
会强引用定时器
定时器
通过target-action
的方式引用控制器,target-action
的设计模式中, 对target
的引用应该是弱引用的, 为什么会造成强引用, 我猜测(知道真相的小伙伴可以留言告诉我)可能是NSTimer
把控制器交给runloop
进行强引用, 以便于在到达注册时间
时发送消息, 因此即便用弱引用的weakSelf
修饰控制器, 依然无法解决内存无法释放的问题, 因为你控制器的指针传到了runloop手里, runloop就将控制器的引用计数+1
了。- 当
pop
或者dismiss
的时候, 控制器引用计数-1
, 然后界面消失, 你就再也找不到控制器了, 然而控制器的引用计数
还有1
呢, 控制器的内存就泄露啊, 没有被销毁; 因为引用计数从1到0的
时候才会调用dealloc
方法, 因此,定时器
也没有被invalidate
, 它会在后台一直循环打印, 不胜其烦!
那么这个问题怎么解决呢?
我之前的解决方式是, 在viewDidDisappear
的时候调用invalidate
, 虽然解决了问题, 但是还是有新问题的, 因为不止是消失的时候viewDidDisappear
会走, push
和present
控制器的时候viewDidDisappear
也会走啊, 那么怎么办?
NSProxy就是你的曙光了
NSProxy
是iOS开发中一个消息转发的基类,它不继承自NSObject
。因为他也是Foundation
框架中的基类, 通常用来实现消息转发
, 我们也可以用它来包装控制器, 达到弱引用的效果。PS: 如果你只是想要解决以上的问题, 可以完全不用理解消息转发机制
, 直接使用代码就够了, 用法超级简单; 如果你想了解, 请点击这里
NSProxy是一个抽象类, 需要使用它的子类, 然后需要实现init以及消息转发的相关方法。
// 当一个消息转发的动作NSInvocation到来的时候,在这里选择把消息转发给对应的实际处理对象- (void)forwardInvocation:(NSInvocation *)anInvocation// 当一个SEL到来的时候,在这里返回SEL对应的NSMethodSignature- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector// 是否响应一个SEL+ (BOOL)respondsToSelector:(SEL)aSelector
首先创建一个NSProxy
的子类WeakProxy
, 并在.h
文件中声明以下属性和方法
@interface WeakProxy : NSProxy@property (weak,nonatomic,readonly)id target;+ (instancetype)proxyWithTarget:(id)target;- (instancetype)initWithTarget:(id)target;@end
.m
里实现如下
@implementation WeakProxy- (instancetype)initWithTarget:(id)target{ _target = target; return self;}+ (instancetype)proxyWithTarget:(id)target{ return [[self alloc] initWithTarget:target];}- (void)forwardInvocation:(NSInvocation *)invocation{ SEL sel = [invocation selector]; if ([self.target respondsToSelector:sel]) { [invocation invokeWithTarget:self.target]; }}- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ return [self.target methodSignatureForSelector:aSelector];}- (BOOL)respondsToSelector:(SEL)aSelector{ return [self.target respondsToSelector:aSelector];}@end
以上代码就可以用了, 用法如下:
@property (nonatomic, weak) NSTimer *timer;- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:[WeakProxy proxyWithTarget:self] selector:@selector(timerRunning) userInfo:nil repeats:YES];}- (void)timerRunning{ NSLog(@"1");}- (void)dealloc{ NSLog(@"FirstViewController --dealloc"); [self.timer invalidate];}
以上, 我们就解决了定时器不释放的问题, 解决原理如下:
我们依然从引用计数的角度分析:
push
或present
进入控制器,引用计数+1
- 定时器向
NSRunloop
注册事件,NSRunloop
强引用WeakProxy
对象,WeakProxy
弱引用控制器(因为是弱引用, 引用计数不变), 当到达定时器注册的时间
时,Runloop
会向WeakProxy
对象发送消息,WeakProxy
触发消息转发, 把消息转发给弱引用的控制器处理 - 当
pop
或dismiss
控制器时,引用计数-1 变为0
触发-dealloc
方法, 调用定时器的invalidate
注销了定时器, 同时Runloop
注销掉对WeakProxy
对象的强引用, 至此, 所有的内存都被释放了, 问题就解决了
- NSProxy 与定时器
- NSProxy
- NSProxy
- NSProxy与消息转发机制
- NSProxy 详解
- NSProxy学习
- ios-NSProxy
- spring定时器与quartz定时器
- NSProxy一些理解
- NSProxy的使用
- NSProxy实现代理方法
- NSProxy的使用
- 关于NSProxy的理解
- 定时器与多线程
- 定时器与中断概念
- Silverlight 动画与定时器
- 中断与内核定时器
- 计数器与定时器
- 解救臃肿的代码,代码封装利器自定义注解深入剖析
- 用形参操控实参时区分地址和内容
- Python&Opencv&Pycharm安装及环境配置
- 简述重载与重写的区别
- C6748_EMIF_NandFlash_访问异步地址
- NSProxy 与定时器
- 点击列表中的某条记录携带信息进行页面跳转
- 采用html 的a标签,href连接为文件时无法下载解决方案
- ROS安装hector_quadrotor
- java
- 设计模式学习--单例模式
- (连载)一个很长的梦——(二)
- Java基础精选,你答对了几道?
- HPU 1458 (数状数组,利用的很神奇)