你要知道的iOS多线程NSThread、GCD、NSOperation、RunLoop都在这里
转载请注明出处 http://blog.csdn.net/u014205968/article/details/78323156
本系列文章主要讲解iOS中多线程的使用,包括:NSThread、GCD、NSOperation以及RunLoop的使用方法详解,本系列文章不涉及基础的线程/进程、同步/异步、阻塞/非阻塞、串行/并行,这些基础概念,有不明白的读者还请自行查阅。本系列文章将分以下几篇文章进行讲解,读者可按需查阅。
- iOS多线程——你要知道的NSThread都在这里
- iOS多线程——你要知道的GCD都在这里
- iOS多线程——你要知道的NSOperation都在这里
- iOS多线程——你要知道的RunLoop都在这里
- iOS多线程——RunLoop与GCD、AutoreleasePool
组织架构说明
本系列文章是按照相关多线程类的抽象层次撰写的,也就是说NSThread
是Foundation
框架提供的最基础的多线程类,每一个NSThread
类的对象即代表一个线程,接下来苹果为开发者封装了GCD(Grand Central Dispatch)
,GCD
相比于NSThread
来说,提供了便捷的操作方法,开发者不需要再关注于管理线程的生命周期,也不需要自行管理一个线程池用于线程的复用,但GCD
是以C函数
对外提供接口,因此Foundation
框架在GCD
的基础上进行了面向对象的封装,提供了面向对象的多线程类NSOperation
和NSOperationQueue
,抽象层次更高。
由于OC
是C语言
的超集,开发者也可以选择使用POSIX
标准的线程pthread
,pthread
和NSThread
都是对内核mach kernel
的mach thread
的封装,所以在开发时一般不会使用pthread
。
RunLoop
是与线程相关的一个基本组成,想要线程在执行完任务后不退出,在没有任务时睡眠以节省CPU资源都需要RunLoop
的实现,因此,正确的理解线程就需要深入理解RunLoop
相关知识。
NSThread的使用姿势全解
在组织架构说明
中讲到,NSThread
是对内核mach kernel
中的mach thread
的封装,所以,每一个NSThread
的对象其实就是一个线程,我们创建一个NSThread
对象也就意味着我们创建了一个新的线程。初始化创建NSThread
的方法有如下几种:
/*
使用target对象的selector作为线程的任务执行体,该selector方法最多可以接收一个参数,该参数即为argument
*/
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
/*
使用block作为线程的任务执行体
*/
- (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
/*
类方法,返回值为void
使用一个block作为线程的执行体,并直接启动线程
上面的实例方法返回NSThread对象需要手动调用start方法来启动线程执行任务
*/
+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
/*
类方法,返回值为void
使用target对象的selector作为线程的任务执行体,该selector方法最多接收一个参数,该参数即为argument
同样的,该方法创建完县城后会自动启动线程不需要手动触发
*/
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
下面分别举几个栗子:
/*
说明: 本文的栗子都是在单视图的工程中执行,防止主线程退出后,其他线程被退出,不方便实验。
*/
//线程的任务执行体并接收一个参数arg
- (void)firstThread:(id)arg
{
for (int i = 0; i < 10; i++)
{
NSLog(@"Task %@ %@", [NSThread currentThread], arg);
}
NSLog(@"Thread Task Complete");
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear: YES];
/*
创建一个线程,线程任务执行体为firstThread:方法
该方法可以接收参数@"Hello, World"
*/
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(firstThread:) object:@"Hello, World"];
//设置线程的名字,方便查看
[thread setName:@"firstThread"];
//启动线程
[thread start];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear: YES];
NSLog("ViewDidAppear");
}
上面的栗子没有什么实际意义,仅仅为了展示如何创建并启动线程,启动程序后就可以看到程序输出了10次
Task <NSThread: 0x1c446f780>{
number = 4, name = firstThread} Hello, World
上面输出了线程的名称,还输出了我们传入的参数,通过很简单的代码就可以创建一个新的线程来执行任务,在开发中尽量将耗时的操作放在其他线程中执行,只将更新UI的操作放在主线程中执行。
一般情况下,通过上述方法创建的线程在执行完任务执行体后就会退出并销毁,可以在firstThread:
方法的第二个NSLog
方法和viewDidAppear:
方法的输出上打断点,然后运行程序查看线程信息,在第一个断点时即firstThread:
方法的断点中,程序中线程信息如下图:
从上图可以看到,现在程序中有一个线程名为firstThread
,该线程即为我们创建的NSThread
对象,而com.apple.main-thread(serial)
即为主线程的名称,其中serial
是指明主线程是串行的,这个内容会在GCD
中进行讲解,我们可以通过类方法[NSThread mainThread]
来获取主线程。接下来继续执行到第二个断点,程序中线程信息如下图: