iOS 多线程的实现方式及应用示例

本文介绍了iOS中多线程的实现方式,包括NSThread的创建与应用,深入探讨了GCD的同步、异步、串行与并行队列,以及GCD的相关函数如dispatch_barrier、dispatch_after等。此外,还讲解了NSOperation和NSOperationQueue的使用,包括自定义Operation和依赖管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述


优点:
  • 把程序中耗时的任务放到后台去处理,如图片、视频的下载等;
  • 充分发挥多核处理器的优势,并发执行让系统运行的更快、更流畅、用户体验更佳。
不足:
  • 大量的线程操作会降低代码的可读性;
  • 大量的线程需要更多的内存空间;
  • 当多个线程对同一资源出现争夺的时候会出现线程安全问题。

目前实现多线程的技术有四种:pthread、NSThread、GCD和NSOperation。

  • pthread:是一套基于C语言的通用多线程API,线程的生命周期需要我们手动管理,使用难度较大,所以我们几乎不会使用。
  • NSThread:是一套面向对象的API,线程的生命周期需要我们手动管理,简单易用,可直接操作线程对象。
  • GCD:是一套底层基于C语言的多线程API,自动管理生命周期,充分利用了多核处理器的优点。
  • NSOperation:是一套底层基于GCD面向对象的多线程API,自动管理生命周期,并且比GCD多了一些更简单实用的功能。

在这里我们暂且不讨论pthread的使用,主要看看后面三个方案都是怎么应用的。

NSThread


一个NSThread对象就代表一个线程,使用NSThread有多种创建线程的方式:

1. 先创建再启动


    // 创建
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"Axe"];
    // 启动
    [thread start];

2. 直接创建并启动线程


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

3. 创建并启动

    [self performSelectorInBackground:@selector(run:) withObject:@"Axe"];
    [NSThread sleepForTimeInterval:2.0];

从三种创建线程的方法可以看到:方法2和3都可以更方便的创建一个线程,并且自启动。而方法一可以很方便的拿到线程对象。

4.应用示例


以下载一张图片为例:

    NSURL *url = [NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/pic/item/203fb80e7bec54e753da379aba389b504fc26a7b.jpg"];
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImageWithURL:) object:url];
    [thread start];
    - (void)downloadImageWithURL:(NSURL *)url {
        NSError *error;
        NSData *data = [NSData dataWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:&error];
        if (error) {
            NSLog(@"error = %@",error);
        } else {
            UIImage *image = [UIImage imageWithData:data];
            [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
        }

    }

GCD


GCD全称是Grand Central Dispatch,可译为“中枢调度器”。GCD是苹果公司为多核的并行运算提出的解决方案,它会自动利用更多的CPU内核来开启线程执行任务。
在了解GCD之前先明白同步/异步、并行/串行这几个名词的概念。
  • 同步线程:在当前线程可立即执行任务,不具备开启线程的能力,会阻塞当前的线程,必须等待同步线程中的任务执行完并返回后,才会执行下一个任务。
  • 异步线程:在当前线程结束后执行任务,具备开启新的线程的能力。
  • 并行队列:允许多个任务同时执行。
  • 串行队列:只有等上一个任务执行完毕后,下一个任务才会被执行。

创建串行队列


    dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

创建并行队列

    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

创建同步+串行队列

    //在当前线程,立即执行任务
    dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"%@",[NSThread currentThread]);
        }
    });

创建同步+并行队列

    //在当前线程,立即执行任务
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(concurrentQueue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"%d --- %@",i, [NSThread currentThread]);
        }
    });

创建异步+串行队列

    //另开启线程,多个任务顺序执行
    dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(serialQueue, ^{
        
        dispatch_async(serialQueue, ^{
            for (int i = 0; i < 10; i++) {
                NSLog(@"%d --- %@",i, [NSThread currentThread]);
            }
        });
        
        dispatch_async(serialQueue, ^{
            for (int i = 0; i < 10; i++) {
                NSLog(@"%d --- %@",i, [NSThread currentThread]);
            }
        });
    });

创建异步+并行队列

    //另开启线程,多个任务一起执行,不分先后
    dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(concurrentQueue, ^{
        
        dispatch_async(serialQueue, ^{
            for (int i = 0; i < 10; i++) {
                NSLog(@"%d --- %@",i, [NSThread currentThread]);
            }
        });
        
        dispatch_async(serialQueue, ^{
            for (int i = 0; i < 10; i++) {
                NSLog(@"%d --- %@",i, [NSThread currentThread]);
            }
        });
    });

应用示例

还是以下载一张图片为例
    NSURL *url = [NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/pic/item/203fb80e7bec54e753da379aba389b504fc26a7b.jpg"];
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.download.image", DISPATCH_QUEUE_CONCURRENT);
    
    __weak typeof(self) weakSelf = self;
    
    dispatch_async(concurrentQueue, ^{
        
        NSError *error;
        
        NSData *data = [NSData dataWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:&error];
        
        if (error) {
            NSLog(@"error = %@",error);
        } else {
            UIImage *image = [UIImage imageWithData:data];
            dispatch_async(dispatch_get_main_queue(), ^{
                weakSelf.imageView.image = image;
            });
        }
    });

GCD其他函数的应用


dispatch_barrier 栅栏


功能描述:先执行栅栏前面的队列,再执行栅栏中的队列,等待栅栏中的队列执行完毕后,才会执行栅栏后面的队列。
注意事项:栅栏在并行队列中使用才有它的意义,强行在无序执行的队列中创造出顺序执行的队列任务,当不能使全局的并行队列,一般会自己创建我们的并行队列
代码示例:
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(concurrentQueue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"1-->%d --- %@",i, [NSThread currentThread]);
        }
    });
    
    dispatch_async(concurrentQueue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"2-->%d --- %@",i, [NSThread currentThread]);
        }
    });
    
    dispatch_barrier_async(concurrentQueue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"3-->%d --- %@",i, [NSThread currentThread]);
        }
    });
    
    dispatch_async(concurrentQueue, ^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"4-->%d --- %@",i, [NSThread currentThread]);
        }
    });
通过log输出可以看到,1和2队列的任务会先无序执行,在其两个队列中的任务执行完毕后,才会执行栅栏队列中的任务,最后才执行队列4。

dispatch_after 延迟


    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
    });

dispatch_once 执行一次


    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //  执行一次
    });

 
应用:常用用于单例中。

dispatch_apply   快速迭代


    NSArray *array = @[@"a",@"b",@"c",@"d",@"e"];
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(array.count, globalQueue, ^(size_t idx) {
        NSLog(@"array[%zu] = %@",idx, array[idx]);
    });


dispatch_group 队列组


功能描述:将多个队列添加到一个队列组中,当队列组中的任务都执行完毕后,会通知我们执行结果。

代码示例:

    dispatch_group_t group = dispatch_group_create();
    
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_group_async(group, globalQueue, ^{
        NSLog(@"任务1");
    });
    
    dispatch_group_async(group, globalQueue, ^{
        NSLog(@"任务3");
    });
    
    dispatch_group_async(group, globalQueue, ^{
        NSLog(@"任务2");
    });
    
    dispatch_group_notify(group, globalQueue, ^{
        NSLog(@"所有任务都已完成");
    });
    
    dispatch_group_async(group, globalQueue, ^{
        NSLog(@"任务4");
    });

GCD定时器


    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, globalQueue);
    
    // 3s后定时器启动
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
    
    // 每1秒执行一次回调
    dispatch_source_set_timer(timer, start, 2.0 * NSEC_PER_SEC, 0);
    
    // 计时
    __block int count = 0;
    
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"%d", count);
        if (count > 10) {
            dispatch_cancel(timer);
        }
        count++;
    });
    dispatch_resume(timer);

NSOperation


NSOperation是一个抽象类,并不具备封装操作的能力,我们必须使用它的子类,为此系统也提供了NSInvocationOperation和NSBlockOperation两个子类供我们使用。当然我们也可以继承NSOperation,创建我们的子类,实现内部相应的方法。

NSInvocationOperation

    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImageWithURL:) object:url];
    [invocationOperation start];
默认情况下,调用start方法后,并不会开启一个新的线程去执行selector,而是在当前线程同步的执行操作,只有将NSOperation添加到NSOperationQueue中,才会执行异步操作。

NSBlockOperation

    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        // 在主线程中执行
        NSLog(@"操作1 --- %@", [NSThread currentThread]);
    }];
    
    [blockOperation addExecutionBlock:^{
        // 在子线程中执行
        NSLog(@"操作2 --- %@", [NSThread currentThread]);
    }];
    
    // 添加额外的任务数大于1才会异步执行
    
    [blockOperation addExecutionBlock:^{
        // 在子线程中执行
        NSLog(@"操作3 --- %@", [NSThread currentThread]);
    }];
    
    [blockOperation addExecutionBlock:^{
        // 在子线程中执行
        NSLog(@"操作4 --- %@", [NSThread currentThread]);
    }];
    
    [blockOperation start];

Operation的其它用法


执行操作

执行一个Operation有两种方式,一种是手动调用start,这种方法调用会在当前线程进行同步执行,因此在主线程里面一定要小心调用,不然就会堵塞主线程。另一种是自动执行,只要将operation添加到operationQueue中,就会尽快执行操作。

取消操作

NSOperation开始执行操作后,会默认一直到操作完成,当然中途我们也可以调用cancel取消操作。
在调用cancel方法的时候,只是将cancelled设置为了YES,因此在每个操作开始前,或者在每个有意义的实际操作完成后,都要先检测isCancelled是否被设置成了YES, 如果已经取消了,那么后面的操作就不用在执行了。

操作完成后的操作

如果想在NSOperation执行完操作后做一些事情,可以调用completionBlock设置,在操作完成后就会回调block里面的内容。

自定义Operation


如果系统提供的NSInvocationOperation和NSBlockOperation两个子类不能满足需求时,我们通过继承自定义之类,并添加需要执行的操作。

这里我们仍然以下载图片为例:
//
//  MyOperation.h
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

typedef void(^MyDownloadFinishedBlock)(UIImage *image);

@interface MyOperation : NSOperation
@property (copy, nonatomic, readonly) NSString *imageURL;

- (instancetype)initWithURLString:(NSString *)urlString downloadFinishedBlock:(MyDownloadFinishedBlock)downloadFinishedBlock;
@end
//
//  MyOperation.m
//

#import "MyOperation.h"

@interface MyOperation ()
@property (copy, nonatomic) MyDownloadFinishedBlock downloadFinishedBlock;
@end

@implementation MyOperation

- (instancetype)initWithURLString:(NSString *)urlString downloadFinishedBlock:(MyDownloadFinishedBlock)downloadFinishedBlock {
    self = [super init];
    if (self) {
        _imageURL = urlString;
        _downloadFinishedBlock = downloadFinishedBlock;
    }
    return self;
}

- (void)main {
    
    // 如果时在异步线程中执行操作,即main方法在异步线程中调用,那么将无法访问主线程的自动释放池,因此创建一个属于当前线程的自动释放池
    
    @autoreleasepool {
        
        // 在main方法中定期的调用isCancelled方法检测操作是否已经被取消
        // 在执行任何实际的工作之前,也就是main方法的开头,就要检测操作是否已经被取消
        // 在执行一段耗时的操作后也需要检测操作是否已经被取消
        
        if (self.isCancelled) {
            return;
        }
        
        NSURL *url = [NSURL URLWithString:_imageURL];
        
        // 此处就是通过网络获取图片data,是比较耗时的操作,所以在后面就需要检测操作是否已经被取消
        NSData *imageData = [NSData dataWithContentsOfURL:url];
        
        if (self.isCancelled) {
            url = nil;
            _imageURL = nil;
            
            return;
        }
        
        UIImage *image = [UIImage imageWithData:imageData];
        
        if (self.isCancelled) {
            image = nil;
            
            return;
        }
        
        if (_downloadFinishedBlock) {
            _downloadFinishedBlock(image);
        }
        
    }
    
}
@end
    MyOperation *operation = [[MyOperation alloc] initWithURLString:@"http://f.hiphotos.baidu.com/image/pic/item/203fb80e7bec54e753da379aba389b504fc26a7b.jpg" downloadFinishedBlock:^(UIImage *image) {
        NSLog(@"%@",[NSThread currentThread]);
        
        [_imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
    }];
    
    [operation start];
说明:如果你要执行一个同步操作,那只需要重写main方法,在里面添加必要的操作。如果执行一个异步的操作,那就需要重写start方法,因为在你把操作添加进去后系统会自动调用start方法,这时将不在调用mian里面的操作。

NSOperationQueue


一个NSOperation对象可以通过start方法来执行任务,默认是同步执行的。也可以将其添加到NSOperationQueue操作队列中去执行,它是异步执行的。

添加NSOperation到NSOperationQueue中


不管通过那种方式添加,只要将operation添加到operationQueue中,通常短时间内就会得到执行(异步)。但是如果存在依赖,或者整个operationQueue被暂停等原因,也可能需要等待。

添加一个operation
    [operationQueue addOperation:invocationOperation];
添加一组operation
    [operationQueue addOperations:@[invocationOperation, blockOperation] waitUntilFinished:YES];
添加一个block形式的operation
    [operationQueue addOperationWithBlock:^{
        NSLog(@"任务");
    }];

添加依赖

概述

所谓依赖就是说,当某个operation对象需要依赖与其它operation对象才能完成时,就可通过addDependency方法添加一个或者多个依赖对象,只有所有依赖的对象都已经完成操作后,当前的operation对象才开始执行操作,当然也可以通过removeDependency方法来移除这种依赖关系。


依赖方式


既可以在同一个operationQueue中不同operation对象添加依赖,也可以在不同的operationQueue之间不同operation对象之间添加依赖,operation对象会管理自己的依赖关系。

限制依赖

虽然可以在一个queue中添加依赖,也可以在不同的queue中添加依赖,但是要特别注意的是,不能添加环形依赖,即a依赖b,b也依赖a。

应用示例:

这里以修改用户头像为例,模拟从用户上传头像到显示头像的过程。

    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1.上传头像");
    }];
    
    NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2.从服务器请求上传头像的url");
    }];
    
    NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3.通过url下载头像");
    }];
    
    [operationQueue addOperation:blockOperation1];
    [operationQueue addOperation:blockOperation2];
    [operationQueue addOperation:blockOperation3];

打印顺序如下:2 - 1 - 3 或者 3 - 2 -1 

这样的顺序显然不符合正常的逻辑顺序,这时候,我们就可以通过添加依赖,达到我们说期望的顺序逻辑。

    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"1.上传头像");
    }];
    
    NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"2.从服务器请求上传头像的url");
    }];
    
    NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"3.通过url下载头像");
    }];
    
    
    // 3 -> 2  2 -> 1 (3 - 2 - 1)
    [blockOperation3 addDependency:blockOperation2];
    [blockOperation2 addDependency:blockOperation1];
    
    
    [operationQueue addOperation:blockOperation1];
    [operationQueue addOperation:blockOperation2];
    [operationQueue addOperation:blockOperation3];


打印顺序如下:1 - 2 - 3
         
这样就符合我们期望的逻辑,即用户先上传图片并完成后,再从服务器获取该用户头像的url地址,最后通过url地址下载相应的头像并显示。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值