ios之block

Block基本概念

Block是OC中的一种数据类型
是一个能工作的代码单元,可以在任何需要的时候被执行
本质上是轻量级的匿名函数,可以作为其他函数的参数或者返回值
块代码本身可能有一个参数列表,也可能有一个返回值
可以把块代码赋给一个变量,并在需要的时候调用,就像调用一个普通函数一样

块代码使用的注意事项
默认情况下,不允许在块代码内部修改外部变量的数值
__block
循环引用 __weak

格式说明:
(返回类型)(^块名称)(参数类型列表) = ^(形参列表) {代码实现};
void (^myBlock)(int,int) = ^(int x , int y) 
{
      //代码实现        
}

//下面是Block的简单使用

#import <Foundation/Foundation.h>

typedef double(^MyBlock)(double, double);
MyBlock area = ^(double x, double y) {
    return x * y;
};

int a = 0;
static int b = 11;

/** block的定义*/
void test1()
{
    // block 定义,是准备好一段代码片段,在需要的时候执行
    // block是C语言的格式
    // 输入:inlineblock
    // 格式:返回类型 (^blockName) (参数类型) = ^(形参列表) { 代码实现 };
    // 提示:如果没有参数,格式可以简化
    // 格式:返回类型 (^blockName) () = ^ { 代码实现 };
    void (^myBlock)() = ^{
        NSLog(@"block的最简单定义");
    };
    //调用myblock
    myBlock();
    
    //最全的定义
    int (^block) (int , int , int) = ^(int x, int y, int z)
    {
        return x + y + z;
    };
    
    int sum = 0;
    sum = block(10,19,20);
    
    NSLog(@"sum - %d",sum);
    
}


/** 外部访问变量,常见面试题1*/
void test2()
{
    int x = 10;
    __block int y = 20;
    NSLog(@"x - %d , x - add - %p",x,&x);
    
    // 在定义block时,如果block中使用到外部的“局部”变量,block会建立该变量的副本(会记录当前x的数值)
    void (^block)() = ^{
/**********在使用局部变量时,默认不允许在块代码中直接修改局部变量的数值********/
//        x = 20;
        
        /************全局变量可以在block中修改其值******************/
        a = 10;
        /**********如果要在block中修改外部变量的值,需要使用 __block***************/
        y = 30;
        NSLog(@"block - x = %d",x);
        NSLog(@"block - x = %p",&x); //默认存储是再栈中
    };
    block();
    NSLog(@"最后的x = %d",x);
    
}

/** 外部访问变量,常见面试题2*/
void test3()
{
    NSMutableString *str = [NSMutableString stringWithFormat:@"hello"];
    NSLog(@"定义之前 - %p 定义之前 - %p", &str, str);
    void (^block)() = ^{
     // 修改指针指向内存空间的内容
        [str setString:@"水水水水"];
        NSLog(@"block里面通过指针 - %p %@", &str, str);
        
    };
    
    block();

}

int main(int argc, const char * argv[])
{

    test3();
    return 0;
}

使用注意点


@property (nonatomic , copy) myblock testblock; //定义用copy


1
//水平移动(向左) 2 - (void)test6 3 { 4 CGPoint p = self.myView.center; 5 __weak typeof(self) weakSelf = self; //弱引用 6 [UIView animateWithDuration:0.9 animations:^{ 7 weakSelf.myView.center = CGPointMake(weakSelf.myView.center.x + 12, weakSelf.myView.center.y); 8 }completion:^(BOOL finished) { 9 weakSelf.testblock = ^{ 10 weakSelf.myView.center = p; 11 }; 12 }]; 13 }

更详细的block介绍

Objective C之Block教程

引言

Block是C级别的语法和运行时特性。Block比较类似C函数,但是Block比之C函数,其灵活性体现在栈内存、堆内存的引用,我们甚至可以将一个Block作为参数传给其他的函数或者Block。

目录

  • 一、Block的基本介绍
    • 1、什么是Block
    • 2、Block和C语言函数指针和什么区别
    • 3、如何调用Block
  • 二、Block内存管理与其他特性
    • 1、Block放在哪里
    • 2、Block引用的变量在哪里
  • 三、Block揭开神秘面纱

一、Block的基本介绍

1、什么是Block

Block是一个C Level的语法以及运行时的一个特性,和标准C中的函数(函数指针)类似,但是其运行需要编译器和运行时支持,从iOS4.0开始就很好的支持Block了。广泛用于两个对象之前的回调函数。

下面我们来看一下Block的声明:

int(^hbFunction) (BOOL a);

其中int为block的返回类型,hbFunction为block名称,a为参数。

2、Block和C语言函数指针和什么区别

首先我们来看看C函数指针:

int (* hbFunction) (int a);  // 函数声明
int resut = hbFunction(10);  // 函数调用

再看看Block的用法:
int (^ hbFunction) (int a);  // 函数声明
int resut = hbFunction(10);   // 函数调用

C语言函数指针typedef
typedef int (*SumFunction)(int a,int b);

Block中的typedef
typedef int (^SumBlock)(int a,int b); 
3、如何调用Blocks
主动调用一下:
- (void)someMethod
{
  BoolBlock ablock = ^(BOOL bValue) {
      NSLog(@"Bool block!");
  };

  ablock();
}

作为参数返回:
typedef void (^BoolBlock)(BOOL);
- (BoolBlock)foo()
{
  BoolBlock ablock = ^(BOOL bValue) {
      NSLog(@"Bool block!");
  };
  return [[ablock copy] autorelease];//一定要copy,将其复制到堆上,更详细的原理,将在后续章节讲解
}
类的一个成员:
@interface OBJ1 : NSObject
@property (nonatomic, copy)BoolBlock block;//理由同上啊,同学们
@end

OBJ1 *obj1 = ...
obj1.block = ^(BOOL bValue) {
          NSLog(@"Bool block!");
};
其他函数的参数:
- (void)foo(BoolBlock block)
{
  if (block) {
      block();
  }
}
甚至其他block的参数:
BoolBlock bBlock = ^(BOOL bV){if(Bv){/*do some thing*/}};
HugeBlock hBlock = ^(BoolBlock bB) {bB();};

hBolck(bBlock); 

二、Block内存管理与其他特性

1、Block放在哪里
1.1栈和堆

以下情况中的block位于堆中:

void foo()
{
  __block int i = 1024;
  int j = 1;
  void (^blk)(void);
  void (^blkInHeap)(void);
  blk = ^{ printf("%d, %d
", i, j);};//blk在栈里
  blkInHeap = Block_copy(blk);//blkInHeap在堆里
}

- (void)fooBar
{
  _oi = 1;
  OBJ1* oj = self;
  void (^oblk)(void) = ^{ printf("%d
", oj.oi);};
  void (^oblkInHeap)(void) = [oblk copy];//oblkInHeap在堆中
}

1.2全局

以下情况中的block位于全局区
static int(^maxIntBlock)(int, int) = ^(int a, int b){return a>b?a:b;};
- (void)fooBar
{
  int(^maxIntBlockCopied)(int, int) =[maxIntBlock copy];
}

void foo()
{
  int(^maxIntBlockCopied)(int, int) = Block_copy(maxIntBlock);
}

需要注意的是,这里复制过后的block依旧位于全局区,实际上,复制操作是直接返回了原block对象。
2、Block引用的变量在哪里
1.全局区

全局区的变量存储位置与block无关:

static int gVar = 0;
//__block static int gMVar = 1;

void foo()
{
  static int stackVar = 0;
//    __block static int stackMVar = 0;
}

注意,static变量是不允许添加__block标记的

2.堆栈
void foo()
{
  __block int i = 1024; // 此时i在栈上
  int j = 1; // 此时j在栈上
  void (^blk)(void);
  blk = ^{
      printf("%d,  %d
",i,j);
  };   //此时blk位于栈上,其使用的变量也在栈上
  
  blk();
  j++;
  int (^blkInHeap)(int, int) = Block_copy(blk);
  // 复制block后,block位于堆上,有__block标记的i会被复制一份至堆,而没有__block标记的j并不会动依旧位于栈上。
  
}

三、Block揭开神秘面纱

1、Block到底是什么

我们使用clang的rewrite-objc命令来获取转码后的代码。

我们来看看最简单的一个block

__block int i = 1024;

int j = 1;
void (^blk)(void);
blk = ^{
    printf("i :%d,j:%d,&i:%p,&j:%p",i,j,&i,&j);
};
这个block仅仅打印栈变量i和j的值,其被clang转码为:

truct __main_block_impl_0{
    struct __block_impl impl;
    struct __main_block_desc_0 * Desc;
    int j;
    __Block_byref_i_0 *i;
    __main_block_impl_0(void * fp, struct __main_block_desc_0 *desc,int _j,__Block_byref_i_0 *_i, int flags = 0) : j(_j),int(_i->__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_i_0 *i = __cself->i;
   int j = __cself->j;
   printf("i :%d,j:%d,&i:%p,&j:%p",i->__forwarding->i,j,&(i->__forwarding->i),&j);
}

static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src){
   _Block_object_assign((void *)&dst->i, (void *)src->i,8);
}

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

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()
{
   __attribute__((__blocks__(byref))) __Block_byref_i_0 = {(void *)0,(__Block_byref_i_0 *)&i,0,sizeof(__Block_byref_i_0),1024};
  int j = 1;
  void (*blk)(void);
  blk = (void (*)())& __main_block_impl_0((void *)__main_block_func_0,&__main_block_desc_0_DATA,j,(__Block_byref_i_0 *)&i,570425344);
}


首先是一个结构体__main_block_impl_0(从图二中的最后一行可以看到,block是一个指向__main_block_impl_0的指针,初始化后被类型强转为函数指针),其中包含的__block_impl是一个公共实现(学过c语言的同学都知道,__main_block_impl_0的这种写法表示其可以被类型强转为__block_impl类型):

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

isa指针说明block可以成为一个objc 对象。
__main_block_impl_0的意思是main函数中的第0个block的implementation,这就是这个block的主体了。


这个结构体的构造函数的参数:

  • 1、block实际执行代码所在的函数的指针,当block真正被执行时,实际上是调用了这个函数,其命名也是类似的方式。

  • 2、block的描述结构体,注意这个结构体声明结束时就创建了一个唯一的desc,这个desc包含了block的大小,以及复制和析构block时需要额外调用的函数。

  • 3、接下来是block所引用到的变量们

  • 4、最后是一个标记值,内部实现需要用到的。(我用计算器看了一下,570425344这个值等于1<<29,即BLOCK_HAS_DESCRIPTOR这个枚举值)

所以,我们可以看到:

  • 1、为什么上一篇我们说j已经不是原来的j了,因为j是作为参数传入了block的构造函数,进行了值复制。
  • 2、带有__block标记的变量会被取地址来传入构造函数,为修改其值奠定了基础

接下来是block执行函数__main_block_func_0:

其唯一的参数是__main_block_impl_0的指针,我们看到printf语句的数据来源都取自__cself这个指针,比较有意思的是i的取值方式(带有__block标记的变量i被转码为一个结构体),先取__forward指针,再取i,这为将i复制到堆中奠定了基础。

再下来是预定义好的两个复制/释放辅助函数,其作用后面会讲到。

最后是block的描述信息结构体 __main_block_desc_0,其包含block的内存占用长度,已经复制/释放辅助函数的指针,其声明结束时,就创建了一个名为__main_block_desc_0_DATA的结构体,我们看它构造时传入的值,这个DATA结构体的作用就一目了然了:

长度用sizeof计算,辅助函数的指针分别为上面预定义的两个辅助函数。


注意,如果这个block没有使用到需要在block复制时进行copy/retian的变量,那么desc中不会有辅助函数


至此,一个block所有的部件我们都看齐全了,一个主体,一个真正的执行代码函数,一个描述信息(可能包含两个辅助函数)。

2、构造一个block

我们进入main函数:

图一中的第三行(block的声明),在图二中,转化为一个函数指针的声明,并且都没有被赋予初始值。 而图一中的最后一行(创建一个block),在图二中,成为了对__main_block_impl_0的构造函数的调用,传入的参数的意义上面我们已经讲过了。

所以构造一个block就是创建了__main_block_impl_0 这个c++类的实例。

3、调用一个block

调用一个block的写法很简单,与调用c语言函数的语法一样:

blk();

 其转码后的语句:

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


将blk这个函数指针类型强转为block_impl类型,然后取其执行函数指针,然后将此指针类型强转为返回void*并接收一个block_impl的函数指针,最后调用这个函数,传入强转为__block_impl类型的blk,

即调用了前述的函数__main_block_func_0

4、objective-c类成员函数中的block

源码如下:

- (void)of1
{
  OBJ1* oj = self;
  void (^oblk)(void) = ^{ printf("%d
", oj.oi);};
  Block_copy(oblk);
}

这里我故意将self赋值给oj这个变量,是为了验证前一章提出的一个结论:无法通过简单的间接引用self来防止retain循环,要避免循环,我们需要__block标记(多谢楼下网友的提醒) 转码如下:
struct __OBJ1__of1_block_impl_0 {
          struct __block_impl impl;
          struct __OBJ1__of1_block_desc_0* Desc;
          OBJ1 *oj;
          __OBJ1__of1_block_impl_0(void *fp, struct __OBJ1__of1_block_desc_0 *desc, OBJ1 *_oj, int flags=0) : oj(_oj) {
  impl.isa = &_NSConcreteStackBlock;
  impl.Flags = flags;
  impl.FuncPtr = fp;
  Desc = desc;
          }
};
static void __OBJ1__of1_block_func_0(struct __OBJ1__of1_block_impl_0 *__cself) {
          OBJ1 *oj = __cself->oj; // bound by copy
      printf("%d
", ((int (*)(id, SEL))(void *)objc_msgSend)((id)oj,         sel_registerName("oi")));
  }

objc方法中的block与c中的block并无太多差别,只是一些标记值可能不同,为了标记其是objc方法中的blcok。

注意其构造函数的参数:OBJ1 *_oj

这个oj在block复制到heap时,会被retain,而oj与self根本就是相等的,所以,最终retain的就是self,所以如果当前实例持有了这个block,retain循环就形成了。

而一旦为其增加了__block标记:

- (void)of1
{
  __block OBJ1 *bSelf = self;
  ^{ printf("%d", bSelf.oi); };
}

其转码则变为:

//增加了如下行

struct __Block_byref_bSelf_0 {
          void *__isa;
  __Block_byref_bSelf_0 *__forwarding;
      int __flags;
      int __size;
      void (*__Block_byref_id_object_copy)(void*, void*);
      void (*__Block_byref_id_object_dispose)(void*);
      OBJ1 *bSelf;
};

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
      _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

static void __Block_byref_id_object_dispose_131(void *src) {
      _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

//声明处变为
__block __Block_byref_bSelf_0 bSelf = {(void*)0,(__Block_byref_bSelf_0 *)&bSelf, 33554432, sizeof(__Block_byref_bSelf_0),   __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, self};
 

clang为我们的bSelf结构体创建了自己的copy/dispose辅助函数,33554432(即1<<25 BLOCK_HAS_COPY_DISPOSE)这个值告诉系统,我们的bSelf结构体具有copy/dispose辅助函数。

而131这个参数(二进制1000 0011,即BLOCK_FIELD_IS_OBJECT (3) |BLOCK_BYREF_CALLER(128)) 中的BLOCK_BYREF_CALLER在内部实现中告诉系统不要进行retain或者copy, 也就是说,在 __block bSelf 被复制至heap上时,系统会发现有辅助函数,而辅助函数调用后,并不retain或者copy 其结构体内的bSelf。 这样就避免了循环retain。

2、内存管理的真面目

objc层面如何区分不同内存区的block

Block_private.h中有这样一组值:

/* the raw data space for runtime classes for blocks */
/* class+meta used for stack, malloc, and collectable based blocks */
BLOCK_EXPORT void * _NSConcreteStackBlock[32];
BLOCK_EXPORT void * _NSConcreteMallocBlock[32];
BLOCK_EXPORT void * _NSConcreteAutoBlock[32];
BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32];
BLOCK_EXPORT void * _NSConcreteGlobalBlock[32];
BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];

其用于对block的isa指针赋值 1.栈

truct __OBJ1__of2_block_impl_0 {
          struct __block_impl impl;
          struct __OBJ1__of2_block_desc_0* Desc;
          OBJ1 *self;
          __OBJ1__of2_block_impl_0(void *fp, struct __OBJ1__of2_block_desc_0 *desc, OBJ1 *_self, int flags=0) : self(_self) {
      impl.isa = &_NSConcreteStackBlock;
      impl.Flags = flags;
      impl.FuncPtr = fp;
      Desc = desc;
          }
};

在栈上创建的block,其isa指针是_NSConcreteStackBlock。

2.全局区

在全局区创建的block,其比较类似,其构造函数会将isa指针赋值为_NSConcreteGlobalBlock。

3.堆

我们无法直接创建堆上的block,堆上的block需要从stack block拷贝得来,在runtime.c中的_Block_copy_internal函数中,有这样几行:

// Its a stack block.  Make a copy.
if (!isGC) {
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    ...
    result->isa = _NSConcreteMallocBlock;
    ...
    return result;
}

可以看到,栈block复制得来的新block,其isa指针会被赋值为_NSConcreteMallocBlock

4.其余的isa类型
BLOCK_EXPORT void * _NSConcreteAutoBlock[32];
BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32];
BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];

其他三种类型是用于gc和arc,我们暂不讨论

复制block

对block调用Block_copy方法,或者向其发送objc copy消息,最终都会调用runtime.c中的_Block_copy_internal函数,其内部实现会检查block的flag,从而进行不同的操作:

static void *_Block_copy_internal(const void *arg, const int flags) {
  ...
  aBlock = (struct Block_layout *)arg;
  ...
}

1.栈block的复制

 // reset refcount
 result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
 result->flags |= BLOCK_NEEDS_FREE | 1;
 result->isa = _NSConcreteMallocBlock;
 if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
     //printf("calling block copy helper %p(%p, %p)...
", aBlock->descriptor->copy, result, aBlock);
     (*aBlock->descriptor->copy)(result, aBlock); // do fixup
 }

除了修改isa指针的值之外,拷贝过程中,还会将BLOCK_NEEDS_FREE置入,大家记住这个值,后面会用到。 最后,如果block有辅助copy/dispose函数,那么辅助的copy函数会被调用。

2.全局block的复制

else if (aBlock->flags & BLOCK_IS_GLOBAL) {
    return aBlock;
}

全局block进行copy是直接返回了原block,没有任何的其他操作。

3.堆block的复制

f (aBlock->flags & BLOCK_NEEDS_FREE) {
    // latches on high
    latching_incr_int(&aBlock->flags);
    return aBlock;
}

栈block复制时,置入的BLOCK_NEEDS_FREE标记此时起作用,_Block_copy_internal函数识别当前block是一个堆block,则仅仅增加引用计数,然后返回原block。

辅助copy/dispose函数

1.普通变量的复制 辅助copy函数用于拷贝block所引用的可修改变量,我们这里以 __block int i = 1024为例: 先看看Block_private.h中的定义:

struct Block_byref {
  void *isa;
  struct Block_byref *forwarding;
  int flags; /* refcount; */
  int size;
  void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
  void (*byref_destroy)(struct Block_byref *);
  /* long shared[0]; */
};

而我们的__block int i = 1024的转码:

struct __Block_byref_i_0 { void *__isa; __Block_byref_i_0 *__forwarding; int __flags; int __size; int i; };//所以我们知道,当此结构体被类型强转为Block_byref时,前四个成员是一致的,访问flags就相当于访问__flags,而内部实现就是这样使用的 ... __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0, (__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1024};//i初始化时__flags为0 static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

此时,复制时调用的辅助函数:

void _Block_object_assign(void *destAddr, const void *object, const int flags) {//此处flags为8,即BLOCK_FIELD_IS_BYREF
...
  if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  {
      // copying a __block reference from the stack Block to the heap
      // flags will indicate if it holds a __weak reference and needs a special isa
          _Block_byref_assign_copy(destAddr, object, flags);
  }
...
}

static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {//此处flags为8,即BLOCK_FIELD_IS_BYREF
  struct Block_byref **destp = (struct Block_byref **)dest;
  struct Block_byref *src = (struct Block_byref *)arg;
  ...
  else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {//当初次拷贝i时,flags为0,进入此分支会进行复制操作并改变flags值,置入BLOCK_NEEDS_FREE和初始的引用计数
   ...
  }
  // already copied to heap
  else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {//当再次拷贝i时,则仅仅增加其引用计数
      latching_incr_int(&src->forwarding->flags);
  }
  // assign byref data block pointer into new Block
  _Block_assign(src->forwarding, (void **)destp);//这句仅仅是直接赋值,其函数实现只有一行赋值语句,查阅runtime.c可知
}

所以,我们知道,当我们多次copy一个block时,其引用的__block变量只会被拷贝一次。

2.objc变量的复制

当objc变量没有__block修饰时:

static void __OBJ1__of2_block_copy_0(struct __OBJ1__of2_block_impl_0*dst, struct __OBJ1__of2_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

void _Block_object_assign(void *destAddr, const void *object, const int flags) {
  ...
  else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
      //printf("retaining object at %p
", object);
      _Block_retain_object(object);//当我们没有开启arc时,这个函数会retian此object
      //printf("done retaining object at %p
", object);
      _Block_assign((void *)object, destAddr);
  }
  ....
}

当objc变量有__block修饰时:
struct __Block_byref_bSelf_0 { void *__isa; __Block_byref_bSelf_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); OBJ1 *bSelf; }; static void __Block_byref_id_object_copy_131(void *dst, void *src) { _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);//131即为BLOCK_FIELD_IS_OBJECT|BLOCK_BYREF_CALLER } static void __Block_byref_id_object_dispose_131(void *src) { _Block_object_dispose(*(void * *) ((char*)src + 40), 131); } ... //33554432即为BLOCK_HAS_COPY_DISPOSE __block __Block_byref_bSelf_0 bSelf = {(void*)0,(__Block_byref_bSelf_0 *)&bSelf, 33554432, sizeof(__Block_byref_bSelf_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, self};
 
 
 转自于http://www.beyondabel.com/blog/2014/02/15/ios-block/

  

 
原文地址:https://www.cnblogs.com/ndyBlog/p/3958956.html