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

Posted chenxianming

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了探索 RunLoop (RunLoop 自己动手实现RunLoop)相关的知识,希望对你有一定的参考价值。

        既然从上一篇文章中已经知道了RunLoop是怎么运行的。那自己动手实现一个又何尝不可。这文章代码较多,但希望看完这篇文章会对你有帮助。在最后

也会有一些总结性的说明。

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

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

SimpleTimer.h

#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
SimpleTimerPrivate.h

#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

SimpleTimer.m


#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类:

SimpleRunLoop.h

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

- (void)addTimer:(SimpleTimer *)timer;
- (void)runUntilDate:(NSDate *)limitDate;
@end
SimpleRunLoop.m

#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); //two 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)类。

NSThread+SimpleRunLoop.h

#import <Foundation/Foundation.h>
#import "SimpleRunLoop.h"
@interface NSThread (SimpleRunLoop)
+ (SimpleRunLoop *)currentSimpleRunLoop;
@end
NSThread+SimpleRunLoop.m

@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的原理,虽然跟NSRunLoop相差很多,但万变不离其宗,原理是一样的。

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

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

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

  [SimpleRunLoop runUntilDate]创建SimpleRunLoop的子循环,并往SimpleRunLoop加入SimpleTimer事件源,

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

  3、系统的NSRunLoop实现肯定没有那么简单。那么我们这个SimpleRunLoop与NSRunLoop相差些什么呢,我觉有以下这些:
  输入事件源SimpleRunLoop的输入源只能addTimer。而系统的NSRunLoop有Port-Based Sources由内核自动发送,Custom Input Sources需要从其他线程手动发送。这个很关键,如果我们能够做到把这两个事件源投进处理队列,那离NSRunLoop就更近一步了。还有RunLoopMode的切换等等。

这些东西都是要跟底层东西打交道的。

以上是关于探索 RunLoop (RunLoop 自己动手实现RunLoop)的主要内容,如果未能解决你的问题,请参考以下文章

iOS 子线程用runloop保活的一个方案

iOS runloop详解

[New Learn] RunLoop学习-官方译文

RunLoop相关知识的总结

1.RunLoop是什么?

Runloop理解