《Objective-C高级编程:iOS与OS X多线程和内存管理》 一 Blocks模式GCD

el/2023/9/24 21:01:07

一、Blocks摘要

Blocks:带有自动变量(局部变量)的匿名函数。

匿名函数:不带有名称的函数。

int func(int cout); //声明名称为func的函数
int result = func(10); // 调用该函数,必须使用该函数的名称
// 若像下面这样,使用函数指针来代替直接调用函数,那么不知道函数名也可调用
int result = (*funptr)(10);
// 但使用函数指针也仍需知道函数名称,在赋值给函数指针时,若不使用想赋值的函数的名称,就无法取得该函数的地址。
int (*funcptr)(int) = &func;
int result = (* funcptr)(10);

而通过Blocks,源代码中就能使用匿名函数,即不带名称的函数。可以保持变量值。

二、Blocks模式

2.1 Block 语法

^ 返回值类型 参数列表 表达式

表达式中含有return 语句时,其类型必须与返回值类型相同。

省略返回值类型:^ 参数列表 表达式

如果表达式中有return语句就使用该返回值的类型,如果表达式中没有return语句就使用void类型。表达式中含有多个return 语句时,所有return的返回值类型必须相同。

^(int count){return count+1;} //按照return 语句的类型,返回int型返回值
^void (void){print("Blocks\n");}//参数列表省略
^{print("Blocks\n");}//返回值类型、参数列表都省略

2.2 Block类型变量

在c语言函数中,可以将所定义函数的地址赋值给函数指针类型变量  (https://www.cnblogs.com/jiangcsu/p/5402719.html)

int func(int count)「return count+1;
}
int (*funcptr)(int) = &func;

函数func的地址就赋值给函数指针变量funcptr中了。

  • 函数指针变量:指向函数入口的指针变量(本质是变量)
  • 类型类型说明符 (*指针变量名)(形参表);          int (*function)(int i);
  • 函数指针变量:指向函数入口的指针变量(本质是变量)
  • 类型说明符 *函数名(形参表){

    }  int  *function(int i){}

同理,在Block语法下,可将Block语法赋值给声明为Block类型的变量中。即源代码中一旦使用Block语法就相当于生成了可赋值给Block类型变量的"值".Blocks中由Block语法生成的值也被称为“Block”。

在有关Blocks的文档中,“Block”既指源代码中的Block语法,也指由Block语法所生成的值。

声明Block类型变量:如上所示,仅仅是将声明函数指针类型变量的"*"换为"^"

int(^blk)(int);

使用Block语法将Block赋值为Block类型变量:

int (^blk)(int) = ^(int count){return count+1;};

由"^"开始的Block语法生成的Block赋值给变量blk中。因为与通常的变量相同,当然也可以由Block类型变量向Block类型变量赋值。

int(^blk1)(int) = blk;
int (^blk2)(int);
blk2 = blk1;

在函数参数中使用Block类型变量可以向函数传递Block。

void func(int (^blk)(int)){
// 在函数返回值中指定Block类型,可以将Block做我饿函数的返回值返回int (^func()(int)){return ^(int count){return count+1;};}
}

则:在函数参数和返回值中使用Block类型变量时,记述方式极为复杂,类似函数指针,使用typedef  声明blk_t 类型变量

typedef int(^blk_int)(int)

变量funptr为函数指针类型时,变量blk为Block类型的情况下 的调用:

int result =(*funptr)(10);
int result = blk(10);

通过Block类型变量调用Block与C语言通常的函数调用一样。在函数参数中使用Block类型变量并在函数中执行Block如下:

int func(blk_t blk, int rate){return blk(rate);
}
====
- (int) methodUsingBlock:(blk_t)blk rate:(int)rate{return blk(rate);
}

Block类型变量可完全像通常的C语言变量一样使用,因此也可以使用指向Block类型变量的指针,即Block的指针类型变量。

typedef int (^blk_t)(int);  //定义一个参数为int 返回值为int 的Block变量blk_t
blk_t blk = ^(int count){return count +1;};
blk_t *blkptr = &blk;
(*blkptr)(10);

2.3 截获自动变量值

https://www.jianshu.com/p/43f1058432e8

int main(int argc, const char * argv[]) {@autoreleasepool {int value0 = 1;int value1 = 2;        void (^myBlock)(void) = ^{NSLog(@"value0:%d   value1:%d", value0, value1);};        value0 = 10;value1 = 20;        myBlock();   // value0:1   value1:2}return 0;
}

在上面的代码中,Block语法的表达式使用的是在Block声明之前的变量value0和value1。Blocks中,Block表达式截获所使用的自动变量的值,保存的是该自动变量的瞬间值,所以即使在声明完Block之后又修改了value0和value1的值,在Block表达式中这两个变量的值还是修改之前的,这就是自动变量值的截获。

2.4 __block说明符

实际上,自动变量值截获只能保存执行Block语法瞬间的值。保存后就不能改写该值。否则会产生编译错误,如下:

int val = 0;
void (^blk)(void) = ^{val = 1};
blk();
printf("val = %d\n",val);

若想在Block语法的表达式中将值赋给在Block语法外声明的自动变量,需要在改自动变量上附加_Block说明符。(即可实现Block内赋值)

_block int val = 0;
void (^blk)(void) = ^{val = 1};
blk();
printf("val = %d\n",val);//val = 1

2.5 截获的自动变量

如果将赋值给Block中截获的自动变量,就会产生编译错误:

int val = 0;
void (^blk)(void) = ^{val =1;};

若截获Objective - c对象,调用变更对象的方法可以:

id array = [[NSMUtable allov] init];
void (^blk)(void) = ^{id obj = [[NSObjectArray alloc]init];[array addObject:obj];}

但向截获的变量array赋值则会产生编译错误:

id array = [[NSMUtable allov] init];
void (^blk)(void) = ^{array = [[NSObjectArray alloc]init];}

若必须给截获变量赋值,则需要给截获的自动变量附加_block说明符:

_block id array = [[NSMUtable allov] init];
void (^blk)(void) = ^{array = [[NSObjectArray alloc]init];}

使用C语言数组时需小心使用其指针。如下:

const cha text[] = "hello";
void (^blk)(void) = ^{printf("%c\n",text[2]);};

只是使用C语言的字符串字面量数组,并没有向截获的自动变量赋值,----编译错误。因为在现在的Blocks中,截获自己变量的方法并没有实现对c语言数组的截获,使用指针可以解决:

const char *text = "hello";
void (^blk)(void) = ^{printf("%c\n",text[2]);
}

三、Blocks的实现

 

四、Grand Central Dispatch

4.1 GCD摘要

4.1.1 GCD

异步执行任务的技术,一般将应用程序记述的线程管理用的代码在系统级中使用。

开发者只需定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。

在后台线程中执行长时间处理,处理结束后,主线程使用该处理结果的源代码:

dispatch_async (queue, ^{/*  长时间处理:例如:数据库访问,AR用画像识别 *//*  长时间处理结束,主线程使用该处理结束 */dispatch_async (dispatch_get_main_queue(), ^{/*  只在主线程可以执行的处理,例如用户界面更新 */});
});

GCD之前,Cocoa框架提供了NSObject类的performSelectorInBackground:withObject实例方法和performSelectorOnMainThread 即可以用performSelector系方法实现GCD源码。

// 主线程处理方法
- (void) donework{
//只有主线程可以执行的处理:例如:用户界面更新
}

4.1.2 多线程编程

多个线程更新相同的资源会导致数据的不一致(数据竞争)、停止等待事件的线程会导致多个线程之间相互持续等待(死锁)、使用太多线程会消耗大量内存等。

                

但 多线程编程可保证应用程序的响应性能。

4.2 GCD的API

4.2.1 Dispatch Queue

使指定的Block在另一个线程中执行

Dispatch Queue:是执行处理的等待队列,开发者通过此函数等API,在Block语法中记述想执行的处理并将其追加到Dispatch Queue中,Dispatch Queue按照追加的顺序(FIFO)执行处理。

在执行处理时存在两种Dispatch Queue:一种是等待现在执行中处理的Serial Dispatch Queue,另一种是不等待现在执行中处理的Concurrent Dispatch Queue。

   

XUN内核决定应当使用的线程数,并只生产所需的线程执行处理。当处理结束,应当执行的处理数减少时,XUN内核会结束不再需要的线程。XUN内核仅使用Concurrent Dispatch Queue 便可完美地管理并执行多个处理的线程。

  

4.2.2 dispatch_queue_create

通过dispatch_queue_create函数可生成Dispatch Queue。

一旦生成Serial Dispatch Queue并追加处理,系统对于一个Serial Dispatch Queue就只生成一个线程。如果生成300个 Serial Dispatch Queue,那么生成300个线程。

同之前的多线程编程,若过多使用多线程,就会消耗大量内存,引起大量的上下文切换,大幅度降低系统的响应性能。

当想并行执行不发生数据竞争等问题的处理时,使用Concurrent Dispatch Queue ,因为Concurrent Dispatch Queue,不管生成多少,由于XUN内核只使用有效管理的线程,因此不会发生Serial Dispatch Queue的那些问题(多线程数据竞争)

dispatch_queue_create()

第一个参数指定Serial Dispatch Queue的名称。Dispatch Queue的名称推荐使用应用程序ID这种逆序全程域名。该名称在Xcode和Instruments的调试中作为Dispatch Queue名称表示。

生成Serial Dispatch Queue时,将第二个参数指定为NULL。生成Concurrent Dispatch Queue是,指定为DISPATCH_QUEUE_CONCURRENT。

dispatch_queue_create 函数的返回值为表示Dispatch Queue的“dispatch_queue_t类型”

4.2.3 Main Dispatch Queue/Global Dispatch Queue

Main Dispatch Queue 是在主线程中执行的Dispatch Queue,因为主线程只有一个,所以Main Dispatch Queue也就是Serial Dispatch Queue。

              

Global Dispatch Queue是所有应用程序都能使用的Concurrent Dispatch Queue。没必要通过dispatch_queue_create 函数逐个生成Concurrent Dispatch Queue。只要获取Global Dispatch Queue使用即可。

Global Dispatch Queue有四个执行优先级。

  

各种Dispatch Queue 的获取方法如下:

Main Dispatch Queue 的获取方法
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
Global Dispatch Queue (高优先级)的获取方法
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
Global Dispatch Queue (默认优先级)的获取方法
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
Global Dispatch Queue (低优先级)的获取方法
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_Low, 0);
Global Dispatch Queue (默认优先级)的获取方法
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

4.2.4 dispatch_set_ target_queue

dispatch_queue_create函数生成的Dispatch Queue 不管是Serial Dispatch Queue还是Concurrent Dispatch Queue,都与默认优先级Global Dispatch Queue相同执行优先级的线程。而变更生成的Dispatch Queue的执行优先级要使用dispatch_set_ target_queue函数。

    

在必须将不可并行执行的处理追加到多个Serial Dispatch Queue中时,如果使用dispatch_set_ target_queue函数将目标制定为某一个Serial Dispatch Queue,即可防止处理并行执行。

4.2.4 dispatch_after

想在指定时间后执行处理的情况,用此函数。

但需注意的是,dispatch_after并不是在指定时间后执行处理,而只是在指定时间追加处理到Dispatch Queue。

第一个参数指定要追加处理的dispatch_time_t 类型的值,该值使用dispatch_time函数或dispatch_walltime函数作成。

dispatch_time函数能够获取从第一个参数dispatch_time_t类型值中指定的时间开始,到第二个参数指定的毫微秒单位时间后的时间。第一次参数经常使用的值是之前源代码中出现的DISPATCH_TIME_NOW 表示现在的时间。

// 表示从现在开始一秒后的dispatch_time_t 类型的值
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, lull * NSEC_PER_SEC);
数值和NSEC_PER_SEC的乘积得到单位为毫微秒的数值。
// 表示从现在开始150毫秒后的dispatch_time_t 类型的值
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 120lull * NSEC_PER_SEC);

第二个参数指定要追加处理的Dispatch Queue。

第三个参数指定记述要执行处理的Block。

 


http://www.ngui.cc/el/3419336.html

相关文章

JXCategoryTitleView的使用

最近写项目,遇到了一个vc下面有多个vc 指示器可以点击切换,也可也左右滑动进行切换,解除了JXCategoryTitleView 感觉很好用; 一般要求主vc遵循JXCategoryViewDelegate、JXCategoryListContainerViewDelegate两个协议 子vc遵循JX…

基本编程能力练习

一、首先下载Java的编译环境并安装 JDK下载地址:https://www.oracle.com/java/technologies/javase-jdk15-downloads.html 选择适合自己系统的版本进行安装 这里我选择的是jdk-15_windows-x64_bin.exe 下载完成后直接运行安装,安装位置选择默认的C盘&am…

基本任务1.1Java语言基础

一、任务要求 完成基本数据类型的使用。完成基本运算符和表达式的使用。完成基本控制语句:判断、分支、循环等语句的使用。完成数组的使用。所有源代码必须加入行一级注释。 二、任务的理解 本次任务是让我们对Java编程中所遇到的基础知识进行学习和掌握&#xf…

基本任务1.1Java语言基础(任务挑战)

任务挑战: 1.编制一个Java application应用程序,把数组中已知的10个数字由小到大排序后输出。 编程前思考: 完成数组排序的方法有多种,其中有调用Arrays类中的sort方法直接对数组进行由小到大的排序,还可以采用冒泡排序…

基本任务1.2Java面向对象程序

一、任务要求 完成一个Java application应用程序,描述一个人类。要求如下:要求此人类必须包含有人的姓名、性别、出生日期等基本属性(变量)。要求此人类描述吃饭的方法(函数)和描述睡觉的方法(…

基本任务1.3Java API

完成一个 java application应用程序,可以接收用户通过键盘输入的文本,并输出此段文本字符的个数。 代码: package task; //包名 import java.util.Scanner; //导入Scanner类 public class JavaAPI { //创建类public static void…

Tensorflow的安装与配置

Tensorflow的安装与配置 经过多次的安装失败,找寻原因,最终安装成功。 1、基于之前安装的anaconda的基础上进行tensorflow的安装,首先通过按windows键和R键弹出运行框,输入CMD回车,进入cmd命令窗口,先查询…

基本任务1.4java异常捕捉机制

Java异常捕捉机制 任务要求: 一、完成一个 java application应用程序,完成ca/b 的计算并输出c的结果,可以为a和b在程序中赋初值、或者接收用户通过键盘输入a和b的数值文本后转换为数字等,在程序要求当 b 为0时c的计算结果正确。…

基本任务4.2WEB服务

任务要求: 一、学习Tomcat服务器的安装和配置,要求把其WEB根路径从默认值改为自定义的路径,要求把其WEB默认服务端口改为80。二、把基本任务4.1基本任务和挑战任务所完成的静态WEB页面放到WEB服务器根路径下,启动WEB服务器&#…

MySQL Server 5.5安装

MySQL数据库安装 一、下载安装包 网站:https://dev.mysql.com/downloads/installer/ 然后点击下载,也可以下载老版本。 二、安装 MySQL Server 5.5安装步骤如下:(其余版本安装步骤略有不同) 三、测试 在命令…