Runloop是面试中的重点,也是开发过程中对性能优化的点
简介
什么是RunLoop
应用范畴
- 定时器(Timer)、PerformSelector
- GCD Async Main Queue
- 事件响应、手势识别、界面刷新
- 网络请求
- AutoreleasePool
作用
如果没有RunLoop
1 2 3 4
| int main(int argc, const char * argv[]) { NSLog(@"Hello World!"); return 0; }
|
执行完第3行代码后,会即将退出程序
如果有了RunLoop
1 2 3 4 5
| int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
|
程序并不会马上退出,而是保持运行状态
RunLoop的基本作用
- 保持程序的持续运行
- 处理App中的各种事件(比如触摸事件、定时器事件等)
- 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
RunLoop对象
iOS中有2套API来访问和使用RunLoop
- Foundation - NSRunLoop
- Core Foundation - CFRunLoopRef
NSRunLoop
和CFRunLoopRef
都代表着RunLoop对象
NSRunLoop
是基于CFRunLoopRef
的一层OC包装
CFRunLoopRef
是开源的
如何获取Runloop对象
Foundation
- [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
- [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
Core Foundation
- CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
- CFRunLoopGetMain(); // 获得主线程的RunLoop对象
RunLoop的5个关键类
类名 |
描述 |
CFRunLoopRef |
Runloop对象 |
CFRunLoopModeRef |
RunLoop的运行模式 |
CFRunLoopSourceRef |
系统事件,触摸事件,基于Port的线程间通信等 |
CFRunLoopTimerRef |
定时器 |
CFRunLoopObserverRef |
监听器状态 |
类关系
属性 |
描述 |
Source0 |
触摸事件处理performSelector:onThread: |
Source1 |
基于Port的线程间通信系统事件捕捉 |
Timers |
NSTimerperformSelector:withObject:afterDelay: |
Observers |
用于监听RunLoop的状态UI刷新(BeforeWaiting)Autorelease pool(BeforeWaiting) |
CFRunLoopRef
列举CFRunloop对象的关键属性
1 2 3 4 5 6 7 8
| typedef struct __CFRunLoop *CFRunLoopRef; struct __CFRunLoop { pthread_t _pthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; CFMutableSetRef _modes; };
|
属性 |
类型 |
描述 |
_pthread |
线程对象 |
与之对应的线程 |
_commonModes |
集合 |
|
_commonModeItems |
集合 |
|
_currentMode |
CFRunLoopModeRef |
当前运行的模式 |
_modes |
集合<CFRunLoopModeRef> |
所有模式 |
一个RunLoop包含若干个Mode
RunLoop启动时只能选择其中一个Mode,作为_currentMode
CFRunLoopModeRef
列举CFRunLoopModeRef对象的关键属性
1 2 3 4 5 6 7 8
| typedef struct __CFRunLoopMode *CFRunLoopModeRef; struct __CFRunLoopMode { CFStringRef _name; CFMutableSetRef _sources0; CFMutableSetRef _sources1; CFMutableArrayRef _observers; CFMutableArrayRef _timers; };
|
属性 |
类型 |
描述 |
_name |
str |
模式名称 |
_sources0 |
集合<CFRunLoopSourceRef> |
|
_sources1 |
集合<CFRunLoopSourceRef> |
|
_observers |
Array<CFRunLoopObserversRef> |
|
_timers |
Array<CFRunLoopTimerRef> |
|
模式种类
CoreFoundation |
Foundation |
描述 |
kCFRunLoopDefaultMode |
NSDefaultRunLoopMode |
App的默认Mode,通常主线程是在这个Mode下运行 |
UITrackingRunLoopMode |
UITrackingRunLoopMode |
界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响 |
UIInitializationRunLoopMode |
- |
在刚启动 App 时进入的第一个 Mode,启动完成后就不再使用 |
GSEventReceiveRunLoopMode |
- |
接受系统事件的内部 Mode,通常用不到 |
kCFRunLoopCommonModes |
NSRunLoopCommonModes |
这是一个占位用的Mode,不是一种真正的Mode |
每个Mode又包含若干个Source0/Source1/Timer/Observer
不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
CFRunLoopObserverRef
RunLoop的状态
1 2 3 4 5 6 7 8 9 10
| typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), kCFRunLoopBeforeTimers = (1UL << 1), kCFRunLoopBeforeSources = (1UL << 2), kCFRunLoopBeforeWaiting = (1UL << 5), kCFRunLoopAfterWaiting = (1UL << 6), kCFRunLoopExit = (1UL << 7), kCFRunLoopAllActivities = 0x0FFFFFFFU };
|
应用场景
应用一(解决NSTimer在滑动时停止工作的问题)
Q:为什么Timer在页面滚动之后会失效?如何解决
A:默认创建Timer是在kCFRunLoopCommonModes
模式下,需要我们把Timer加入到kCFRunLoopCommonModes
下就可以了;
1 2 3 4 5 6
| NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer *timer) { }];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
|
应用二(性能优化)
Q:在主线程中做复杂运算
A:监听主线程Runloop的运行状态,在Runloop空闲的时候做操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| CFRunLoopObserverRef observerRef = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { switch (activity) { case kCFRunLoopEntry: NSLog(@"即将进入Loop"); break; case kCFRunLoopBeforeTimers: NSLog(@"即将处理timer事件"); break; case kCFRunLoopBeforeSources: NSLog(@"即将处理sources事件"); break; case kCFRunLoopBeforeWaiting: NSLog(@"即将进入休眠"); break; case kCFRunLoopAfterWaiting: NSLog(@"刚从休眠中唤醒"); break; case kCFRunLoopExit: NSLog(@"即将推出Loop"); break; default: break; } }); CFRunLoopAddObserver(CFRunLoopGetCurrent(), observerRef, kCFRunLoopDefaultMode); CFRelease(observerRef)
|
应用三(控制线程生命周期-线程保活)
Q:当子线程中的任务执行完毕之后就被销毁了;那么如果我们需要开启一个子线程,在程序运行过程中永远都存在,那么我们就会面临一个问题,如何让子线程永远活着?
A:常驻线程:给子线程开启一个RunLoop
A:AFNetworking 线程保活,防止线程频繁创建销毁浪费性能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #import <Foundation/Foundation.h>
typedef void(^RCPermenantThreadBlock)(void); @interface RCPermenantThread : NSObject
- (void)executeTaskWithTarget:(id)target action:(SEL)action object:(id)object;
- (void)executeBlock:(RCPermenantThreadBlock)taskBlock;
- (void)stop;
@end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| #import "RCPermenantThread.h"
@interface RCPermenantThread () @property (nonatomic, strong) NSThread *innerThread; @property (nonatomic, assign) BOOL stopped; @end
@implementation RCPermenantThread
- (void)dealloc { [self performSelector:@selector(__stop) onThread:_innerThread withObject:nil waitUntilDone:YES]; }
- (instancetype)init { self = [super init]; if (self) { _stopped = NO; __weak typeof(self)weakSelf = self; _innerThread = [[NSThread alloc] initWithBlock:^{ NSLog(@"%@", [NSThread currentThread]); [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; while (weakSelf && !weakSelf.stopped) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } NSLog(@"RCPermenantThread Runloop over"); }]; [_innerThread start]; } return self; }
#pragma mark - Public
- (void)stop { if (!_innerThread || _stopped) { return; } [self performSelector:@selector(__stop) onThread:_innerThread withObject:nil waitUntilDone:YES]; }
- (void)executeTaskWithTarget:(id)target action:(SEL)action object:(id)object { if (!_innerThread || !target || !action) { return; } [target performSelector:action onThread:_innerThread withObject:object waitUntilDone:NO]; }
- (void)executeBlock:(RCPermenantThreadBlock)taskBlock { if (!_innerThread || !taskBlock) { return; } [self performSelector:@selector(__executeTask:) onThread:_innerThread withObject:taskBlock waitUntilDone:NO]; }
#pragma mark - Private
- (void)__stop { _stopped = YES; CFRunLoopStop(CFRunLoopGetCurrent()); _innerThread = nil; }
- (void)__executeTask:(RCPermenantThreadBlock)taskBlock { if (taskBlock) { taskBlock(); } }
@end
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #import "ViewController.h" #import "RCPermenantThread.h" @interface ViewController ()
@property (nonatomic, strong) RCPermenantThread *thread;
@end @implementation ViewController
- (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; _thread = [[RCPermenantThread alloc] init]; }
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self.thread executeBlock:^{ NSLog(@"执行任务:%s", __func__); }]; }
@end
|
应用四(监控应用卡顿)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| @interface MCLagMonitor() { int timeoutCount; CFRunLoopObserverRef runLoopObserver; @public dispatch_semaphore_t dispatchSemaphore; CFRunLoopActivity runLoopActivity; }
@property (nonatomic, strong) NSTimer *cpuMonitorTimer;
@end
@implementation MCLagMonitor
+ (instancetype)shareInstance { static MCLagMonitor *monitor = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ monitor = [[MCLagMonitor alloc] init]; }); return monitor; }
- (void)startMonitor { if (runLoopObserver) { return; } dispatchSemaphore = dispatch_semaphore_create(0); CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context); CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes); dispatch_async(dispatch_get_global_queue(0, 0), ^{ while (YES) { long semaphoreWait = dispatch_semaphore_wait(self->dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC)); if (semaphoreWait != 0) { if (!self->runLoopObserver) { self->timeoutCount = 0; self->dispatchSemaphore = 0; self->runLoopActivity = 0; return; } if (self->runLoopActivity == kCFRunLoopBeforeSources || self->runLoopActivity == kCFRunLoopAfterWaiting) { if (++ self->timeoutCount < 5) { continue; } else { NSLog(@"卡顿了"); } } } self->timeoutCount = 0; } }); }
- (void)stopMonitor { [self.cpuMonitorTimer invalidate]; if (!runLoopObserver) { return; } CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes); CFRelease(runLoopObserver); runLoopObserver = NULL; }
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { MCLagMonitor *lagMonitor = (__bridge MCLagMonitor*)info; lagMonitor->runLoopActivity = activity; dispatch_semaphore_t semaphore = lagMonitor->dispatchSemaphore; dispatch_semaphore_signal(semaphore); }
|