315,Block由浅入深(5):三种类型的Block(面试点:1.globalBlock:没有访问auto(局部)变量,存储在.data 区域,2.栈block:有访问auto(局部)变量,存...

静态变量和全局变量为什么不用加__block都可以修改值

1、静态变量和全局变量在加和不加__block都会直接引用变量地址。也就意味着可以修改变量的值。在没有加__block关键字的情况下。
2、常量变量(NSString *a=@"hello"; a为变量,@“hello”为常量。)
  不加__block类型,block会直接取常量值(浅拷贝)。
  加__block类型,block会去引用变量的地址。(如:a变量,a = @"abc".可以任意修改a 指向的内容。)
如果不加__block 直接在block内部修改变量 ,会编译报错。block内部改变量是只读的。

Block的分类

  • block主要分为三类:
    ① 全局block:_NSConcreteGlobalBlock;存储在全局内存中,相当于单例。
    ② 栈block:_NSConcreteStackBlock;存储在栈内存中,超出其作用域则马上被销毁。
    ③ 堆block:_NSConcreteMallocBlock;存储在堆内存中,是一个带引用计数的对象,需要
    自行管理其内存。
auto变量:

栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配是有alloc函数进行分配的,但是栈的动态分配和堆是不同的,他的动态分配由编译器进行释放,无需我们手工实现。

即:
block的类型 环境
__NSGlobalBlock__ 没有访问 auto 变量
__NSStackBlock__ 访问了 auto 变量
__NSMallocBlock__ NSStackBlock 调用了 copy

这三种block各自的存储区域如下图:


image.png

简而言之,存储在栈中的block就是栈块,存储在堆区的就是堆块,既不在栈区也不在堆区的就是全局块

  • 当我们遇到一个block,怎么去判定这个block的存储位置呢?

外部变量

(1)block不访问外部变量(包括栈和堆中的变量)
此时block既不在栈中,也不在堆中,在代码段中。ARCMRC下都是如此。
此时为全局block:_NSConcreteGlobalBlock

void(^block)(void) = ^{
};
NSLog(@"%@", block);
/*输出结果为*/
<__NSGlobalBlock__: 0x100004030>

(2)block访问外部变量
MRC环境下:访问外部变量的block默认是存储在中的。
ARC环境下:访问外部变量的block默认是存储在中的(实际是放在区,然后ARC情况下又自动拷贝到),自动释放。

  • MRC 环境:
int a = 10;
void(^block)(void) = ^{
    NSLog(@"%d", a);
};
NSLog(@"%@", block);
/*输出结果为*/
<__NSStackBlock__: 0x7ffeefbff3e8>
  • ARC 环境下
int a = 10;
void(^block)(void) = ^{
    NSLog(@"%d", a);
};
NSLog(@"%@", block);
/*输出结果为*/
<__NSMallocBlock__: 0x1040508b0>
  • 在ARC环境下我们怎么获取栈block呢?
    我们可以这样做:
int a = 10;
void(^ __weak block)(void) = ^{
    NSLog(@"%d", a);
};
NSLog(@"%@", block);
/*输出结果为*/
<__NSStackBlock__: 0x7ffeefbff3e8>

此时我们通过__weak不进行强持有,block就还是栈区的block

  • ARC环境下,访问外部变量的block为什么要自动从栈区拷贝到堆区呢?
    因为:栈上的block,如果其所属的变量作用域结束,该block就会被废弃,如同一般的自动变量。当然,block中的__block变量也同时会被废弃。
image.png

为了解决栈块在其变量作用域结束之后被废弃(释放)的问题,我们需要把block复制到堆中,延长其生命周期。开启ARC时,大多数情况下编译器会恰当的进行判断是否有必要将block从栈复制到堆,如果有,自动生成将block从栈复制到堆的代码。block的复制操作执行的是Copy实例方法。block只要调用了Copy方法,栈块就会变成堆块。

image.png

eg:

typedef int(^myblock)(int);

myblock func(int a) {
    return ^(int b) {
        return a * b;
    };
}

上面的代码中,函数返回的block是配置在栈上的,所以返回返回函数调用方法时,block变量作用域就被释放了,block也会被释放。但是,在ARC环境下是有效的,这种情况编译器会自动完成复制。

在非ARC情况下则需要开发者调用Copy方法手动复制。

block的类型 副本源的配置存储区域 复制效果
_NSConcreteGlobalBlock 程序的数据区域 什么也不做
_NSConcreteStackBlock 栈区 从栈区复制到堆区
_NSConcreteMallocBlock 堆区 引用计数增加

根据表格我们知道,block在堆区Copy会造成引用计数增加,这与其它OC对象是一样的。虽然block在栈中也是以对象的身份存在,但是栈区没有引用计数,因为不需要,我们都知道栈区的内存由编译器自动分配释放。

三、block 底层分析

int a = 10;
void(^ block)(void) = ^{
    NSLog(@"%d", a);
};
NSLog(@"%@", block);

使用 clangOC代码转换成C++文件,查看block的方法。

  • 在命令行输入下面的指令(XXX.m就是要编译的文件,需在当前文件夹下面执行)
clang -rewrite-objc XXX.m
  • 执行网上面的指令之后,当前文件夹中会多一个XXX.cpp的文件。此时在命令行输入open XXX.cpp 或者 直接打开文件

  • 打开XXX.cpp文件,在文件底部我们可以看到main函数被编译之后的样式:

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 10;
        void(* block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__pbdfr2t16xx3pkwg63c2y5m80000gn_T_main_c75271_mi_1, block);
    }
    return 0;
}

我们从main函数中提取一下block

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

/*简化一下,去除强制转换*/
void(* block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a); ///构造函数

可以看到构造函数名为__main_block_impl_0
下面我们再寻找一下__main_block_impl_0

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __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;
  }
};
  • 可以看到__main_block_impl_0是一个结构体。同时我们也可以说block是一个__main_block_impl_0类型的对象,这也是为什么block能够%@打印的原因

1、block自动捕获外部变量

  • block自动捕获的外部变量,在block的函数体内是不允许被修改的。
    ① 通过上面的代码我们可以看到__main_block_impl_0函数的定义:
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a; ///编译器自动生成的名字相同的变量
  __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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy 值拷贝 

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__pbdfr2t16xx3pkwg63c2y5m80000gn_T_main_ab7cb4_mi_0, a);
        }

可以看到,编译器会自动生成一个同名的变量
__main_block_func_0a是值拷贝。
因此,在block内存会生成一个,内容一样的同名变量,此时如果在函数体内进行a++的操作,则编译器就不清楚该去修改哪个变量。所以block自动捕获的变量,在函数体内部是不允许修改的。

  • 那么我们要修改外部变量要怎么办呢?
    1、__block修饰外部变量。
    2、将变量定义成全局变量
    3、将变量用参数的形式,传入block里面。
    第2种和第3种方式,想必大家都非常的熟悉,在这里就不再赘述。下面我们来看一下第1种方式,底层究竟做了些什么。

__block 原理

  • 现在我们对a进行__block编译,之后我们就可以在block内部对a进行修改。
__block int a = 10;
        void(^ block)(void) = ^{
            a++;
            NSLog(@"%d", a);
        };
        block();

下面我们再通过clang来观察一下,底层代码有了什么变化。

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref 指针拷贝
            /// 等同于外界的 a++
            (a->__forwarding->a)++;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_w__pbdfr2t16xx3pkwg63c2y5m80000gn_T_main_b21337_mi_0, (a->__forwarding->a));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

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*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
        void(* block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
  • 首先我们看到,在main函数里面,此时a变成了成了一个__Block_byref_a_0类型的对象。
  • 同时在__main_block_func_0函数中,由之前的值拷贝(bound by copy) 变成了现在的指针拷贝(bound by ref)
    *在main函数中传入的a是一个对象,同时在__main_block_func_0函数内部,对a进行指针拷贝;则此时创建的对象a和传入的对象a指向同一片内存空间。

总结:__block修饰外界变量的时候:
1、外界变量会生成__Block_byref_a_0结构体
2、结构体用来保存原始变量的指针(可以在上面编译后的代码中找到)
3、将变量生成的结构体对象指针地址传递给block,然后在block内部就可以对外界变量进行修改了。

接下来,在给大家看一个东西:

  • 在上面的C++代码中,__main_block_func_0函数中,大家会注意到执行a++的是这段代码(a->__forwarding->a)++;,那么这个__forwarding又是什么呢?
    接下来我们先看一下__Block_byref_a_0结构体长什么样子:
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

可以看到,__forwarding是一个指向自己本身的指针(自身为结构体)。
那么,在Copy操作之后,既然__block变量也被Copy到堆区上了,那么访问该变量是访问栈上的还是堆上的呢?这个时候我们就要来看一下,在Copy过程中__forwarding的变化了:

image.png

可以看到,通过__forwarding,无论是在block中,还是block外访问__block变量,也不管该变量是在栈上或是在堆上,都能顺利的访问同一个__block变量。

注意:这里与上面的结论并不矛盾。大家要主要到局部变量a__block修饰之后,会变成__Block_byref_a_0结构体对象。所以无论是在栈区还是在堆区,只要__forwarding指向的地址一样,那么就可以在block内部修改外界变量。这里大家要仔细观察一下__Block_byref_a_0结构体

Block对局部变量的修改特殊情况如:局部变量为NSMutableArray类型,在block初始化了以后,对NSMutableArray这些add或者remove操作,都会同步到Block里边的

-(void )test3
{
    NSString *_person2=@"person2";
    NSMutableArray *_listTest=[[NSMutableArray alloc]init];
    //初始值
    NSLog(@"init _person2:%@,%p",_person2,_person2);
    NSLog(@"init _listTest:%@,%p",_listTest,_listTest);
    void (^myBlock)(int) = ^(int num) {
        //block内赋值
        //        _weakPerson2=@"person22";
        NSLog(@"excuteing _person2:%@,%p",_person2,_person2);
        NSLog(@"excuteing _listTest:%@,%p",_listTest,_listTest);
    };
    //修改前赋值
    _person2=@"person22";
    [_listTest addObject:@"1212"];
    NSLog(@"excutebefore _person2:%@,%p",_person2,_person2);
    NSLog(@"excutebefore _listTest:%@,%p",_listTest,_listTest);
    myBlock(1);
    //block执行后
    NSLog(@"excuteafter _person2:%@,%p",_person2,_person2);
    NSLog(@"excuteafter _listTest:%@,%p",_listTest,_listTest);
}

输出结果

2014-07-29 11:05:29.460 Test[2540:60b] init _person2:person2,0xb18ec
2014-07-29 11:05:29.463 Test[2540:60b] init _listTest:(
),0x17d98560
2014-07-29 11:05:29.464 Test[2540:60b] excutebefore _person2:person22,0xb193c
2014-07-29 11:05:29.465 Test[2540:60b] excutebefore _listTest:(
    1212
),0x17d98560
2014-07-29 11:05:29.467 Test[2540:60b] excuteing _person2:person2,0xb18ec
2014-07-29 11:05:29.468 Test[2540:60b] excuteing _listTest:(
    1212
),0x17d98560
2014-07-29 11:05:29.470 Test[2540:60b] excuteafter _person2:person22,0xb193c
2014-07-29 11:05:29.471 Test[2540:60b] excuteafter _listTest:(
    1212
),0x17d98560

从日志可以看出:block内部对于可变、不可变的变量都无法修改,而且

1.在block初始化后对于NSString 变量 _person2 的修改,并没有同步到block内部,因为这时block外部的变量_person2指针重新指向另外一块内存
2.在block初始化后对于NSMutableArray变量 _listTest 的修改,同步到block内部,因为这时block外部的变量 _listTest 指针指向的内存地址没有变,只是对这块内存的值进行了操作。

我们可以借助 clang -rewrite-objc 转换.c文件得到.cpp文件,也可以转换.m也可以得到cpp文件(可能会有些报错)
以下是部分转换后的代码
//这里就是block对象的结构
//imp:函数指针对象,FuncPtr指向具体block实现的函数
//_person2:截获的变量
//isa、flags、funcptr、desc后面会说道。

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
struct __KDBlockTest__test3_block_impl_0 {
  struct __block_impl impl;
  struct __KDBlockTest__test3_block_desc_0* Desc;
  NSString *_person2;
  NSMutableArray *_listTest;
  __KDBlockTest__test3_block_impl_0(void *fp, struct __KDBlockTest__test3_block_desc_0 *desc, NSString *__person2, NSMutableArray *__listTest, int flags=0) : _person2(__person2), _listTest(__listTest) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//block实现的函数

static void __KDBlockTest__test3_block_func_0(struct __KDBlockTest__test3_block_impl_0 *__cself, int num) {
  NSString *_person2 = __cself->_person2; // bound by copy
  NSMutableArray *_listTest = __cself->_listTest; // bound by copy



        NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_4,_person2,_person2);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_5,_listTest,_listTest);
    }

//block对象的描述信息(大小等等)

static struct __main1_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main1_block_desc_0_DATA = { 0, sizeof(struct __main1_block_impl_0)};

//这是objc测试函数test

static void _I_KDBlockTest_test3(KDBlockTest * self, SEL _cmd) {
    NSString *_person2=(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_1;
    NSMutableArray *_listTest=((id (*)(id, SEL))(void *)objc_msgSend)((id)((id (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_2,_person2,_person2);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_3,_listTest,_listTest);
    void (*myBlock)(int) = (void (*)(int))&__KDBlockTest__test3_block_impl_0((void *)__KDBlockTest__test3_block_func_0, &__KDBlockTest__test3_block_desc_0_DATA, _person2, _listTest, 570425344);

    _person2=(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_6;
    ((void (*)(id, SEL, id))(void *)objc_msgSend)((id)_listTest, sel_registerName("addObject:"), (id)(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_7);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_8,_person2,_person2);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_9,_listTest,_listTest);
    ((void (*)(__block_impl *, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 1);

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_10,_person2,_person2);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_11,_listTest,_listTest);
}

简单分析block截获变量:
1).block初始化

void (*myBlock)(int) = (void (*)(int))&__KDBlockTest__test3_block_impl_0((void *)__KDBlockTest__test3_block_func_0, &__KDBlockTest__test3_block_desc_0_DATA, _person2, _listTest, 570425344);

传入了参数:函数指针、block描述、外部变量 _person2 和 _listTest,这时候在block内部对 _person2、_listTest 进行了引用

: _person2(__person2), _listTest(__listTest)

1.在block初始化后,我们对 _person2 做了修改,重新指向了 0xb193c 这块内存,但是不会影响block结构体成员_person2,因为成员 _person2 指向的是 0xb18ec。
2.向 _listTest 数组中添加了一个元素,并没有改变它的内存地址,依然还是 0x17d98560

_person2=(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_6;
    ((void (*)(id, SEL, id))(void *)objc_msgSend)((id)_listTest, sel_registerName("addObject:"), (id)(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_7);

2).执行block

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

其实还是调用了block对象里的函数对象(_block_imp1)的函数指针(FuncPtr) 所指向的函数__main1_block_func_0,并把block自己作为参数传递进去。

static void __KDBlockTest__test3_block_func_0(struct __KDBlockTest__test3_block_impl_0 *__cself, int num) {
  NSString *_person2 = __cself->_person2; // bound by copy
  NSMutableArray *_listTest = __cself->_listTest; // bound by copy



        NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_4,_person2,_person2);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_6946c5_mi_5,_listTest,_listTest);
    }

总结:对于局部变量,变量不加__block修饰符,在block内部是无法修改变量的值。而且

  1. 对值类型的修改,如果block初始化后,无法同步到block内部
  2. 对于引用类型的修改,如果block初始化后,修改指针指向,即指向另外一块内存,这样也是无法同步到block内部
  3. 对于引用类型的修改,如果block初始化后,对指针指向的内存进行修改,即NSMutableArray add 、remove操作,这样是可以用同步到block内部,但block内部同样无法修改。

Block对成员变量

结论:
对于成员变量,结果却不一样,加了__block和不加__block修饰符效果都是一样的,而且不用区分是引用类型和值类型,block初始化后,对于block内部引用的变量的修改,也能同步到block内部,并且在block内部可以修改成员变量的值。

Demo:

声明两个变量:_person2、_person3

@interface KDBlockTest()
{
    NSString *_person2;
    __block NSString *_person3;
}

添加测试方法,输出变量的值、地址、指针地址

-(void )test3
{
    _person2=@"person2";
    _person3=@"person3";
    //初始值
    NSLog(@"init _person2:%@,%p",_person2,_person2);
    NSLog(@"init _person3:%@,%p",_person3,_person3);
    void (^myBlock)(int) = ^(int num) {
        //block内赋值
        _person3=@"person33";
        NSLog(@"excuteing _person2:%@,%p",_person2,_person2);
        NSLog(@"excuteing _person3:%@,%p",_person3,_person3);
    };
    //修改前赋值
    _person2=@"person22";
    NSLog(@"excutebefore _person2:%@,%p",_person2,_person2);
    NSLog(@"excutebefore _person3:%@,%p",_person3,_person3);
    myBlock(1);
    //block执行后
    NSLog(@"excuteafter _person2:%@,%p",_person2,_person2);
    NSLog(@"excuteafter _person3:%@,%p",_person3,_person3);
}

执行结果如下:

2014-07-29 12:06:11.526 Test[2575:60b] init _person2:person2,0x10790c
2014-07-29 12:06:11.529 Test[2575:60b] init _person3:person3,0x10791c
2014-07-29 12:06:11.530 Test[2575:60b] excutebefore _person2:person22,0x10797c
2014-07-29 12:06:11.531 Test[2575:60b] excutebefore _person3:person3,0x10791c
2014-07-29 12:06:11.532 Test[2575:60b] excuteing _person2:person22,0x10797c
2014-07-29 12:06:11.534 Test[2575:60b] excuteing _person3:person33,0x10794c
2014-07-29 12:06:11.535 Test[2575:60b] excuteafter _person2:person22,0x10797c
2014-07-29 12:06:11.536 Test[2575:60b] excuteafter _person3:person33,0x10794c

从日志可以看出,

  1. block内部修改了成员变量_person3(没有用__block修饰符),并且同步到block外部,修改前和修改后地址是一样的。
  2. block初始化后,执行前,修改成员变量_person2的值,可以同步到block内部(没有用__block修饰符),修改前和修改后地址是一样的。

我们来看一下clang转换后的代码就会知道原因了

struct __KDBlockTest__test3_block_impl_0 {
  struct __block_impl impl;
  struct __KDBlockTest__test3_block_desc_0* Desc;
  KDBlockTest *self;
  __KDBlockTest__test3_block_impl_0(void *fp, struct __KDBlockTest__test3_block_desc_0 *desc, KDBlockTest *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

对于局部变量,block结构体里对应一个变量,都会有一个成员。
对于成员变量,block结构体里只会有一个成员变量,即 KDBlockTest *self,不管你是否用__block修饰了,此时对self产生了强引用

void (*myBlock)(int) = (void (*)(int))&__KDBlockTest__test3_block_impl_0((void *)__KDBlockTest__test3_block_func_0, &__KDBlockTest__test3_block_desc_0_DATA, self, 570425344);

在初始化的时候,把self传到block结构体构造函数里,block对象对self产生了引用,此时我们对成员变量进行修改

_person2=@"person22";
_person3=@"person33";

转换后代码

 (*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person2))=(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_3beba7_mi_8;

这段代码大致是修改self的objc变量。下面开始执行block,即调用对应的函数指针。

((void (*)(__block_impl *, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 1);
static void __KDBlockTest__test3_block_func_0(struct __KDBlockTest__test3_block_impl_0 *__cself, int num) {
  KDBlockTest *self = __cself->self; // bound by copy


        (*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person3))=(NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_3beba7_mi_5;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_3beba7_mi_6,(*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person2)),(*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person2)));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_5l_2l25j3tn0wl_3zy1hpsq1rhc0000gp_T_KDBlockTest_3beba7_mi_7,(*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person3)),(*(NSString **)((char *)self + OBJC_IVAR_$_KDBlockTest$_person3)));
    }

函数实现里通过引用block结构体的成员self,再引用到对应的objc变量_person2和_person3。

小结:

  1. 对于一个、多个成员变量,不管是否用__block修饰(用不用都没任何影响),block结构体会生成一个成员 :self,并且会引用成员变量所属的对象实例 self。
  2. 对于成员变量的修改都是通过对象self指针引用来实现的。
  3. block内部对于成员变量的访问也是通过block结构体对象的成员self 指针引用来实现的。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容