iOS 底层学习 -Blocks

738 阅读13分钟

Block 概念

  • Block是带有自动变量的匿名函数。如字面意思,Block没有函数名,另外Block带有插入记号"^",插入记号便于查找到Block,后面跟的一个括号表示块所需要的一个参数列表。和函数一样,可以给块传递参数,并且也具有返回值。

  • 不同点在于,块定义在函数或者方法内部,并且能够访问在函数或方法范围内的任何变量。通常情况下,这些变量能够访问但不能改变其值,有一个特殊的块修改器(由块前面由两个下划线字符组成)能够修改块内变量的值。

  • Blocks 也被称作 闭包、代码块。展开来讲,Blocks就是一个代码块,把你想要执行的代码封装在这个代码块里,等到需要的时候再去调用。

首先写一个简单的Block

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^{
            NSLog(@"B123");
        };
        block();
 }
    return 0;
}

使用命令行将代码转化为c++查看其内部结构

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

C++代码如下

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;//描述block的信息
// 构造函数(类似于OC的init方法),返回结构体对象
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp; //block内代码地址
    Desc = desc;  // 储存block对象占用的大小
  }
};

// 封装了block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_7jst2vt56bd_nhbrl1hlv5s00000gn_T_main_e9907b_mi_0);
        }
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

 	// 定义block变量以及实现
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
	 // 执行block内部的代码
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);


    }
    return 0;
}
对比之下,原来OC下的Block相关代码
 void(^block)(void) = ^{
            NSLog(@"B123");
        };
对应的C++代码就是:
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

通过C++代码发现,block的调用实际上就是__main_block_impl_0这个结构体,结构体实现如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;//描述block的信息
// 构造函数(类似于OC的init方法),返回结构体对象
 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp; //block内代码地址
    Desc = desc;  // 储存block对象占用的大小
  }
};

__main_block_impl_0结构体内部有一个与结构体同名的__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0)函数,这是C++结构体的写法,该函数为结构体的构造函数,相当于OC类中的- (instancetype)init;方法。 __main_block_impl_0函数携带三个参数

1. void *fp 
2. struct __main_block_desc_0 *desc
3.  int flags=0

它有两个必传的参数,一个是函数指针fp ,一个是结构体指针desc,关于结构体指针所指向的结构体就是上面分析到的__main_block_desc_0,那么第一个参数函数指针fp到底是什么? 在这个demo的C++实现代码中,fp指向的函数为__main_block_func_0,而__main_block_func_0就是block里面执行的那段代码,这里是封装成了这样一个函数:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

         NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_7jst2vt56bd_nhbrl1hlv5s00000gn_T_main_e9907b_mi_0);
        }

接下来我们来看第二个参数 struct __main_block_desc_0 *desc

static struct __main_block_desc_0 {
  size_t reserved; // 0
  size_t Block_size; //Block的内存大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

我们在执行 void(block)(void) = ((void ()())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); 传入这个函数的地址然后赋值给结构体内部变量 最后一个参数为可选的,默认值为0。 由此可得知Block本质是一个结构体,那我们看一下这个结构体内部

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;

接下来在对比一下调用代码

OC
 block();

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

我们可以看到调用Block是先强制转换为impl,然后调用FuncPtr

复杂一点的Block

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age;
        age = 30;
      void(^block)(void) = ^(){
                  NSLog(@"%d",age);
              };
        age = 40
        block();

    }

    return 0;
}

用过Block的人肯定都熟悉,打印结过为30,那我我们看一下底层实现

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
int age;

};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy

                  NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_7jst2vt56bd_nhbrl1hlv5s00000gn_T_main_440a9c_mi_0,age);
              }

可以看出Block内部生成了一个age变量用来存储,而构造block的值会将其传入内部,所以其值不会引起改变

那现在也可以很清楚Block底层的内部结构如下

6.png

capture

概念

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制 Capture 变量,可以 Capture Block 定义所在的作用域内的变量,类似于保存上下文。之后,Block 在别处被调用时,就好像 Block 还在原本上下文一样,可以正常执行。这些都是 OC 自动做的事情。而我们在定义 Block 的时候,当前作用域内的变量,都可以在 Block 中使用。不需要顾及当 Block 在别处被调用

我们定义三种类型的变量

  1. 全局变量
  2. 局部变量
  3. 静态变量
int  globalint ;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
      auto int stakeint = 10;
        static int staticint = 20;
      void(^block)(void) = ^(){
                  NSLog(@"%d%d%d",globalint,stakeint,staticint);
              };
       
//        void(^block)(int,int) = ^(int a,int b){
//            NSLog(@"%d",a + b);
//        };
                
        struct __main_block_impl_0 * blockstruct = (__bridge struct __main_block_impl_0*)block;
        block();

    }

C++底层
//Block结构体声明
int globalint ;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int stakeint;
  int *staticint;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _stakeint, int *_staticint, int flags=0) : stakeint(_stakeint), staticint(_staticint) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
// 调用函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int stakeint = __cself->stakeint; // bound by copy
  int *staticint = __cself->staticint; // bound by copy

       NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_7jst2vt56bd_nhbrl1hlv5s00000gn_T_main_faa523_mi_0,globalint,stakeint,(*staticint));
           }

总结一下 原理图

当我们判断是否Block会捕获对象是我们需要判断是否为全局变量,若是则不捕获,反之则捕获

7.png

Block的类型 看了底层实现 肯定会发现 其中有一句 如下

impl.isa = &_NSConcreteStackBlock;

这其实就是在说明Block的对象

我们先声明一个这个Block ,然后打印其父类至根类

  void(^blockTest)(void) = ^(){
                         NSLog(@"Test");
                     };
        
        NSLog(@"%@",[blockTest class]);
        //NSLog(@"%@",[[blockTest class] superclass]);
        NSLog(@"%@",[blockTest superclass]);
                  
        NSLog(@"%@",[[blockTest superclass]superclass]);
        NSLog(@"%@",[[[blockTest superclass]superclass]superclass]);

打印结果如下

8.png

我们可以看到最后的根类为NSObject,所以也可以说明Block是一个OC对象

类型详解

block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型

  • NSGlobalBlock ( _NSConcreteGlobalBlock )
  • NSStackBlock ( _NSConcreteStackBlock )
  • NSMallocBlock ( _NSConcreteMallocBlock )

Block在内存中的布局

1.jpeg Block的类型分配

2.png 🍪

oid (^Block)(void);
void test3() {
    int number = 10;
    Block = ^{
        NSLog(@"%d", number);
    };
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test3();
        Block();               
    }
    return 0;
}

首先说明 在ARC和MRC 会是不同的结果 ,了解过内存管理应该知道ARC是编译器特性,就是编译器帮助我们做了很多事情,简化了我们的代码量先附上MRC的打印结果

3.png 其次我们可以看到Block的类型为

4.png

而这里之所以会打印为错值,道路也很简单就是因为你之前Block结构体的内存空间在Stack 上,而test 方法使用后便会被回收,所以打印错值,那想要解决这个问题也很简单,就是我们让Block存储在堆上即可,那么根据之前的流程图,只要我们进行一次copy 操作即可 首先我们看Block的类型为 其次打印值也为10

5.png

那我们在看第二个测试结果

void test3() {
    int number = 10;
    Block = [^{
        NSLog(@"%d", number);
    } copy];
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
      
     void (^BlockTest)(void)= [^{
            NSLog(@"123");
        } copy];
        NSLog(@"%@", [BlockTest class]);
    }
    test3();
    [Block copy];
     NSLog(@"%@", [Block class]);
	[Block release];
    
    return 0;
}
// BlockTest 为 __NSGlobalBlock__
// 打印结果 __NSGlobalBlock__
//     __NSMallocBlock__

ARC下的copy

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况

  • block作为函数返回值时
  • 将block赋值给__strong指针时
  • block作为Cocoa API中方法名含有usingBlock的方法参数时
  • block作为GCD API的方法参数时

总结一下

每一种类型的block调用copy后的结果如下所示

2.jpeg

对象类型的auto变量

一个例子引出这个

 {
        Person * person = [[Person alloc] init];
        person.age =10;
        }
        NSLog(@"1111111");
   Block block;
        {
        Person * person = [[Person alloc] init];
        person.age =10;
           block  = ^{
                NSLog(@"%d",person.age);
            };
        
        }
        NSLog(@"1111111");
打印结果 
// 第一个在打印1111111之前对象会释放
//第二个在打印1111111之前对象不会释放

那我们继续转成C++代码查看一下底层实现

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  Person *person = __cself->person; // bound by copy

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_7jst2vt56bd_nhbrl1hlv5s00000gn_T_main_00319d_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
            }

我们可以看到这个Block内部会生成一个person对象,而 Person *person = __cself->person,就是让Block内部的person指向 外面实例对象的person,由于外面的实例对象此时被block持有所以自然不会被释放,当Block被释放的时候,person就会被释放

  Block block;
        {
        Person * person = [[Person alloc] init];
        person.age =10;
            __weak Person * weakPerson = person;
        block = ^{
                NSLog(@"%d",weakPerson.age);
            };
        
        }
        NSLog(@"1111111");
    
   // 在打印1111111之前对象会释放
       
C++底层实现
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__weak weakPerson;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  Person *__weak weakPerson = __cself->weakPerson; // bound by copy

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_7jst2vt56bd_nhbrl1hlv5s00000gn_T_main_9af25a_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)weakPerson, sel_registerName("age")));
            }
            
//我们可以发现底层person的修饰为weak 
//同时我们发现多出了两个函数
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}


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};

总结一下

当block内部访问了对象类型的auto变量时 如果block是在栈上,将不会对auto变量产生强引用如果block被拷贝到堆上 会调用block内部的copy函数 copy函数内部会调用_Block_object_assign函数_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用如果block从堆上移除 会调用block内部的dispose函数 dispose函数内部会调用_Block_object_dispose函数 _Block_object_dispose函数会自动释放引用的auto变量(release)

3.jpeg

6.png 说到这里我们可以看一下完整的Block的内部结构

__block

作用

__block可以用于解决block内部无法修改auto变量值的问题 __block不能修饰全局变量、静态变量(static)

typedef void(^Block)(void);
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int  age = 5 ;
        Block block = ^{
            age = 10;
            NSLog(@"%d",age);
        };
        block();
    }

那我们继续转换成C++代码

typedef void(*Block)(void);
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__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_age_0 *age = __cself->age; // bound by ref

            (age- g->age) = 10;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_7jst2vt56bd_nhbrl1hlv5s00000gn_T_main_526302_mi_0,(age->__forwarding->age));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 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};


// __block int  age = 5 ;
 __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,
 (__Block_byref_age_0 *)&age,
  0,
   sizeof(__Block_byref_age_0),
    5 };


 Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
  &__main_block_desc_0_DATA,
   (__Block_byref_age_0 *)&age,
    570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

我们可以发现 编译器会将__block变量包装成一个对象,从而可以用于解决block内部无法修改auto变量值的问题

⚠️ 那么这里我们如果打印age是那个值呢?

__block int  age = 5 ;
    Block block = ^{
        age = 10;
        NSLog(@"%d",age);
    };
    block();
    struct __main_block_impl_0 * blockLook = (__bridge struct __main_block_impl_0*)block;
    NSLog(@"%p",&age);

在这里我们可以看到是

7.png

(__Block_byref_age_0 *) age = 0x00000001005526b0

8.png 但是我们的打印结果为 相差了 24 ,我们回过头看这个结构体答案就应该显而易见了

9.png 就是里面的age,所以我们修改的时候是封装后的值

__block的内存管理

当block在栈上时,并不会对__block变量产生强引用 当block被copy到堆时 会调用block内部的copy函数 copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会对__block变量形成强引用(retain)

如下图

10.jpeg

111.jpeg

当block从堆中移除时 会调用block内部的dispose函数 dispose函数内部会调用_Block_object_dispose函数 _Block_object_dispose函数会自动释放引用的__block变量(release)

12.jpeg

13.jpeg

__block修饰对象

先写一个简单的例子

//OC
  __block Person * person = [[Person alloc] init];
        MyBlock block = ^{
            NSLog(@"%p", person);
        };


C++
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *person;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_person_0 *person; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__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_person_0 *person = __cself->person; // bound by ref

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_3f_7jst2vt56bd_nhbrl1hlv5s00000gn_T_main_fd7a72_mi_0, (person->__forwarding->person));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 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*);
}

15.png

我们可以了解到其内存结构如下图

那么是指针是强还是弱,我们可以用__weak修饰一下

   Person * person = [[Person alloc] init];
        __block __weak Person * weakperson = person;
        MyBlock block = ^{
            NSLog(@"%p", weakperson);
        };


struct __Block_byref_weakperson_0 {
  void *__isa;
__Block_byref_weakperson_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *__weak weakperson;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_weakperson_0 *weakperson; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakperson_0 *_weakperson, int flags=0) : weakperson(_weakperson->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

得知__block的对象一直为强指针,但是内部指针是强还是弱取决于你的修饰,我们在看一下 __Block_byref_weakperson_的内部结构

struct __Block_byref_weakperson_0 {
  void *__isa;
__Block_byref_weakperson_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *__weak weakperson;
};

发现内部也有

void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);

这其实也对其对象进行了引用技术管理

概括一下

对象类型的auto变量、__block变量

相同点

当block在栈上时,对它们都不会产生强引用

当block拷贝到堆上时,都会通过copy函数来处理它们

__block变量(假设变量名叫做a)

_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

对象类型的auto变量(假设变量名叫做p)

_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

当block从堆上移除时,都会通过dispose函数来释放它们 __block变量(假设变量名叫做a)

_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);

对象类型的auto变量(假设变量名叫做p)

_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

不同点 __Block都会对其对象进行持有

__block的__forwarding指针

刚才有说到对其赋值的时候是这样的 (age->__forwarding->age) = 10;

之所以这样请看下图,从而我们不论在栈还是队列都可以访问到__block修饰的变量

14.jpeg

15.jpeg

_forwarding指向了自身。 在block构造的时候我们用到了a.__forwarding, 在使用的时候(__BlockTest__test_block_func_0)也是通过(a->__forwarding->a)这样的方式找到最终修改过的数值‘8’。 能做到这样的原因上面已经说过了,这里复述一下: 栈上的__block变量复制到堆上时,会将成员变量__forwarding的值替换为复制到堆上的__block变量用结构体实例的地址。所以“不管__block变量配置在栈上还是堆上,都能够正确的访问该变量”,这也是成员变量__forwarding存在的理由。参照上面的图

Block的循环引用

Block 实质

  • block本质上也是一个对象,而这个对象是一个结构体,其内部含有一个isa指针指向自己的类。
  • block是封装了函数调用以及函数调用环境的OC对象
  • block是封装函数及其上下文的OC对象。 参考的相关资料 Objecitive-C高级编程

点击:获取更多资料

收录:原文链接