码迷,mamicode.com
首页 > 其他好文 > 详细

Block

时间:2016-05-13 04:08:05      阅读:195      评论:0      收藏:0      [点我收藏+]

标签:

block的定义

block和函数有很多相同点:

  • 可以保存代码
  • 有返回值
  • 有形参
  • 调用方式一样

没有返回值,没有形参的block:

// 定义block
void (^block)() = ^{
        NSLog(@"------block----");

    };

// 直接调用
block();

有形参,有返回值的block:

// 求和block
int (^sumBlock)(int ,int) = ^(int a, int b){
        return a + b;
};

其实由此可以看出,block的定义和指向函数的指针非常像:

// sum函数
int sum(int a, int b)
{
    return a + b;
}

- (void)test
{
    // 指针p指向sum函数
    int (*p)(int, int) = sum;
}

block对变量的访问

局部变量

block在访问局部变量的时候,首先会拷贝它,然后内部会增加一个该类型的成员变量,用来存储这个拷贝进来局部变量,但是这次拷贝只是一次“值传递”,block 默认是将其复制到其数据结构中来实现访问的,这就意味着我们不能修改该局部变量!
技术分享

技术分享

因此我们可以通过指针来进行地址传递:

- (void)test
{
    int a;
    int *p = &a;
    void (^block)() = ^(){
        *p = 10;
    };
}

但需要注意的是:变量a的生命周期是和方法test相关联的,当test运行结束,栈随之销毁,变量a也会销毁,这个时候p就变成了野指针。如果block是作为参数或者返回值,这些类型就会跨栈,再次调用就会引发野指针错误!

因此可以在声明变量的时候加上__block修饰符,这样就可以在块内修改了!当加上__block修饰符后,block内部就会拷贝其引用地址来实现访问的。
技术分享

(静态)全局变量

因为全局变量都是在“静态数据存储区”,在程序结束前不会销毁,所以block直接访问了对应的变量(可以直接修改变量值),并没有给它预留位置。

静态局部变量

其实它和全局变量是一样的,唯一的不同就是作用域!


block的内部结构

block本身也可视为对象,在存放block的内存区域中,首个变量是指向Class对象的指针,该指针叫做isa。其余内存里含有块对象正常运行所需的各种信息,其内存布局如下(该图在Effective Objective-C 2.0中的第37条):
技术分享

该结构在apple的开源代码中也有写。

/* Revised new layout. */
struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size; // 块内存大小
    void (*copy)(void *dst, void *src); // 复制函数
    void (*dispose)(void *); // 销毁函数
};


struct Block_layout {
    void *isa; 
    int flags; 
    int reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

isa:指向该block的类Class,同时isa也是OC对象的标识,这就说明block也是对象

flags:按bit位表示一些block的附加信息,比如判断block类型、判断block引用计数、判断block是否需要执行辅助函数等

reserved:保留变量,留给以后升级使用

invoke:函数指针,指向block的实现代码

descriptor:指向结构体的指针,每一个block都包含此结构体,其中声明了block的总体大小,copy和dispose两个辅助函数所对应的函数指针,copy函数会retain住已经拷贝的对象,而dispose函数则是release对象。

其次,block还会把它所访问的所有变量都拷贝一份,这个变量在descriptor变量后面,捕获了多少个变量,就要占据多少内存空间。

需要注意的是:invoke函数为何要把block对象当做参数传进来呢?原因就在于,执行block时,要从内存中把这些访问到的变量读出来。


block的类型

在OC中,一共有三种类型的block:

_NSConcreteGlobalBlock:全局的静态block,这种block不会访问任何状态(比如外部的变量等),运行时也无需有任何状态来参与。block所使用的整个内存区域,在编译期就已经完全确定了,因此全局block是声明在全局内存中。同时,全局block的copy操作是一个空的操作,因为全局块不可能被系统所回收,相当于一个单例。

_NSConcreteStackBlock:栈上的block,在函数返回时会销毁,但是给栈block发送copy消息,就可以把block从栈上拷贝到堆上去了,一旦拷贝到堆上,block就成了带引用计数的对象了,而后续的拷贝操作都不会真的执行,只是递增block的引用计数罢了。

_NSConcreteMallocBlock:堆上的block,引用计数为0时销毁。


ARC下的block

技术分享
apple官方文档中有说明,在ARC模式下,在栈间传递block时,不需要手动copy栈中的block,即可让NSConcreteStackBlock 的 block 被 NSConcreteMallocBlock 类型的 block 替代,因为ARC会自动执行copy操作。

参考文献

谈Objective-C block的实现
Block技巧与底层解析
Objective-C中的Block

Block

标签:

原文地址:http://blog.csdn.net/ldszw/article/details/51337258

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!