原文:http://lianxu.me/2012/11/10-cocoa-objc-newbie-problems/
http://blog.csdn.net/dongdongdongjl/article/details/7794050
thread和runloop在以前,开发者根本不太当成一个问题。因为没有静态语言里runloop就是固定的线程执行loop。而现在Cocoa新手搞不明白的太多了,因为没有从动态角度看它,首先回想一下第2点介绍的runtime概念,接着出一个思考题。
现在有一个程序片段如下:
1
2
3
4
5
6
7
8
9
10
11
|
- (void)myThread:(id)sender
{
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
while (TRUE) {
//do some jobs
//break in some condition
usleep(10000);
[pool drain];
}
[pool release];
}
|
现在要求,做某些设计,使得当这个线程运行的同时,还可以从其它线程里往它里面随意增加或去掉不同的计算任务。 这,就是NSRunloop的最原始的开发初衷。让一个线程的计算任务更加灵活。 这个功能在c, c++里也许可以做到但是非常难,最主要的是因为语言能力的限制,以前的程序员很少这么去思考。
好,现在我们对上面代码做一个非常简单的进化:
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
|
NSMutableArray *targetQueue;
NSMutableArray *actionQueue;
- (void)myThread:(id)sender
{
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
while (TRUE) {
//do some jobs
//break in some condition
int n=[targetQueue count];
assert(n==[actionQueue count]);
for(int i=0;i<n;i++){
id target=[targetQueue objectAtIndex:i];
SEL action=NSSelectorFromString([actionQueue objectAtIndex:i]);
if ([target respondsToSelector:action]) {
[target performSelector:action withObject:nil];
}
}
usleep(10000);
[pool drain];
}
[pool release];
}
|
注意,这里没有做线程安全处理,记住Mutable container is not thread safe. 这个简单的扩展,让我们看到了如何利用runtime能力让线程灵活起来。当我们从另外线程向targetQueue和actionQueue同时加入对象和方法时候,这个线程函数就有了执行一个额外代码的能力。
有人会问,哪里有runloop? 那个是nsrunloop? 看不出来啊。
1
2
3
|
while (TRUE) {
//break in some condition
}
|
这个结构就叫线程的runloop, 它和NSRunloop这个类虽然名字很像,但完全不是一个东西。以前在使用静态语言开始时候,程序员没有什么迷惑,因为没有NSRunloop这个东西。 我接着来说,这个NSRunloop是如何来得。
第二段扩展代码里面确实没有NSRunloop这个玩意儿,我们接着做第3次改进。 这次我们的目的是把其中动态部分抽象出来。
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
|
@interface MyNSTimer : NSObject
{
id target;
SEL action;
float interval;
CFAbsoluteTime lasttime;
}
- (void)invoke;
@end
@implementation MyNSTimer
- (void)invoke;
{
if ([target respondsToSelector:action]) {
[target performSelector:action withObject:nil];
}
}
@end
#!objc
@interface MyNSRunloop : NSObject
{
NSMutableArray *timerQueue;
}
- (void)addTimer:(MyNSTimer*)t;
- (void)executeOnce;
@end
@implementation MyNSRunloop
- (void)addTimer:(MyNSTimer*)t;
{
@synchronized(timerQueue){
[timerQueue addObject:t];
}
}
- (void)executeOnce;
{
CFAbsoluteTime currentTime=CFAbsoluteTimeGetCurrent();
@synchronized(timerQueue){
for(MyNSTimer *t in timerQueue){
if(currentTime-t.lasttime>t.interval){
t.lasttime=currentTime;
[t invoke];
}
}
}
}
@end
#!objc
@interface MyNSThread : NSObject
{
MyNSRunloop *runloop;
}
- (void)main:(id)sender;
@end
@implementation MyNSThread
- (void)main:(id)sender
{
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
while (TRUE) {
//do some jobs
//break in some condition
[runloop executeOnce];
usleep(10000);
[pool drain];
}
[pool release];
}
@end
|
走到这里,我们就算是基本把Runloop结构抽象出来了。例如我有一个MyNSThread实例,myThread1。我可以给这个实例的线程添加需要的任务,而myThread1内部的MyNSRunloop对象会管理好这些任务。
1
2
3
4
5
|
MyNSTimer *timer1=[MyNSTimer scheduledTimerWithTimeInterval:1 target:obj1 selector:@selector(download1:)];
[myThread1.runloop addTimer:timer1];
MyNSTimer *timer2=[MyNSTimer scheduledTimerWithTimeInterval:2 target:obj2 selector:@selector(download2:)];
[myThread1.runloop addTimer:timer2];
|
当你看懂了上面的代码也许会感叹,‘原来是这么回事啊!为什么把这么简单的功能搞这么复杂呢?’ 其实就是这么回事,把Runloop抽象出来可以使得线程任务管理更加loose coupling,给设计模式提供更大的空间。这样第三方开发者不需要过深入的涉及线程内部代码而轻松管理线程任务。另外请注意,这里MyNSRunloop, MyNSTimer等类是我写得一个模拟情况,真实的NSRunloop实现肯定不是这么简单。这里为了说明一个思想。这种思想贯穿整个cocoa framework,从界面更新到event管理。
仅当在为你的程序创建辅助线程的时候,你才需要显式运行一个 run loop。Run loop 是程序主线程基础设施的关键部分。所以,Cocoa 和 Carbon 程序提供了代码运 行主程序的循环并自动启动 run loop。IOS 程序中 UIApplication 的 run 方法(或 Mac OS X 中的 NSApplication)作为程序启动步骤的一部分,它在程序正常启动的时 候就会启动程序的主循环。类似的,RunApplicationEventLoop 函数为 Carbon 程序 启动主循环。如果你使用 xcode 提供的模板创建你的程序,那你永远不需要自己去显 式的调用这些例程。
对于辅助线程,你需要判断一个 run loop 是否是必须的。如果是必须的,那么 你要自己配置并启动它。你不需要在任何情况下都去启动一个线程的 run loop。比 如,你使用线程来处理一个预先定义的长时间运行的任务时,你应该避免启动 run loop。Run loop 在你要和线程有更多的交互时才需要,比如以下情况:
使用端口或自定义输入源来和其他线程通信 使用线程的定时器 Cocoa 中使用任何 performSelector...的方法 使线程周期性工作