Run loop mode是一组用于监控的input sources和timers以及一组用于通知的run loop observers。每次运行一个run loop,你指定(显式或隐式)run loop的运行模式。当相应的模式传递给run loop时,只有与该模式对应的input sources才被监控并允许run loop对事件进行处理(与此类似,也只有与该模式对应的observers才会被通知)。与指定模式不同的事件将被事件源保存下来直到相关的模式被注册后才被分发。
在你的代码中,mode使用名称来标识。在Cocoa和Core Foundation都定义了缺省模式和几个通用模式,使用字符串来对它们进行标识。你可以通过使用自定义的字符串来自定义模式。虽然可以为自定义的模式指定任意的名字,但模式所包含的内容却是固定的。也就是你必须为你自定义的模式添加一个或多个input sources,Timers或run-loop observers供模式使用。
你使用mode来对unwanted sources中通过run loop传递的事件进行过滤。大多数情况下,你希望你的run loop运行在系统定义的缺省模式下。A modal panel, however, might run in the “modal” mode. While in this mode, only sources relevant to the modal panel would deliver events to the thread. For secondary threads, you might use custom modes to prevent low-priority sources from delivering events during time-critical operations.
注:mode只对事件源进行区分,而不是事件类型。例如:你不能使用mode来匹配光标按键事件或键盘事件。你可以使用mode来监听一组不同的端口,使timer临时挂起或者改变当前监控的sources或run-loop observers。
下表为Cocoa和Core Foundation中定义的模式及简要描述。name列中是你在代码中指定的mode常量:
Mode | Name | Description |
---|---|---|
default | NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation) |
default mode是大多数操作中使用的模式。大多数时间,你使用该模式来启动run loop并配置你的input sources。 |
connection | NSConnectionReplyMode (Cocoa) |
Cocoa使用该模式与NSConnection对象联接用于监控响应。你通常不需要在你的代码中使用该模式。 |
modal | NSModalPanelRunLoopMode (Cocoa) |
Cocoa使用该模式来标识用于modal panel(模式面板)的事件。 |
event tracking | NSEventTracking- RunLoopMode (Cocoa) | Cocoa使用该模式来限制光标拖动循环中上报的事件或其它用户界面相关的trace loop。 |
common modes | NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation) |
这是一组可配置的通用模式。将input sources与该模式关联则同时也将input sources与该组中的其它模式进行了关联。对于Cocoa应用,该模式缺省的包含了default,modal以及event tracking模式。而Core Foundation则在初始化时只包含了default模式。你可以使用CFRunLoopAddCommonMode为该模式添加自定义的模式。 |
input sources将事件以异步的形式发送给你的thread。事件源依赖于input sources的类型,通常分成一或两组。基于端口的事件源监控你机器的端口,自定义输入源监控事件的自定义源。Run loop并不关心input sources是基于端口或自定义的事件源。两个不同的输入源唯一的区别就是它们的标识方式。基于端口的输入源标识由内核自动生成,自定义的输入源由另外的thread手动产生。
当你创建了一个input sources,将其指定给你的run loop并分配一个或多个mode。当mode分配时会对input source的监控产生即时的影响。大多数情况,你运行你的run loop在缺省模式下,同时你也可以指定自定义模式。如果一个input sources不在当前监控的模式中,任何由该sources产生的事件都将被保存直至run loop运行于相应的模式下。
Cocoa和Core Foundation内置提供使用port相关的对象和函数创建port-based sources。例如,在Cocoa中你从来就不需要直接创建input sources。你只需要使用port对象以及NSPort的方法将port添加到run loop。Port对象将为你创建和配置input sources。
在Core Foundation,你需要自己创建port和run loop输入源。此时,你将使用与port不透明类型相关的函数(CFMachPortRef, CFMessagePortRef,CFSocketRef)来创建合适的对象。
要创建自定义输入源,你必须使用与CFRunLoopSourceRef不透明类型相关的函数。你将使用数个回调函数来配置自定义输入源。Core Foundation通过在不同的时间来调用这些回调函数以完成source配置,处理事件以及当该source从run loop中移除时关闭该source。
除了定义当有事件到达时自定义输入源的行为,你还必须定义事件投递机制。This part of the source runs on a separate thread and is responsible for providing the input source with its data and for signaling it when that data is ready for processing. The event delivery mechanism is up to you but need not be overly complex.
除了port-based sources,Cocoa定义了一组自定义输入源用于在任何thread中执行一个方法(selector)。与port-based源相类似,perform selector在thread中被序列化执行,这样就缓和了许多在同一个thread中运行多个方法所产生的同步问题。与port-based sources不同的是,perform selector source在运行完selector后自动从run loop中移除。
当在非main thread中perform selector时,其thread中必须有一个激活的run loop。对于你自己创建的thread而言,只有你的代码显式的运行一个run loop后该perform selector才能得到执行。Run loop在当loop运行时处理所有已排队的perform selector,而不是在一个loop循环时只处理某一个perform selector。
下表展示了perform selector调用方法:
Methods | Description |
---|---|
performSelectorOnMainThread: withObject: waitUntilDone: performSelectorOnMainThread: withObject: waitUntilDone:modes: |
在应用程序的main thread的下一个run loop周期内调用指定的selector。这些方法为你提供了堵塞当前thread执行直至selector执行完成。 |
performSelector: onThread:withObject: waitUntilDone: performSelector: onThread:withObject: waitUntilDone:modes: |
在已有的thread中调用指定的selector。这些方法为你提供了堵塞当前thread执行直至selector执行完成。 |
performSelector: withObject: afterDelay: performSelector: withObject: afterDelay:inModes: |
在当前的thread的下一个run loop周期内并延迟一个可选的时间,调用指定的selector。 |
cancelPreviousPerformRequestsWithTarget: cancelPreviousPerformRequestsWithTarget: selector:object: |
用于取消使用第三行中方法发向thread的消息。 |
只有在你创建了第二个thread时你才需要显式的运行run loop。对于main thread的run loop是作为框架的一部分。所以Cocoa和Carbon框架提供自动运行应用程序主循环的代码。IOS中UIApplication的run方法用于启动应用程序主循环并作为启动序列的一部分。如果你是使用Xcode template生成应用程序,则不需要对run方法进行显式的调用。
对于第二个thread,你需要决定是否有必要使用run loop,如果需要,则需要你手工进行配置和启动。你并不需要为每个新建的thread都建立run loop。例如:如果你使用你的thread运行一些长时间运行且可预知结果的任务,你就可以不启动该thread的run loop。Run loop所要解决的问题是你需要与该thread有很多的交互。
如遇到以下的情况,则你就需要启动thread内的run loop:
使用ports或自定义的input sources与其它thread进行交互;
在thread中使用Timers(定时器);
在Cocoa应用中使用任意performSelector...方法;
需要使用thread完成周期性的任务。
如果你决定使用run loop,则配置和运行将很直接。就像所有的多thread编程,你将考虑你新建thread的中止条件。总是将新建的thread安全退出好过强制退出。
如果你在非main thread中运行run loop,你必须至少为该run loop添加一个input sources或timer。如果你运行的run loop没有监控任何的输入源,该run loop将在你运行后立即退出。
使用detachNewThreadSelector:toTarget:withObject:创建一个thread:
启动run loop的方法:无条件启动,设置时间限制启动,在特殊的模式下启动。
以无条件模式进入run loop是最简单的选择,但并不是最好的选择。以无条件的形式运行run loop将使thread进入一个永久的循环,这样的操作会使用户很难对run loop进行控制。你可以为该run loop添加input source或timer,但能退出该run loop的方法就是kill。这种启动情况下并不能让run loop运行于自定义模式中。
不同于无条件方式运行run loop,使用时间限制的方式启动run loop更好。当你使用超时时间来对run loop的运行加以限制,则run loop一直运行直至事件到达或达到超时时间。如果是事件到达,run loop将事件分发给handler(处理器)进行处理并在处理完成后退出。你的代码然后重启run loop来处理下一个事件。如果是因到达超时时间而退出,you can simply restart the run loop or use the time to do any needed housekeeping。
除了超时时间,你还可以运行run loop在特定的模式下。模式和超时时间并不互斥,你可以在启动一个run loop时同时指定超时时间和模式。模式限制了投递事件给run loop的sources的类型,详细的见“iphone——NSRunLoop概念”。
退出run loop的方法:以超时时间配置run loop启动,显式的停止run loop(调用CFRunLoopStop函数)。
1.NSRunLoop是IOS消息机制的处理模式
NSRunLoop的主要作用:控制NSRunLoop里面线程的执行和休眠,在有事情做的时候使当前NSRunLoop控制的线程工作,没有事情做让当前NSRunLoop的控制的线程休眠。
2.NSRunLoop 就是一直在循环检测,从线程start到线程end,检测inputsource(如点击,双击等操作)同步事件,检测timesource同步事件,检测到输入源会执行处理函数,首先会产生通知,corefunction向线程添加runloop observers来监听事件,意在监听事件发生时来做处理。
3.runloopmode是一个集合,包括监听:事件源,定时器,以及需通知的runloop observers
模式包括:
default模式:几乎包括所有输入源(除NSConnection) NSDefaultRunLoopMode模式
mode模式:处理modal panels
connection模式:处理NSConnection事件,属于系统内部,用户基本不用
event tracking模式:如组件拖动输入源 UITrackingRunLoopModes 不处理定时事件
common modes模式:NSRunLoopCommonModes 这是一组可配置的通用模式。将input sources与该模式关联则同时也将input sources与该组中的其它模式进行了关联。
4.每次运行一个run loop,你指定(显式或隐式)run loop的运行模式。当相应的模式传递给run loop时,只有与该模式对应的 input sources才被监控并允许run loop对事件进行处理(与此类似,也只有与该模式对应的observers才会被通知)
5.NSTimer默认添加到当前NSRunLoop中,也可以手动制定添加到自己新建的NSRunLoop的中
[NSTimer schduledTimerWithTimeInterval: target:selector:userInfo:repeats];
此方法默认添加到当前NSRunLoop中
NSTimer *timer = [NSTimer timerWithTimeInterval: invocation:repeates:];
NSTimer *timer = [[NSTimer alloc] initWithFireDate:...];
创建timer [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
注意 timer的释放
例:
1). 在timer与table同时执行情况,当拖动table时,runloop进入UITrackingRunLoopModes模式下,不会处理定时事 件,此时timer不能处理,所以此时将timer加入到NSRunLoopCommonModes模式(addTimer forMode)
2).在滚动一个页面时来松开,此时connection不会收到消息,由于scroll时runloop为UITrackingRunLoopModes模式,不接收输入源,此时要修改connection的mode
[scheduleInRunLoop:[NSRunLoop currentRunLoop]forMode:NSRunLoopCommonModes];
6、子线程中的NSRunLoop需要手动启动,在子线程中使用timer要启动NSRunLoop。
7、关于-(BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)date;方法
指定runloop模式来处理输入源,首个输入源或date结束退出。
暂停当前处理的流程,转而处理其他输入源,当date设置为[NSDate distantFuture](将来,基本不会到达的时间),所以除非处理其他输入源结束,否则永不退出处理暂停的当前处理的流程。
8.while(A){
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
当前A为YES时,当前runloop会一直接收处理其他输入源,当前流程不继续处理,出为A为NO,当前流程继续
9 、perform selector在thread中被序列化执行,这样就缓和了许多在同一个thread中运行多个方法所产生的同步问题。perform selector source在运行完selector后自动从run loop中移除。
当 在非main thread中perform selector时,其thread中必须有一个激活的run loop。对于你自己创建的thread而 言,只有你的代码显式的运行一个run loop后该perform selector才能得到执行。Run loop在当loop运行时处理所有已排队 的perform selector,而不是在一个loop循环时只处理某一个perform selector。
10.performSelector 关于内存管理的执行原理是这样的执行 [self performSelector:@selector(method1:) withObject:self.tableLayer afterDelay:3]; 的 时候,系统会将tableLayer的引用计数加1,执行完这个方法时,还会将tableLayer的引用计数减1,由于延迟这时tableLayer的 引用计数没有减少到0,也就导致了切换场景dealloc方法没有被调用,出现了内存泄露。
利用如下函数:
[NSObject cancelPreviousPerformRequestsWithTarget:self]
当然你也可以一个一个得这样用:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(method1:) object:nil]
加上了这个以后,顺利地执行了dealloc方法
在touchBegan里面
[self performSelector:@selector(longPressMethod:) withObject:nil afterDelay:longPressTime]
然后在end 或cancel里做判断,如果时间不够长按的时间调用:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(longPressMethod:) object:nil]
取消began里的方法
线程实现的几种方式:
1. Operation Objects // NSOperation及相关子类
2. ***** // dispatch_async等相关函数
3. Idle-time notifications // NSNotificationQueue,低优先级
3. Asynchronous functions // 异步函数
4. Timers // NSTimer
5. Separate processes // 没用过
线程创建的成本:
kernel data structures 约1KB
Stack space 512KB(secondary threads)
1MB(iOS main thread)
Creation time 约90 microseconds
Run Loop和线程的关系:
1. 主线程的run loop默认是启动的,用于接收各种输入sources
2. 对第二线程来说,run loop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程执行一个长时间已确定的任务则不需要。
Run Loop什么情况下使用:
a. 使用ports 或 input sources 和其他线程通信 // 不了解
b. 在线程中使用timers // 如果不启动run loop,timer的事件是不会响应的
c. 在Cocoa 应用中使用performSelector...方法 // 应该是performSelector...这种方法会启动一个线程并启动run loop吧
d. 让线程执行一个周期性的任务 // 如果不启动run loop, 线程跑完就可能被系统释放了
注:timer的创建和释放必须在同一线程中。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 此方法会retain timer对象的引用计数
NSTimer与NSURLConnection默认运行在default mode下,这样当用户在拖动UITableView处于UITrackingRunLoopMode模式时,NSTimer不能fire,NSURLConnection的数据也无法处理。
NSTimer的例子:
在一个UITableViewController中启动一个0.2s的循环定时器,在定时器到期时更新一个计数器,并显示在label上。
-(void)viewDidLoad { label =[[[UILabel alloc]initWithFrame:CGRectMake(10, 100, 100, 50)]autorelease]; [self.view addSubview:label]; count = 0; NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval: 1 target: self selector: @selector(incrementCounter:) userInfo: nil repeats: YES]; } - (void)incrementCounter:(NSTimer *)theTimer { count++; label.text = [NSString stringWithFormat:@"%d",count]; }
在正常情况下,可看到每隔0.2s,label上显示的数字+1,但当你拖动或按住tableView时,label上的数字不再更新,当你手指离开时,label上的数字继续更新。当你拖动UItableView时,当前线程run loop处于UIEventTrackingRunLoopMode模式,在这种模式下,不处理定时器事件,即定时器无法fire,label上的数字也就无法更新。
解决方法,一种方法是在另外的线程中处理定时器事件,可把Timer加入到NSOperation中在另一个线程中调度;还有一种方法时修改Timer运行的run loop模式,将其加入到UITrackingRunLoopMode模式或NSRunLoopCommonModes模式中。
即
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
或
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
NSURLConnection也是如此,见SDWebImage中的描述,以及SDWebImageDownloader.m代码中的实现。修改NSURLConnection的运行模式可使用scheduleInRunLoop:forMode:方法。
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15]; NSURLConnection *connection = [[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]autorelease]; [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; [connection start];
NSRunLoop
is one of the mysterious classes in the frameworks that few seem to understand really well. I recently encountered difficulties with some aynshcronous networking code (more on that in a future post) and thought I’d share what I learned about run loops.
A run loop - NSRunLoop
in Cocoa - is a class of objects that manages input sources, like user events (mouse, keyboard, etc.), NSPort
events, and those that emanate from NSConnection
objects. You might think that the latter is the superclass of NSURLConnection
objects; but you’d be wrong.NSURLConnection
is a subclass of NSObject
- even though run loops manage events from NSURLConnection
also. So, you get the picture? NSRunLoop
manages events.
If you create a thread, you get an NSRunLoop
with it.
You must explictly run any run loop other than the main thread run loop.
Look closely at the documentation for NSRunLoop
run
method: “If no input sources or timers are attached to the run loop, this method exits immediately…” This means that if you want a run loop to keep turning, you need to find an event source to attach to it. For example:
But simply removing all input sources from a run loop is not guaranteed to stop it. IF you want to stop a run loop, you must explicitly do so: `
Run loops have modes that specify groups of input sources that are monitored for the run loop running in that mode. Usually you will just use the NSDefaultRunLoopMode
; but the others are:
NSConnectionReplyMode
used with NSConnection
objects
NSModalPanelRunLoopMode
used with events associated with modal panels in OS X
NSEventTrackingRunLoopMode
used with UI tracking events
NSRunLoopCommonModes
is a configurable group of common modes. In Foundation is includes all of the modes except NSConnectionReplyMode
by default.
If you want to create your own mode, just use a different string:
Apple recommends reverse DNS notation to avoid stepping on someone else’s run loop.
Because the main run loop is vital to the application, the run
method on NSApplication
and UIApplication
start the main run loop during the startup sequence. Otherwise, even for threads that you create yourself, you probably do not need to start its run loop. If the thread needs to work with ports, input sources, timers, or certain connections, then you need to start and manage its run loop for those events. More on some of those situations in a future post.