一、进程(Process)
在 iOS 开发中,进程是一个基本的概念,虽然通常作为开发者,你不需要像在某些其他操作系统那样进行直接的进程管理,因为 iOS 提供了很多高级别的抽象。不过,了解进程的概念对于理解应用程序的运行环境仍然很重要。
1.进程定义
进程是一个执行中的程序的实例。它是系统资源(如 CPU 时间、内存空间、文件描述符等)分配的基本单位。每个进程都运行在其自己的隔离空间内,操作系统负责管理进程的生命周期以及它们对系统资源的使用。
2.iOS进程特点
沙盒环境
出于安全考虑,iOS 上的每个应用程序都运行在一个称为“沙盒”的隔离环境中。这意味着应用程序只能访问分配给自己的文件系统目录,并受限于对设备其他部分的访问。这有助于防止恶意软件破坏系统或窃取数据。
单进程模型
iOS 应用通常作为单个进程运行。尽管应用可以启动多个线程,但它们都是属于同一个进程的。
生命周期管理
iOS 操作系统严格管理应用进程的生命周期。应用程序状态(如启动、挂起、恢复、终止)由系统根据资源需求和用户行为管理。
后台执行限制
为了保持电池寿命和系统性能,iOS 对后台进程的执行有严格的限制。应用程序通常情况下在退到后台后会被挂起,除非它们声明了后台执行的任务。
内存管理
iOS 使用引用计数(如 ARC - Automatic Reference Counting)来管理内存。当系统内存不足时,它会自动终止后台进程来释放资源。
接口限制
iOS 应用程序不能直接调用大多数系统调用,而是通过 iOS SDK 提供的 API 与操作系统交互。这些 API 为进程间通信、资源访问等提供了接口。
3.进程间通信
在 iOS 中,进程间通信被限制和控制,但仍然提供了几种机制:
- URL Schemes:应用可以注册 URL schemes 来与其他应用交互。
- UIActivityViewController:允许用户在不同的应用之间分享内容。
- Universal Links:通过网页链接直接打开应用,提供更加无缝的体验。
- App Extensions:允许应用在某些受限的环境中(如通知中心、键盘扩展等)提供功能。
- Handoff:支持在不同设备的同一应用之间继续任务。
- 共享容器:允许同一开发者的多个应用共享数据。
二、线程(Thread)
在iOS开发中,线程是一个比进程更轻量级的执行单元。一个进程内可以有多个线程,它们共享进程的内存空间和资源,但是每个线程拥有自己的执行序列、栈空间和线程状态。线程可以并行或并发地执行多个任务,从而提高应用程序的效率和响应性。
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
1.线程的基本概念
线程隔离
每个线程都有自己的私有栈空间,执行上下文和程序计数器。
创建成本
线程的创建和上下文切换比进程轻量得多。
资源共享
同一进程下的线程共享进程的内存和资源。
同步机制
为了防止数据冲突,通常需要通过锁、信号量等同步机制来控制线程对共享资源的访问。
2.iOS中的线程管理
iOS提供了几种不同的方式来管理和使用线程:
(1).POSIX Threads (pthreads)
这是一种跨平台的线程API,允许更精细的线程控制。但它不提供自动内存管理,并且使用起来比较复杂。
Swift:
import Foundation// 线程执行的函数
func threadFunction(arg: UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer? {print("Thread is now running")// 执行一些任务print("Thread finishing")return nil
}// 创建线程
var thread = pthread_t(bitPattern: 0)
var attribute = pthread_attr_t()pthread_attr_init(&attribute)
pthread_create(&thread, &attribute, { arg inreturn threadFunction(arg: arg)
}, nil)
pthread_attr_destroy(&attribute)// 等待线程结束
pthread_join(thread!, nil)
OC:
#import <Foundation/Foundation.h>
#import <pthread.h>void *threadFunction(void *arg) {NSLog(@"Thread is now running");// 执行一些任务NSLog(@"Thread finishing");return NULL;
}int main(int argc, const char * argv[]) {@autoreleasepool {pthread_t thread;pthread_attr_t attribute;pthread_attr_init(&attribute);pthread_create(&thread, &attribute, threadFunction, NULL);pthread_attr_destroy(&attribute);// 等待线程结束pthread_join(thread, NULL);}return 0;
}
(2).NSThread
NSThread是Objective-C提供的面向对象的线程操作方式。它相对于pthreads简单一些,但仍然需要开发者手动管理线程的生命周期和同步。
Swift:
import Foundation// 线程调用的方法
@objc class ThreadExample: NSObject {@objc func threadMethod() {print("Thread is now running")// 执行一些任务print("Thread finishing")}
}// 创建并启动线程
let example = ThreadExample()
let thread = Thread(target: example, selector: #selector(ThreadExample.threadMethod), object: nil)
thread.start()// 主线程继续执行其它任务
print("Main thread is not blocked")
OC:
#import <Foundation/Foundation.h>@interface ThreadExample : NSObject
- (void)threadMethod;
@end@implementation ThreadExample
- (void)threadMethod {@autoreleasepool {NSLog(@"Thread is now running");// 执行一些任务NSLog(@"Thread finishing");}
}
@endint main(int argc, const char * argv[]) {@autoreleasepool {ThreadExample *example = [[ThreadExample alloc] init];NSThread *thread = [[NSThread alloc] initWithTarget:example selector:@selector(threadMethod) object:nil];[thread start];// 主线程继续执行其它任务NSLog(@"Main thread is not blocked");}return 0;
}
(3).Grand Central Dispatch (GCD)
GCD是Apple推荐的多线程解决方案,它使用队列来抽象线程的创建和管理。GCD可以自动管理线程池,优化线程数量,并提供了简单的API来执行异步任务。
Swift:
import Foundation// 创建串行队列
let serialQueue = DispatchQueue(label: "com.example.serialQueue")// 创建并发队列
let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)// 在串行队列上异步执行任务
serialQueue.async {for i in 1...3 {print("Serial Queue Async Task \(i)")}
}// 在并发队列上异步执行任务
concurrentQueue.async {for i in 1...3 {print("Concurrent Queue Async Task \(i)")}
}// 在主队列上延迟执行任务
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {print("This task is run on the main queue after 2 seconds")
}// 使用全局并发队列
DispatchQueue.global().async {print("Global queue async task")
}// 主线程继续执行其他任务
print("Main thread is not blocked")
下面示例代码的打印顺序?
override func viewDidLoad() {super.viewDidLoad()DispatchQueue.main.async {print("1")}print("2")// 创建串行队列let serialQueue = DispatchQueue(label: "com.example.serialQueue")// 在串行队列上异步执行任务serialQueue.async {print("3")}print("4")// 创建并发队列let concurrentQueue = DispatchQueue(label: "com.example.concurrentQueue", attributes: .concurrent)// 在并发队列上异步执行任务concurrentQueue.async {print("5")}print("6")DispatchQueue.global().sync {print("7")}print("8")// 获取一个后台队列(优先级为默认)let backgroundQueue = DispatchQueue.global(qos: .background)// 在后台队列上异步执行任务backgroundQueue.async {// 放置后台线程需要执行的任务print("9")}print("10")DispatchQueue.global().async {print("11")}print("12")}
DispatchQueue.main.async { print("1") }
这个异步调用将任务排队到主队列,但是它不会立即执行。由于当前代码块已经在主线程上执行(viewDidLoad
),所以这个异步任务将会在当前代码块完成后的某个时间点执行。serialQueue.async { print("3") }
在一个自定义的串行队列上异步执行。它将会在某个不确定的时间点执行,因为它不会阻塞当前线程。但由于它是串行队列,它会保证按照被添加到队列中的顺序执行。concurrentQueue.async { print("5") }
在一个自定义并发队列上异步执行。它可能几乎立即开始执行,但实际的执行时间取决于系统的调度。DispatchQueue.global().sync { print("7") }
在全局并发队列上同步执行,这将会阻塞主线程,直到这个同步任务完成,所以print("7")
会在print("6")
之后,而且在print("8")
之前执行。backgroundQueue.async { print("9") }
在一个全局队列上异步执行,这个队列的优先级是后台。它将在未来某个不确定的时间点执行,不会阻塞主线程。但由于QoS 级别是一个提示给系统的优先级标识,用于决定哪些任务应该先执行。.background
QoS 是最低的优先级,所以执行顺序会靠后。DispatchQueue.global().async { print("11") }
在全局并发队列上异步执行,它会在未来某个不确定的时点执行。
综上所述,同步执行的 print
调用将按顺序立即打印,按照 2, 4, 6, 7, 8, 10, 12 的顺序。异步派发的任务,print("1")确定会在最后打印. 其它
的print("3")
, print("5")
, print("9")
, 和 print("11")
将在未来某个时点执行,它们的执行顺序取决于系统对任务的调度和队列的类型(串行或并发), 但print("9")级别最低会在最后执行
。因此,其他异步任务的执行顺序是不确定的。
实际打印顺序为:
2/4/3/6/5/7/8/10/12/11/9/1
也打印成这种:
2/4/6/7/8/3/5/10/12/11/9/1
也会打印成这种 :
2/4/6/3/5/7/8/10/12/11/9/1
OC:
#import <Foundation/Foundation.h>int main(int argc, const char * argv[]) {@autoreleasepool {// 创建串行队列dispatch_queue_t serialQueue = dispatch_queue_create("com.example.serialQueue", DISPATCH_QUEUE_SERIAL);// 创建并发队列dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);// 在串行队列上异步执行任务dispatch_async(serialQueue, ^{for (int i = 1; i <= 3; i++) {NSLog(@"Serial Queue Async Task %d", i);}});// 在并发队列上异步执行任务dispatch_async(concurrentQueue, ^{for (int i = 1; i <= 3; i++) {NSLog(@"Concurrent Queue Async Task %d", i);}});// 在主队列上延迟执行任务dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"This task is run on the main queue after 2 seconds");});// 使用全局并发队列dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{NSLog(@"Global queue async task");});// 主线程继续执行其他任务NSLog(@"Main thread is not blocked");}return 0;
}
(4).Operation Queues
Operation Queues是基于GCD但更高层次的抽象。它使用NSOperation对象来表示单个任务,可以设置任务的依赖关系,并通过NSOperationQueue来管理任务的执行。
Swift:
import Foundation// 创建一个操作队列
let operationQueue = OperationQueue()// 创建一个NSBlockOperation来执行任务
let operation1 = BlockOperation {print("Operation 1 is being executed")// 模拟一些工作Thread.sleep(forTimeInterval: 2)print("Operation 1 has finished executing")
}// 创建另一个操作
let operation2 = BlockOperation {print("Operation 2 is being executed")// 模拟一些工作Thread.sleep(forTimeInterval: 1)print("Operation 2 has finished executing")
}// 添加操作依赖,operation2将会在operation1完成后才开始执行
operation2.addDependency(operation1)// 将操作添加到队列中
operationQueue.addOperation(operation1)
operationQueue.addOperation(operation2)// 添加一个操作到队列中,使用addOperation的闭包形式
operationQueue.addOperation {print("Operation 3 is being executed")// 执行一些工作print("Operation 3 has finished executing")
}// 主线程继续执行其他任务
print("Main thread is not blocked")
OC:
#import <Foundation/Foundation.h>int main(int argc, const char * argv[]) {@autoreleasepool {// 创建操作队列NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];// 创建NSBlockOperation来执行任务NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{NSLog(@"Operation 1 is being executed");// 模拟一些工作[NSThread sleepForTimeInterval:2];NSLog(@"Operation 1 has finished executing");}];// 创建另一个操作NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{NSLog(@"Operation 2 is being executed");// 模拟一些工作[NSThread sleepForTimeInterval:1];NSLog(@"Operation 2 has finished executing");}];// 添加操作依赖,operation2将会在operation1完成后才开始执行[operation2 addDependency:operation1];// 将操作添加到队列中[operationQueue addOperation:operation1];[operationQueue addOperation:operation2];// 使用addOperationWithBlock添加另一个操作到队列中[operationQueue addOperationWithBlock:^{NSLog(@"Operation 3 is being executed");// 执行一些工作NSLog(@"Operation 3 has finished executing");}];// 主线程继续执行其他任务NSLog(@"Main thread is not blocked");}return 0;
}
3.线程的同步
由于线程共享内存和资源,同步变得非常重要。
iOS提供了多种同步机制来避免数据竞争和条件竞争:
(1).锁(Locks)
如NSLock、NSRecursiveLock、NSCondition等,用于控制对共享资源的访问。
Swift:
import Foundationvar sharedResource = [String]()let lock = NSLock()func accessSharedResource() {lock.lock()// 安全地访问共享资源sharedResource.append("Data")lock.unlock()
}// 在不同的线程中调用accessSharedResource()
DispatchQueue.global().async {accessSharedResource()
}DispatchQueue.global().async {accessSharedResource()
}
OC:
#import <Foundation/Foundation.h>NSMutableArray *sharedResource = [NSMutableArray array];NSLock *lock = [[NSLock alloc] init];void accessSharedResource() {[lock lock];// 安全地访问共享资源[sharedResource addObject:@"Data"];[lock unlock];
}// 在不同的线程中调用accessSharedResource()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{accessSharedResource();
});dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{accessSharedResource();
});
(2).信号量(Semaphores)
GCD中的dispatch_semaphore_t
可以用来控制对资源的并发访问。
Swift:
import Foundationlet semaphore = DispatchSemaphore(value: 1)func accessSharedResourceWithSemaphore() {semaphore.wait() // 等待信号量// 安全地访问共享资源sharedResource.append("Data")semaphore.signal() // 释放信号量
}// 在不同的线程中调用accessSharedResourceWithSemaphore()
DispatchQueue.global().async {accessSharedResourceWithSemaphore()
}DispatchQueue.global().async {accessSharedResourceWithSemaphore()
}
OC:
#import <Foundation/Foundation.h>dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);void accessSharedResourceWithSemaphore() {dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 等待信号量// 安全地访问共享资源[sharedResource addObject:@"Data"];dispatch_semaphore_signal(semaphore); // 释放信号量
}// 在不同的线程中调用accessSharedResourceWithSemaphore()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{accessSharedResourceWithSemaphore();
});dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{accessSharedResourceWithSemaphore();
});
(3).原子操作
如OSAtomic
系列函数,提供了简单的原子性操作。
#import <libkern/OSAtomic.h>__block volatile int32_t counter = 0;void incrementCounter() {OSAtomicIncrement32(&counter);
}// 在不同的线程中调用incrementCounter()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{incrementCounter();
});dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{incrementCounter();
});
(4).@synchronized
Objective-C中的关键字,可以简化锁的使用。
#import <Foundation/Foundation.h>void accessSharedResourceWithSynchronized() {@synchronized(sharedResource) {// 安全地访问共享资源[sharedResource addObject:@"Data"];}
}// 在不同的线程中调用accessSharedResourceWithSynchronized()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{accessSharedResourceWithSynchronized();
});dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{accessSharedResourceWithSynchronized();
});
4.主线程和后台线程
iOS中有一个特别重要的线程概念是主线程(Main Thread):
主线程
用于更新UI和处理UI事件,以保证用户界面的流畅性。
Swift:
DispatchQueue.main.async {// 更新UI// 例如,更新一个标签的文本:// self.label.text = "Updated Text"
}
OC:
dispatch_async(dispatch_get_main_queue(), ^{// 更新UI// 例如,更新一个标签的文本:// self.label.text = @"Updated Text";
});
后台线程
用于执行耗时的任务,如网络请求或大量数据处理,避免阻塞主线程。
Swift:
DispatchQueue.global(qos: .background).async {// 在这里执行耗时的任务// 例如,进行一个网络请求或大量数据处理let result = "Some Result"// 当需要更新UI时,回到主线程DispatchQueue.main.async {// 更新UI// self.label.text = result}
}
OC:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{// 在这里执行耗时的任务// 例如,进行一个网络请求或大量数据处理NSString *result = @"Some Result";// 当需要更新UI时,回到主线程dispatch_async(dispatch_get_main_queue(), ^{// 更新UI// self.label.text = result;});
});
开发者应该始终记住在主线程上更新UI,并将耗时的工作放在后台线程上执行。例如,使用GCD可以很容易地在后台执行任务:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// 在后台线程上执行耗时任务// ...dispatch_async(dispatch_get_main_queue(), ^{// 回到主线程更新UI// ...});
});
在iOS开发中,合理使用线程对于创建流畅、响应式的应用至关重要。避免在主线程上执行耗时操作,以及处理好线程间的同步和通信,是高效iOS应用程序的标志。
在 Swift 和 Objective-C 这两种 iOS 主要的编程语言中,线程使用的概念和操作都是相似的。
三、队列(Queue)
队列可以被视为对线程行为的一层抽象,但它们并不是线程本身。
队列提供了一种机制来组织和调度执行任务的顺序,而不需要开发者直接管理线程的创建、同步和其他复杂的线程生命周期问题。
当你操控队列时,你实际上是在告诉系统如何安排这些任务的执行。在iOS中,这通常是通过使用Grand Central Dispatch (GCD) 来实现的。GCD后台自动管理着一个线程池,并根据系统的负载动态地调整线程的使用。这意味着:
- 当你向一个队列中添加任务时,你并不需要指定具体运行在哪个线程上,GCD会为你处理这些细节。
- 如果任务被添加到串行队列,GCD会保证这些任务按照他们被添加的顺序一个接一个地运行。
- 如果任务被添加到并发队列,GCD会同时运行多个任务,但具体并行的任务数目会由系统根据当前的负载情况来决定。
- 对于主队列(主线程的队列),GCD会保证所有的任务都在主线程上执行,通常用于UI更新,以确保界面的流畅性。
由于队列抽象了线程的管理,所以开发者不需要(也不应该)去假设某个任务会在特定的线程上执行。相反,应该关注的是任务的组织、同步和他们的执行方式(串行或并发)。
总之,通过操作队列,你间接地管理了线程的行为,但你并没有直接操控线程。队列的抽象让你能够更专注于任务的逻辑本身,而不是底层的线程管理。这极大地简化了并发编程,并提高了代码的安全性和可维护性。
1.队列类型
(1).串行队列(Serial Queue)
- 一次只执行一个任务。下一个任务必须等待当前任务完成后才能开始。
- 任务按照添加到队列的顺序依次执行。
- 保证任务执行的顺序性和排他性。
(2).并发队列(Concurrent Queue)
- 可以同时执行多个任务。但是任务的开始仍然按照它们被添加到队列的顺序。
- 任务可以并行执行,不保证任务的开始和结束顺序。
- 适合执行互不依赖的任务。
(3).主队列(Main Queue)
主队列是一个特殊的串行队列,它用于在主线程上执行任务。由于它在主线程上执行任务,因此它通常用于更新 UI 或处理 UI 事件,以确保用户界面的更新是平滑且同步的。
获取主队列的方式如下:
Swift:
let mainQueue = DispatchQueue.main
使用 DispatchQueue.main
,你可以将任务异步或同步地派发到主线程。通常,你会异步地将任务派发到主线程,以避免阻塞当前线程:
DispatchQueue.main.async {// 更新UI等主线程任务
}
请注意,尽管你可以同步地派发任务到主队列,但如果你在主线程上这样做,将会导致死锁,因为主线程会等待自己完成任务,这是一个逻辑错误。因此,通常你只会异步地将任务派发到主队列。
OC:
dispatch_queue_t mainQueue = dispatch_get_main_queue();
(4).全局并行队列(Global Concurrent Queues)
GCD 提供了几个不同优先级的全局并行队列,这些队列在应用程序是共享的。你可以使用它们来执行并发任务,而不需要自己创建并行队列。全局并行队列有四个优先级:高、默认、低和后台。
获取全局并行队列的方式如下:
Swift:
let globalQueue = DispatchQueue.global(qos: .default)
OC:
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
优先级可以是以下之一:
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND
(5).自定义队列(Custom Queues)
在iOS开发中,使用Grand Central Dispatch (GCD)可以创建自定义队列(Custom Queues)。自定义队列可以是串行的也可以是并行的,取决于你的需求。以下是创建自定义队列的基本步骤和示例:
创建串行队列
串行队列(Serial Queue)保证任务按照添加到队列中的顺序执行,一个接一个地执行。
Swift:
// 创建自定义串行队列
let serialQueue = DispatchQueue(label: "com.example.mySerialQueue")// 异步添加任务到队列
serialQueue.async {// 这里添加需要执行的任务print("Task 1 started")// 假设这里有一些耗时操作print("Task 1 finished")
}serialQueue.async {// 添加另一个任务print("Task 2 started")// 同样这里可能有耗时操作print("Task 2 finished")
}// Task 1 和 Task 2 将按照添加的顺序执行
OC:
// 创建自定义串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.mySerialQueue", DISPATCH_QUEUE_SERIAL);// 异步添加任务到队列
dispatch_async(serialQueue, ^{NSLog(@"Task 1 started");// 假设这里有一些耗时操作NSLog(@"Task 1 finished");
});dispatch_async(serialQueue, ^{NSLog(@"Task 2 started");// 同样这里可能有耗时操作NSLog(@"Task 2 finished");
});// Task 1 和 Task 2 将按照添加的顺序执行
创建并行队列
并行队列(Concurrent Queue)允许多个任务并发执行。任务开始的顺序仍然符合其被添加到队列中的顺序,但是它们的执行和结束顺序可能是不一致的,因为任务可以在不同的线程上并发运行。
Swift:
// 创建自定义并行队列
let concurrentQueue = DispatchQueue(label: "com.example.myConcurrentQueue", attributes: .concurrent)// 异步添加任务到队列
concurrentQueue.async {// 添加任务print("Task 1 started")// 假设耗时操作print("Task 1 finished")
}concurrentQueue.async {// 添加另一个任务print("Task 2 started")// 假设耗时操作print("Task 2 finished")
}// Task 1 和 Task 2 可能会同时执行
OC:
// 创建自定义并行队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);// 异步添加任务到队列
dispatch_async(concurrentQueue, ^{NSLog(@"Task 1 started");// 假设耗时操作NSLog(@"Task 1 finished");
});dispatch_async(concurrentQueue, ^{NSLog(@"Task 2 started");// 假设耗时操作NSLog(@"Task 2 finished");
});// Task 1 和 Task 2 可能会同时执行
设置队列的优先级
自定义队列还可以设置服务质量(Quality of Service,QoS),以指示任务的重要性和执行优先级。
Swift:
// 创建具有优先级的自定义串行队列
let highPriorityQueue = DispatchQueue(label: "com.example.myHighPriorityQueue", qos: .userInitiated)// 用户初始化的任务通常比较紧急,但不会阻塞UI,比如加载操作
highPriorityQueue.async {// 执行高优先级任务
}
服务质量(QoS)选项包括:
.userInteractive
: 需要立即结果的任务,用于更新UI、处理事件等。.userInitiated
: 用户期望立即得到结果的任务,比如滑动停止后的加载操作。.default
: 默认优先级,如果没有设置QoS,则队列会使用这个。.utility
: 长时间运行的任务,通常带有用户可见的进度指示,如下载文件。.background
: 用户不直接感知的任务,比如数据库维护、数据备份等。.unspecified
: 没有设置QoS。
通过设置不同的QoS,系统会为这些任务分配不同的资源,从而影响它们的执行优先级。
OC:
// 创建具有优先级的自定义串行队列
dispatch_queue_attr_t qosAttribute = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
dispatch_queue_t highPriorityQueue = dispatch_queue_create("com.example.myHighPriorityQueue", qosAttribute);// 用户初始化的任务通常比较紧急,但不会阻塞UI,比如加载操作
dispatch_async(highPriorityQueue, ^{// 执行高优先级任务NSLog(@"High priority task is being executed.");
});
2.同步和异步执行
在GCD中,你可以选择同步(sync)或异步(async)地将任务添加到队列中:
同步 (sync
)
在当前线程中执行任务,并阻塞该线程,直到任务完成。
Swift:
// 获取一个队列(非主队列)
let queue = DispatchQueue(label: "com.example.queue")// 同步执行任务
queue.sync {// 这个任务会立即执行,并且调用线程会等待直到任务完成
}// 这行代码只有在上面的同步任务完成后才会执行
注意:不要在主队列上同步提交任务,因为这会导致死锁。
OC:
dispatch_sync(queue, ^{// 这个任务会立即执行,并且提交任务的线程会等待直到任务完成
});// 这行代码只有在上面的任务完成后才会执行
使用同步执行时要特别注意,如果你在主队列(主线程的串行队列)上同步提交任务,将会导致死锁,因为主队列等待这个同步任务完成,而同步任务又在等待主队列(也就是它自己)空闲,从而形成了相互等待的局面。
异步 (async
)
允许任务在另一线程中执行,不会阻塞当前线程,调用线程会等待任务完成。
Swift:
// 获取一个队列
let queue = DispatchQueue(label: "com.example.queue")// 异步执行任务
queue.async {// 这个任务会被加入队列中,但不一定会立即执行
}// 这行代码会在上面的异步任务提交后立即执行,而不会等待那个任务结束
OC:
dispatch_async(queue, ^{// 这个任务会被加入队列中,但不会立即执行
});// 这行代码会在上面的异步任务提交后立即执行,而不会等待那个任务结束
异步执行是并发程序设计中的一项基本技术,允许多个任务并行进行。在 GCD 中,异步提交到串行队列将使任务一个接一个地执行,而异步提交到并发队列则可以让多个任务同时进行。
3.队列的选择和使用
在iOS开发中,选择合适的队列和执行方式(同步或异步)对于保证应用性能和响应性至关重要。以下是一些基本指南:
- 使用全局并发队列执行耗时的后台任务,如数据处理或网络请求。
- 使用主队列更新UI或执行需要在主线程完成的任务,确保UI更新是安全的。
- 使用自定义串行队列来执行需要顺序执行的任务,或者当你需要创建一个锁或者保护资源的访问时。
- 使用自定义并发队列来自定义任务的并发执行,或者当你需要控制执行任务的优先级时。
- 使用同步执行来等待当前任务完成,这通常在你需要立即得到任务结果时使用。
- 使用异步执行来允许当前线程继续工作,不需要等待任务完成,这有助于避免阻塞UI或者响应用户交互。
四、任务(Task)
在iOS开发中,"任务"一词通常指的是需要完成的工作单元。任务可以是任何事情,从简单的计算到复杂的网络请求。iOS提供了几种不同的方式来处理任务,尤其是在多线程和并发编程方面。
以下是一些iOS开发中任务的基本概念和处理方式。
1.任务的类型
计算密集型任务
这些任务需要大量的CPU资源来进行计算。
例子:图像处理,大量数据计算。
I/O密集型任务
这些任务包括文件读写、网络请求等,它们通常等待系统资源的响应。
例子:从硬盘读取文件,从服务器下载数据。
UI更新任务
这些任务涉及更新用户界面,必须在主线程上执行。
例子:刷新表视图,更新进度条。
2.GCD中的任务
在Grand Central Dispatch (GCD)中,任务通常以两种形式提交给队列:
同步执行(Synchronous Execution)
dispatch_sync
函数用于同步添加任务到指定的队列。- 调用线程会停止执行直到任务在队列中完成。
- 这种方式可能会导致死锁,尤其是当在主队列上同步提交任务时。
异步执行(Asynchronous Execution)
dispatch_async
函数用于异步添加任务到指定的队列。- 调用线程不会等待任务完成,可以继续执行其他工作。
- 异步执行是实现并发的主要方式。
使用GCD
创建任务
let queue = DispatchQueue(label: "com.example.myQueue", attributes: .concurrent)queue.async {// 执行耗时的任务let result = performCalculations()DispatchQueue.main.async {// 在主线程更新UIupdateUI(with: result)}
}
任务的取消和暂停
在GCD中,你不能直接取消已经在执行的任务,但是你可以通过在任务代码中添加取消检查点来协作地取消任务。
let queue = DispatchQueue(label: "com.example.myQueue")
var isCancelled = falsequeue.async {if isCancelled { return }// 执行任务
}// 取消任务
isCancelled = true
3.NSOperation中的任务
NSOperation
是另一种用于执行任务的抽象,它比GCD提供更多的控制和灵活性。任务以NSOperation
对象的形式表示,可以添加到NSOperationQueue
中执行。
NSOperation
- 是一个抽象类,需要使用其子类
NSBlockOperation
或自定义子类。 - 可以设置完成块、添加依赖以及观察任务状态。
NSOperationQueue
- 管理一组
NSOperation
对象的执行。 - 可以控制并发操作的数量,以及启动、暂停、取消操作。
使用NSOperation
创建任务
let queue = OperationQueue()let operation = BlockOperation {// 执行耗时的任务let result = performCalculations()OperationQueue.main.addOperation {// 在主线程更新UIupdateUI(with: result)}
}queue.addOperation(operation)
任务的取消和暂停
在NSOperation中,你可以使用cancel
方法来请求取消一个操作,操作对象会检查isCancelled
属性决定是否提前结束任务。
let operation = BlockOperation {// 执行任务if operation.isCancelled {return}// 继续执行任务
}operationQueue.addOperation(operation)// 取消操作
operation.cancel()
4.任务的挑战
在处理任务时,开发者必须注意以下几个挑战:
线程安全
确保在多线程环境中共享资源的访问是安全的,例如使用锁或串行队列。
性能
合理利用多线程和并发,避免过多地创建线程导致的上下文切换开销。
死锁
避免因同步执行或资源竞争造成的死锁情况。
资源管理
合理分配和回收资源,如内存、文件句柄等。
用户体验
确保耗时操作不会阻塞主线程,影响UI的流畅度。