深入理解Objective-C:Block

1、定义

【1】首先我们看下block的定义。

Block_private.h文件中:

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    unsigned long int reserved;
    unsigned long int size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    void (*copy)(void *dst, const void *src);
    void (*dispose)(const void *);
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;
};

struct Block_layout {
    void *isa;
    volatile int flags; // contains ref count
    int reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

在上面Block_layout中,包含一个isa的指针。

所以block可以理解为: block在本质上是一个指向结构体的指针。

【2】脱字符(^)是块的语法标记。

按照我们熟悉的参数语法规约所定义的返回值以及块的主体(也就是可以执行的代码)。下图是如何把块变量赋值给一个变量的语法讲解:

结构

2、写法

下面写几个例子,稍微展示下block的写法。 扔个砖头,仅此而已。

例子1:

在文件顶部定义block,然后在.m文件中引用。

    typedef void(^idBlock_int)(int);

    idBlock_int  name = ^(int NumVersion){

        NSLog(@"%d",NumVersion);
    };

例子2:

定义一个名称为VoidBlock_int 的block作为参数使用。

@interface ViewController ()

@property (nonatomic,copy) void(^VoidBlock_int)(int);

@end
self.VoidBlock_int = ^(int number) {

     NSLog(@"%d",number);

};

例子3:

在.m文件中定义一个block。然后调用。

    int(^intBlock_int)(int) = ^(int NSNumber){

        return (NSNumber + 100);

    };

    int result = intBlock_int(10);
    NSLog(@"result is %d",result);  //result is 110

3、Block真面目(三打白骨精)

1、简单调用block

我们创建一个Block.m文件,然后自定义一个block

#import "Block.h"
@implementation Block

- (void)creat{

    void (^strBlock)() = ^(){

        printf("hello world");
    };

    strBlock();
}
@end

然后我们使用clang命令查看一下:

clang -rewrite-objc Block.m

我们会看到下面的代码:

struct __Block__creat_block_impl_0 {
  struct __block_impl impl;
  struct __Block__creat_block_desc_0* Desc;
  __Block__creat_block_impl_0(void *fp, struct __Block__creat_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __Block__creat_block_func_0(struct __Block__creat_block_impl_0 *__cself) {


        printf("hello world");
    }

static struct __Block__creat_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __Block__creat_block_desc_0_DATA = { 0, sizeof(struct __Block__creat_block_impl_0)};

static void _I_Block_creat(Block * self, SEL _cmd) {


    void (*strBlock)() = ((void (*)())&__Block__creat_block_impl_0((void *)__Block__creat_block_func_0, &__Block__creat_block_desc_0_DATA));

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

}

通过这个,我们发现了什么呢?

定义完block之后,其实是创建了一个函数,在创建结构体的时候把函数的指针一起传给了block,所以之后可以拿出来调用。

2、看值捕获的问题

#import "Block.h"

@implementation Block

- (void)creat{

    int a = 10;

    void (^strBlock)() = ^(){

        printf("a = %d",a);
    };

    strBlock();
}
@end

然后我们使用clang命令查看一下:

clang -rewrite-objc Block.m

我们会看到下面的代码:

在其中我们可以发现,

定义block的时候,变量a的值就传递到了block结构体中,仅仅是值传递,
所以在block中修改a是不会影响到外面的a变量的。

3、添加__block前缀

#import "Block.h"

@implementation Block

- (void)creat{

    __block int a = 10;

    void (^strBlock)() = ^(){

        a++;
        printf("a = %d",a);
    };

    strBlock();
}
@end

然后我们使用clang命令查看一下:

clang -rewrite-objc Block.m

我们会看到下面的代码:

在其中,我们可以发现:

并不是直接传递a的值了,而是把a的地址传过去了,
所以在block内部便可以修改到外面的变量了。

3、总结

1、Block常规存储地址

【1】 在Block中,如果只使用全局或静态变量 或者 不使用外部变量。

那么Block块的代码会存储在全局区。

【2】在Block中,如果使用了外部变量。

在ARC中,Block块的代码会存储在堆区;
在MRC中,Block块的代码会存储在栈区。

2、Block默认情况下不能修改外部变量, 只能读取外部变量。

【在ARC中】

外部变量存在堆中。这个变量在block块内与block块外地址相同;
外部变量存在栈区。这个变量会被copy盗block代码块所分配的堆中。

【在MRC中】

外部变量存在堆中。这个变量在block块内和block块外地址相同;
外部变量在栈区,这个变量会被copy到block代码块所分配的栈中。

3、如果需要修改外部变量,需要在外部变量前面声明_block

【在ARC中】

外部变量存在堆中。这个变量在block块内与block块外地址相同;
外部变量存在栈中。这个变量会被转移到堆区,不是复制,是转移。

【在MRC中】

外部变量存在堆中。这个变量在block块内与block块外地址相同;
外部变量在栈中。这个变量在block块内与block块外地址相同。

4、使用block应该避免循环引用

使用block代码块应注意内部循坏引用, 导致循环引用应在block定义前加上__weak声明:

    __weak typeof(<#obj#>) weak<#obj#> = <#obj#>;

例如:

    __weak typeof(self)weakSelf = self;

另外特别感谢知乎上各位的回答 传送门

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值