前言:
多线程在开发中是时刻存在的,对于提高用户体验是至关重要的。本blog粗略的记录下NSThread,NSOperationQueue,GCD等多线程技术。
NSThread
使用NSThread创建线程有很多方法:
类方法直接生成一个子线程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;
使用实例方法,然后调用
start
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument; NSThread *hardThread = [[NSThread alloc] initWithTarget:self selector:@selector(doHardWork) object:nil]; [hardThread start];
调用NSObject的
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg ;
[self performSelectorInBackground:@selector(hardWork) withObject:nil];
创建一个NSThread子类,然后调用子类实例的start方法。
根据大神的blog,iOS下主要成本开销包括构造内核数据结构(大约1kb)、栈空间(子线程512kb、主线程1MB,可使用
-setStackSize:
自定义,注意必须是4k的倍数,而且最小是16k),创建线程大约需要90毫秒的创建时间。
第二种和第四种可以使用- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
在该线程上执行方法。相对于NSPort(用于通讯)的麻烦设置来说简单方便。Apple建议使用这个接口运行的方法不要是耗时或者频繁的操作,以免子线程的负载过重。
如果需要,可以设置线程的优先级(-setThreadPriority:
)如果要在线程中保存一些状态信息,还可以使用到-threadDictionary
得到一个NSMutableDictionary
,以key-value的方式保存信息用于线程内读写。
NSThread的入口方法
下面是大神的示例代码:
- (void)threadRoutine
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
BOOL moreWorkToDo = YES;
BOOL exitNow = NO;
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
[threadDict setValue:[NSNumber numberWithBool:exitNow] forKey:@"ThreadShouldExitNow"];
//添加事件源
[self myInstallCustomInputSource];
while (moreWorkToDo && !exitNow)
{
//执行线程真正的工作方法,如果完成了可以设置moreWorkToDo为False
[runLoop runUntilDate:[NSDate date]];
exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue];
}
[pool release];
}
- 大神说必须创建一个NSAutoreleasePool,因为子线程不会自动创建。同时如果线程中会大量创建autoreleased对象的话,则只有在该子线程退出时才回回收,就需要在NSRunloop每次迭代时创建和销毁一个NSAutoreleasePool。
- 最好在子线程中设置一个异常处理函数,若子线程无法处理抛出的异常,会导致Crash。
- (可选)设置RunLoop,如果子线程进入一个循环需要不断处理一些事情,那么设置一个RunLoop是最好的处理方式,如果需要Timer,那么RunLoop就是必须的。
- 如果需要在子线程运行的时候让子线程结束操作,子线程每次RunLoop迭代中检查相应的标志位来判断是否还需要继续执行,可以使用threadDictionary以及设置InputSource的方式来通知这个子线程。
Run Loop
- 每个线程都有一个Run Loop,主线程的Run Loop会在App运行时自动运行,子线程中需要手动运行.
- 每个Run Loop都会以一个模式mode来运行,可以使用NSRunLoop的
-(BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate
方法运行在某个特定模式mode。 - Run Loop的处理2大类事件源:Timer Source和Input Source(包括performSelector *)方法簇、Port或者自定义Input Source)、每个事件源都会绑定在Run Loop的某个特定模式mode上,而且只有RunLoop在这个模式运行的时候才会触发该Timer和Input Source。
- 如果没有任何事件源添加到RunLoop上,Run Loop就会立刻exit。
Run Loop三个运行接口:
-(void)run;
无条件运行不建议使用,该接口会导致Run Loop永久性的运行在NSDefaultRunLoopMode模式,即使使用CFRunLoopStop(runloopRef);也无法停止Run Loop的运行,那么这个子线程久无法停止,只能一直运行。
- (void)runUntilDate:(NSDate *)limitDate;
有一个超时时间限制是运行在NSDefaultRunLoopMode模式,有个超时时间,可以控制没次Run Loop的运行时间。 这个方法运行Run Loop一段时间会退出给你检查运行条件的机会。如果需要可以再次运行Run Loop。 注意CFRunLoopStop(runloopRef);也无法停止Run Loop的运行,因此最好自己设置一个合理的Run Loop运行时间。 示例: while (!Done) { [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]; NSLog(@"exiting runloop.........:"); }
- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
这个接口在非Timer事件触发、显示的用CFRunLoopStop停止RunLoop、到达limitDate后会退出返回。如果仅是Timer事件触发并不会让Run Loop退出返回;如果仅是Timer事件触发并不会让Run Loop退出返回;如果是PerformSelector*事件或者其他Input Source事件触发处理后,runloop会退出返回YES。示例:while (!Done) { BOOL ret = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; NSLog(@"exiting runloop.........: %d", ret); }
什么时候需要使用Port或者自定义InputSource与其他线程进行通讯。
- 需要在线程中使用Timer。
- 需要在线程上使用performSelector*方法。
- 需要让线程执行周期性的工作。