添加权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
MyGesture.java
private Gesture ges; private GestureLibrary lib; private GestureOverlayView overlay; private Button button01, button02 , button03; private EditText et; private String gesPath; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.my_gesture); /* 查看SDCard是否存在 */ if (!Environment.MEDIA_MOUNTED.equals(Environment .getExternalStorageState())) { /* SD卡不存在,显示Toast信息 */ toast("SD卡不存在!程序无法运行"); } /* 以findViewById()取得对象 */ et = (EditText) this.findViewById(R.id.myEditText1); button01 = (Button) this.findViewById(R.id.myButton1); button02 = (Button) this.findViewById(R.id.myButton2); button03 = (Button) this.findViewById(R.id.myButton3); overlay = (GestureOverlayView) findViewById(R.id.myGestures1); /* 取得GestureLibrary的文件路径 */ gesPath = new File(Environment.getExternalStorageDirectory(), "gestures").getAbsolutePath(); /* 设置EditText的OnKeyListener */ et.setOnKeyListener(new EditText.OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { /* 名称与手写都已设置时将新增的Button enable */ if (ges != null && et.getText().length() != 0) { button01.setEnabled(true); } else { button01.setEnabled(false); } return false; } }); /* 设定Overlay的OnGestureListener */ overlay.addOnGestureListener(new GestureOverlayView.OnGestureListener() { public void onGesture(GestureOverlayView overlay, MotionEvent event) { } /* 开始画手势时将新增的Button disable,并清除Gesture */ public void onGestureStarted(GestureOverlayView overlay, MotionEvent event) { button01.setEnabled(false); ges = null; } /* 手势画完时判断名称与手写是否完整建立 */ public void onGestureEnded(GestureOverlayView overlay, MotionEvent event) { ges = overlay.getGesture(); if (ges != null && et.getText().length() != 0) { button01.setEnabled(true); } } public void onGestureCancelled(GestureOverlayView overlay, MotionEvent event) { } }); /* 设定button01的OnClickListener */ button01.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { final String gesName = et.getText().toString(); try { File file = new File(gesPath); lib = GestureLibraries.fromFile(gesPath); if (!file.exists()) { /* 文件不存在就直接写入 */ lib.addGesture(gesName, ges); if (lib.save()) { /* 将设定画面数据清除 */ et.setText(""); button01.setEnabled(false); overlay.clear(true); /* 保存成功,显示Toast信息 */ toast("保存成功,保存路径为:" + gesPath); } else { /* 保存失败,显示Toast信息 */ toast("保存失败"); } } else { /* 文件存在时因读取保存的Gesture */ if (!lib.load()) { /* Library读取失败,显示Toast讯息 */ toast("Library读取失败"); } else { /* 如果Library中存在相同名称,则因将其移除再写入 */ final Set<String> en = lib.getGestureEntries(); if (en.contains(gesName)) { new AlertDialog.Builder(MyGesture.this) .setTitle("提醒") .setMessage("文件已存在,确定要覆盖吗?") .setNegativeButton("取消", null) .setPositiveButton( "确定", new DialogInterface.OnClickListener() { public void onClick( DialogInterface dialoginterface, int i) { ArrayList<Gesture> al = lib .getGestures(gesName); for (int t = 0; t < al .size(); t++) { lib.removeGesture( gesName, al.get(t)); } lib.addGesture(gesName, ges); if (lib.save()) { /* 将设定画面数据清除 */ et.setText(""); button01.setEnabled(false); overlay.clear(true); /* 保存成功,显示Toast信息 */ toast("保存成功,保存路径为:" + gesPath); } else { /* 保存失败,显示Toast信息 */ toast("保存失败"); } } }).show(); } else { lib.addGesture(gesName, ges); if (lib.save()) { /* 将设定画面数据清除 */ et.setText(""); button01.setEnabled(false); overlay.clear(true); /* 保存成功,显示Toast信息 */ toast("保存成功,保存路径为:" + gesPath); } else { /* 保存失败,显示Toast信息 */ toast("保存失败"); } } } } } catch (Exception e) { e.printStackTrace(); } } }); /* 设置button02的OnClickListener */ button02.setOnClickListener(new Button.OnClickListener() { public void onClick(View v) { et.setText(""); button01.setEnabled(false); overlay.clear(true); } }); button03.setOnClickListener(new OnClickListener(){ public void onClick(View v) { Intent intent = new Intent(MyGesture.this , ReadGesture.class); startActivity(intent); } }); } public void toast(String str) { Toast.makeText(MyGesture.this, str, Toast.LENGTH_LONG).show(); }
ReadGesture.java
private SimpleAdapter listItemAdapter; // ListView的适配器 private ArrayList<HashMap<String, Object>> listItem; // ListView的数据源,这里是一个HashMap的列表 private ListView myList; // ListView控件 private List<Gesture> gesList; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.phone_info); listItem = new ArrayList<HashMap<String, Object>>(); listItemAdapter = new SimpleAdapter(this, listItem, R.layout.list_item3, new String[] { "image", "text" }, new int[] { R.id.imageView1, R.id.textView1 }); myList = (ListView) findViewById(R.id.listView1); myList.setAdapter(listItemAdapter); gesList = new ArrayList<Gesture>(); try{ String gesPath = new File(Environment.getExternalStorageDirectory(), "gestures").getAbsolutePath(); File gesFile = new File(gesPath); GestureLibrary lib = GestureLibraries.fromFile(gesFile); if(gesFile.exists()){ if(!lib.load()){ toast("读取Library失败"); } else{ Object[] entries = lib.getGestureEntries().toArray(); for(int i = 0 ; i < entries.length ; i++){ ArrayList<Gesture> gestureArray = lib.getGestures(entries[i].toString()); for(int j = 0; j < gestureArray.size() ; j++){ String gestureName = entries[i].toString(); addItem(R.drawable.doc,gestureName); Gesture gesture = (Gesture)gestureArray.get(j); gesList.add(gesture); } } } } }catch(Exception e){ e.printStackTrace(); } myList.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { Bitmap bitmap = gesList.get(arg2).toBitmap(64, 64, 12, Color.BLUE); ImageView gesImage = new ImageView(ReadGesture.this); gesImage.setImageBitmap(bitmap); new AlertDialog.Builder(ReadGesture.this) .setTitle("查看Gesture") .setView(gesImage) .setPositiveButton("确定", new DialogInterface.OnClickListener() { public void onClick( DialogInterface dialoginterface, int i) { dialoginterface.cancel(); } }).show(); } }); } private void addItem(int image, String str) { HashMap<String, Object> map = new HashMap<String, Object>(); map.put("image", image); map.put("text", str); listItem.add(map); listItemAdapter.notifyDataSetChanged(); } public void toast(String str) { Toast.makeText(ReadGesture.this, str, Toast.LENGTH_LONG).show(); }
1.
布局文件中
<ProgressBar android:id="@+id/ProgressBar01" android:layout_width="250dip" android:layout_height="300dip" android:layout_marginLeft="27dip" />
2.
styles.xml
<?xml version="1.0" encoding="UTF-8"?> <resources> <style name="animStyle" parent="@android:style/Widget.ProgressBar.Large"> <item name="android:indeterminateDrawable">@anim/loading_anim</item> </style> </resources>
3.
loading_anim.xml
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true" > <item android:drawable="@drawable/loading_pic0001" android:duration="85"/> <item android:drawable="@drawable/loading_pic0002" android:duration="85"/> <item android:drawable="@drawable/loading_pic0003" android:duration="85"/> <item android:drawable="@drawable/loading_pic0004" android:duration="85"/> <item android:drawable="@drawable/loading_pic0005" android:duration="85"/> <item android:drawable="@drawable/loading_pic0006" android:duration="85"/> </animation-list>
使用performSelectorOnMainThread进行ui的更新:
[self performSelectorOnMainThread:@selector(refresh) withObject:nil waitUntilDone:NO];
//------------
MoneyCat 22:59:15 performSelectorInBackground:(SEL) withObject:(id) 或 NSThread detachNewThreadSelector:(SEL )toTarget:(id) withObject:(id) 启动一个新线程,两者没多大差别,具体看你习惯哪种。 用 performSelectorOnMainThread:(SEL) withObjec:(id) 回调。 Dodo 23:00:11 我就是用NSThread detachNewThreadSelector:(SEL )toTarget:(id) withObject:(id) 的... MoneyCat 23:00:35 在 pool release 之前 Dodo 23:01:32 performSelectorOnMainThread:(SEL) withObjec:(id) 这个和那两个有区别么... MoneyCat 23:02:04 如,主线程中有一个静态类 AppAPI的话。即 AppAPI performSelectorOnMainThread:(@selector(XXX)):withObject(NSNumber numberWithInt:(123)) performSelectorOnMainThread:是用来在线程中访问主线程的方法的。 MoneyCat 23:03:04 比如你在MainViewController中开了一个线程。 在这个新线程中,想要再访问MainViewController中的某些对象或是函数,如果直接的 MainViewController setTag 是不行的。 MoneyCat 23:04:11 虽然有办法但没有使用performSelectorOnMainThread好,且安全。 Dodo 23:04:15 额.需要消化下. performSelectorOnMainThread 这个没开启新线程吧. Dodo 23:06:44 MoneyCat 23:08:22 例:MainViewController中 开线程是: [self performSelectorInBackground:(@selector(StartThread:)) withObject:(self)]; 在StartThread中即是 - (void) StartThread:(id)delegate { [delegate performSelectorOnMainThread:(@selector(CallbackFunction:)) withObject:(newValue)]; } - (void) CallbackFunction:(id)newValue { textFiled.text = newValue.Name; } MoneyCat 23:08:55 看清楚。。。 OnMianThread是用于回调的。 detachNewThreadSelector 才是开新线程。 MoneyCat 23:10:47 我比如喜欢使用 performSelectorInBackground 来开线程,detachNewThreadSelector 的 toTarget:参数如果是一个静态类的话,得 AppAPI self 多打几个字符。有点麻烦。 上面的简例应该足够说明了。呵呵。慢慢看下吧。。 Dodo 23:11:53 嗯.谢谢师父 MoneyCat 23:12:10 不客气。。//----------------------------------以下原文:http://mmz06.blog.163.com/blog/static/12141696201111641741284/
多线程在各种编程语言中都是难点,很多语言中实现起来很麻烦,objective-c虽然源于c,但其多线程编程却相当简单,可以与java相媲美。这篇文章主要从线程创建与启动、线程的同步与锁、线程的交互、线程池等等四个方面简单的讲解一下iphone中的多线程编程。
一、线程创建与启动
线程创建主要有二种方式:
- (id)init; // designated initializer - (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;当然,还有一种比较特殊,就是使用所谓的convenient method,这个方法可以直接生成一个线程并启动它,而且无需为线程的清理负责。这个方法的接口是:
+ (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument前两种方法创建后,需要手机启动,启动的方法是:
- (void)start;二、线程的同步与锁
要说明线程的同步与锁,最好的例子可能就是多个窗口同时售票的售票系统了。我们知道在java中,使用synchronized来同步,而iphone虽然没有提供类似java下的synchronized关键字,但提供了NSCondition对象接口。查看NSCondition的接口说明可以看出,NSCondition是iphone下的锁对象,所以我们可以使用NSCondition实现iphone中的线程安全。这是来源于网上的一个例子:
SellTicketsAppDelegate.h 文件// SellTicketsAppDelegate.h import <UIKit/UIKit.h> @interface SellTicketsAppDelegate : NSObject <UIApplicationDelegate> { int tickets; int count; NSThread* ticketsThreadone; NSThread* ticketsThreadtwo; NSCondition* ticketsCondition; UIWindow *window; } @property (nonatomic, retain) IBOutlet UIWindow *window; @endSellTicketsAppDelegate.m 文件
// SellTicketsAppDelegate.m import "SellTicketsAppDelegate.h" @implementation SellTicketsAppDelegate @synthesize window; - (void)applicationDidFinishLaunching:(UIApplication *)application { tickets = 100; count = 0; // 锁对象 ticketCondition = [[NSCondition alloc] init]; ticketsThreadone = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; [ticketsThreadone setName:@"Thread-1"]; [ticketsThreadone start]; ticketsThreadtwo = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; [ticketsThreadtwo setName:@"Thread-2"]; [ticketsThreadtwo start]; //[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; // Override point for customization after application launch [window makeKeyAndVisible]; } - (void)run{ while (TRUE) { // 上锁 [ticketsCondition lock]; if(tickets > 0){ [NSThread sleepForTimeInterval:0.5]; count = 100 - tickets; NSLog(@"当前票数是:%d,售出:%d,线程名:%@",tickets,count,[[NSThread currentThread] name]); tickets--; }else{ break; } [ticketsCondition unlock]; } } - (void)dealloc { [ticketsThreadone release]; [ticketsThreadtwo release]; [ticketsCondition release]; [window release]; [super dealloc]; } @end三、线程的交互
线程在运行过程中,可能需要与其它线程进行通信,如在主线程中修改界面等等,可以使用如下接口:- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait由于在本过程中,可能需要释放一些资源,则需要使用NSAutoreleasePool来进行管理,如:
- (void)startTheBackgroundJob { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // to do something in your thread job ... [self performSelectorOnMainThread:@selector(makeMyProgressBarMoving) withObject:nil waitUntilDone:NO]; [pool release]; }
IOS4 已经支持多线程了,我的EASYWEB在打开多个网页时会卡得要命,决定把它改成多线程方式进行加载网页
IOS4的多线程,基于Objective-c 相对 C++ JAVA来说简单不少技术要点:
一 线程创建与启动
线程类 NSThread包含如下线程操作方法:
//返回当前线程
+ (NSThread *)currentThread;// 通过类方法创建一个线程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;// 判断是否为多线程
+ (BOOL)isMultiThreaded;
- (NSMutableDictionary *)threadDictionary;+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 退出线程
+ (void)exit;// 线程属性值
+ (double)threadPriority ;
+ (BOOL)setThreadPriority:(double)p ;// 线程函数地址
+ (NSArray *)callStackReturnAddresses;// 设置与返回线程名称
- (void)setName:(NSString *)n;
- (NSString *)name;// 线程堆栈
- (NSUInteger)stackSize;
- (void)setStackSize:(NSUInteger)s;// 判断当前线程是否为主线程
- (BOOL)isMainThread;
+ (BOOL)isMainThread;+ (NSThread *)mainThread;
// 线程对象初始化操作 (通过创建线程对象 ,需要 手工指定线程函数与各种属性)
- (id)init;// 在线程对象初始化时创建一个线程(指定线程函数)
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;// 是否在执行
- (BOOL)isExecuting;// 是否已经结束
- (BOOL)isFinished;// 是否取消的
- (BOOL)isCancelled;// 取消操作
- (void)cancel;// 线程启动
- (void)start;- (void)main; // thread body method
推荐方式// 通过类方法创建一个线程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;// 在线程对象初始化时创建一个线程(指定线程函数)
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;主要通过selector:(SEL)selector 指定个功能函数,系统使其与主线程分开运行,以达到多线程的效果.
以上方式创建线程,非类方法创建需要调用 start才能让线程真正运行起来.
当多个线程同时运行,就会出现访问资源的同步问题
二 线程同步操作
IPHONE 使用NSCondition来进行线程同步,它是IPHONE的锁对象,用来保护当前访问的资源.
大致使用方法
NSCondition* mYLock = [[NSCondition alloc] init];[mYLock lock]
资源....
[mYLock unLock];
[mYLock release];
三 线程的交互
使用线程对象的
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
进行交互操作
主要是调用 主线程中指定的方法来执行一些相关操作四 线程池 NSOperation
NSInvocationOperation是 NSOperation的子类 具体使用代码// 建立一个操作对象
NSInvocationOperation* theOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(myTaskMethod:) object:data];// 将操作对象 加到系统已经的操作对列里, 这时候 myTaskMethod以一个线程的方式与主线程分开执行.
[[MyAppDelegate sharedOperationQueue] addOperation:theOp];// 这个是真正运行在另外一个线程的“方法”
- (void)myTaskMethod:(id)data
{
// Perform the task.
}以上是使用系统操作对列,可以使用 NSOperationQueue创建自己的线程对列
NSOperationQueue *operationQueue;
operationQueue = [[NSOperationQueue alloc] init]; //初始化操作队列
[operationQueue setMaxConcurrentOperationCount:n]; // 可以设置队列的个数
[operationQueue addOperation:otherOper];线程创建与撤销遵循 OC的内存管理规则.
以下原文:http://www.cnblogs.com/kesalin/archive/2011/08/18/cocoa_thread.html
iOS 支持多个层次的多线程编程,层次越高的抽象程度越高,使用起来也越方便,也是苹果最推荐使用的方法。下面根据抽象层次从低到高依次列出iOS所支持的多线程编程范式:
1, Thread;
2, Cocoa operations;
3, Grand Central Dispatch (GCD) (iOS4 才开始支持)
下面简要说明这三种不同范式:Thread 是这三种范式里面相对轻量级的,但也是使用起来最负责的,你需要自己管理thread的生命周期,线程之间的同步。线程共享同一应用程序的部分内存空间,它们拥有对数据相同的访问权限。你得协调多个线程对同一数据的访问,一般做法是在访问之前加锁,这会导致一定的性能开销。在 iOS 中我们可以使用多种形式的 thread:
Cocoa threads: 使用NSThread 或直接从 NSObject 的类方法 performSelectorInBackground:withObject: 来创建一个线程。如果你选择thread来实现多线程,那么 NSThread 就是官方推荐优先选用的方式。
POSIX threads: 基于 C 语言的一个多线程库,
Cocoa operations是基于 Obective-C实现的,类 NSOperation 以面向对象的方式封装了用户需要执行的操作,我们只要聚焦于我们需要做的事情,而不必太操心线程的管理,同步等事情,因为NSOperation已经为我们封装了这些事情。 NSOperation 是一个抽象基类,我们必须使用它的子类。iOS 提供了两种默认实现:NSInvocationOperation 和 NSBlockOperation。
Grand Central Dispatch (GCD): iOS4 才开始支持,它提供了一些新的特性,以及运行库来支持多核并行编程,它的关注点更高:如何在多个 cpu 上提升效率。
有了上面的总体框架,我们就能清楚地知道不同方式所处的层次以及可能的效率,便利性差异。下面我们先来看看 NSThread 的使用,包括创建,启动,同步,通信等相关知识。这些与 win32/Java 下的 thread 使用非常相似。
线程创建与启动NSThread的创建主要有两种直接方式:
[NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];
和
NSThread* myThread = [[NSThread alloc] initWithTarget:self
selector:@selector(myThreadMainMethod:)
object:nil];
[myThread start];
这两种方式的区别是:前一种一调用就会立即创建一个线程来做事情;而后一种虽然你 alloc 了也 init了,但是要直到我们手动调用 start 启动线程时才会真正去创建线程。这种延迟实现思想在很多跟资源相关的地方都有用到。后一种方式我们还可以在启动线程之前,对线程进行配置,比如设置 stack 大小,线程优先级。
还有一种间接的方式,更加方便,我们甚至不需要显式编写 NSThread 相关代码。那就是利用 NSObject 的类方法 performSelectorInBackground:withObject: 来创建一个线程:[myObj performSelectorInBackground:@selector(myThreadMainMethod) withObject:nil];
其效果与 NSThread 的 detachNewThreadSelector:toTarget:withObject: 是一样的。
线程同步线程的同步方法跟其他系统下类似,我们可以用原子操作,可以用 mutex,lock等。
iOS的原子操作函数是以 OSAtomic开头的,比如:OSAtomicAdd32, OSAtomicOr32等等。这些函数可以直接使用,因为它们是原子操作。
iOS中的 mutex 对应的是 NSLock,它遵循 NSLooking协议,我们可以使用 lock, tryLock, lockBeforeData:来加锁,用 unLock来解锁。使用示例:BOOL moreToDo = YES;
NSLock *theLock = [[NSLock alloc] init];
...
while (moreToDo) {
/* Do another increment of calculation */
/* until there’s no more to do. */
if ([theLock tryLock]) {
/* Update display used by all threads. */
[theLock unlock];
}
}
我们可以使用指令 @synchronized 来简化 NSLock的使用,这样我们就不必显示编写创建NSLock,加锁并解锁相关代码。- (void)myMethod:(id)anObj
{
@synchronized(anObj)
{
// Everything between the braces is protected by the @synchronized directive.
}
}
还有其他的一些锁对象,比如:循环锁NSRecursiveLock,条件锁NSConditionLock,分布式锁NSDistributedLock等等,在这里就不一一介绍了,大家去看官方文档吧。
用NSCodition同步执行的顺序NSCodition 是一种特殊类型的锁,我们可以用它来同步操作执行的顺序。它与 mutex 的区别在于更加精准,等待某个 NSCondtion 的线程一直被 lock,直到其他线程给那个 condition 发送了信号。下面我们来看使用示例:
某个线程等待着事情去做,而有没有事情做是由其他线程通知它的。
[cocoaCondition lock];
while (timeToDoWork <= 0)
[cocoaCondition wait];
timeToDoWork--;
// Do real work here.
[cocoaCondition unlock];其他线程发送信号通知上面的线程可以做事情了:
[cocoaCondition lock];
timeToDoWork++;
[cocoaCondition signal];
[cocoaCondition unlock];
线程间通信线程在运行过程中,可能需要与其它线程进行通信。我们可以使用 NSObject 中的一些方法:
在应用程序主线程中做事情:performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:
在指定线程中做事情:performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:
在当前线程中做事情:performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:
取消发送给当前线程的某个消息cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:
如在我们在某个线程中下载数据,下载完成之后要通知主线程中更新界面等等,可以使用如下接口:- (void)myThreadMainMethod
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// to do something in your thread job
...
[self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:NO];
[pool release];
}
RunLoop
说到 NSThread 就不能不说起与之关系相当紧密的 NSRunLoop。Run loop 相当于 win32 里面的消息循环机制,它可以让你根据事件/消息(鼠标消息,键盘消息,计时器消息等)来调度线程是忙碌还是闲置。
系统会自动为应用程序的主线程生成一个与之对应的 run loop 来处理其消息循环。在触摸 UIView 时之所以能够激发 touchesBegan/touchesMoved 等等函数被调用,就是因为应用程序的主线程在 UIApplicationMain 里面有这样一个 run loop 在分发 input 或 timer 事件。
参考资料:NSRunLoop概述和原理:http://xubenyang.me/384
官方文档:http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/Multithreading/