当前位置: 首页 > 编程日记 > 正文

通过NSProxy来解决NSTimer使用不当造成内存泄漏的问题

NSTimer的一般使用:

 1 @interface ViewController : UIViewController
 2 @property (nonatomic, strong) NSTimer *timer;
 3 @end
 4 
 5 @implementation ViewController
 6 - (void)viewDidLoad {
 7     [super viewDidLoad];
 8     [self startTimer];
 9 }
10 
11 - (void)startTimer {
12     self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerTask) userInfo:nil repeats:YES];
13     [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
14 }
15 
16 - (void)timerTask {
17     NSLog(@"do something");
18 }
19 
20 - (void)dealloc {
21     [self.timer invalidate];
22     self.timer = nil;
23 }
24 
25 @end

主要代码如上, 现在问题出来了, 当我们页面移除之后, 控制台依然一直输出 do something, 说明timer依然存在, 那么viewController还存在, 都没有被销毁, 断点dealloc发现根本不进断点.

网上查了一圈,发现如下解释:

为了保证参数的生命周期,NSTimer会对target对象retain一次,做强引用。以保证即便target销毁了,定时器还能正常调用timeEvent。因为定时器要加到RunLoop中,所以RunLoop强引用着NSTimer,一般情况下你的target就是当前的控制器,如果你想让控制器如你所愿的销毁了,首先得销毁NSTimer。不然NSTimer强引用着self,self就无法销毁,从而导致内存泄漏。
timer被schedule的时候,timer会持有target对象,NSRunLoop对象会持有timer。当invalidate被调用时,NSRunLoop对象会释放对timer的持有,timer会释放对target的持有。除此之外,没有途径可以释放timer对target的持有。所以解决内存泄露就必须撤销timer,若不撤销,target对象将永远无法释放。

最简单的解决办法:

  1. 在viewDidDisappear中调用[self.timer invalidate]来结束timer的任务;
  2. 使用block而不用target,这样肯定不会对viewController做retain;

OK, 接下来是要说的重点, 既然有最简单的解决办法, 那就有不那么简单的办法: 做一个中间代理, 用消息转发来找到对应的方法实现, 同时timer和ViewController之间不做相互强引用, 以此来解决内存无法释放的问题, 这样可以实现不在ViewController生命周期中的NSTimer的引用问题.

这个地方我们用一个官方给的中间代理类NSProxy来实现中间代理, 具体代码如下:

@interface TimerWeakProxy : NSProxy@property (nonatomic, weak) id target;+ (instancetype)proxyWithTarget:(id)target;@end@implementation TimerWeakProxy+ (instancetype)proxyWithTarget:(id)target {TimerWeakProxy *proxy = [TimerWeakProxy alloc];proxy.target = target;return proxy;
}- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {return [self.target methodSignatureForSelector:sel];
}- (void)forwardInvocation:(NSInvocation *)invocation {[invocation invokeWithTarget:self.target];
}@end

接下来就是把NSTimer的target换成中间代理:

self.timer = [NSTimer timerWithTimeInterval:1.0 target:[TimerWeakProxy proxyWithTarget:self] selector:@selector(timerTask) userInfo:nil repeats:YES];[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];

至此, 中间代理改造完毕, 其实也没有那么复杂, 重点就是NSProxy这个类, 官方的文档是这样说的:

Typically, a message to a proxy is forwarded to the real object or causes the proxy to load (or transform itself into) the real object. Subclasses of NSProxy can be used to implement transparent distributed messaging (for example, NSDistantObject) or for lazy instantiation of objects that are expensive to create.

NSProxy implements the basic methods required of a root class, including those defined in the NSObject protocol. However, as an abstract class it doesn’t provide an initialization method, and it raises an exception upon receiving any message it doesn’t respond to. A concrete subclass must therefore provide an initialization or creation method and override the forwardInvocation: and methodSignatureForSelector: methods to handle messages that it doesn’t implement itself. A subclass’s implementation of forwardInvocation: should do whatever is needed to process the invocation, such as forwarding the invocation over the network or loading the real object and passing it the invocation. methodSignatureForSelector: is required to provide argument type information for a given message; a subclass’s implementation should be able to determine the argument types for the messages it needs to forward and should construct an NSMethodSignature object accordingly. See the NSDistantObject, NSInvocation, and NSMethodSignature class specifications for more information.

简单来说, 就是说这是一个抽象类, 不提供初始化方法, 需要子类来提供初始化方法, 并且需要重写forwardInvocation: 和 methodSignatureForSelector: 方法来做消息转发.

参考博文: https://www.jianshu.com/p/ee58de47fa5c

https://www.jianshu.com/p/d4589134358a

转载于:https://www.cnblogs.com/GaryZhang/p/11477189.html

相关文章:

Blender 2.9中的真实感三维产品全流程制作学习教程

MP4 |视频:h264,1280720 |音频:AAC,44.1 KHz,2 Ch 语言:英语中英文字幕(根据原英文字幕机译更准确) |时长:41节课(6h 23m) |大小:5.83 GB 含课程文件 使用blender 2.9建模、纹理、光照和渲染真实的吉他 …

SpringBoot中的SFL4J日志

SpringBoot:底层是Spring框架,Spring框架默认使用的是JCL日志的抽象层 SpringBoot选用SLF4J和logback 如何系统地在系统中使用SLF4J 在开发的使用,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象…

web开发性能优化---用户体验篇

怎样从技术角度怎样增强用户体验。都是非常多平台都在做的事情,依据个人实际经验碰到几种体验做下总结。1、降低页面刷新白屏适当使用ajax技术。改善刷新白屏现象。2、信息提醒,邮件、站内信、短信在购物流程、售后流程适当添加信息温馨提醒环节&#xf…

linux命令2--cd和pwd

2019独角兽企业重金招聘Python工程师标准>>> cd是linux中最为常见的一个命令,其作用就是切换到某一个路径下 例子1 到自己的用户目录下 cd ~ 也可以直接cd 例子2 返回进入当前目录之前的目录 cd - 例子3 把上一个命令作为cd的参数命令 cd !$ 参考文档&am…

COALESCE语句解救sql的sum问题

mysqlmybatis有一个sql语句是统计用的 <select id"getNum" resultType"map"> 结果是一个map&#xff0c; select语句的结果是一些sum select sum(t.anum),sum(t.bnum)from tableSum twhere t.id #{id} 调试发现&#xff0c;数据库明明记录已经落入&…

在UE5创造一个多山的松树森林场景学习教程

UE5游戏场景设计制作视频教程 大小解压后&#xff1a;4.37G 1920X1080 mp4 语言&#xff1a;英语中英字幕&#xff08;机译&#xff09;时长&#xff1a;5小时 20分 课程获取&#xff1a;在UE5创造一个多山的松树森林场景学习教程

编写纳新网站后端的相关知识总结

使用HSSFWorkbook导出数据库中的数据 导入Apache POI Maven jar包 <!-- Apache POI --> <dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.6</version> </dependency>在控制层编…

IOSUIcontrol事件

UIKit提供了一组控件&#xff1a;UISwitch开关、UIButton按钮、UISegmentedControl分段控件、UISlider滑块、UITextField文本字段控件、UIPageControl分页控件。 控件是对UIView派生类的实用增强及补充&#xff0c;并可以直接附着于导航栏、表格单元&#xff0c;甚至更大的对象…

Xcode Debugging

程序员日常开发中有大量时间都会花费在 debug 上&#xff0c;从事 iOS 开发不可避免地需要使用 Xcode。这篇博客就主要介绍了 Xcode 中几种能够大幅提升代码调试效率的方式。“If debugging is the process of removing bugs, then programming must be the process of putting…

css小技巧 -- 单标签实现单行文字居中,多行文字居左

可能出现的尺寸场景: 代码如下&#xff1a; <!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv&q…

Unity游戏开发大师班

大小解压后&#xff1a;8.63G 持续时间19h 包含项目文件 1280X720 MP4 语言&#xff1a;英语中英文字幕&#xff08;根据原英文字幕机译更准确&#xff09; Unity游戏开发大师班 信息: 要求 –没有课程要求&#xff0c;展示了开发过程的每一步(包括解释的每一行代码) 这门课…

GC处理的分析

GC处理的深入分析 在Java中针对垃圾收集也提供了多种不同的处理分类 1.引用计数&#xff1a; 一个实例化对象&#xff0c;如果有程序使用了这个引用对象&#xff0c;引用计数加一&#xff0c;当一个对象使用完毕&#xff0c;引用计数减一&#xff0c;当引用计数为0时&#x…

2016.1.20 dubbo启动之后机器ip有问题

环境 100.0.4.222 zookerper 2182 100.0.4.221 dubbo 20871 1.问题 dubbo 对应的机器ip 是202.106.199.37 不是100.0.4.221 2.解决 说明 主机名有问题 修改主机名 使之生效 重启zookeeper服务 和dubbo服务 搞定 转载于:https://www.cnblogs.com/huawei306/p…

在Eclipse或MyEclipse中安装findbugs插件

2019独角兽企业重金招聘Python工程师标准>>> 我们都知道&#xff0c;在Eclipse或MyEclipse中安装插件有两种方式&#xff0c;一种是在线安装&#xff0c;第二种是先下载插件然后在本地安装。 在这里我们先介绍第一种在线安装。 Eclipse上在线安装findbugs(具体步骤如…

Jboss解决只能通过localhost访问而不能使用IP访问项目的问题

jboss的配置文件&#xff0c;如 E:\downloadChrome\wildfly-10.1.0.Final\standalone\configuration\shandalone.xml <interfaces> <interface name"management"> <inet-address value"${jboss.bind.address.management:127.0.0.1}"/>…

一.JDBC概述

第1章&#xff1a;JDBC概述 1.1 数据的持久化 持久化(persistence)&#xff1a;把数据保存到可掉电式存储设备中以供之后使用。大多数情况下&#xff0c;特别是企业级应用&#xff0c;数据持久化意味着将内存中的数据保存到硬盘上加以”固化”&#xff0c;而持久化的实现过程大…

在Blender中创建惊人的低多边形动画

大小解压后&#xff1a;13.5G 持续时间10小时 包括项目文件 1920X1080 MP4 语言&#xff1a;英语中英文字幕&#xff08;根据原英文字幕机译更准确&#xff09; 标题:CGBoost学院–立方体世界在Blender中创建惊人的低多边形动画 信息: 创建漂亮的3D动画&#xff0c;就像玩乐高…

开发Eclipse自定义控件

摘自&#xff1a;http://www.ibm.com/developerworks/cn/opensource/os-eclipcntl/ 我们在开发自定义控件时主要考虑以下问题&#xff1a; 1、 自定义控件的绘制&#xff1a;通常我们需要自己对控件的形状或图案进行绘制&#xff1b; 2、 控件对键盘事件的响应&#xff1a;当焦…

小米手机无法连接eclipse调试案例

按照参考博客2的方法 小米2S手机在Eclipse真机调试时&#xff0c;设备选择列表无法显示手机&#xff0c;DDMS也连接不上设备&#xff0c;解决步骤&#xff1a; 1.打开手机设置中开发者选项 - USB调试开启&#xff1b; 2.保证小米2S手机Windows下设备驱动已安装&#xff0c;可…

手机号码格式验证和 FASTDFS 工具类

常见大陆和香港号码格式验证 import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException;public class PhoneFormatCheckUtils {// 大陆号码或香港号码均可public static boolean isPhoneLegal(String str) throws Patt…

Maya摄像机动画技能学习教程

MP4 |视频:h264&#xff0c;1280720 |音频:AAC&#xff0c;44.1 KHz&#xff0c;2 Ch 语言&#xff1a;英语中英文字幕&#xff08;根据原英文字幕机译更准确&#xff09; |时长:4h 38m |大小解压后:1.65 GB 含课程文件 Maya的相机工具是所有3D包中最好的。在本课程中&#x…

前端解决跨域问题的8种方案(最新最全)

.同源策略如下&#xff1a; URL说明是否允许通信http://www.a.com/a.jshttp://www.a.com/b.js同一域名下允许http://www.a.com/lab/a.jshttp://www.a.com/script/b.js同一域名下不同文件夹允许http://www.a.com:8000/a.jshttp://www.a.com/b.js同一域名&#xff0c;不同端口不允…

IDEA中修改文件的默认创建方式

今天很手欠地在创建application-dev.yml文件的时候将application-dev这个名字的文件成功变成了一个我不认识的文件打开方式了&#xff0c;如下图&#xff1a; 可以看到&#xff0c;这个application-dev.yml文件从原来的SpringBoot的配置文件变成一个我们不认识的文件格式… 补…

ArcEngine 直连连接SDE

关键代码IPropertySet pPropertySet new PropertySetClass();pPropertySet.SetProperty("SERVER", sde.IpAdress);pPropertySet.SetProperty("INSTANCE", "sde:oracle11g$//" sde.IpAdress "/" sde.Instance); pPrope…

Dubbo和Spring Cloud微服务架构对比

一、为什么要使用微服务&#xff1f; 微服务提倡将单一应用程序划分成一组小的服务&#xff0c;服务之间互相协调、互相配合。 今天我们来了解下业内主要的微服务框架&#xff1a;Dubbo 和 Spring Cloud 微服务主要的优势 降低复杂度将原来耦合在一起的复杂业务拆分为单个服务,…

Unity 3D为策略游戏创建地图学习教程

MP4 |视频:h264&#xff0c;1280720 |音频:AAC&#xff0c;44.1 KHz&#xff0c;2 Ch 语言&#xff1a;英语中英文字幕&#xff08;根据原英文字幕机译更准确&#xff09; |时长:30节课(7h 42m) |大小:5 GB 含项目文件 沿着曲线、高程图和数据结构移动对象 你会学到: Unity&…

Lambda表达式(简单解析)

Lambda表达式 Lambda表达式其实是JDK8的一个新特性。所谓Lambda表达式指的是应用在单一抽象方法接口环境下的一种简化定义形式&#xff0c;可以用于解决匿名内部类中定义复杂的问题。 我们先来看一段代码&#xff0c;如下&#xff1a; interface Customer{public void print(…

YTU 2899: D-险恶逃生 I

2899: D-险恶逃生 I 时间限制: 1 Sec 内存限制: 128 MB提交: 130 解决: 55题目描述 Koha被邪恶的巫师困在一个m*n的矩阵当中&#xff0c;他被放在了矩阵的最左上角坐标(0,0)处&#xff0c;而唯一的出口在矩阵最右下角坐标(m-1,n-1)处&#xff0c;他每次只能向右或者向下移动一…

2021-2027年中国托育服务行业市场研究及前瞻分析报告

【报告类型】产业研究 【报告价格】4500起 【出版时间】即时更新&#xff08;交付时间约3个工作日&#xff09; 【发布机构】智研瞻产业研究院 【报告格式】PDF版 本报告介绍了中国托育服务行业市场行业相关概述、中国托育服务行业市场行业运行环境、分析了中国托育服务行…

2021全国高校计算机能力挑战赛(初赛)Java试题一

从大于等于N 的正整数里找到一个最小的数M,使之满足:M和M的逆序数(如1230的逆序数为321)的和为一个[100000,200000]区间内的值。 import java.util.Scanner; public class Main {public static void main(String[] args){Scanner scanner new Scanner(System.in);int N scann…