了解IOS Block

来源:互联网 发布:三星c5网络权限设置 编辑:程序博客网 时间:2024/06/05 19:18

OC中多线程编程的核心就是block与gcd。这虽然是两种不同的技术,但他们是一并引入的。block是一种可在C、C++及OC代码中使用的“词法闭包”(lexical closure),它极为有用,借此机制,开发者可将代码像对象一样传递,令其在不同环境(context)下运行。在block的范围内,它可以访问到其中的全部变量。

    gcd是一种与block有关的技术,它提供了对线程的抽象,而这种抽象基于“派发队列”(dispatch queue)。开发者可将block排入队列中,有gcd负责处理所有调度事宜。gcd会根据系统资源情况,适时得创建、复用、摧毁后台线程,以便处理每个队列。此外,使用GCD还可以方便的完成常见编程任务,比如编写“只执行一次的线程安全代码”(thread-safe single-code execution),或者根据可用的系统资源来并发执行多个操作。

    block和gcd是当前OC的编程基石。因此必须理解其工作原理及功能

第37条:理解block这一概念

    block可以实现闭包。这项语言特性是作为扩展而加入GCC编译器中的,在近期版本的Clang中都可以使用。从技术上讲,这是位于C语言层面的特性,因此只要有支持此特性的编译器以及能执行block的运行期组件,就可以在C、C++、OC,PC++代码中使用它。

block的基础知识

    block其实就是个值,而且自有其相关类型。语法和函数指针近似
[objc] view plain copy
  1. void (^someBlock)() = ^{  
  2.     //Block implementation here  
  3. };  
    block的强大之处是:在声明它的范围内,所有变量都可以为其所捕获。例如下面代码:
[objc] view plain copy
  1. int additional = 5;  
  2. int(^addBlock)(int a ,int b) = ^(int a,int b){  
  3.     return a+b+additional;  
  4. };  
  5. int add = addBlock(2,5);//add = 12  
    默认情况下,为block捕获的变量,是不可以修改的。如果在block中需要修改需要使用__block修饰符,修饰变量。
[objc] view plain copy
  1. NSArray *array = @[@0,@1,@2,@3,@4,@5];  
  2. __block NSInteger count =0;  
  3. [array enumerateObjectUsingBlock:^(NSNumber* number,NSUInteger idx, BOOLBOOL *stop){  
  4.     if([number compare:@2]==NSOrderAscending){  
  5.         count++;  
  6.     }  
  7. }];  
  8. //count =2  
    block中直接使用count的值。如果block捕获的变量是对象类型,那么就会自动保留它。系统在释放这个block的时候也会将其一并释放,block本身也是变量,有引用计数。
    block还可以使用self变量。block总能修改实例变量,所以在声明时无需加__block。但是self却被保留了。如果self所指代的哪个对象同时页保留了块,那么这种情况就会导致“循环引用”。更多见本章40条

block的内部结构

    每个OC对象都占据者某个内存区域。block本身也是对象,在存放对象的内存区域中,首个变量是指向Class对象的指针,该指针也叫isa。其余内存里含有block对象正常运转所需的各种信息,block对象的内部实现参考:点击打开链接 如图:

    在内存布局中,最重要的是invoke变量,这是个函数指针,指向block的实现代码。descriptor变量是指向结构体的指针,每个block都包含了此结构体,其中声明了block对象的总体大小,还声明了copy与dispose两个辅助番薯所对应的函数指针。前者是保留捕获的对象,后者则将之释放。
    block还会把它所捕获的所有变量都拷贝一份。这些拷贝放到descriptor变量后面,捕获了多少对象,就要占据多少内存。请注意,拷贝的并不是对象本身,而是指向这些变量的指针变量。invoke函数为何要把block对象作为参数传进来呢?原因就在于,执行block时,要从内存中把这些捕获的变量读出来。
全局block,栈block,堆block
   定义block的时候,是分配在栈上的,block只在定义它的范围有效。下面的写法很危险:
[objc] view plain copy
  1. void (^block)();  
  2. if(/*some condition*/){  
  3.     block = ^{NSLog(@"Block A");};  
  4. }else {  
  5.     block = ^{NSLog(@"Block B");};  
  6. }  
  7. block();  
    定义在if-else中的两个block都是在栈上的,作用范围只限于两个大括号之内。所以上述可能运行正确,可能错误。解决这个问题的办法是给block对象发送copy消息。这样就可以把block复制到堆上了。修改后代码:
[objc] view plain copy
  1. void (^block)();  
  2. if(/*some condition*/){  
  3.     block = [^{NSLog(@"Block A");} copy];  
  4. }else {  
  5.     block = [^{NSLog(@"Block B");}copy];  
  6. }  
  7. block();  
采用手动计数的,需要将其release,采用ARC则不用。
    除了“栈block”、“堆block”之外,还有一类block叫做“全局Block”。这种block不会捕捉任何状态(比如外围的变量等),运行时也无需有状态来参与。block所需要的整个内存区域,在编译期已经完全确定了,因此,全局block可以声明在全局内存里,而不需要在每次用到的时候于栈中创建。另外,全局block的拷贝操作是一个空操作,因为全局block绝不可能为系统回收。这种block相当于单例。
更多block存储区域参考点击打开链接
【本节要点】
● block是C、C++、OC中的词法闭包
● block可以接收参数、也可以返回值
● block可以在栈上、堆上、全局。分配在栈上的block可以拷贝到堆上。

第38条:为常用的块类型创建typedef

看两个例子:
[objc] view plain copy
  1. //before  
  2. -(void)startWithCompletionHandler:(void (^) (NSData *data,NSError *error))completion;  
  3. //after  
  4. typedef void(^EOCCompletionHandler) (NSData *data,NSError *error);  
  5. -(void)startWithCompletionHandler:(EOCCompletionHandler)completion;  

第39条:用handler block降低代码分散程度

【本节要点】
● 在创建对象时,可以使用内联的handler block将相关业务逻辑一并声明。
● 在有多个实例需要监视时,如果采用delegate模式,那么经常需要根据传入的对象来切换。而偌该用handler实现,则可直接将block与相关对象放在一起。
● 涉及API时如果用到了handler block,那么可以增加一个参数,使调用者可通过此参数来决定鹰把block安排在哪个队列上执行。

第40条:用block引用其所属对象时不要出现循环引用

    在没有出现block copy的情况下,是不会出现循环引用的。因为:栈上的block,虽然引用了self,构成环形引用,但是,最终栈上的block是需要释放的,这是一个出口。
1
0 0
原创粉丝点击