RunLoop学习笔记,从CF层面了解由于CFRunLoopMode机制iOS程序ScrollView的滑动为何如此平滑的原因。还有介绍AFNetworking如何单独发起一个global threa
2015-08-06 16:39
831 查看
RunLoop学习笔记,从CF层面了解由于CFRunLoopMode机制iOS程序ScrollView的滑动为何如此平滑的原因。还有介绍AFNetworking如何单独发起一个global thread内置runloop达到不占用主线程又不耗CPU资源的。
简介
简单的说run loop是事件驱动的一个大循环,如下代码所示int main(int argc, char * argv[]) { //程序一直运行状态 while (AppIsRunning) { //睡眠状态,等待唤醒事件 id whoWakesMe = SleepForWakingUp(); //得到唤醒事件 id event = GetEvent(whoWakesMe); //开始处理事件 HandleEvent(event); } return 0; }
Cocoa会涉及到Run
Loops的
系统级:GCD,mach kernel,block,pthread应用层:NSTimer,UIEvent,Autorelease,NSObject(NSDelayedPerforming),NSObject(NSThreadPerformAddition),CADisplayLink,CATransition,CAAnimation,dispatch_get_main_queue()(GCD中dispatch到main queue的block会被dispatch到main RunLoop执行),NSPort,NSURLConnection,AFNetworking(这个第三方网络请求框架使用在开启新线程中添加自己的run
loop监听事件)
在Main
thread堆栈中所处位置
堆栈最底层是start(dyld),往上依次是main,UIApplication(main.m) -> GSEventRunModal(Graphic Services) -> RunLoop(包含CFRunLoopRunSpecific,CFRunLoopRun,__CFRunLoopDoSouces0,__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION)-> Handle Touch Event
RunLoop原理
CFRunLoop开源代码:http://opensource.apple.com/source/CF/CF-855.17/
执行顺序的伪代码
SetupThisRunLoopRunTimeoutTimer(); // by GCD timer do { __CFRunLoopDoObservers(kCFRunLoopBeforeTimers); __CFRunLoopDoObservers(kCFRunLoopBeforeSources); __CFRunLoopDoBlocks(); __CFRunLoopDoSource0(); CheckIfExistMessagesInMainDispatchQueue(); // GCD __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting); var wakeUpPort = SleepAndWaitForWakingUpPorts(); // mach_msg_trap // Zzz... // Received mach_msg, wake up __CFRunLoopDoObservers(kCFRunLoopAfterWaiting); // Handle msgs if (wakeUpPort == timerPort) { __CFRunLoopDoTimers(); } else if (wakeUpPort == mainDispatchQueuePort) { // GCD __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() } else { __CFRunLoopDoSource1(); } __CFRunLoopDoBlocks(); } while (!stop && !timeout);
构成
Thread包含一个CFRunLoop,一个CFRunLoop包含一种CFRunLoopMode,mode包含CFRunLoopSource,CFRunLoopTimer和CFRunLoopObserver。
CFRunLoopMode
RunLoop只能运行在一种mode下,如果要换mode当前的loop也需要停下重启成新的。利用这个机制,ScrollView过程中NSDefaultRunLoopMode的mode会切换UITrackingRunLoopMode来保证ScrollView的流畅滑动不受只能在NSDefaultRunLoopMode时处理的事件影响滑动。同时mode还是可定制的。NSDefaultRunLoopMode:默认,空闲状态
UITrackingRunLoopMode:ScrollView滑动时
UIInitializationRunLoopMode:启动时
NSRunLoopCommonModes:Mode集合 Timer计时会被scrollView的滑动影响的问题可以通过将timer添加到NSRunLoopCommonModes来解决
//将timer添加到NSDefaultRunLoopMode中 [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTick:) userInfo:nil repeats:YES]; //然后再添加到NSRunLoopCommonModes里 NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerTick:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
f35b
CFRunLoopTimer
NSTimer是对RunLoopTimer的封装+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo; + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo; - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes; + (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel; - (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;
CFRunLoopSource
source0:处理如UIEvent,CFSocket这样的事件source1:Mach port驱动,CFMachport,CFMessagePort
CFRunLoopObserver
Cocoa框架中很多机制比如CAAnimation等都是由RunLoopObserver触发的。observer到当前状态的变化进行通知。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 };
使用RunLoop的案例
AFNetworking
使用NSOperation+NSURLConnection并发模型都会面临NSURLConnection下载完成前线程退出导致NSOperation对象接收不到回调的问题。AFNetWorking解决这个问题的方法是按照官方的guidhttps://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html#//apple_ref/occ/instm/NSURLConnection/initWithRequest:delegate:startImmediately:上写的NSURLConnection的delegate方法需要在connection发起的线程runloop中调用,于是AFNetWorking直接借鉴了Apple自己的一个Demohttps://developer.apple.com/LIBRARY/IOS/samplecode/MVCNetworking/Introduction/Intro.html的实现方法单独起一个globalthread,内置一个runloop,所有的connection都由这个runloop发起,回调也是它接收,不占用主线程,也不耗CPU资源。
+ (void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; } } + (NSThread *)networkRequestThread { static NSThread *_networkRequestThread = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; [_networkRequestThread start]; }); return _networkRequestThread; }
类似的可以用这个方法创建一个常驻服务的线程。
TableView中实现平滑滚动延迟加载图片
利用CFRunLoopMode的特性,可以将图片的加载放到NSDefaultRunLoopMode的mode里,这样在滚动UITrackingRunLoopMode这个mode时不会被加载而影响到。UIImage *downloadedImage = ...; [self.avatarImageView performSelector:@selector(setImage:) withObject:downloadedImage afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
接到程序崩溃时的信号进行自主处理例如弹出提示等
CFRunLoopRef runLoop = CFRunLoopGetCurrent(); NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runLoop)); while (1) { for (NSString *mode in allModes) { CFRunLoopRunInMode((CFStringRef)mode, 0.001, false); } }
异步测试
- (BOOL)runUntilBlock:(BOOL(^)())block timeout:(NSTimeInterval)timeout { __block Boolean fulfilled = NO; void (^beforeWaiting) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { fulfilled = block(); if (fulfilled) { CFRunLoopStop(CFRunLoopGetCurrent()); } }; CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, beforeWaiting); CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode); // Run! CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, false); CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode); CFRelease(observer); return fulfilled; }
相关文章推荐
- MPMoviePlayerController 添加自定义控件
- 解决国内使用NPM/GEM下载速度缓慢问题
- npm在使用包时使用频率较高的命令
- 最流行的开源飞控项目ArduPilot Mega(APM)介绍及发展历史
- PMD规则
- 使用苹果原生视频播放器MPMoviePlayerController播放来自优酷等网页的视频
- 产品经理有我们想象中重要吗?
- 采用fpm工具制作rpm包
- 解决Popmetal板上I2C挂载额驱动不正常,含陀螺仪、重力传感器
- lbypmall系统备份恢复
- I帧转图片(BMP、PPM、JPG)
- IPM
- 【杂感】我理解的产品经理做的是什么?
- npm国内镜像
- 如何去面试产品经理
- BPM领域常用的两款工作流JBPM和CCBPM的对比
- nrm —— 快速切换 NPM 源 (附带测速功能)
- 论产品经理的听说读写,写给产品新人
- 如何选择一款好的 APM 工具?
- Activiti5在线Web流程设计器