当前位置: 首页 > 编程笔记 >

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

贺波
2023-03-14
本文向大家介绍iOS如何巧妙解决NSTimer的循环引用详解,包括了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循环引用的几种解决办法的使用技巧和注意事项,需要的朋友参考一下 发生场景 在 Controller B 中有一个 NSTimer 你创建了它,并挂载到 main runloop 然后退出 Controller B 的时候,忘记关掉 timer 了 Controller B 将不会释放,B 与 timer 循环引

  • 本文向大家介绍iOS NSTimer循环引用的办法,包括了iOS NSTimer循环引用的办法的使用技巧和注意事项,需要的朋友参考一下 在当前控制器(ViewController)的view上添加了一个自定义的view(LXFTimerView), LXFTimerView在成功创建出来后添加了定时器NSTimer并加入RunLoop开始工作, 当在当前控制器里将LXFTimerView移除掉后,

  • 本文向大家介绍深拷贝里的循环引用如何解决?相关面试题,主要包含被问及深拷贝里的循环引用如何解决?时的应答技巧和注意事项,需要的朋友参考一下 考察的是如何实现深拷贝问题。深拷贝需要为每一个对象属性创建新的对象,但是如果单纯这样做碰到含有循环引用的对象,就会进入死循环。 这么操作当然是错误的,为了正确进行深拷贝,不出现这种错误,就需要: 遍历原对象每个节点的时候,记录该节点是否被访问过,这样当在遍历过

  • 本文向大家介绍详解在springmvc中解决FastJson循环引用的问题,包括了详解在springmvc中解决FastJson循环引用的问题的使用技巧和注意事项,需要的朋友参考一下 我们先来看一个例子: 输出: 可以看到,这个json如果发到前端是无法使用的,幸好FastJson提供了解决办法,我们来看下,解决办法为禁用循环引用检测,代码如下: 输出如下: 问题是如果我们在spring mvc中

  • 问题内容: 如果我从json.net获得序列化的JSON,如下所示: 我想让淘汰赛在FooList上输出foreach,但是我不确定如何继续,因为$ ref东西可能会抛出东西。 我在想解决方案将以某种方式通过不使用以下方式强制将所有Foos呈现在FooList中: 但这似乎很浪费。 问题答案: 从服务器接收的json对象包含循环引用。在使用对象之前,您必须首先 从对象中删除所有属性,这意味着`$r

  • 本文向大家介绍Mysql如何巧妙的绕过未知字段名详解,包括了Mysql如何巧妙的绕过未知字段名详解的使用技巧和注意事项,需要的朋友参考一下 前言 本文介绍的是DDCTF第五题,绕过未知字段名的技巧,这里拿本机来操作了下,思路很棒也很清晰,分享给大家,下面来看看详细的介绍: 实现思路 题目过滤空格和逗号,空格使用%0a,%0b,%0c,%0d,%a0,或者直接使用括号都可以绕过,逗号使用join绕过