既然从上一篇文章中已经知道了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的切换等等。
这些东西都是要跟底层东西打交道的。