iOS如何巧妙解决NSTimer的循环引用详解

一 发现问题

我们都知道NSTimer采用target-action的方式,通常target又是类本身,我们为了方便又把NSTimer声明为属性变量,这样就难免会造成循环引用(需要反复执行计时任务时,如果是单次的任务就不会造成循环引用)。

例如:

_timer = [NSTimer scheduledTimerWithTimeInterval:5.0
           target:self
selector:@selector(startTimer) userInfo:nil
repeats:YES];

深入理解,类有一个成员变量_timer,给_timer设置的target为这个类本身。这样类保留_timer,_timer又保留了这个类,就会出现循环引用的问题,最后导致类无法正确释放。

大家可能觉得解决这个问题很简单,在合适的时机释放NSTimer,大多人多会选择viewWillDisappear,viewDidDisappear,dealloc。当然了如果选择在dealloc释放NSTimer的且觉得这样没问题的,那是你不够了解dealloc的执行时间,科普下dealloc的执行时机是在self释放之后执行的。这样就排除了dealloc,那就只能选择viewWillDisappear,viewDidDisappear(push和pop都会执行)。但是这两个方法往往不能满足需求。

二 解决问题

有去了解NSTimer循环引用的同学,知道有两种常见的方法可以解决:

  • 采用block封装,target设置为NSTimer本身
  • 既然是因为target是self本身造成的,那就把target设置为其他对象

(第一种block就不用说了,大家也比较喜欢这种方式,但是有时候就不想用block呢,想用第二种方法,但是用起来有很多不便之处,target是其他对象,action也要在其他对象,这样在action想要访问self的相关信息就很不方便。于是就有第三种方法诞生了。)

3.用一个含有weak属性的对象A包裹self作为target,再对A进行消息转发,访问A就相当于访问self,这样就完美的解决了循环引用,且保留了target-action方式。

大家比较好奇的是有weak属性的对象A的类怎么实现,下面来看看代码:

#import <Foundation/Foundation.h>
#pragma mark -
#pragma mark - 内置weak对象(可用于分类定义weak属性)
@interface XWWeakObject : NSObject

@property (nullable, nonatomic, weak, readonly) id weakObject;

- (instancetype _Nullable )initWeakObject:(id _Nullable )obj;

+ (instancetype _Nullable )proxyWeakObject:(id _Nullable )obj;
@end

#import "XWWeakObject.h"
@implementation XWWeakObject
-(instancetype)initWeakObject:(id)obj{
  _weakObject = obj;
  return self;
}

+(instancetype)proxyWeakObject:(id)obj{

  return [[XWWeakObject alloc] initWeakObject:obj];
}

- (id)forwardingTargetForSelector:(SEL)selector {
  return _weakObject;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
  void *null = NULL;
  [invocation setReturnValue:&null];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
  return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}

- (BOOL)respondsToSelector:(SEL)aSelector {
  return [_weakObject respondsToSelector:aSelector];
}

- (BOOL)isEqual:(id)object {
  return [_weakObject isEqual:object];
}

- (NSUInteger)hash {
  return [_weakObject hash];
}

- (Class)superclass {
  return [_weakObject superclass];
}

- (Class)class {
  return [_weakObject class];
}

- (BOOL)isKindOfClass:(Class)aClass {
  return [_weakObject isKindOfClass:aClass];
}

- (BOOL)isMemberOfClass:(Class)aClass {
  return [_weakObject isMemberOfClass:aClass];
}

- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
  return [_weakObject conformsToProtocol:aProtocol];
}

- (BOOL)isProxy {
  return YES;
}

- (NSString *)description {
  return [_weakObject description];
}

- (NSString *)debugDescription {
  return [_weakObject debugDescription];
}
@end

XWWeakObject类有一个weak只读weakObject对象(这个类也可以用于分类声明weak属性:分类是本身是不能声明weak属性的)。

用运行时对该类的对象做了消息转发,对象转发,在访问XWWeakObject对象的时候相当于访问其属性weakObject对象。

最后看下怎么用代码实现的:

- (void)viewDidLoad {
  [super viewDidLoad];
  XWWeakObject *target = [XWWeakObject proxyWeakObject:self];
  self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:target selector:@selector(timerCount) userInfo:nil repeats:YES];
}

-(void)timerCount{
}

-(void)dealloc{
  [_timer invalidate];
   _timer = nil;
}

前提timer是self的一个属性,创建一个XWWeakObject对象target,target是内部weak属性指向self,相当于target拥有self且是weak,self的retain没有加1,timer拥有XWWeakObject对象target,target的retain加1,timer和self的直接关系是timer仅是self的一个属性,这样看来并没有形成循环引用。

三 写在最后

虽然这种方式没有block简便,但不失为一种好的方法,保存了系统的方式。喜欢用target-action方式的或者不太熟悉block的可以学一学哦,且XWWeakObject能做的不仅仅这些,XWWeakObject可以解决很多类似的循环引用问题,解决分类定义weak属性等等

有人可能有疑问,为什么都同样是target-action方式button就不会出现循环引用的问题,有去研究的同学应该都知道UIControl的内部做了weak操作,即真正持有的时候是weak的并没有导致retain加1,而NSTimer由于runloop的原因并没有做weak操作。

闲言杂语

以上内容仅代表个人想法,如果您有更好的想法,更好的解决办法,可以一起探讨。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

您可能感兴趣的文章:

  • IOS开发代码分享之用nstimer实现倒计时功能
  • iOS中的NSTimer定时器的初步使用解析
  • iOS NSTimer循环引用的几种解决办法
  • IOS 中NSTimer定时器的使用
  • iOS NSTimer循环引用的办法
时间: 2018-03-24

iOS NSTimer循环引用的办法

在当前控制器(ViewController)的view上添加了一个自定义的view(LXFTimerView), LXFTimerView在成功创建出来后添加了定时器NSTimer并加入RunLoop开始工作, 当在当前控制器里将LXFTimerView移除掉后,定时器还在工作,而且LXFTimerView里的dealloc并没有调用 代码 LXFTimerView.m #import "LXFTimerView.h" @interface LXFTimerView() /** 定时器

iOS中的NSTimer定时器的初步使用解析

创建一个定时器(NSTimer) - (void)viewDidLoad { [super viewDidLoad]; [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(actionTimer:) userInfo:nil repeats:YES]; } - (void)actionTimer:(NSTimer *)timer { } NSTimer默认运行在default mode下,default

IOS开发代码分享之用nstimer实现倒计时功能

用nstimer实现倒计时功能,废话不多说,直接上代码,详细解释请参照注释 // [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFireMethod:) userInfo:nil repeats:YES];   // - (void)timerFireMethod:(NSTimer *)theTimer {     BOOL timeStart = YES;     NSCalend

IOS 中NSTimer定时器的使用

IOS 中NSTimer定时器的使用 NSTimery 定时器,主要用于进行定时执行指定方法,常用场景如:获取验证码的按钮倒计时:图片轮播定时. 1 使用注意事项: 1.1 倒计时时间间隔(时间单位是秒) 1.2 指定的执行方法 1.3 实现指定执行方法的对象 1.4 是否重复执行 2 对象的内存管理及销毁 2.1 使用方法" invalidate "进行停止 2.2 将对象设置为" nil " 2.3 特别是在返回到其他视图控制器的时候,要在方法" -

iOS NSTimer循环引用的几种解决办法

发生场景 在 Controller B 中有一个 NSTimer @property (strong, nonatomic) NSTimer *timer; 你创建了它,并挂载到 main runloop self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction:) userInfo:nil repeats:true]; 然后退出 Controller B 的

详解spring中aop不生效的几种解决办法

先看下这个问题的背景:假设有一个spring应用,开发人员希望自定义一个注解@Log,可以加到指定的方法上,实现自动记录日志(入参.出参.响应耗时这些) package com.cnblogs.yjmyzz.springbootdemo.aspect; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy

Oracle表空间不足的两种解决办法

首先查询表空间的大小以及文件路径地址 select tablespace_name, file_id, file_name,round(bytes/(1024*1024),0) total_space from dba_data_files order by tablespace_name; 解决方案一 //修改表空间大小(32000可改为想要的数值) alter database datafile '要修改的数据文件地址' resize 32000m; 解决方案二 //新增数据文件 ALTER

iOS下border-image不起作用的解决办法

上周五突然接到现场的一个需求,做一个移动端的劳模展示页面.现场美工把原型图发了过来.这个样子的: 说实在的很想吐槽一下我们美工的审美哈,不过这不是重点. 因为边框是需要特殊花纹的所以打算用border-image实现. .example{ -- border:6px solid transparent; -webkit-border-image:url(../img/border_img.jpg) 6 6 round; border-image:url(../img/border_img.jpg

Ajax打开新窗口被浏览器拦截的两种解决办法

最近在做支付时发现打开支付窗口时被浏览器拦截了,百度了一下才发现是因为打开窗口前用ajax验证是否能支付,所以不是用户主动触发的打开ixin窗口,浏览器认为这样不安全,所以给拦截了. 解决办法一 先开始打开一个空的新窗口,然后改变新窗口的url,具体代码为 var wd = window.open(); $.ajax({ type: "POST", dataType: "json", url: URL, data: {orderNo:orderNo}, succes

Android Button 自带阴影效果另一种解决办法

在Android 5.0以后的版本中,定义一个button时,系统自动会加一个阴影的效果,有的时候这种效果看起来比较好,有的时候不符合UI的设计要求,这时候就需要手动去掉阴影. 网上很多文章写了解决办法,就是给button加一句话style="?android:attr/borderlessButtonStyle",这个确实能解决问题,但是又带来了另外一个问题,就是一般情况下,在写布局的时候,都会给每个控件写一个style,这样方便复用,比如我写了一个button,引了一个style,

iOS UIButton 点击无响应的解决办法

前言 在日常开发中我们经常和按钮打交道,但是有时候会碰到比较难处理的问题,就是按钮不响应点击事件,这时候我们需要从下面几方面排查 按钮不响应的原因 1.按钮添加到了一个没有开启用户交互的父View上,例如UIImageView,这时候开启父试图的交互 view.userInteractionEnabled = YES 设为YES就可以解决问题 2.按钮自身被遮挡,点击的时候根本就没有点击到button,而是他上面一层View,自然就不会响应 这里有个看图层的方法,下图点击那个红框的按钮就可以看到

IOS上iframe的滚动条失效的解决办法

问题描述: iframe设置了高度(例如500px).倘若iframe的内容足够长超出了iframe设定的高度时,在ipad等设备上.iframe内部html的滚动条不出现.并且活生生的从500px处截断,(类似overflow:hidden的效果)下面的内容不再显示. 问题重现: 结构: index.html : <style> #iframe{height:500px;} </style> <div id="content"> <ifram

PHP错误Allowed memory size of 67108864 bytes exhausted的3种解决办法

今天在百度知道看php相关资料时发现一个网友问一个wordpress安装提示错误的一个问题,说他在安装wordpress安装包时遇到这样一个问题:Fatal error:Allowed memory size of 67108864 bytes exhausted (tried to allocate 76 bytes) in-..这个问题小编以前没遇到过,根据提示肯定是内存溢出造成的.至于为什么会出现这样的提示呢?下面我把自己整理的出错原因和解决办法给phper分享一下,希望有所帮助. 出错原