探索 RunLoop (二)(RunLoop 自己动手实现RunLoop)

既然从上一篇文章中已经知道了RunLoop是怎么运行的。那自己动手实现一个又何尝不可。这文章代码较多,仔细把代码看懂会有收获。在最后

也会有一些说明。

本文中所用到的demo为在我的gitHub上的SimpleRunLoop

   首先RunLoop那一定要有事件输入源。创建一个定时输入源的类SimpleTimer:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface SimpleTimer : NSObject
+ (SimpleTimer *)scheduledTimerWithTimerInterval:(CGFloat)interal target:(id)target selector:(SEL)selector repeat:(BOOL)repeat;
@end
#import <Foundation/Foundation.h>
#import "SimpleTimer.h"
@interface SimpleTimer ()
@property (nonatomic, strong) id target;
@property (nonatomic, assign) SEL action;
@property (nonatomic, assign) CFAbsoluteTime lasttime;
@property (nonatomic, assign) CGFloat interval;
@property (nonatomic, assign) BOOL isRepeat;
- (void)invoke;
@end
#import "SimpleTimer.h"
#import "SimpleTimerPrivate.h"

@implementation SimpleTimer

+ (SimpleTimer *)scheduledTimerWithTimerInterval:(CGFloat)interal target:(id)target selector:(SEL)selector repeat:(BOOL)repeat;
{
    SimpleTimer *timer = [[SimpleTimer alloc] init];
    timer.target = target;
    timer.action = selector;
    timer.interval = interal;
    timer.lasttime = CFAbsoluteTimeGetCurrent();
    timer.isRepeat = repeat;
    return timer;
}

- (void)invoke
{
    //remove the warning
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    if ([self.target respondsToSelector:self.action])
    {
        [self.target performSelector:self.action withObject:nil];
    }
#pragma clang diagnostic pop
}
@end


接下来就是收到事件时进行调用的RunLoop,SimpleRunLoop类:

#import <Foundation/Foundation.h>
@class SimpleTimer;
@interface SimpleRunLoop : NSObject

- (void)addTimer:(SimpleTimer *)timer;
- (void)runUntilDate:(NSDate *)limitDate;
@end
#import "SimpleRunLoop.h"
#import "SimpleTimerPrivate.h"

@interface SimpleRunLoop ()
{
    NSMutableArray<SimpleTimer *> *_timerQueue;
}
@end

@implementation SimpleRunLoop

- (id)init
{
    self = [super init];

    if (self)
    {
        _timerQueue = [NSMutableArray<SimpleTimer *> array];
    }
    return self;
}

- (void)runUntilDate:(NSDate *)limitDate
{
    BOOL finish = NO;
    while (!finish)
    {
        usleep(2 * 1000); //tow second
        [self executeOnce];
        NSDate *date = [NSDate date];
        if ([date compare:limitDate] == NSOrderedDescending)
        {
            finish = YES;
        }
    }
}

- (void)addTimer:(SimpleTimer *)timer
{
    [_timerQueue addObject:timer];
}

- (void)executeOnce
{
    CFAbsoluteTime currentTime = CFAbsoluteTimeGetCurrent();
    NSMutableArray<SimpleTimer *> *tempToDeleteArray = [NSMutableArray<SimpleTimer *> array];
    NSMutableArray<SimpleTimer *> *enumArray = [_timerQueue copy];
    for (SimpleTimer *timer in enumArray)
    {
        if (currentTime - timer.lasttime >= timer.interval)
        {
            if (timer.isRepeat)
            {
                timer.lasttime = currentTime;
            }
            else
            {
                [tempToDeleteArray addObject:timer];
            }
            [timer invoke];
        }
    }

    [_timerQueue removeObjectsInArray:tempToDeleteArray];
}
@end


再想一想,NSRunLoop每个线程只会有一个,那么要实现这个,我就加了个NSThread(SimpleRunLoop)类。

#import <Foundation/Foundation.h>
#import "SimpleRunLoop.h"
@interface NSThread (SimpleRunLoop)
+ (SimpleRunLoop *)currentSimpleRunLoop;
@end
@implementation NSThread (SimpleRunLoop)
+ (SimpleRunLoop *)currentSimpleRunLoop;
{
    SimpleRunLoop *simpleRunLoop = nil;
    NSThread *currentThread = [NSThread currentThread];
    static const void *kSimpleHashKey = &kSimpleHashKey;
    simpleRunLoop = objc_getAssociatedObject(currentThread, kSimpleHashKey);

    if (!simpleRunLoop)
    {
        simpleRunLoop = [[SimpleRunLoop alloc] init];
        objc_setAssociatedObject(currentThread, kSimpleHashKey, simpleRunLoop, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

    return simpleRunLoop;
}
@end


贴了这么多代码,总要讲怎么调用吧,以下就是使用的方式,想知道打印出什么把demo下下来运行一下就知道了。

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.

    NSLog(@"viewDidLoad begin");

    //create a input source
    SimpleTimer *timer = [SimpleTimer scheduledTimerWithTimerInterval:2 target:self selector:@selector(timerFire:) repeat:YES];

    //add input source to RunLoop
    SimpleRunLoop *simpleRunLoop = [NSThread currentSimpleRunLoop];
    [simpleRunLoop addTimer:timer];

    //begin the runloop
    [simpleRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]; //run 10 second

    NSLog(@"viewDidLoad end");
}

- (void)timerFire:(NSTimer *)timer
{
    NSLog(@"timerFire begin");
    NSLog(@"timerFire end");
}


这样就实现了我们自己的runloop了。

说明:1、做这个SimpleRunLoop只是为了让大家更清晰的了解RunLoop的原理,并不会投入实际使用。

   2、SimpleRunLoop中的executeOnce函数中一定要把_timerQueue 拷贝到enumArry,因为在

   遍历过程中是不能对数组的元数进行修改。这样如果在ViewController的timerFire中还可以继续新建

  SimpleTimer事件源添加到队列。或者ViewController的timerFire中继续调用

  [SimpleRunLoop runUntilDate]创建SimpleRunLoop的子循环。此时如果往SimpleRunLoop加上SimpleTimer事件源,

  事件触发的调用就会在这个子循环里被调用。就是上一篇文章中NSRunLoop的行为一样。

  3、系统的NSRunLoop实现肯定没有那么简单。那么我们这个SimpleRunLoop与NSRunLoop相差些什么呢,我觉有以下这些:
  输入事件源SimpleRunLoop的输入源只能addTimer。而系统的有Port-Based Sources由内核自动发送,Custom Input Sources需要从其他线程手动发送。这个很关键,如果我们能够做到这两个其实也就可以做个真正的NSRunLoop。这可以继续深入研究。

时间: 2024-12-28 15:21:36

探索 RunLoop (二)(RunLoop 自己动手实现RunLoop)的相关文章

SharePoint 2013开发入门探索(二)- 列表操作

我们如何用代码对SharePoint列表做些例如增删改查的操作呢?如果您的程序可以部署到服务器上,就可以使用 服务器对象模型,因为服务器对象模型提供的功能最多,限制最少:否则可能要选择客户对象模型等其他方式,这可能会遇到一些功能限制:另外还有一些其他的访问方式,例如Web服务等.如何在 SharePoint 2013 中选择正确的 API 集请参考链接 http://msdn.microsoft.com/zh-cn/library/jj164060.aspx. 我们首先研究下服务器对象模型.使用

容器学习(二):动手模拟AOP

简单来说,Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架.上文已经介绍模拟IoC实现,这篇文章来动手模拟AOP. AOP简述 面向对象强调"一切皆是对象",是对真实世界的模拟.然而面向对象也并非完美无缺的,它更注重于对象层次结构方面的东西,对于如何更好的管理对象行为内部结构,还存在着些许不足.那么我们如何使这个问题的得到更完美的解决呢?答案就是AOP. AOP:Aspect-Oriented Programming.AOP是OOP的补充,是GOF的延续.我们

COMET探索系列二【Ajax轮询复用模型】

COMET探索系列二[Ajax轮询复用模型] 写在前面:Ajax轮询相信大家都信手拈来在用,可是有这么一个问题,如果一个网站中同时有好多个地方需要用到这种轮询呢?就拿我们网站来说,有一个未读消息数提醒.还有一个时实时加载最新说说.昨天又加了一个全网喊话,以后还会要有类似功能添加是肯定的,难道要为每个功能都创建一个独立的轮询?要知道轮询请求中有大半是无用,会对服务器资源和宽带造成巨大的浪费.因此在页面中每增加一个轮询点,对服务器的压力及宽带浪费都将成倍的增长.再考虑一个情况,如果当前网页中需要的不

Android深度探索(二)

第五章    搭建S3C6410开发板的测试环境 本章的学习,我对s3c6410开发板的测试环境有了一定的认识,并掌握了以下知识:一.对于s3c6410这款开发板,它是一款低功耗.高性价比的处理器,它是基于ARM11的内核.二.不同开发板的区别主要在烧录嵌入式系统的方式上.三.安装串口调试工具的步骤:minicom的步骤1.检测当前环境是否支持usb转串口# lsmod | grep usbserial2.安装minicom# apt-get install minicom3.配置minicom

Android插件化探索(二)资源加载

前情提要 在探索资源加载方式之前,我们先来看看上一篇中没细讲的东西.还没看过的建议先看上一篇Android插件化探索(一)类加载器DexClassLoader. PathClassLoader和DexClassLoader的区别 DexClassLoader的源码如下: public class DexClassLoader extends BaseDexClassLoader { //支持从任何地方的apk/jar/dex中读取 public DexClassLoader(String dex

scrapy研究探索(二)——爬w3school.com.cn

下午被一个问题困扰了好一阵.终于使用还有一种方式解决. 開始教程二.关于Scrapy安装.介绍等请移步至教程(一)(http://blog.csdn.net/u012150179/article/details/32343635). 在開始之前如果你已经安装成功一切所需,整怀着一腔热血想要抓取某站点. 一起来have a try. 1. 前期基础准备. Oh,不能在准备了,直接来. (1) 创建项目. 输入: scapy startproject w3school 以上创建项目w3school.

关于解决乱码问题的一点探索之二(涉及Unicode(utf-16)和GBK)

    在上篇日志中(链接),我们讨论了utf-8编码和GBK编码之间转化的乱码问题,这一篇我们讨论Unicode(utf-16编码方式)与GBK编码之间转换的乱码问题.     在Windows系统自带的记事本中,我们按照图中所示使用Unicode编码保存.     在Visual Studio 2005中,单击"文件|高级保存选项"中选择Unicode-代码页1200. 文件中只有乱码与ASCII码     按照上一篇日志中的方法,我们使用WinHex软件查看文件的16进制数据,如

FireDAC探索 (二)

又花时间试了试FireDAC,本想找到一些办法,让FireDAC取数据能和DBX样快,最终还是失败了,DBX实现是太快了,3472第记录(110个字段的表),0毫秒就抓过来了, FireDAC最快也要将近20毫秒.不过FireDAC已经把数据抓到TFDDatSTable中,知道记录条数了,(比DBX要强,DBX的DBXReader是不知道记录数的) 如果只是让FDCommand执行SQL后,不Feach到TFDDatSTable, 那么也是0毫秒(但读取不了数据的). 除此之外, FDMemTa

泛型编程深入探索之二,模板递归与可变参数模版

以构建一个n纬网格为例,讲述模板递归. 首先是一个简单的一纬网格的实现,这个网格实现了规定长度的网格的实例化,并且能够在不同大小的网格类中自由的转型(通过模版嵌套的cast_ctr) (使用到的技术,非类型参数模版,模版嵌套,类模版特例化,模版友元函数) #include <cassert> #include <iostream> using namespace std; template <typename T,int LENGTH> class grid; temp