多线程第二弹 - 常用的GCD函数

2016-03-22  本文已影响66人  Alexander

前言

前面第一章知识简单介绍了多线程的理论知识,并没有太多的实际示例,那么本章就主要介绍关于GCD相关的函数,通过每个示例,更加深刻的了解多线程.如果文中观点有错,请大家提出来,相互进步

- (void)viewDidLoad {
    [super viewDidLoad];

    /*
     const char *label : 队列的标签
     dispatch_queue_attr_t attr : 队列的类型(串行还是并发)
     #define DISPATCH_QUEUE_SERIAL 或 NULL :都表示串行
     #define DISPATCH_QUEUE_CONCURRENT
     */
    dispatch_queue_t queue = dispatch_queue_create("com.William.Alex", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{

        // 执行的任务
    });
}
- (void)viewDidLoad {
    [super viewDidLoad];

    // 获取当前线程
    [NSThread currentThread];

    // 获取主队列
    NSLog(@"%@",dispatch_get_main_queue());

    // 获取全局并发队列
    /*
     long identifier : 优先级(0:表示默认优先级)
     unsigned long flags : 预留字段,填0即可
     */
    NSLog(@"%@",dispatch_get_global_queue(0, 0));
}

延迟操作
上一章我们大概提了一下延迟操作的三种方法,但是没有细说,在实际开发中,很多时候都需要使用到延迟操作,比如说使用GCD的延迟操作来模拟网络延迟,从而找出隐藏较深的Bug.本章主要讲GCD的延迟操作.

// 参数 1 :时间  参数 2 : 队列  参数3 : 任务block代码块
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, ^(void)block);

//  参数 : delayInSeconds : 延迟的时间,表示多少时间后执行block代码块中的代码
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 需要执行的任务
    });

dispatch_after示例


// 示例虽然简单,但是通俗易懂
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{

    NSLog(@"点击屏幕就会立即打印出来");

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        NSLog(@"点击屏幕后,2秒之后才会答应出来,不信自己去试试");
    });

}

补充 : performSelector方法虽然不是很常用,但是最好也要知道有这个方法可以实现延迟操作

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"仍然需要有一个参照,不然谁知道是不是延迟了3秒");

    [self performSelector:@selector(run) withObject:nil afterDelay:3.0];
}

- (void)run {

    NSLog(@"看来真的延迟了3秒后才打印");
}
- (void)viewDidLoad {
    [super viewDidLoad];

    // 创建一条线程
    NSThread *thread = [[NSThread alloc] initWithTarget:self
                                              selector:@selector(run)
                                                object:nil];

    // 开启线程(当线程执行完毕之后,自动进入死亡状态)
    [thread start];

    // 手动阻塞线程
    [NSThread sleepForTimeInterval:0.2];

    // 强制停止线程(一旦线程停止(死亡)状态,就无法再次开启该线程)
    [NSThread exit];
    }

- (void)run {

    NSLog(@"跑你妹啊");
}

线程的安全隐患问题

经典示例 : 资源共享

互斥锁

互斥锁的优缺点

互斥锁的注意点

互斥锁的应用场景

示例

(忽略多次点击程序会崩溃,这里主要想演示线程加锁)

#import "ViewController.h"

@interface ViewController ()

/** 火车票数量 */
@property (nonatomic, assign) NSInteger titketCount;

/** 售票员1 */
@property(nonatomic, strong) NSThread *conductor1;
/** 售票员2 */
@property(nonatomic, strong) NSThread *conductor2;
/** 售票员3 */
@property(nonatomic, strong) NSThread *conductor3;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // 假设有50张火车票
    self.titketCount = 50;

    // 创建三个售票窗口
    self.conductor1 = [[NSThread alloc] initWithTarget:self
                                                   selector:@selector(saleTitkets)
                                                     object:nil];
    self.conductor1.name = @"售票员1";

    self.conductor2 = [[NSThread alloc] initWithTarget:self
                                                   selector:@selector(saleTitkets)
                                                     object:nil];
    self.conductor2.name = @"售票员2";

    self.conductor3 = [[NSThread alloc] initWithTarget:self
                                                   selector:@selector(saleTitkets)
                                                     object:nil];
    self.conductor3.name = @"售票员3";
}

#pragma mark - 开启线程

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{

    [self.conductor1 start];
    [self.conductor2 start];
    [self.conductor3 start];
}

#pragma mark - 开始售票

- (void)saleTitkets {

    while (1) {
    @synchronized(self) {
            NSInteger count = self.titketCount;
            if (count > 0) {
                self.titketCount = count - 1;

                NSLog(@"%@卖了一张火车票,还剩%ld张火车票",[NSThread currentThread].name, self.titketCount);
            } else
            {
                NSLog(@"没票了");
                break;
            }
        }
    }
}

打印结果(没有枷锁时的部分打印)

2016-03-22 14:57:28.770 01 - 多线程[1241:105971] 售票员3卖了一张火车票,还剩47张火车票
2016-03-22 14:57:28.770 01 - 多线程[1241:105969] 售票员1卖了一张火车票,还剩49张火车票
2016-03-22 14:57:28.770 01 - 多线程[1241:105970] 售票员2卖了一张火车票,还剩48张火车票
2016-03-22 14:57:28.771 01 - 多线程[1241:105971] 售票员3卖了一张火车票,还剩46张火车票
2016-03-22 14:57:28.771 01 - 多线程[1241:105969] 售票员1卖了一张火车票,还剩45张火车票
2016-03-22 14:57:28.771 01 - 多线程[1241:105970] 售票员2卖了一张火车票,还剩44张火

打印结果(加锁后的部分打印)

2016-03-22 15:02:55.688 01 - 多线程[1268:108069] 售票员1卖了一张火车票,还剩49张火车票
2016-03-22 15:02:55.689 01 - 多线程[1268:108070] 售票员2卖了一张火车票,还剩48张火车票
2016-03-22 15:02:55.689 01 - 多线程[1268:108071] 售票员3卖了一张火车票,还剩47张火车票
2016-03-22 15:02:55.689 01 - 多线程[1268:108069] 售票员1卖了一张火车票,还剩46张火车票
2016-03-22 15:02:55.689 01 - 多线程[1268:108070] 售票员2卖了一张火车票,还剩45张火车票
2016-03-22 15:02:55.690 01 - 多线程[1268:108071] 售票员3卖了一张火车票,还剩44张火车票
2016-03-22 15:02:55.690 01 - 多线程[1268:108069] 售票员1卖了一张火车票,还剩43张火

一次性代码(单例:下一章专门讲单例模式)

- (void)once {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"内部的代码系统默认是线程安全的");
        NSLog(@"整个进程运行过程中,只会有一份内存");
    });

}

快速迭代遍历

快速剪切文件-dispatch_apply示例

- (void)apply {
/*
思路 :
1, 获取文件所在的原始文件夹路径
2, 获取文件要去的新文件夹的路径
3, 创建一个文件管理者
4, 拿到原始路径下的所有文件
5, 遍历原始路径下的的文件夹中的所有文件
6, 拼接原始路径和新路径为全路径
7, 使用异步 + 并发队列 将会话管理者中的所有文件从原始文件移动到新文件夹下.
*/
    // 创建一个全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    // 设置剪切文件的路径
    NSString *from = @"/Users/mac/Desktop/Frome";
    NSString *to = @"/Users/mac/Desktop/To";

    /*
     NSFileManager也是一个单例
     创建文件管理者
     */
    NSFileManager *fileManager = [NSFileManager defaultManager];

    // 拿到from文件路径中所有子路径
    NSArray *subPaths = [fileManager subpathsAtPath:from];

    /*
     参数 1: from路径下的所有子路径的总数. 参数 2 : 队列  参数 3: block代码块,双击后需要添加索引index
     */
    dispatch_apply(subPaths.count, queue, ^(size_t index) {

        // 根据索引拿到需要剪切的文件
        NSString *subpath = subPaths[index];

        // 拼接全路径
        NSString *fullFromPath = [from stringByAppendingPathComponent:subpath];
        NSString *fullToPath = [to stringByAppendingPathComponent:subpath];

        /*
         剪切文件:从from文件夹中剪切文件到to文件夹
         */
        [fileManager moveItemAtPath:fullFromPath toPath:fullToPath error:nil];
    });
}

队列组 : dispatch_group

示例

要求分别下载两张网络上的图片,然后将两张图片合成一张显示出来

#import "ViewController.h"

@interface ViewController ()

/**
 *  显示下载的图片
 */
@property (weak, nonatomic) IBOutlet UIImageView *AppleImage;

/**
 *  图片 1
 */
@property (nonatomic, weak) UIImage *image1;

/**
 *  图片 2
 */
@property (nonatomic, weak) UIImage *image2;


@end

@implementation ViewController

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{

    [self group];
}

- (void)group {

    // 创建全局队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 创建一个队列组
    dispatch_group_t groupQueue = dispatch_group_create();

    // 下载第一张图片
    dispatch_group_async(groupQueue, queue, ^{

        // 根据url下载图片
        NSURL *url = [NSURL URLWithString:@"网络图片Ur"];

        // 将图片以二进制的形式保存到本地
        NSData *imageData = [NSData dataWithContentsOfURL:url];

        // 显示图片
        self.image1 = [UIImage imageWithData:imageData];
    });


    // 下载第二种图片
    dispatch_group_async(groupQueue, queue, ^{

        // 通过url找到图片
        NSURL *url = [NSURL URLWithString:@"网络图片Url"];

        // 将图片保存到本地
        NSData *imageData = [NSData dataWithContentsOfURL:url];

        // 显示图片
        self.image2 = [UIImage imageWithData:imageData];
    });


    // 执行完毕耗时操作,将图片合二为一,然后刷新界面显示图片
    dispatch_group_notify(groupQueue, queue, ^{

        // 开启上下文
        UIGraphicsBeginImageContext(CGSizeMake(100, 100));

        // 绘制图片
        [self.image1 drawInRect:CGRectMake(0, 0, 100, 100)];
        [self.image2 drawInRect:CGRectMake(50, 0, 100, 100)];

        // 获取合成的图片
       UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

       // 关闭上下文
        UIGraphicsEndImageContext();

        // 回到主线程刷新界面,显示图片
        dispatch_async(dispatch_get_main_queue(), ^{

            // 显示图片
            self.AppleImage.image = image;
        });
    });
}

@end

线程间的通信

在应用程序运行过程中,线程之间并不是孤立存在的,它们之间是可以线程通讯的

下载图片的线程原理

线程间的通信.png

线程之间通信的三种方法

// 跳转到指定线程上执行任务(或者将数据传递到指定线程)
[self performSelector:(nonnull SEL) onThread:(nonnull NSThread *) withObject:(nullable id) waitUntilDone:(BOOL)];
[self performSelector:(nonnull SEL) onThread:(nonnull NSThread *) withObject:<#(nullable id)#> waitUntilDone:(BOOL) modes:(nullable NSArray<NSString *> *)];

// 跳转到主线程上执行任务(或者将数据传递到主线程)
[self performSelectorOnMainThread:(nonnull SEL) withObject:(nullable id) waitUntilDone:(BOOL)]
[self performSelectorOnMainThread:(nonnull SEL) withObject:(nullable id) waitUntilDone:(BOOL) modes:(nullable NSArray<NSString *> *)]

下面介绍一种比较常用的


    // 创建全局的并发队列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_async(globalQueue, ^{

        // 执行子线程上耗时的操作

        dispatch_async(dispatch_get_main_queue(), ^{

            // 回到主线程,执行UI刷新的操作
        });

    });
上一篇 下一篇

猜你喜欢

热点阅读