iOS图形图像及核心动画实战三GCD基础知识

本文深入探讨了Grand Central Dispatch (GCD) 的核心概念和技术细节,包括不同类型的队列、同步与异步的区别、并发与串行的概念以及如何使用 GCD 进行高效的线程管理和任务调度。

本教程是一个合集,涉及到的目录结构:
基础知识总结
Block基础知识
GCD实战
CoreGraphics & ImageIO实战
CoreAnimation实战

Grand Central Dispatch(GCD)概要

Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。

先来个例子: 

dispatch_queue_t queue = dispatch_queue_create("com.sharemerge.gcd",NULL);
dispatch_async(queue,^ {
    /* 
        1. 执行图片数据下载
        2. 将下载好的数据流转换成UIImage
    */
    dispatch_async(dispatch_get_main_queue(),^{
        /*
            将转换后的UIImage绑定到UIImageView是
        */
    });
}
特别说明

有4个术语比较容易混淆:同步、异步、并发、串行

同步和异步决定了要不要开启新的线程

  • 同步:在当前线程中执行任务,不具备开启新线程的能力 
  • 异步:在新的线程中执行任务,具备开启新线程的能力 

并发和串行决定了任务的执行方式

  • 并发:多个任务并发(同时)执行
  • 串行:一个任务执行完毕后,再执行下一个任务
各种队列执行效果对比
类型 主队列 全局并发队列 手动创建串行队列 
同步(Sync) 没有开启新线程;串行执行任务 没有开启新线程;串行执行任务 没有开启新线程;串行执行任务
异步(Async) 没有开启新线程;串行执行任务 有开启新线程;并发执行任务 有开启新线程;串行执行任务

创建Dispatch Queue

  1. 通过API生成
    • 同步队列 
      • dispatch_queue_t queue = dispatch_queue_create("com.sharemerge.gcd",NULL); 
      • dispatch_queue_t queue = dispatch_queue_create("com.sharemerge.gcd",DISPATCH_QUEUE_SERIAL);
    • 异步队列 
      • dispatch_queue_t queue = dispatch_queue_create("com.sharemerge.gcd",DISPATCH_QUEUE_CONCURRENT);
  2. 获取系统标准提供
    • Main Dispatch Queue(同步)
      • dispatch_queue_t queue = dispatch_get_main_queue();
    • Global Dispatch Queue(异步)
      • 高优先级
        • dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0) 
      • 默认优先级
        • dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0) 
      • 低优先级
        • dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0) 
      • 后台优先级
        • dispatch_queue_t = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0)
对比表
名称 Dispatch Queue的种类 说明
Main Dispatch Queue Serial Dispatch Queue 主线程执行
Global Dispatch Queue(High Priority) Concurrent Dispatch queue 执行优先级:高(最高优先)
Global Dispatch Queue(Default Priority) Concurrent Dispatch queue 执行优先级:默认
Global Dispatch Queue(Low Priority) Concurrent Dispatch queue 执行优先级:低
Global Dispatch Queue(Background Priority) Concurrent Dispatch queue 执行优先级:后台

dispatch_set_target_queue

dispatch_queue_create函数生成的Dispatch Queue不管是Serial Queue还是Concurrent Queue都是Default Priority,如果需要更改优先级,则需要使用dispatch_set_target_queue 

使用方法: 

dispatch_queue_t queue = dispatch_queue_create("com.sharemerge.gcd",NULL);
dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
dispatch_set_target_queue(queue,highPriorityQueue);

如果多个Dispatch Queue(例如1个Concurrent Dispatch Queue、2个Serial Dispatch Queue,或都是Serial Dispatch Queue(就算都是Serial Dispatch Queue,理论上对所有Queue来说也应该是并行))用dispatch_set_target_queue函数指定为同一个Serial Dispatch Queue,那么原本应该是并行执行的将变成串行执行

dispatch_after

经常会有这样的场景,想在3秒后执行处理,这个需求可使用dispatch_after函数来实现。 

使用方法: 

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,3ull*NSEC_PER_SEC);
dispatch_after(time,dispatch_get_main_queue(),^{
    /*
        3秒后自动执行任务
    */
});

需要注意的是,dispatch_after函数并不是在3秒后执行处理,而只是在3秒后追加处理到Dispatch Queue。上述代码中的Block最快在3秒后执行,最慢在3秒+1/60秒后执行,如果Main Dispatch Queue有大量处理追加或主线程的处理本身有延迟时,这个时间会更长。

创建dispatch_time_t

除了dispatch_time(DISPATCH_TIME_NOW,3ull*NSEC_PER_SEC)函数可创建dispatch_time_t外,另外也可通过dispatch_walltime(const struct timespec *when, int64_t delta)生成!

  • dispatch_time通常用于计算相对时间
  • dispatch_walltime通常用于计算觉得时间
dispatch_time_t getDispatchTimeByNSDate(NSDate *date) {
    NSTimeInterval interval;
    double second,subsecond;
    struct timespec time;
    dispatch_time_t milestone; /**< Dispatch time*/

    interval = [date timeIntervalSinceInterval970];
    subsecond = modf(interval,&second);
    time.tv_sec = second;
    time.tv_nsec = subsecond * NSEC_PER_SEC;
    milestone = dispatch_walltime(&time,0);

    return milestone;
}

Dispatch Group

当追加到Dispatch Queue中的多个处理全部结束后想执行结束处理,这时可使用Dispatch Group。

使用dispatch_group_notify

使用例子: 

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group,queue,^{ // 注意区别于dispatch_async,但是它们的用处是一样的
    NSLog(@"block1");
});

dispatch_group_async(group,queue,^{
    NSLog(@"block2");
});

dispatch_group_async(group,queue,^{
    NSLog(@"block3");
});

dispatch_group_notify(group,dispatch_get_main_queue(),^{
    NSLog(@"All finished.");
});

该源代码的执行结果为(Concurrent Dispatch Queue): 

  • block2 
  • block3 
  • block1 
  • All finshed. 

因为是向Global Dispatch Queue(即Concurrent Dispatch Queue)追加处理,所以结果表现为多个线程并行处理。

使用dispatch_group_wait

使用例子: 

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group,queue,^{ // 注意区别于dispatch_async,但是它们的用处是一样的
    NSLog(@"block1");
});

dispatch_group_async(group,queue,^{
    NSLog(@"block2");
});

dispatch_group_async(group,queue,^{
    NSLog(@"block3");
});

dispatch_group_wait(group,DISPATCH_TIME_FOREVER);

如果需要检测是否已经执行完毕,则可通过dispatch_group_wait的返回值进行检测
使用如下:

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,1ull*NSEC_PER_SEC);
long result = dispatch_group_wait(group,time);
if (0 == result) {
    /*
        属于Dispatch Group的全部处理执行结束
    */
} else {

    /*
        属于Dispatch Group的某一个处理还在执行中
    */
}

上述的time出现了2种情况: 

  • DISPTACH_TIME_NOW 
  • DISPTACH_TIME_FOREVER 

他们具体的含义得好好说一下:如果给定的是DISPATCH_TIME_NOW,那么wait函数不用任何等待,马上返回。这是有可能返回值是1(也就是未执行完block),然后RunLoop再循环一次的时候,我们可以再判定返回值,这时有可能就是0(全部执行完毕)了;如果给定的是DISPTACH_TIME_FOREVER,那么执行dispatch_group_wait函数的现在的线程(当前线程)停止。一直等,直到Dispatch Group的处理全部执行完毕。当然除了DISPATCH_TIME_NOW或DISPATCH_TIME_FOREVER之外,我们也可指定某个wait时间

使用Dispatch Group进行“打包”

可使用dispatch_group_enter()和dispatch_group_leave()进行在区间类进行数据“打包”。

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
//Enter group
dispatch_group_enter(group);
[manager GET:@"http://www.baidu.com" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
    //Deal with result...
    //Leave group
    dispatch_group_leave(group);
}    failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    //Deal with error...
    //Leave group
    dispatch_group_leave(group);
}];
//More request...

使用dispatch_group_enter,dispatch_group_leave就可以方便的将一系列网络请求“打包”起来~

如果没有什么特别场景,建议使用dispatch_group_notify,因为可以简化源代码!

dispatch_barrier_async

一般来说,为了高效率的进行访问数据库或文件时,read处理会追加到Concurrent Dispatch Queue中,write处理在任一个read处理没有执行的状态下,追加到Serial Dispatch Queue中即可(在write处理结束之前,read处理不可执行)

使用方法:

dispatch_async(queue,blk0_reading);
dispatch_async(queue,blk1_reading);
dispatch_async(queue,blk2_reading);
dispatch_async(queue,blk3_reading);

dispatch_barrier_async(queue,blk_writing);

dispatch_async(queue,blk4_reading);
dispatch_async(queue,blk5_reading);
dispatch_async(queue,blk6_reading);
dispatch_async(queue,blk7_reading);

上述代码执行过程为: 

  • blk2_reading
  • blk1_reading
  • blk0_reading
  • blk3_reading

  • blk_writing -- 由dispatch_barrier_async追加的处理

  • blk5_reading

  • blk6_reading
  • blk4_reading
  • blk7_reading

在dispatch_barrier_async追加writng操作前,总共有4个block需要执行;在之后,总共也有4个block需要执行!从执行结果,我们不难发现,在writing之前的还是并行的处理,当碰到dispatch_barrier_async之后,整个流程就感觉变成了Serial Dispatch Queue(也可认为是等待dispatch_barrier_async执行完)。

特别说明:barrier的中文翻译为:障碍物、屏障

dispatch_async和dispatch_sync对比

前面的例子一直使用的dispatch_async,既然有async(非同步 - asynchronous),当然也就有sync(同步 - synchronous)落!

  1. dispatch_async是将指定的Block“非同步”地追加到指定的Dispatch Queue中,dispatch_async函数不做任何等待
  2. dispatch_sync将指定的Block"同步"地追加到指定的Dispatch Queue中,在追加Block结束之前,dispatch_sync函数会一直等待

另外类比的还有dispatch_barrier_async和dispatch_barrier_sync两个函数!!

sync这类函数容易造成程序死锁,所以建议少用!!!
例如:

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue,^{
    NSLog(@"hello?");
});

dispatch_async(queue,^{
    dispatch_sync(queue,^{
        NSLog(@"helloc?");

dispatch_apply

dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API,该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。

使用方法(遍历NSArray):

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAULT,0);
dispatch_apply([array count],queue,^(size_t index){
    NSLog(@"%zu:%@",index,[array objectAtIndex:index]);
});
NSLog(@"done");

输出结果:
4
1
0
3
5
2
6
8
9
7
done 

另外由于dispatch_apply函数与dispatch_sync一样会等待处理执行结束,因此推荐在dispatch_async函数中异步执行dispatch_apply函数。
使用方法: 

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAULT,0);
dispatch_async(queue,^{
    dispatch_apply([array count],queue,^(size_t index){
        NSLog(@"%zu:%@",index,[array objectAtIndex:index]);
    });

    dispatch_async(dispatch_get_main_queue(),^{
        /*
            更新UI
        */
        NSLog(@"done");
    });
});

dispatch_suspend、dispatch_resume

当追加大量处理到Dispatch Queue(一定要注意是Queue)时,在追加处理的过程中,有时希望不执行已追加的处理。在这种情况情况下,只要挂起Dispatch Queue即可,当可以执行时再恢复。

挂起指定的Dispatch Queue

dispatch_suspend(queue)

恢复指定的Dispatch Queue

dispatch_resume(queue)

这些函数对已经执行的处理没有影响,当挂起后,追加到Dispatch Queue中但尚未执行的处理在此之后停止执行,当恢复后,这些处理将继续执行。

Dispatch Semaphone

Dispatch Semaphone是持有计数的信号,该计数是多线程编写中的计数类型信号。所谓信号,类似于过马路时常用的手旗。可以通过时举起手旗,不可通过时放下手旗。而在Dispatch Semaphone中,使用计数来实现该功能。计数为0时等待,计数为1或大于1时,减去1而不等待。

使用方法:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAULT,0);
dispatch_semaphone_t semaphone = dispatch_semaphone_create(1);
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < 100000; i++) {
    dispatch_async(queue,^{
        dispatch_semaphone_wait(semaphone,DISPATCH_TIME_FOREVER); // 计数值-1
        [array addObject:@(i)];
        dispatch_semaphone_signal(semaphone); // 计数值+1
    });
}

在没有Serial Dispatch Queue和dispatch_barrier_async函数那么大粒度且一部分处理需要进行排他控制的情况下,Dispatch Semaphone便可发挥威力。

dispatch_once

dispatch_once函数是保证在应用程序执行中只执行一次指定处理的API。主要是在多核CPU中,可能会发生多次执行某个操作。使用dispatch_once就可保证多核CPU、多线程中都仅仅执行一次!

多使用于Objective-C中的单例模式

Dispatch I/O

在读取较大文件时,如果将文件分成合适的大小并使用Global Dispatch Queue并列读取的话,应该会比一般的读取速度快不少。现今的输入/输出硬件已经可以做到一次使用多个线程更快的并列读取了,能实现这一功能的就是Dispatch I/O和Dispatch Data。

通过Dispatch I/O读写文件时,使用Global Dispatch Queue将1个文件按某个大小read/write。 

dispatch_async(queue,^{
    // 读取 0 ~ 8191字节
});
dispatch_async(queue,^{
    // 读取 8192 ~ 16383字节
});
dispatch_async(queue,^{
    // 读取 16384 ~ 24575字节
});
dispatch_async(queue,^{
    // 读取 24576 ~ 32767字节
});
dispatch_async(queue,^{
    // 读取 32768 ~ 40959字节
});

可像上面一下,将文件分割为一块一块读取。读取的数据可以通过Dispatch Data进行结合。例如:

dispatch_queue_t pipe_q = dispatch_queue_create("PipeQ", NULL);
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
    close(fd);
});

*out_fd = fdpair(1);

dispatch_io_set_water(pipe_channel,SIZE_MAX);

dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
    if (err == 0) {
        size_t len = dispatch_data_get_size(pipedata);
        if (len ==0 ) {
            const char *bytes = NULL;
            char *encoded;

            dispatch_data_t md = dispatch_data_create_map(pipedata, (const void**)&bytes, &len);

            ....
        }
    }
});

如果想提高文件读取速度,可以尝试使用Dispatch I/O

Dispatch Source

监控文件夹的变化
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSURL *directoryURL = [NSURL fileURLWithPath:documentsDirectory isDirectory:YES];
int const fd = open([[directoryURL path] fileSystemRepresentation], O_EVTONLY);
if (fd < 0) {
    char buffer[80];
    strerror_r(errno, buffer, sizeof(buffer));
    NSLog(@"Unable to open \"%@\": %s (%d)", [directoryURL path], buffer, errno);
    return;
}
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd,
                                                      DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_event_handler(source, ^(){
    unsigned long const data = dispatch_source_get_data(source);
    if (data & DISPATCH_VNODE_WRITE) {
        NSLog(@"The directory changed.");
    }
    if (data & DISPATCH_VNODE_DELETE) {
        NSLog(@"The directory has been deleted.");
    }
});
dispatch_source_set_cancel_handler(source, ^(){
    close(fd);
});
dispatch_resume(source);




原文转载自简书:http://www.jianshu.com/p/82bb26c5b6c1

【无线传感器】使用 MATLAB和 XBee连续监控温度传感器无线网络研究(Matlab代码实现)内容概要:本文围绕使用MATLAB和XBee技术实现温度传感器无线网络的连续监控展开研究,介绍了如何构建无线传感网络系统,并利用MATLAB进行数据采集、处理与可视化分析。系统通过XBee模块实现传感器节点间的无线通信,实时传输温度数据至主机,MATLAB负责接收并处理数据,实现对环境温度的动态监测。文中详细阐述了硬件连接、通信协议配置、数据解析及软件编程实现过程,并提供了完整的MATLAB代码示例,便于读者复现和应用。该方案具有良好的扩展性和实用性,适用于远程环境监测场景。; 适合人群:具备一定MATLAB编程基础和无线通信基础知识的高校学生、科研人员及工程技术人员,尤其适合从事物联网、传感器网络相关项目开发的初学者与中级开发者。; 使用场景及目标:①实现基于XBee的无线温度传感网络搭建;②掌握MATLAB与无线模块的数据通信方法;③完成实时数据采集、处理与可视化;④为环境监测、工业测控等实际应用场景提供技术参考。; 阅读建议:建议读者结合文中提供的MATLAB代码与硬件连接图进行实践操作,先从简单的点对点通信入手,逐步扩展到多节点网络,同时可进一步探索数据滤波、异常检测、远程报警等功能的集成。
内容概要:本文系统讲解了边缘AI模型部署与优化的完整流程,涵盖核心挑战(算力、功耗、实时性、资源限制)与设计原则,详细对比主流边缘AI芯片平台(如ESP32-S3、RK3588、Jetson系列、Coral等)的性能参数与适用场景,并以RK3588部署YOLOv8为例,演示从PyTorch模型导出、ONNX转换、RKNN量化到Tengine推理的全流程。文章重点介绍多维度优化策略,包括模型轻量化(结构选择、输入尺寸调整)、量化(INT8/FP16)、剪枝与蒸馏、算子融合、批处理、硬件加速预处理及DVFS动态调频等,显著提升帧率并降低功耗。通过实战案例验证优化效果,最后提供常见问题解决方案与未来技术趋势。; 适合人群:具备一定AI模型开发经验的工程师,尤其是从事边缘计算、嵌入式AI、计算机视觉应用研发的技术人员,工作年限建议1-5年;熟悉Python、C++及深度学习框架(如PyTorch、TensorFlow)者更佳。; 使用场景及目标:①在资源受限的边缘设备上高效部署AI模型;②实现高帧率与低功耗的双重优化目标;③掌握从芯片选型、模型转换到系统级调优的全链路能力;④解决实际部署中的精度损失、内存溢出、NPU利用率低等问题。; 阅读建议:建议结合文中提供的代码实例与工具链(如RKNN Toolkit、Tengine、TensorRT)动手实践,重点关注量化校准、模型压缩与硬件协同优化环节,同时参考选型表格匹配具体应用场景,并利用功耗监测工具进行闭环调优。
(清零流程:进维修模式—打开软件清零) 一、清零操作 第一步:打印机进入维修模式(查看维模式进法)。 第二步:废墨计数器:一般选【主要】 ,如报错002请选择【全】或【其它选项】。清零须用USB线把打印机接上电脑,进入维修模式放上纸,再点【清零】操作,提示【恭喜您!成功啦!】重开打印机清零完成。 报错提示: 1. 如报错006 001 005说明没进到维修模式。 2. 报错009说明硬件有问题,可点【读取】查看错误代码, 正常关闭打印机排除硬件问题再操作。 3. 报错002说明有废墨计数器未选对或软件不支持该型号。 4. 打印机有其它硬件问题时,点了【清零】后软件变灰不提示成功,过一分钟直接关打印机重开即可。 二、维修模式的进法(不同机型进法不同,认真阅读再操作) [G1800 G2800 G3800 G4800 IP8780 IP7280 IX6880 IX6780 MG3580 MG3680 TS5080 TS6080 TS6020......]维修模式方法如下: 1.先关闭电源 打印机放纸 2.按下【停止】键,再按【电源】 键。(两键都不松开) 3.当电源灯点亮时,不松【电源】键,只松【停止】键 4.连按5次【停止】键,两键同时松开。 5.电源灯长亮,进入成功。(有时两个灯) [G1810 G2810 G3810 G4810 G5080 G6080 G8080 GM2020 GM4080 TS3380 TS3480 TSS708 TS5120 TS5320 TS5180 TS6120 TS6180 TS6280 TS6220 TS6380 TS6320 TR4580 TR4520 TR7520] 维修模式方法如下: 1.先关闭打印机电源,机子里放纸,按下【电源】键不放手。 2.当电源灯亮时,不松【电源】键,连按5次【停止】键,两键同时松开。 3.电源灯长亮
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值