多线程的概念在各个操作系统上都会接触到,windows、Linux、mac os等等这些常用的操作系统,都支持多线程的概念。

当然ios中也不例外,但是线程的运行节点可能是我们平常不太注意的。

例如:

 - (void)viewDidLoad
{
[super viewDidLoad];
for(int i = ; i < ; i++)
{
NSLog(@"===%@===%d" , [NSThread currentThread].name , i);
if(i == )
{
// 创建线程对象
NSThread *thread = [[NSThread alloc]initWithTarget:self
selector:@selector(run) object:nil];
// 启动新线程
[thread start];
// // 创建并启动新线程
// [NSThread detachNewThreadSelector:@selector(run) toTarget:self
// withObject:nil];
}
}
}
- (void)run
{
for(int i = ; i < ; i++)
{
NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
}
}

上面打印的内容每一次都是不同的,什么意思呢?

当我们创建了4个线程后,加上UI主线程一共5个线程。

新的线程在执行start方法之后,并不会立即执行。他们会被cpu随机的执行,只是间隔非常短,以至于我们感觉上是多个线程在同时执行。

所以线程有这么一个特点:执行的随机性。

但是我们可以设置线程的优先级,让优先级更高的线程获得更多的执行机会。

那么什么时候要使用多线程编程呢?

相信有过开发经验的程序员都知道,当我们把代码写完后,程序是一行一行逐行执行代码的,当其中一行代码需要执行较长时间(例如select一个教复杂的语句或者较多的数据时),那么程序就会出现卡顿的现象,不会相应用户的操作。

因为开启程序后会默认开启一个主线程,即UI线程。当处于刚才那种情况时,比如一个windows程序,就会出现程序暂时无响应的提示,好像电脑卡主的感觉,这是非常不好的一种感受。。。。

当我们要避免这种情况的时候,最好的方式就是多线程,开启一个新的线程,用来执行一个耗时的操作,执行完成后再让主线程来修改ui页面(如果需要的话)。

介绍完了线程的一些知识,那么下面来具体看ios中多线程的几种实现方式,主要有一下三种:

1、NSThread :就是刚刚例子中使用的方式,但是使用上比较繁琐,而且需要控制好数据的同步和异步问题

2、NSOperation 和 NSOperationQueue : 这种方式代码比较简洁,可读性强,而且使用队列的形式管理多个任务,本人比较喜欢

3、使用GCD( Grand Central Dispatch ) :相较于NSThread使用简单,使用队列管理任务

一、首先来介绍NSThread

1、创建NSThread的两种方式

-(id) initWithTarget:(id) target selector:(SEL) selector object:(id) arg:

+(void)detachNewThreadSelector:(SEL) selector toTarget:(id) target withObject:(id) arg:

第二种方式,创建NSThread后会自动启动

2、NSThread的常用方法

+currentThread : 返回当前正在执行的线程对象

3、线程的状态

一开始的例子中提了一下,线程创建后,执行了start方法并不是立即就执行了。可能ui线程执行了几毫秒后,cpu才执行它,执行几毫秒后再执行ui线程,但这个过程是随机发生的。

如果想让线程立即执行,那么可以让ui线程sleep 1毫秒,这样cpu就会执行其他可执行的线程,可以达到立即执行的效果

 [NSThread sleepForTimeInterval:0.001];//让当前运行的线程睡眠1毫秒

线程正在执行时,调用isExecuting方法返回 YES ,线程执行完成后调用 isFinished 方法就会返回 YES

4、终止子线程

线程会以一下3种方式之一结束,结束后就处于死亡状态

1)线程执行的方法体执行完成,线程正常结束

2)执行过程中出现了错误

3)调用NSThread 类的 exit 方法来终止当前线程

在UI 线程中 ,NSThread 并没有提供方法来结束其他的子线程。但是我们可以利用 NSThread 的cancel 方法,执行该方法后, 该线程的状态为 isCancelled = YES,但并不会结束线程。

 NSThread* thread;
- (void)viewDidLoad
{
[super viewDidLoad];
// 创建新线程对象
thread = [[NSThread alloc] initWithTarget:self selector:@selector(run)
object:nil];
// 启动新线程
[thread start];
}
- (void)run
{
for(int i = ; i < ; i++)
{
if([NSThread currentThread].isCancelled)
{
// 终止当前正在执行的线程
[NSThread exit];
}
NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
// 每执行一次,线程暂停0.5秒
[NSThread sleepForTimeInterval:0.5];
}
}
- (IBAction)cancelThread:(id)sender
{
// 取消thread线程,调用该方法后,thread的isCancelled方法将会返回NO
[thread cancel];
}

利用例子中代码的形式,我们就可以达到在UI线程中结束其他子线程的目的了。

5、线程睡眠

要让线程进入阻塞状态或者睡眠状态,可以执行sleepXXX格式的方法:

+(void) sleepUntilDate:(NSDate *) aDate  : 让线程睡眠,知道aDate那个时间点再醒过来

-(void)sleepForTimeInterval :让线程睡眠多少秒

6、改变线程优先级

NSThread 提供了如下几个方法来获取和设置线程的优先级

+threadPriority: 获取当前正在执行的线程的优先级

-threadPriority:获取线程实例的优先级

+setThreadPriority :(double) priority : 设置当前正在执行的线程的优先级

-setThreadPriority :(double) priority : 设置线程实例的优先级

(double) priority的 取值范围是0.0~1.0;优先级越高的线程获得的执行机会越多

 - (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"UI线程的优先级为:%g" , [NSThread threadPriority]);
// 创建第一个线程对象
NSThread* thread1 = [[NSThread alloc]
initWithTarget:self selector:@selector(run) object:nil];
// 设置第一个线程对象的名字
thread1.name = @"线程A";
NSLog(@"线程A的优先级为:%g" , thread1.threadPriority);
// 设置使用最低优先级
thread1.threadPriority = 0.0;
// 创建第二个线程对象
NSThread* thread2 = [[NSThread alloc]
initWithTarget:self selector:@selector(run) object:nil];
// 设置第二个线程对象的名字
thread2.name = @"线程B";
NSLog(@"线程B的优先级为:%g" , thread2.threadPriority);
// 设置使用最高优先级
thread2.threadPriority = 1.0;
// 启动2个线程
[thread1 start];
[thread2 start];
}
- (void)run
{
for(int i = ; i < ; i++)
{
NSLog(@"-----%@----%d" , [NSThread currentThread].name, i);
}
}

二、使用GCD实现多线程

GCD简化了多线程的实现,主要有两个核心概念:

1、队列:队列负责管理开发者提交的任务,以先进先出的方式来处理任务。

1)串行队列:每次只执行一个任务,当前一个任务执行完成后才执行下一个任务

2)并行队列:多个任务并发执行,所以先执行的任务可能最后才完成(因为具体的执行过程导致)

2、任务:任务就是开发者提供给队列的工作单元,这些任务将会提交给队列底层维护的线程池,因此这些任务将会以多线程的方式执行。

3、创建队列

1)获取系统默认的全局并发队列:

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );

2) 获取系统主线程关联的穿行队列

 dispatch_queue_t queue = dispatch_get_main_queue();

如果将任务提交给主线程关联的串行队列,那么就相当于在程序主线程中去执行该任务。

3)创建穿行队列

 dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_SERIAL);

4)创建并发队列

 dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_CONCURRENT);

5)获取当前执行代码所在队列

dispatch_get_current_queue,返回一个dispatch_queue_t类型的值

4、提交任务

使用下面的方法将任务以同步或者异步的方式提交到队列

 //将代码块以异步的方式提交给指定队列
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block); //将函数以异步的方式提交给指定队列,一般执行函数的方法与执行代码块的方法比,方法名多了一个_f的后缀
void dispatch_async_f(dispatch_queue_t queue, void* context, dispatch_function_t work); //将代码块以同步的方式提交给指定队列
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block); //将函数以同步的方式提交给指定队列,一般执行函数的方法与执行代码块的方法比,方法名多了一个_f的后缀
void dispatch_sync_f(dispatch_queue_t queue, void* context, dispatch_function_t work);
 //将代码块以异步的方式提交给指定队列,队列的线程池负责在指定时间点 when 之后执行
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); //将函数以异步的方式提交给指定队列,队列的线程池负责在指定时间点 when 之后执行
void dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void* context, dispatch_function_t work); //将代码块以异步的方式提交给指定队列,队列的线程池将会重复多次执行该任务
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void(^block)(size_t)); //将函数以异步的方式提交给指定队列,队列的线程池将会重复多次执行该任务
void dispatch_apply_f(size_t iterations, dispatch_queue_t queue, void* context, void(*work)(void*, size_t)); //将代码块提交给指定队列,在应用的某个生命周期内金执行一次
void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);

下面给出一个以异步方式向串行队列、并发队列添加任务的实例

 // 定义2个队列
dispatch_queue_t serialQueue;
dispatch_queue_t concurrentQueue;
- (void)viewDidLoad
{
[super viewDidLoad];
// 创建串行队列
serialQueue = dispatch_queue_create("fkjava.queue", DISPATCH_QUEUE_SERIAL);
// 创建并发队列
concurrentQueue = dispatch_queue_create("fkjava.queue"
, DISPATCH_QUEUE_CONCURRENT);
}
- (IBAction)serial:(id)sender
{
// 依次将2个代码块提交给串行队列
// 必须等到第1个代码块完成后,才能执行第2个代码块。
dispatch_async(serialQueue, ^(void)
{
for (int i = ; i < ; i ++)
{
NSLog(@"%@=====%d" , [NSThread currentThread] , i);
}
});
dispatch_async(serialQueue, ^(void)
{
for (int i = ; i < ; i ++)
{
NSLog(@"%@------%d" , [NSThread currentThread] , i);
}
});
}
- (IBAction)concurrent:(id)sender
{
// 依次将2个代码块提交给并发队列
// 两个代码块可以并发执行
dispatch_async(concurrentQueue, ^(void)
{
for (int i = ; i < ; i ++)
{
NSLog(@"%@=====%d" , [NSThread currentThread] , i);
}
});
dispatch_async(concurrentQueue, ^(void)
{
for (int i = ; i < ; i ++)
{
NSLog(@"%@------%d" , [NSThread currentThread] , i);
}
});
}

提交同步任务:

 - (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)clicked:(id)sender
{
// 以同步方式先后提交2个代码块
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, )
, ^(void){
for (int i = ; i < ; i ++)
{
NSLog(@"%@=====%d" , [NSThread currentThread] , i);
[NSThread sleepForTimeInterval:0.1];
}
});
// 必须等第一次提交的代码块执行完成后,dispatch_sync()函数才会返回,
// 程序才会执行到这里,才能提交第二个代码块。
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, )
, ^(void){
for (int i = ; i < ; i ++)
{
NSLog(@"%@-----%d" , [NSThread currentThread] , i);
[NSThread sleepForTimeInterval:0.1];
}
});
}

多次执行的任务:

 - (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)clicked:(id)sender
{
// 控制代码块执行5次
dispatch_apply(
, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, )
// time形参代表当前正在执行第几次
, ^(size_t time)
{
NSLog(@"===执行【%lu】次===%@" , time
, [NSThread currentThread]);
});
}

只执行一次的任务

 @implementation FKViewController
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (IBAction)clicked:(id)sender
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"==执行代码块==");
// 线程暂停3秒
[NSThread sleepForTimeInterval:];
});
}

三、使用NSOperation 和 NSOPerationQueue 实现多线程

和GCD差不多,也是有队列和任务的概念

NSOperationQueue:代表一个先进先出的队列,负责管理系统提交的多个NSOperation。底层维护一个线程池,会按顺序启动线程来执行提交给队列的NSOperation

NSOperation:代表多线程任务。一般不直接使用NSOperation,而是使用NSOperation的子类。或者使用NSInvocationOperation和NSBlockOperation(这两个类继承自NSOperation);

1、NSOperation的使用

NSOperation 的使用相较于GCD是面向对象的,OC实现的,而GCD应该是C实现的(看函数的定义和使用)。

使用NSOperation 只需两步:

1)创建 NSOperationQueue 队列,并未该队列设置相关属性

2)创建 NSOperation 子类对象,并将该对象提交给 NSOperationQueue 队列,该队列将会按顺序依次启动每个 NSOperation。

2、NSOperationQueue的常用方法:

 +currentQueue //类方法,返回执行当前NSOperation的NSOperationQueue队列

 +mainQueue //返回系统主线程的NSOperationQueue队列

 -(void) addOperation:(NSOperation *) operation //将operation添加到NSOperationQueue队列中

 -(void) addOperations:(NSArray *) ops waitUnitlFinished:(BOLL) wait //将NSArray中包含的所有NSOperation添加到NSOperationQueue。如果第二个参数指定为YES,将会阻塞当前线程,直到提交的所有NSOperation执行完成。如果第二个参数为NO,该方法立即返回,NSArray包含的NSOperation将以异步方式执行,不会阻塞当前线程。

 - operations //只读属性,返回该NSOperationQueue管理的所有NSOperation
-operationCount //只读属性,返回该NSOperationQueue管理的所有NSOperation数量 -cancelAllOperations: //取消NSOperationQueue队列中所有正在排队和执行的NSOperation -waitUntilAllOperationsAreFinished://阻塞当前线程,直到该NSOperationQueue中所有排队和执行的NSOperation执行完成再接触阻塞 -(NSInteger) maxConcurrentOperationCount://返回该队列最大支持多少个并发线程 -setMaxConcurrentOperationCount:(NSInteger) count //设置该队列最大支持多少个并发线程 -setSuspended:(BOOL) suspend: //设置NSOperationQueue是否已经暂停调度正在排队的NSOperation -(BOLL) isSuspended: //返回NSOperationQueue是否已经暂停调度正在排队的NSOperation

3、使用NSInvocationOperation 和 NSBlockOperation

NSInvocationOperation 和 NSBlockOperation 继承自 NSOperation,所以可以直接使用,用于封装需要异步执行的任务。

使用它们实现图片异步下载:

 NSOperationQueue* queue;
- (void)viewDidLoad
{
[super viewDidLoad];
queue = [[NSOperationQueue alloc]init];
// 设置该队列最多支持10条并发线程
queue.maxConcurrentOperationCount = ;
}
- (IBAction)clicked:(id)sender
{
NSString* url = @"http://www.......jpg";
// 以传入的代码块作为执行体,创建NSOperation
NSBlockOperation* operation = [NSBlockOperation
blockOperationWithBlock:^{
// 从网络获取数据
NSData *data = [[NSData alloc]
initWithContentsOfURL:[NSURL URLWithString:url]];
// 将网络数据初始化为UIImage对象
UIImage *image = [[UIImage alloc]initWithData:data];
if(image != nil)
{
// 在主线程中执行updateUI:方法
[self performSelectorOnMainThread:@selector(updateUI:)
withObject:image waitUntilDone:YES];
}
else
{
NSLog(@"---下载图片出现错误---");
}
}];
// 将NSOperation添加给NSOperationQueue
[queue addOperation:operation];
}
-(void)updateUI:(UIImage*) image
{
self.iv.image = image;
}
 NSOperationQueue* queue;
- (void)viewDidLoad
{
[super viewDidLoad];
queue = [[NSOperationQueue alloc]init];
// 设置该队列最多支持10条并发线程
queue.maxConcurrentOperationCount = ;
}
- (IBAction)clicked:(id)sender
{
NSString* url = @"http://www.......jpg";
// 以self的downloadImageFromURL:方法作为执行体,创建NSOperation
NSInvocationOperation* operation = [[NSInvocationOperation alloc]
initWithTarget:self selector:@selector(downloadImageFromURL:)
object:url];
// 将NSOperation添加给NSOperationQueue
[queue addOperation:operation];
} // 定义一个方法作为线程执行体。
-(void)downloadImageFromURL:(NSString *) url
{
// 从网络获取数据
NSData *data = [[NSData alloc]
initWithContentsOfURL:[NSURL URLWithString:url]];
// 将网络数据初始化为UIImage对象
UIImage *image = [[UIImage alloc]initWithData:data];
if(image != nil)
{
// 在主线程中执行updateUI:方法
[self performSelectorOnMainThread:@selector(updateUI:)
withObject:image waitUntilDone:YES];
}
else
{
NSLog(@"---下载图片出现错误---");
}
}
-(void)updateUI:(UIImage*) image
{
self.iv.image = image;
}

4、自定义NSOperation 的子类

创建 NSOperation 的子类,需要重写一个方法:-(void) main,该方法的方法体将作为 NSOperationQueue 完成的任务

下面自定义一个NSOperation 子类来实现下载图片的功能

 @interface MyDownImageOperation : NSOperation
@property (nonatomic , strong) NSURL* url;
@property (nonatomic , weak) UIImageView* imageView;
- (id)initWithURL:(NSURL*)url imageView:(UIImageView*)iv;
@end
 @implementation MyDownImageOperation
- (id)initWithURL:(NSURL*)url imageView:(UIImageView*)iv
{
self = [super init];
if (self) {
_imageView = iv;
_url = url;
}
return self;
}
// 重写main方法,该方法将作为线程执行体
- (void)main
{
// 从网络获取数据
NSData *data = [[NSData alloc]
initWithContentsOfURL:self.url];
// 将网络数据初始化为UIImage对象
UIImage *image = [[UIImage alloc]initWithData:data];
if(image != nil)
{
// 在主线程中执行updateUI:方法
[self performSelectorOnMainThread:@selector(updateUI:)
withObject:image waitUntilDone:YES]; // ①
}
else
{
NSLog(@"---下载图片出现错误---");
}
}
-(void)updateUI:(UIImage*) image
{
self.imageView.image = image;
}

viewController代码:

 NSOperationQueue* queue;
- (void)viewDidLoad
{
[super viewDidLoad];
queue = [[NSOperationQueue alloc]init];
// 设置该队列最多支持10条并发线程
queue.maxConcurrentOperationCount = ;
}
- (IBAction)clicked:(id)sender
{
// 定义要加载的图片的URL
NSURL* url = [NSURL URLWithString:@"http://www.crazyit.org/logo.jpg"];
// 创建FKDownImageOperation对象
MyDownImageOperation* operation = [[MyDownImageOperation alloc]
initWithURL:url imageView:self.iv];
// 将NSOperation的子类的实例提交给NSOperationQueue
[queue addOperation:operation];
}

IOS高级编程之三:IOS 多线程编程的更多相关文章

  1. 新手浅谈Task异步编程和Thread多线程编程

    初学Task的时候上网搜索,看到很多文章的标题都是task取代thread等等相关,我也一直以为task和thread是一类,其实task是.net4.0提出的异步编程,在之前.net1.0有dele ...

  2. iOS开发之再探多线程编程:Grand Central Dispatch详解

    Swift3.0相关代码已在github上更新.之前关于iOS开发多线程的内容发布过一篇博客,其中介绍了NSThread.操作队列以及GCD,介绍的不够深入.今天就以GCD为主题来全面的总结一下GCD ...

  3. 《Python核心编程》18.多线程编程(三)

    18.6使用threading模块 #!/usr/bin/env python # -*- coding:utf-8 -*- """从Thread类中派生出一个子例,创建 ...

  4. 《Python核心编程》 18.多线程编程(一)

    一进程和线程 1参考链接: http://www.zhihu.com/question/25532384 中OF小工和zhonyong 的回答 总结他们两的回答: 引言: 1.电脑的运行,在硬件上是C ...

  5. 【C编程基础】多线程编程

    基础知识 1.基本概念 (1)线程,即轻量级进程(LWP:LightWeight Process),是程序执行流的最小单元. 线程是进程中的一个实体,是被系统独立调度和分派的基本单位. (2)线程同步 ...

  6. 《Python核心编程》18.多线程编程(二)

    18.1没有线程支持 #!/usr/bin/env python # -*- coding:utf-8 -*- from time import sleep, ctime def loop0(): p ...

  7. Java之旅_高级教程_多线程编程

    摘自:http://www.runoob.com/java/java-multithreading.html Java 多线程编程 Java 给多线程编程提供了内置的支持.一条线程指的是进程中的一条执 ...

  8. android: 多线程编程基础

    9.1   服务是什么 服务(Service)是 Android 中实现程序后台运行的解决方案,它非常适合用于去执行那 些不需要和用户交互而且还要求长期运行的任务.服务的运行不依赖于任何用户界面,即使 ...

  9. C语言多线程编程

    HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUT ...

  10. 初识Java多线程编程

    Java 多线程编程 Java给多线程编程提供了内置的支持.一个多线程程序包含两个或多个能并发运行的部分.程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径. 多线程是多任务的一种特别 ...

随机推荐

  1. React学习笔记---项目构建

    简介 ReactJs由于有FB的支持,得到了社区的极大关注,同时由于ReactJs只希望专一的做好View层次上的工作,所以本身并没有涉及很多周边工具. 今天要介绍一款工具,同时包含一个构建项目模板的 ...

  2. phpmyadmin Wrong permissions on configuration file, should not be world writable!

    巴拉巴拉,实际场景是这样,因为有需要,所以想用django 做个rest服务给其他平台提供服务,发现以前正常的页面都无法运行,奇怪发现有一个页面提示连接不上mysql 难道mysql挂了,打开phpm ...

  3. nginx(2、反向代理)

    反向代理是nginx最重要的特性之一,与正向代理相反,它代理的不是客户端,而是目标源,即我代理目标源满足客户端给出的请求. 在nginx中反向代理的简单配置如下: server { listen 80 ...

  4. 【css3】--四种气泡

    在聊天的场景中,聊天内容需要用到气泡修饰,如下图.下面一一讲解. 图片式: 第一个样式是京东客服,气泡的圆角和钩子都是用了图片.使用了一个table组合成了一个圆角的框框.lm样式拼出了钩子. < ...

  5. 解决在onCreate()过程中获取View的width和Height为0的4种方法

    很经常当我们动态创建某些View时,需要通过获取他们的width和height来确定别的view的布局,但是在onCreate()获取view的width和height会得到0.view.getWid ...

  6. Setting up SSL for SCM-Manager with Microsoft CA and TortoiseHg

    You can configure SSL for SCM-Manager so that the communication of your repositories are encrypted. ...

  7. Git学习笔记(7)——多人协作

    本文主要记录了,多人协作时,产生冲突时的解决情况. 多人环境创建 首先我们需要模拟一个多人环境.前面的Git的学习都是在Ubuntu上面,现在我们也搭建一个win环境吧.安装win环境下的Git,很简 ...

  8. Android中pullToRefresh使用

    pullToRefresh的导入 首先,点击new按钮 -> import Module 然后在 New Module界面选择已经在本地的含有源代码的pullToRefresh. 打开如下图所示 ...

  9. NodeJS系列~第四个小例子,NodeJs处理Get请求和Post请求

    返回目录 说在前 对于HTTP请求来说,我们通常使用的是Get和Post,除此之外还有put,delete等,而对于get来说,比较lightweight,只是对字符串的传输,它会被添加到URL地址里 ...

  10. c#设计模式-命令模式

    一. 命令(Command)模式 命令(Command)模式属于对象的行为模式[GOF95].命令模式又称为行动(Action)模式或交易(Transaction)模式.命令模式把一个请求或者操作封装 ...