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

一、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。

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值