本篇文章整理了几种iOS中主要的多线程方案,提供了Swift和Objective-C两种语言的写法。

概述

iOS目前有四种多线程解决方案:

  • NSThread
  • GCD
  • NSOperation
  • Pthread

Pthread这种方案太底层啦,实际开发中很少用到,下文主要介绍前三种方案

NSThread

NSThread是基于线程使用,轻量级的多线程编程方法(相对GCD和NSOperation),一个NSThread对象代表一个线程,需要手动管理线程的生命周期,处理线程同步等问题。

创建方法

Objective-C:

线程的创建与启动

// 初始化线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadFunction:) object:nil]; // 启动线程
[thread start];

或者创建后直接启动

[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];

Swift:

//创建
let thread = NSThread(target: self, selector: "threadFunction:", object: nil) //启动
thread.start() 

或者

NSThread.detachNewThreadSelector("run:", toTarget: self, withObject: nil)

OC中还有一种使用NSObject的方法创建线程并直接启动的方法

[self performSelectorInBackground:@selector(run:) withObject:nil];

Swift中由于安全问题,苹果去掉了这个方法。

其他一些常用的方法

//获取当前线程信息
+ (NSThread *)currentThread; //获取主线程信息
+ (NSThread *)mainThread; //取消线程
- (void)cancel; //获取线程状态
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled; //设置和获取线程名字
-(void)setName:(NSString *)n;
-(NSString *)name; //使当前线程暂停一段时间,或者暂停到某个时刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;

Swift的方法名和调用方式和OC基本一致,这里就不一一列举了。

GCD

Grand Central Dispatch (GCD)是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。

GCD是一个替代诸如NSThread等技术的很高效和强大的技术。GCD完全可以处理诸如数据锁定和资源泄漏等复杂的异步编程问题。GCD的工作原理是让一个程序,根据可用的处理资源,安排他们在任何可用的处理器核心上平行排队执行特定的任务。这个任务可以是一个功能或者一个程序段。

简单的说,GCD会自动的管理线程生命周期(创建、调度、销毁),我们只需要告诉它该做什么,其他的事它都会帮我们搞定,是不是很方便?

要了解GCD的使用方法,首先要明白四个概念:任务、队列、同步执行、异步执行

任务:在GCD中任务可以被理解为一段Block代码,即你想要执行的操作。

同步执行(同步派发):任务在同一线程内按顺序一个一个执行,前一个任务执行完毕后下一个任务才会开始。换言之,当前线程会被阻塞。

异步执行(异步派发):任务在不同的线程中执行,下一个任务不用等待前一个任务执行完毕后才开始。换言之,当前线程不会被阻塞。

队列:用来放置任务的资源池,并按照一定的规则取出并执行任务。队列分为串行队列并行队列

  • 串行队列:遵循FIFO(先进先出)原则一个一个取出任务并执行,前一个任务完成后再取出下一个任务并执行。无论是同步还是异步执行,串行队列都是一个一个执行任务,也就是说同一个串行队列中的任务都是在同一个线程中执行的。
  • 并行队列:同样遵循FIFO原则一个一个取出任务,与串行队列的区别在于取出一个任务就会新开一个线程并在新线程中执行。同一个并行队列中的任务一般都在不同的线程中执行

队列的执行方式不同,产生的效果不同

  同步执行 异步执行
串行队列 阻塞当前线程,直到串行队列中的任务都执行完毕。系统不会另起线程,会在当前线程按顺序一个一个地执行任务。 不会阻塞当前线程,系统会另起一个新线程,在新线程中按顺序一个一个地执行任务。
并行队列 阻塞当前线程,直到并行队列中的同步派发任务都执行完毕。系统不会另起线程,会在当前线程按顺序执行同步派发的任务。 不会阻塞当前线程,系统会另起新线程并执行,每多一个异步派发的任务系统就会多开一个线程。

死锁的形成

当以同步派发的方式往当前线程所在的串行队列添加任务时,就会形成死锁。因为同步派发会阻塞当前线程,直到派发的任务执行完成线程才会继续执行。而派发的任务又恰好是加入的当前被阻塞的线程,这样会导致任务无法被执行(因为任务所在线程已被阻塞),于是形成死锁。

创建队列

1、主队列

这是系统定义的串行队列,任何会刷新UI的任务都要在主队列中进行。因此,尽量不要在主队列中添加很耗时的任务,确保刷新界面的任务不会被阻塞,给用户以流畅的交互体验。

//Objective-C
dispatch_queue_t queue = dispatch_get_main_queue(); //SWIFT
let queue = dispatch_get_main_queue()

2、自定义队列

可以创建串行或并行队列,第二个参数传DISPATCH_QUEUE_SERIAL或NULL为串行队列,传DISPATCH_QUEUE_CONCURRENT为并行队列。

//OBJECTIVE-C
//串行队列
dispatch_queue_t queue = dispatch_queue_create("serialTestQueue", NULL);
dispatch_queue_t queue = dispatch_queue_create("serialTestQueue", DISPATCH_QUEUE_SERIAL);
//并行队列
dispatch_queue_t queue = dispatch_queue_create("concurrentTestQueue", DISPATCH_QUEUE_CONCURRENT); //SWIFT
//串行队列
let queue = dispatch_queue_create("serialTestQueue", nil);
let queue = dispatch_queue_create("serialTestQueue", DISPATCH_QUEUE_SERIAL)
//并行队列
let queue = dispatch_queue_create("concurrentTestQueue", DISPATCH_QUEUE_CONCURRENT)

3、全局并行队列

这是系统定义的并行队列,并行任务一般都加入这个队列中执行。

//OBJECTIVE-C
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ); //SWIFT
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, )

创建任务

1、同步派发(sync)

同步派发会阻塞当前线程。不管是往串行还是并行队列中同步派发任务,都不会新开线程,只会在当前线程中执行任务。注意,如果当前线程是在一个串行队列中执行,则不能在当前线程中向此队列同步派发任务,否则会形成死锁。

// Objective-C
dispatch_sync(<#传入队列#>, ^{
//执行代码块
}); // SWIFT
dispatch_sync(<#传入队列#>, { () -> Void in
//执行代码块
})

2、异步派发(async)

异步派发不会阻塞当前线程。往非当前线程所在队列异步派发任务时,系统一定会另起新线程;往当前线程所在队列异步派发任务时有两种情况:1、如果当前线程所在队列是串行队列,系统不会另起新线程,添加的任务会在队列中前面的任务都执行完毕后才会执行(FIFO);2、如果当前线程所在队列是并行队列,系统会另起新线程,添加的任务会与队列中的其他任务同时执行。

// Objective-C
dispatch_async(<#传入队列#>, ^{
//执行代码块
}); // SWIFT
dispatch_async(<#传入队列#>, { () -> Void in
//执行代码块
})

队列组

队列组可以将不同类型的队列添加到一个组里,当所有队列都执行完毕的时候有一个回调方法:

//Objective-C
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"队列中的任务都已完成");
}); //SWIFT
dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in
NSLog("对了中的任务都已完成")
}

其他实用方法

func dispatch_barrier_async(_ queue: dispatch_queue_t, _ block: dispatch_block_t):

当第一个参数传入的是自定义的并行队列时,添加的任务会阻塞这个队列,当排在它前面的任务都执行完毕后再开始执行这个任务,并且当这个任务执行完毕后才会取消队列的阻塞状态;当第一个参数传入值为其他情况,此方法的作用和dispatch_async一样。

func dispatch_barrier_sync(_ queue: dispatch_queue_t, _ block: dispatch_block_t):

当第一个参数传入的是自定义的并行队列时,这个方法和上一个方法作用类似,区别在于此方法还会阻塞当前线程;当第一个参数传入值为其他情况,此方法的作用和dispatch_sync一样。

NSOperation

NSOperation其实就是对GCD用面向对象的方式进行的封装。

任务->NSOperation      队列->NSOperationQueue

添加任务

NSOperation是一个基类,它有两个子类:NSInvocationOperation 和 NSBlockOperation。创建任务后需要调用start方法来启动任务,默认再当前队列同步执行。可以在中途调用cancel方法中止任务的执行。

NSInvocationOperation

Objective-C

//1.创建对象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationBlock) object:nil]; //2.开始执行
[operation start];

因为安全问题,Swift不能使用这个类

NSBlockOperation

//Objective-C
//1.创建对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}]; //2.开始任务
[operation start];

//--------------------------------------------------//
//SWIFT
//1.创建对象
let operation = NSBlockOperation { () -> Void in
println(NSThread.currentThread())
} //2.开始任务
operation.start()

那如何并行执行任务呢?NSBlockOperation 有一个方法:addExecutionBlock:可以给一个Opertion添加多个任务,这些任务会并行执行

Objective-C:

//1.创建NSBlockOperation对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}]; //添加多个Block
for (NSInteger i = ; i < ; i++) {
[operation addExecutionBlock:^{
NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
}];
} //2.开始任务
[operation start];

SWIFT:

//1.创建NSBlockOperation对象
let operation = NSBlockOperation { () -> Void in
NSLog("%@", NSThread.currentThread())
} //2.添加多个Block
for i in ..< {
operation.addExecutionBlock { () -> Void in
NSLog("第%ld次 - %@", i, NSThread.currentThread())
}
} //3.开始任务
operation.start()

注意:addExecutionBlock 方法必须在 start() 方法之前执行,否则就会报错。

自定义Operation

除了上面的两种 Operation 以外,还可以自定义 Operation。继承 NSOperation 类,并实现其 main 方法,因为在调用 start 方法的时候,内部会调用 main方法完成相关逻辑。除此之外,你还需要实现 cancel在内的其他方法。

创建队列

调用operation的start方法是同步执行的,也就是说它会阻塞当前线程,那怎么用异步执行的方式呢?这里就要使用队列了NSOperationQueue

主队列

这是系统定义的队列,任何会刷新UI的任务都要在主队列中进行。为了能给用户流程的UI体验,主队列中的线程优先级是最高的。这个队列中的任务都是串行执行的。

//OBJECTIVE-C
NSOperationQueue *queue = [NSOperationQueue mainQueue]; //SWIFT
let queue = NSOperationQueue.mainQueue()

其他队列

自定义的队列,就是其他队列。默认情况下其他队列的任务会在不同的线程中并行执行。它有一个参数maxConcurrentOperationCount最大并发数,用来设置最多能有多少个并行任务同时执行。把它设置成1就表明这是一个串行队列。

//Objective-C 创建其他队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //Swift 创建其他队列
let queue = NSOperationQueue()

添加任务

// Objective-C 往队列里添加任务
[queue addOperation:operation]; // Swift 往队列里添加任务
queue.addOperation(operation)

往队列里添加任务后会自动开始执行,不用调用start。

还有一个添加任务的方法-(void)addOperationWithBlock:(void (^)(void))block; 可以直接往将想执行的代码块添加到队列中了,不用再创建一个Operation。

添加依赖项

当任务A必须要在任务B完成后才能开始执行,这时就可以添加  A依赖B。

[operationA addDependency:operationB];

注意:1、不能相互添加依赖(A依赖B,B依赖A),这样会形成死锁;2、可以使用removeDependency来解除依赖;3、可以在不同队列中的任务添加依赖。

其他方法

NSOperation

BOOL executing; //判断任务是否正在执行

BOOL finished; //判断任务是否完成

void (^completionBlock)(void); //用来设置完成后需要执行的操作

- (void)cancel; //取消任务

- (void)waitUntilFinished; //阻塞当前线程直到此任务执行完毕

NSOperationQueue

NSUInteger operationCount; //获取队列的任务数

- (void)cancelAllOperations; //取消队列中所有的任务

- (void)waitUntilAllOperationsAreFinished; //阻塞当前线程直到此队列中的所有任务执行完毕

[queue setSuspended:YES]; // 暂停queue

[queue setSuspended:NO]; // 继续queue

线程同步

当有一个线程在对内存中的某一资源进行操作时,其他线程都不可以对这个资源进行操作,直到该线程完成操作。这样做主要是为了保证一些资源数据的安全性和可靠性。

实现线程同步可以采用以下方式:

互斥锁

//Objective-C
@synchronized(self) {
//需要执行的代码块
} //SWIFT
objc_sync_enter(self)
//需要执行的代码块
objc_sync_exit(self) 

同步执行

可以使用上文介绍的串行队列知识,把每一个要访问此段资源的代码都添加到同一个串行队列中,这样就可以实现线程同步。

总结

第一次写博客,把自己以前用到的知识进行归纳总结,还是挺有用的。以后还会写更多东西分享给大家,同时也方便自己查阅。

感谢简书上的作者伯恩的遗产(http://www.jianshu.com/p/0b0d9b1f1f19)给了我很多帮助。

iOS多线程方案总结及使用详解的更多相关文章

  1. iOS多线程(上)——GCD详解(上)

    GCD(Grand central Dispatch)是Apple开发的一个多核编程的较新的解决方法.它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统.下面我讲讲述关于GCD的点,通篇读完 ...

  2. 李洪强iOS经典面试题156 - Runtime详解(面试必备)

    李洪强iOS经典面试题156 - Runtime详解(面试必备)   一.runtime简介 RunTime简称运行时.OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制. 对于C ...

  3. iOS开发--常用技巧 (MJRefresh详解)

         iOS开发--常用技巧 (MJRefresh详解) https://github.com/CoderMJLee/MJRefresh 下拉刷新01-默认 self.tableView.head ...

  4. ios开发——实战OC篇&FMDB详解

    FMDB详解 前一篇文章中我们介绍的SQLite的使用,在iOS中原生的SQLite API在使用上相当不友好. 于是,就出现了一系列将SQLite API进行封装的库,例如FMDB.Plausibl ...

  5. iOS学习——iOS项目Project 和 Targets配置详解

    最近开始学习完整iOS项目的开发流程和思路,在实际的项目开发过程中,我们通常需要对项目代码和资料进行版本控制和管理,一般比较常用的SVN或者Github进行代码版本控制和项目管理.我们iOS项目的开发 ...

  6. iOS开发——屏幕适配篇&Masonry详解

    Masonry详解 前言 MagicNumber -> autoresizingMask -> autolayout 以上是纯手写代码所经历的关于页面布局的三个时期 在iphone1-ip ...

  7. [置顶] Objective-C ,ios,iphone开发基础:UIAlertView使用详解

    UIAlertView使用详解 Ios中为我们提供了一个用来弹出提示框的类 UIAlertView,他类似于javascript中的alert 和c#中的MessageBox(); UIAlertVi ...

  8. Java SE之快速失败(Fast-Fail)与快速安全(Fast-Safe)的区别[集合与多线程/增强For](彻底详解)

    声明 特点:基于JDK源码进行分析. 研究费时费力,如需转载或摘要,请显著处注明出处,以尊重劳动研究成果:博客园 - https://www.cnblogs.com/johnnyzen/p/10547 ...

  9. iOS学习——(转)UIResponder详解

    本文转载自:ios开发 之 UIResponder详解 我们知道UIResponder是所有视图View的基类,在iOS中UIResponder类是专门用来响应用户的操作处理各种事件的,包括触摸事件( ...

随机推荐

  1. typedef void (*funcptr)(void) typedef void (*PFV)(); typedef int32_t (*PFI)();

    看到以下代码,不明白查了一下: /** Pointer to Function returning Void (any number of parameters) */ typedef void (* ...

  2. 第二篇:怕碰到是因为没掌握,来吧,zTree!

    一直以来看见web项目中的树就头疼.这次又给碰上了,什么也别说,这次自己整理一个版本出来实践一下.zTree v3.2的API界面非常清爽,但是在查看API之前,你需要自己先实践一下,知道基本的概念和 ...

  3. ASP.Net调整允许上传文件的大小

    1.用户上传视频文件注意:调整允许上传文件的大小:ASP.Net为了防止过大的http恶意请求阻塞网站,所以限定了每次上传文件最大4M,asp.net1.1中把用户上传的文件先放到内存中,2.0后如果 ...

  4. 【颓废篇】人生苦短,我用python(一)

    谁渴望来一场华(ang)丽(zang)的python交易! 最近突然产生了系统学习python的想法. 其实自从上次luogu冬日绘板dalao们都在写脚本就有这种想法了. 最近被计算几何势力干翻的我 ...

  5. UNIX环境高级编程------apue.h找不到

    运行1-3代码时,出现问题:apue.h 没有找到问题 1.去此网址下载源码: http://www.apuebook.com/code3e.html 压缩包名为:src.3e.tar.gz 2.解压 ...

  6. SpringMVC参数绑定(未完待续)

    1. Strut2与SpringMVC接收请求参数的区别 Struts2通过action类的成员变量接收SpringMVC通过controller方法的形参接收 2. SpringMVC参数绑定流程 ...

  7. leetcode-123-买卖股票的最佳时机③

    题目描述: 方法一: class Solution: def maxProfit(self, prices: List[int]) -> int: dp_i1_0 = 0 dp_i1_1 = f ...

  8. Nginx 和 Gunicorn 部署 Django项目

    目录 Nginx 和 Gunicorn 部署 Django项目 配置Nginx 安装配置Gunicorn 通过命令行直接启动 Gunicorn 与 uwsgi 的区别,用哪个好呢 Gunicorn u ...

  9. 模拟+贪心——cf1131E

    超级恶心的题,写了好久,直接倒序模拟做,但是网上有博客好像是直接正序dp做的.. 因为左端点和右端点是永远不会变的,然后情况要考虑全 /* 从后往前插 只要记录左连续,右连续,中间连续 左端点一定是L ...

  10. axios请求头几种区别:application/x-www-form-urlencoded

    今天小伙伴问我们项目axios默认请求头是application/x-www-form-urlencoded;charset=UTF-8, 现在有个后端接口要求请求头方式为application/js ...