文档章节

iOS容易造成循环引用的三种场景NSTimer以及对应的使用方法(一)

super_co
 super_co
发布于 2015/06/12 16:32
字数 2101
阅读 20
收藏 0
点赞 0
评论 0

对,这就是NSTimer ,

1.NSTimer会retain你添加调用方法的对象

        2.NSTimer是要加到runloop中才会起作用

3.NSTimer会并不是准确的按照你指定的时间触发的

        4.NSTimer就算添加到runloop了也不一定会按照你想象中的那样执行

NSTimer和它调用的函数对象间到底发生了什么

  那么timer会在未来的某个时刻执行一次或者多次我们指定的方法,这也就牵扯出一个问题,如何保证timer在未来的某个时刻触发指定事件的时候,我们指定的方法是有效的呢?

 解决方法很简单,只要将指定给timer的方法的接收者retain一份就搞定了,实际上系统也是这样做的。不管是重复性的timer还是一次性的timer都会对它的方法的接收者进行retain,这两种timer的区别在于“一次性的timer在完成调用以后会自动将自己invalidate,而重复的timer则将永生,直到你显示的invalidate它为止”。

     一方面,NSTimer经常会被作为某个类的成员变量,而NSTimer初始化时要指定self为target,容易造成循环引用。 另一方面,若 timer一直处于validate的状态,则其引用计数将始终大于0。

LIST:

#import <Foundation/Foundation.h>

#import <UIKit/UIKit.h>

@interface TsetClass : NSObject

- (void)clearTimer;

@end


//  TsetClass.m

//  TestRuntime

//

//  Created by 58 on 15/6/1.

//  Copyright (c) 2015年 58. All rights reserved.

//

#import "TsetClass.h"


@interface TsetClass ()

{

    

    NSTimer *_myTimer;

      

}

@end



@implementation TsetClass

   - (id)init

   {

        if (self = [super init]) {

       _myTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTimer:)

                                                                    userInfo:nil repeats:YES];

            }

         return  self;

      }


- (void)handleTimer:(id)sender{

   NSLog(@"%@ say: testTimer!", [self class]);

}

   - (void)clearTimer

   {

        [_myTimer invalidate];

       _myTimer = nil;

     }

- (void)dealloc

 {

         [self clearTimer];

         NSLog(@"[Friend class] is dealloced");

      }

@end

在类外部初始化一个TsetClass对象,并延迟5秒后将friend释放(外部运行在非arc环境下)

         TsetClass *f = [[TsetClass alloc] init];         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
             [f release];         });

     我们所期待的结果是,初始化5秒后,f对象被release,f的dealloc方法被调用,在dealloc里面timer失效,对象被析构。但结果却是如此:

2015-06-02 14:04:08.229 TestRuntime[8883:135129] TsetClass say: testTimer!

2015-06-02 14:04:09.231 TestRuntime[8883:135129] TsetClass say: testTimer!

2015-06-02 14:04:10.227 TestRuntime[8883:135129] TsetClass say: testTimer!

2015-06-02 14:04:11.228 TestRuntime[8883:135129] TsetClass say: testTimer!

2015-06-02 14:04:12.231 TestRuntime[8883:135129] TsetClass say: testTimer!

2015-06-02 14:04:13.227 TestRuntime[8883:135129] TsetClass say: testTimer!

2015-06-02 14:04:14.229 TestRuntime[8883:135129] TsetClass say: testTimer!

2015-06-02 14:04:15.231 TestRuntime[8883:135129] TsetClass say: testTimer!

2015-06-02 14:04:16.229 TestRuntime[8883:135129] TsetClass say: testTimer!

2015-06-02 14:04:17.231 TestRuntime[8883:135129] TsetClass say: testTimer!

2015-06-02 14:04:18.230 Te...

这是为什么呢?主要是因为从timer的角度,timer认为调用方(Friend对象)被析构时会进入dealloc,在dealloc可以顺便将timer的计时停掉并且释放内存;但是从Friend的角度,他认为timer不停止计时不析构,那我永远没机会进入dealloc。循环引用,互相等待,子子孙孙无穷尽也。问题的症结在于-(void)cleanTimer函数的调用时机不对,显然不能想当然地放在调用者的dealloc中。一个比较好的解决方法是开放这个函数,让Friend的调用者显式地调用来清理现场。如下:


TsetClass *f = [[TsetClass alloc] init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    [f clearTimer];
    [f release];
});

到此timer的循环引用就说完了!!!

综上: timer都会对它的target进行retain,我们需要小心对待这个target的生命周期问题,尤其是重复性的timer。(NSTimer初始化后,self的retainCount加1。 那么,我们需要在释放这个类之前,执行[timer invalidate];否则,不会执行该类的dealloc方法。)

那么Timer 的使用你到底会了吗

NSTimer不是一个实时系统,因此不管是一次性的还是周期性的timer的实际触发事件的时间可能都会跟我们预想的会有出入。差距的大小跟当前我们程序的执行情况有关系,比如可能程序是多线程的,而你的timer只是添加在某一个线程的runloop的某一种指定的runloopmode中,由于多线程通常都是分时执行的,而且每次执行的mode也可能随着实际情况发生变化。假设你添加了一个timer指定2秒后触发某一个事件,但是签好那个时候当前线程在执行一个连续运算(例如大数据块的处理等),这个时候timer就会延迟到该连续运算执行完以后才会执行。重复性的timer遇到这种情况,如果延迟超过了一个周期,则会和后面的触发进行合并,即在一个周期内只会触发一次。但是不管该timer的触发时间延迟的有多离谱,他后面的timer的触发时间总是倍数于第一次添加timer的间隙。

当线程空闲的时候timer的消息触发还是比较准确的


NSTimer为什么要添加到RunLoop中才会有作用

  前面的例子中我们使用的是一种便利方法,它其实是做了两件事:首先创建一个timer,然后将该timer添加到当前runloop的default mode中。也就是这个便利方法给我们造成了只要创建了timer就可以生效的错觉,我们当然可以自己创建timer,然后手动的把它添加到指定runloop的指定mode中去。

  NSTimer其实也是一种资源,如果看过多线程变成指引文档的话,我们会发现所有的source如果要起作用,就得加到runloop中去。同理timer这种资源要想起作用,那肯定也需要加到runloop中才会又效喽。如果一个runloop里面不包含任何资源的话,运行该runloop时会立马退出。你可能会说那我们APP的主线程的runloop我们没有往其中添加任何资源,为什么它还好好的运行。我们不添加,不代表框架没有添加,如果有兴趣的话你可以打印一下main thread的runloop,你会发现有很多资源。

NSTimer加到了RunLoop中但迟迟的不触发事件

1、runloop是否运行

  每一个线程都有它自己的runloop,程序的主线程会自动的使runloop生效,但对于我们自己新建的线程,它的runloop是不会自己运行起来,当我们需要使用它的runloop时,就得自己启动。

  那么如果我们把一个timer添加到了非主线的runloop中,它不会按照预期按时触发

2、mode是否正确

  我们前面自己动手添加runloop的时候,可以看到有一个参数runloopMode,这个参数是干嘛的呢?

  前面提到了要想timer生效,我们就得把它添加到指定runloop的指定mode中去,通常是主线程的defalut mode。但有时我们这样做了,却仍然发现timer还是没有触发事件。这是为什么呢?

  这是因为timer添加的时候,我们需要指定一个mode,因为同一线程的runloop在运行的时候,任意时刻只能处于一种mode。所以只能当程序处于这种mode的时候,timer才能得到触发事件的机会。

  举个不恰当的例子,我们说兄弟几个分别代表runloop的mode,timer代表他们自己的才水桶,然后一群人去排队打水,只有一个水龙头,那么同一时刻,肯定只能有一个人处于接水的状态。也就是说你虽然给了老二一个桶,但是还没轮到它,那么你就得等,只有轮到他的时候你的水桶才能碰上用场


综上: 要让timer生效,必须保证该线程的runloop已启动,而且其运行的runloopmode也要匹配。


© 著作权归作者所有

共有 人打赏支持
super_co
粉丝 1
博文 10
码字总数 7205
作品 1
东城
高级程序员
iOS查看屏幕帧数工具--YYFPSLabel

学习 YYKit 代码时,发现 ibireme 在项目里加入的一个查看当前屏幕帧数的小工具,效果如下: 挺实用,实现方法也很简单,但是思路特别棒。 这里是Demo: YYFPSLabel 这里我把这个小工具从 中...

yehot
2016/04/05
0
0
说说 NSTimer 的新 API

本文是我首发在iOS知识小集团队的,欢迎关注微博话题#ios知识小集#。 在以往的 iOS 版本中,我们为了避免 NSTimer 的循环引用问题,一个比较常用的解决办法是为 NSTimer 添加一个 category,...

halohily
05/03
0
0
iOS天气动画、高仿QQ菜单、放京东APP、高仿微信、推送消息等源码

iOS精选源码 TYCyclePagerView iOS上的一个无限循环轮播图组件(http://www.code4app.com/thread-14507-1-1.html) iOS高仿微信完整项目源码(http://www.code4app.com/thread-14695-1-1.html)......

sunnyaigd
06/12
0
0
iOS中结合代码看内存管理(一)

阅读本文前,建议移步先去了解下内存管理相关知识。 1: iOS内存管理机制(百度goole大法可以获得很多推荐)。 2: iOS中的动态内存分配 3: 堆栈的原理:堆栈 百科 1:自动释放池的常见问题: ...

Nlinger
2017/06/07
0
0
iOS中的CADisplayLink定时器

iOS中的CADisplayLink定时器 说到定时器,在iOS中最常用的为NSTimer类,其实CADisplayLink类在某些场景下使用,要比NSTimer类更加适合。首先CADisplayLink也是一种定时器,并且其和屏幕的刷新...

珲少
07/05
0
0
【IOS视频教学】三个月学会IOS开发

一、windows系统下安装虚拟机-mac系统-视频教程-安装件全套下载:http://www.wyzc.com/forum/56212.html 二、史上最佳0基础Swift语言视频教程下载链接:http://www.wyzc.com/forum/56744.htm...

马洪伟
2014/12/25
0
0
iOS源码补完计划--AFNetworking 3.1.0源码研读

参拜一下AFNetworking的源码。 第四篇源码、暂时来看也是iOS方向的最后一篇、撸完准备趁着热乎撸一撸网络协议。 目录 准备工作 功能模块 AFURLSessionManager/AFHTTPSessionManager AFNetwo...

kirito_song
05/25
0
0
Dhar/YTTInjectedContentKit

YTTInjectedContentKit iOS壳版本场景下的批量修改类名、属性名、插入混淆代码、修改项目名称的shell脚本 具体的实现和使用方法请参考我的博客文章: iOS使用shell脚本注入混淆内容 iOS使用S...

Dhar
05/04
0
0
iOS逆向工程- 学习整理(工具详解)

前言 一、逆向工程的要求 具备丰富的 iOS 开发经验 最好能非常熟悉 iOS 设备的硬件构成,iOS 系统的运行原理。 拿到任意一个 App 之后能够大致推断出它的项目规模和使用的技术,比如它的MVC模...

_小迷糊
05/11
0
0
我看完227篇文章,总结出一份Flutter入门教程

2018年6月21日,Google在GTMC大会上发布了 Flutter preview 1。这标志着 Flutter 发展已经进入到一个新阶段,即将迎来 1.0 的稳定版本。 本文致力于整理Flutter的入门的文章。作为自己学习 ...

掘金官方
07/04
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

实现异步有哪些方法

有哪些方法可以实现异步呢? 方式一:java 线程池 示例: @Test public final void test_ThreadPool() throws InterruptedException { ScheduledThreadPoolExecutor scheduledThre......

黄威
31分钟前
0
0
linux服务器修改mtu值优化cpu

一、jumbo frames 相关 1、什么是jumbo frames Jumbo frames 是指比标准Ethernet Frames长的frame,即比1518/1522 bit大的frames,Jumbo frame的大小是每个设备厂商规定的,不属于IEEE标准;...

六库科技
今天
0
0
牛客网刷题

1. 二维数组中的查找(难度:易) 题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入...

大不了敲一辈子代码
今天
0
0
linux系统的任务计划、服务管理

linux任务计划cron 在linux下,有时候要在我们不在的时候执行一项命令,或启动一个脚本,可以使用任务计划cron功能。 任务计划要用crontab命令完成 选项: -u 指定某个用户,不加-u表示当前用...

黄昏残影
昨天
0
0
设计模式:单例模式

单例模式的定义是确保某个类在任何情况下都只有一个实例,并且需要提供一个全局的访问点供调用者访问该实例的一种模式。 实现以上模式基于以下必须遵守的两点: 1.构造方法私有化 2.提供一个...

人觉非常君
昨天
0
0
《Linux Perf Master》Edition 0.4 发布

在线阅读:https://riboseyim.gitbook.io/perf 在线阅读:https://www.gitbook.com/book/riboseyim/linux-perf-master/details 百度网盘【pdf、mobi、ePub】:https://pan.baidu.com/s/1C20T......

RiboseYim
昨天
1
0
conda 换源

https://mirrors.tuna.tsinghua.edu.cn/help/anaconda/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/conda config --add channels https://mir......

阿豪boy
昨天
1
0
Confluence 6 安装补丁类文件

Atlassian 支持或者 Atlassian 缺陷修复小组可能针对有一些关键问题会提供补丁来解决这些问题,但是这些问题还没有放到下一个更新版本中。这些问题将会使用 Class 类文件同时在官方 Jira bug...

honeymose
昨天
0
0
非常实用的IDEA插件之总结

1、Alibaba Java Coding Guidelines 经过247天的持续研发,阿里巴巴于10月14日在杭州云栖大会上,正式发布众所期待的《阿里巴巴Java开发规约》扫描插件!该插件由阿里巴巴P3C项目组研发。P3C...

Gibbons
昨天
1
0
Tomcat介绍,安装jdk,安装tomcat,配置Tomcat监听80端口

Tomcat介绍 Tomcat是Apache软件基金会(Apache Software Foundation)的Jakarta项目中的一个核心项目,由Apache、Sun和其他一些公司及个人共同开发而成。 java程序写的网站用tomcat+jdk来运行...

TaoXu
昨天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部