block的使用总结

来源:互联网 发布:python udp编程 编辑:程序博客网 时间:2024/05/16 01:21

一.block的原理:


Block的实质是指向结构体的指针,

查看block的底层代码:在终端中cd到工程路径,然后执行clang -rewrite-objc main.m

 block的数据结构定义

我们通过大师文章中的一张图来说明:

上图这个结构是在栈中的结构,我们来看看对应的结构体定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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. */
};

从上面代码看出,Block_layout就是对block结构体的定义:

isa指针:指向表明该block类型的类。

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

reserved:保留变量,我的理解是表示block内部的变量数。

invoke:函数指针,指向具体的block实现的函数调用地址。

descriptor:block的附加描述信息,比如保留变量数、block的大小、进行copy或dispose的辅助函数指针。

variables:因为block有闭包性,所以可以访问block外部的局部变量。这些variables就是复制到结构体中的外部局部变量或变量的地址。

3.2 block的类型

block有几种不同的类型,每种类型都有对应的类,上述中isa指针就是指向这个类。这里列出常见的三种类型:

_NSConcreteGlobalBlock:全局的静态block,不会访问任何外部变量,不会涉及到任何拷贝,比如一个空的block。例如:

1
2
3
4
5
#include int main()
{
    ^{ printf("Hello, World!\n"); } ();
    return 0;
}

_NSConcreteStackBlock:保存在栈中的block,当函数返回时被销毁。例如:

1
2
3
4
5
6
#include int main()
{
    char a = 'A';
    ^{ printf("%c\n",a); } ();
    return 0;
}

_NSConcreteMallocBlock:保存在堆中的block,当引用计数为0时被销毁。该类型的block都是由_NSConcreteStackBlock类型的block从栈中复制到堆中形成的。例如下面代码中,在exampleB_addBlockToArray方法中的block还是_NSConcreteStackBlock类型的,在exampleB方法中就被复制到了堆中,成为_NSConcreteMallocBlock类型的block:

1
2
3
4
5
6
7
8
9
10
11
12
void exampleB_addBlockToArray(NSMutableArray *array) {
    char b = 'B';
    [array addObject:^{
            printf("%c\n", b);
    }];
}
void exampleB() {
    NSMutableArray *array = [NSMutableArray array];
    exampleB_addBlockToArray(array);
    void (^block)() = [array objectAtIndex:0];
    block();
}

总结一下:

_NSConcreteGlobalBlock类型的block要么是空block,要么是不访问任何外部变量的block。它既不在栈中,也不在堆中,我理解为它可能在内存的全局区。

_NSConcreteStackBlock类型的block有闭包行为,也就是有访问外部变量,并且该block只且只有有一次执行,因为栈中的空间是可重复使用的,所以当栈中的block执行一次之后就被清除出栈了(block所在的函数结束就出栈 了),所以无法多次使用。

_NSConcreteMallocBlock类型的block有闭包行为,并且该block需要被多次执行。当需要多次执行时,就会把该block从栈中复制到堆中,供以多次执行。

===========================

先看一个只输出一句话的block是怎么样的。

生成中间代码,得到片段如下:

首先出现的结构体就是__main_block_impl_0,可以看出是根据所在函数(main函数)以及出现序列(第0个)进行命名的。如果是全局block,就根据变量名和出现序列进行命名。__main_block_impl_0中包含了两个成员变量和一个构造函数,成员变量分别是__block_impl结构体和描述信息Desc,之后在构造函数中初始化block的类型信息和函数指针等信息。

接着出现的是__main_block_func_0函数,即block对应的函数体。该函数接受一个__cself参数,即对应的block自身。

再下面是__main_block_desc_0结构体,其中比较有价值的信息是block大小。

最后就是main函数中对block的创建和调用,可以看出执行block就是调用一个以block自身作为参数的函数,这个函数对应着block的执行体

这里,block的类型用_NSConcreteStackBlock来表示,表明这个block位于栈中。同样地,还有_NSConcreteMallocBlock_NSConcreteGlobalBlock

由于block也是NSObject,我们可以对其进行retain操作。不过在将block作为回调函数传递给底层框架时,底层框架需要对其copy一份。比方说,如果将回调block作为属性,不能用retain,而要用copy。我们通常会将block写在栈中,而需要回调时,往往回调block已经不在栈中了,使用copy属性可以将block放到堆中。或者使用Block_copy()和Block_release()。

========
带局部变量的block;

Block是一种比较特殊的数据类型。它可以保存一段代码,在合适的时候取出来调用。

ARC情况下1.如果用copy修饰Block,该Block就会存储在堆空间。则会对Block的内部对象进行强引用,导致循环引用。内存无法释放。解决方法:新建一个指针(__weak typeof(self) weakSelf = self)指向Block代码块里的对象,然后用weakTarget进行操作。就可以解决循环引用问题。

2.如果用weak修饰Block,该Block就会存放在栈空间。不会出现循环引用问题。

MRC情况下用copy修饰后,如果要在Block内部使用对象,则需要进行(__block typeof(Target) blockTarget = Target )处理。在Block里面用blockTarget进行操作。

Block的定义格式

       返回值类型(^block变量名)(形参列表) = ^(形参列表) {                                                                                         };      调用Block保存的代码:如下       block变量名(实参);

 1,默认情况下,在Block内部不能修改block外的局部变量,

  2.在block内部可以修改所有函数以外的全局变量,

  4, 在block内部可以定义和外部同名的变量;

  5.如果是block外的局部变量,当定义block的时候,会把外部的变量的值放到栈中,把外部的局部变量拷贝一份到block中,是以const的形式拷贝过来的,所以不能改变.

  6.下面这种情况可以修改block外的局部变量:   在block外部定义:     __block int m=10;

   在block内部可以改变 m 的值;

只要加上__block,以后的m都存在堆区的,block以后的变量m的值都取block中的m值;

7.常量变量(NSString *a = @"abc"中 a是变量,@“abc”是常量); 不加__block类型修饰,block会引用常量的地址(浅拷贝)。加__block类型修饰block会去引用【常量变量】(如:a变量,a = @“abc”,可以任意修改a指向的内容)的地址.

在ARC模式下,在定义block的时候回默认返回一个堆block,即拷贝一份block内部的代码到堆中.

栈区的局部变量在控制台打印出来的地址:最后四位 有值,倒数5至8位是0;
堆区打印出来的地址:最后6位都有值;

============================================

    //如果一个block外部的变量加了 __block修饰系统就会创建一个结构体内部封装了变量的地址

    __block int a =10;

    

    //创建了一个变量

    //结构体__Block_byref_a_0的变量

    struct __Block_byref_a_0 {

        void *__isa;  0

        __Block_byref_a_0 *__forwarding; //保存自己的地址

        int __flags; // 0

        int __size;  //当前结构体的大小

        int a; 20

    };

    __attribute__((__blocks__(byref)))__Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a,0, sizeof(__Block_byref_a_0),10};

    

    

    void (^block)() = ^{

   

       printf("%d",a);

   

    };

    

    //传址  int a =10;替换成了结构体并且把结构体的地址传给了 block的结构体

    // &a

    

    void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a,570425344));

    //__main_block_impl_0 函数  参数:参数1 __main_block_func_0指针参数2 __main_block_desc_0_DATA :描述结构体大小 参数就是block之外的变量添加了__block修饰的变量 a ->会被创建成结构对象 取其地址传递进去当做参数

    

    

    a = 20;

    

    block();

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    return 0;

}

===============

传值:

    void (*block)() = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA,10));

    

    //__main_block_impl_0 函数  参数:参数1 __main_block_func_0指针参数2 __main_block_desc_0_DATA :描述结构体大小 参数就是block之外的变量 a

    

    //

    struct __main_block_impl_0 {

        //

        struct __block_impl impl; __main_block_func_0 =>impl.FuncPtr :函数 这个函数封装了之前block中代码

        struct __main_block_desc_0* Desc;

        int a; //10

        //

        __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a,int flags=0) : a(_a) {

            impl.isa = &_NSConcreteStackBlock;

            impl.Flags = flags;

            impl.FuncPtr = fp;

            Desc = desc;

        }

    };

    

    //block 结构体  里面有一个函数  __main_block_impl_0构造函数

    //C++ 代码  里面所有的构造函数跟结构体(相当于对象:属性 方法 )名称一致 ===> oc init开头 ==> 初始化对象

    

    

    void (*block)() : 函数指针

    

    //=====

    

    a = 20;

    

    block();

    //====

    FuncPtr  函数指针

    函数名字

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

    //====

    FuncPtr((__block_impl *)block);

block内部是调用了一个结构体中的函数: 
static struct main_block_desc_0 { 
size_t reserved; 
size_t Block_size; 
void (copy)(struct __main_block_impl_0, struct 
main_block_impl_0); 
void (
dispose)(struct __main_block_impl_0*); 
}

然后经过分析该c++文件我们知道 
block实际上是: 指向结构体的指针 
编译器会将block的内部代码生成对应的函数

============================================

二.block的基本使用

1,申明一个block:

我们通过^符号来声明block类型,形式如下:

void (^lamboBlock)();

其中第一个void是返回值,可以是任意类型,中间括号中^后面的是这个block变量的名字,我把它命名为lamboBlock,最后一个括号中是参数,如果多参数,用逗号隔开,可以写成如下样式:

int (^lamboBlock)(int,int);

同样,你也可以加上参数名,如:

int (^lamboBlock)(int a,int b);

很多时候,我们需要将我们声明的block类型作为函数的参数,也有两种方式:

1、-(void)func:(int (^)(int a,int b))block;//作为函数的参数

     @property(nonatomic,copy)int (^lamboBlock)(int a,int b);//作为属性名

作为函数参数举例:

- (void)viewDidLoad {

    [superviewDidLoad];

    [selffunc:^int(int a, int b) {

        NSLog(@"dddd");

        return 2;//这个地方必须要要返回值,因为参数是有返回值的block

    }];

}

-(void)func:(int (^)(int a,int b))block{

    NSLog(@"我是");

}

=====
举例2:
//定义一个有参数无返回值的函数(参数是block)
void  test(int(^block)(int x,int y)){
}
//调用函数
test(^int(int x,int y){
}

第二种方式是通过typedef定义一种新的类型(也就是起个别名),这也是大多数情况下采用的方式:

2、typedef int (^lamboBlock)(int a,int b) ;//起别名

-(void)func:(lamboBlock)block ;//作为函数参数

     @property(nonatomic,copy) lamboBlock block;//作为属性名

3.block作为函数的返回值
typedef void(^newTypeBlock)();//定义一个别名block;
//定义一个返回值是newTypeBlock的函数
newTypeBlock text(){
                  newTypeBlock w=^{  NSLog(@"我是新的block");
                                                    return w;
                                                   }
 newTypeBlock n=test();      //用一个newTypeBlock类型的block接受test()函数返回的值;
    n();       //调用block ;
}

===================================

2.实现block:

 无参数无返回值

void (^emptyBlock)() = ^(){        NSLog(@"无参数,无返回值的Block");    };    emptyBlock();
无参数有返回值
int (^emptyBlock)(void)= ^(){
        NSLog(@"无参数,无返回值的Block");    };    emptyBlock();



有参数无返回值

void (^sumBlock)(int ,int ) = ^(int a,int b){        NSLog(@"%d + %d = %d",a,b,a+b);    };    /**     *  调用这个sumBlock的Block,得到的结果是20     */    sumBlock(10,10);



    有参数有返回值

   方法一:申明和定义(实现)分开(有参数有返回值)


 NSString* (^lamboBlock)(NSString*,int);

lamboBlock = ^(NSString *name, int age){    return [NSString stringWithFormat:@"My name is %@,I‘m %d !",name,age];//有返回值};NSString *str = lamboBlock(@"lamboBlock",19);//block 的调用

方法二:同时申明和定义(有参数有返回值)

NSString* (^lamboBlock)(NSString*,int)= ^(NSString *name,int age){

    return [NSString stringWithFormat:@"My name is %@,I‘m %d !",name,age];//有返回值};NSString *str = lamboBlock(@"lamboBlock",19);//block 的调用


====================================
block传值



0 0