[iOS开发]Block

Block概要规范

什么是Blocks?

  • 一句话简单概括一下:带有自动变量(局部变量)的匿名函数
    顾名思义:Block没有函数名,另外Block带有^标记,插入记号便于查找到Block
  • Block也被称作闭包、代码块。也就是说我们可以把我们想要执行的代码封装在这个代码块中,我们需要的时候直接进行调用
  • Block共享局部作用域的数据。如果实现一个方法,并且该方法定义一个块,则该块可以访问该方法的局部变量和参数(包括堆栈变量),以及函数和全局变量(包括实例变量)。这种访问是只读的,如果使用__block修饰符来声明变量,我们就可以在Block内修改其值。这些我们后面再说

Block语法

格式

标准格式

请添加图片描述
like:

^int (int count){return count + 1;}
省略格式

返回值类型默认void

如果是void 我们也可以默认省略(void)

请添加图片描述
like:

^{return count + 1;}

Block变量

  • Block变量类似于函数指针
  • 声明Block类型变量仅仅是将声明函数指针类型变量的"*“变为”^"
int (^blk)(int)

block变量与c语言变量完全相同,可以作为以下用途:

  • 自动变量
  • 函数参数
  • 静态变量
  • 静态全局变量
  • 全局变量

截获自动变量

定义时的:带有自动变量值在Block中表现为“截获自动变量值”。

int dmy = 256;
int val = 10;
void (^blk)(void) = ^{
	printf("val = %d\n",val);
};
val = 2;
blk();
return 0;

可以看到val = 10
并没有改变

也就是说变量在代码运行到定义那一块时就被截获了,执行的时候已经不是原变量了

__block说明符

block可以截获变量,但是在块里不能修改变量的值
修改会报错

此时我们使用__block修饰符修饰变量,对需要在block内进行复制的变量,使用修饰符修饰,保证可以对变量进行赋值

int main(int argc, const char * argv[]) {
   __block int val = 10;
    void (^blk)(void) = ^{
//        val = 5;
        printf("val = %d\n",val);
    };
    val = 2;
    blk();
    return 0;
}

刚刚上面的代码 这样就会修改成2了
如果在内部赋值成5 就会变成5了

截获的自动变量

书上这里没有说什么有用的信息
需要注意一句话,在现在的Blocks中呢,截获自动变量的方法并没有实现对C语言数组的截获
也就意味着

const char text[] = "hello";
void(^blk)(void) = ^{
	printf("%c\n",text[2]);
};

会报错
在这里插入图片描述
然而用指针表示这个数组就没问题了
在这里插入图片描述

Block的循环引用

- (id)init {
	self = [super init];
	blk_ = ^{NSLog(@"self = %@", self);};
	return self;
}

初始化这个类的实例时,就会造成循环引用,因为Block语法赋值在了成员变量blk中,因此通过Block语法生成在栈上的Block此时由栈复制到堆,并持有这个self。
self持有Block,Block持有self,循环引用了。

使用__block变量可以避免循环引用
优点:

  • 通过__block变量可以控制对象的持有空间

缺点:

  • 为避免循环引用必须执行Block

Block的实现

Block的实现是基于指针和函数指针,Block属性是指向结构体的指针。

转c++源码然后进行分析

正常的Block流程

void(^myBlock)(void) = ^{
        printf("myBlock");
    };
    myBlock();

源码部分

int main(int argc, const char * argv[]) {
    void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    return 0;
}

上票!这什么玩意
分析一下
初始化定义部分block语法变成了&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
调用block的过程是myBlock->FuncPtr(myBlock)
我们来看一下其中涉及到的对应的部分

初始化Block部分

//Block结构体
struct __main_block_impl_0 {
  struct __block_impl impl;//impl:Block的实际函数指针,就是指向包含Block主体部分的__main_block_func_0结构体
  struct __main_block_desc_0* Desc;//Desc指针,指向包含Block附加信息的__main_block_desc_0()结构体
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {//__main_block_impl_0:Block构造函数
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

所以我们可以看出,__main_block_impl_0结构体也就是Block结构体包含了三个部分:

  1. 成员变量impl;
  2. 成员变量Desc指针;
  3. __main_block_impl_0构造函数

struct __block_impl结构

包含Block实际函数指针的结构体

struct __block_impl {
  void *isa;	//用于保存Block结构体的实例指针
  int Flags;	//标志位
  int Reserved;	//今后版本升级所需的区域大小
  void *FuncPtr;	//函数指针
};
  • __block_impl包含了Block实际函数指针FuncPtrFuncPtr指针指向Block的主体部分,也就是Block对应OC代码中的^{...}的部分
  • 还包含了标志位Flags,在实现block的内部操作时可能会用到
  • 今后版本升级所需的区域大小Reserved
  • __block_impl结构体的实例指针isa

struct __main_block_desc_0

Block附加信息结构体:包含今后版本升级所需区域的大小,Block的大小

static struct __main_block_desc_0 {
  size_t reserved;	// 今后版本升级所需区域大小
  size_t Block_size;	// Block 大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

__main_block_impl_0的构造函数
即然都是构造函数了,其就负责初始化__main_block_impl_0结构体(也就是Block结构体)的成员变量

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }

关于构造函数对各个成员变量的赋值我们回到上面刚刚转成C++的地方

struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *myBlock = &temp;

该代码通过__main_block_impl_0构造函数,生成的__main_block_impl_0结构体(Block结构体)类型实例的指针,赋值给__main_block_impl_0结构体(Block结构体)类型的指针变量myBlock

可以看到,调用Block结构体构造函数的时候,传入了两个参数

第一个参数:__main_block_func_0

  1. 其就是Block对应的Block语法的主体部分,看一下__main_block_func_0结构体在底层c++部分的定义
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("myBlock");
    }

和我们OC中的代码块一样

这里传入的参数__cself是指向Block的值的指针变量,相当于OC中的self
第二个参数:__main_block_desc_0_DATA包含该Block的相关信息

调用部分

Block结构体和成员变量以及分析完了 我们看一下main函数中是如何调用Block的

((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
  • myBlock结构体的第一个成员变量为__block_impl,所以myBlock的首地址,就是__block_impl impl的首地址,也就意味着我们第一个强制类型转换可以直接转换成__block_impl类型
  • ((void (*)(__block_impl *))是__block_impl中Func的类型
  • ((__block_impl *)myBlock)->FuncPtr)调用函数
  • ((__block_impl *)myBlock);函数参数
    对于这个调用参数我理解的意思就是传递其本身到func_0的参数__cself中去,这也就可以看出Block正是作为参数进行的传递。

Block的实质总结

用一句话来说,Block是个对象(其内部第一个成员为isa指针)

在构造函数中,我们可以看到impl.isa = &_NSConcreteStackBlock;
我们对isa指针进行了赋值,_NSConcreteStackBlock相当于该block实例的父类。在将Block作为OC对象调用时,关于该类的信息放置于_NSConcretestackBlock中

  • 对于对象来说,其内部第一个成员为isa指针;
  • 对于其实封装了函数调用来说:Block内代码块封装了函数调用,调用Block,就是调用该封装的函数
  • 对于执行上下文而言,Block的描述Desc,其描述对象包含了Block信息以及捕获变量的内存相关函数,还有Block所在的上下文?

Block的类型

//MRC环境下
int age = 10;
        void (^block)(void) = ^{
            NSLog(@"%d %d", height, age);
        };
        
        NSLog(@"%@\n %@\n %@\n",
              [block class],
              [[block class] superclass],
              [[[block class] superclass] superclass]);
        
        return 0;

对应的结果
__NSStackBlock __
NSBlock
NSObject
这也就说明上面的这个Block的类型是NSStackBlock
并且它最终继承自NSObject也可以说明Block的本质是OC对象

Block有三种类型

  • NSMallocBlock(_NSConcreteMallocBlock) 对象存储在堆区
  • NSStackBlock(_NSConcreteStackBlock) 对象存储在栈区
  • NSGlobalBlock(_NSConcreteGlobalBlock)对象存储在数据区

简单来说

  • 捕获了自动变量 就是Block就是栈类型
  • 没有捕获就是数据区
  • 不会有一创建就在堆区的,堆区的意义可以理解为和autorelease一样 延长作用域 Stack类型的Block进行了copy操作之后变成了堆区

详细了解一下下面三种情况

NSGlobalBlock
  1. 记述全局变量的地方使用Block就会默认为Global
    在这里插入图片描述

  2. Block语法的表达式没有截获自动变量

NSStackBlock

除了上述的情况 其他情况下创建的Block都是NSConcreteStackBlock对象

其存储在栈区。如果其所属的变量作用域结束,则该Block就会被废弃,如果Block使用了__block变量,则__block变量同样被废弃

NSMallocBlock

arc下没有栈block了。因为block会自动copy变成堆block

为了解决栈上的Block在变量作用域结束被废弃这一问题,Block提供了复制的功能,可以将Block对象和__block变量从栈区复制到堆区上。

所以后面即使栈区的作用域结束时,堆区上的Block和__block变量仍然可以继续存在,也可以继续使用

即书上的这个图

请添加图片描述
block作为属性,用什么关键字修饰?

mrc下:使用copy修饰。因为block申明在栈区,使用copy修饰可以将block从栈区copy到堆区。

arc下:使用copy与strong修饰都可以。因为就算用strong,程序会自动copy将block从栈区copy到堆区。

实际测试了一下 无论ARC还是MRC,strong和copy 都可以使对象到堆上 嗯…优化了吧

拷贝情况

在ARC环境下编译器会进行判断,三种情况会自动复制

  • Block作为函数返回值返回
  • 向方法或函数中传递Block,使用以下两种方法的情况下
    • Cocoa框架的方法且方法名中含有usingBlock等时
    • GCD的API
  • 将Block赋值给类的附有__strong修饰符的id类型 或 Block类型的成员变量时

在这里插入图片描述

当然有自动拷贝我们也可以手动拷贝

我们手动拷贝对不同的Block类有不同的效果

  • 栈区 -> 从栈拷贝到堆
  • 数据区 -> 不改变
  • 堆区 -> 引用计数增加

__block变量的拷贝
在使用__block变量的Block从栈复制到堆上时,__block变量也会受到影响

按照oc的内存管理机制来管理,此时两者的关系就从block使用__block变成了block持有__block
在这里插入图片描述

Block截获变量

ARC的时候我们就了解过,变量的几种修饰符,我们来看一下Block如何捕获不同修饰符的类型变量

先扔个结论后面好用

全局变量: 不捕获
局部变量: 捕获值
静态全局变量: 不捕获
静态局部变量: 捕获指针

先看正常的

局部变量的例子

int c = 30;
int main() {
    int a = 10, b = 20;
    void(^myLocalBlock)(void) = ^{
        NSLog(@"%d\n%d\n%d\n",a,b,c);
    };
    void(^Block)(int, int, int) = ^(int a, int b, int c) {
        NSLog(@"%d\n%d\n%d\n",a,b,c);
    };
    a = 40;
    b = 50;
    myLocalBlock();
    Block(a,b,c);
    return 0;
}

直接访问会被捕获
间接不会被捕获

Block表达式截获所使用的局部变量的值,保存了该变量的瞬时值。

Block的自动变量的截获只针对Block中使用的自动变量

直接访问局部变量

带着疑问,为什么仅仅截获瞬时值,而不是局部变量的当前值?看一下对应的c++源码

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  int b;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int _b, int flags=0) : a(_a), b(_b) {
    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
  int b = __cself->b; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_pt_w7b9v92s197g97khtskqdnn40000gn_T_main_1f40e5_mi_0,a,b,c);
    }

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 a = 10, b = 20;
    void(*myLocalBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, b));
    a = 40;
    b = 50;
    ((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);
    return 0;
}
  • 可以看到__main_block_impl_0结构体中多了两个成员变量a,b,这两个的值来自__main_block_impl_0构造函数中传递的值
    构造函数加冒号就是相当于一个赋值的过程
    在这里插入图片描述
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int _b, int flags=0) : a(_a), b(_b) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
  • __main_block_func_0结构体中,变量a,b的值使用的是__cself获取的值
    a = __cself->a; b = __cself->b;这也就说明了a,b只是Block内部的变量,改变Block外部的局部变量值,并不能改变Block内部的变量值
  • 我们也可以发现全局变量并没有存储在Block的结构体中,而是在调用的时候通过直接访问的方式来调用

小小的总结一下这个流程

Block语法部分的源码就是
调用__main_block_impl_0的构造函数,构造函数正常有两个参数,func_0和desc_0,在截获自动变量时,会把需要截获的自动变量也放入参数列表中,同时__main_block_impl_0中也会增加两个成员变量a,b,构造函数带参数就是自动给这两个成员变量赋值。在调用func_0时,直接通过__cself(相当于self,其传递的参数也是其本身)的a、b(此时的a,b就是成员变量中的,因为上面的构造函数已经赋值了)

通过传值间接访问局部变量
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    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, int b, int c) {

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_pt_w7b9v92s197g97khtskqdnn40000gn_T_main_ce211a_mi_0,a,b,c);
    }

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 a = 10, b = 20;
    void(*Block)(int, int, int) = ((void (*)(int, int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    a = 40;
    b = 50;
    ((void (*)(__block_impl *, int, int, int))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block, a, b, c);
    return 0;
}
  • __main_block_impl_1结构体并没有变量a、变量b,说明通过直接传值的方式,变量并没有存进Block的结构体中。
  • func_0函数中,直接通过参数列表把a,b传进去了,并且在调用时直接传入a,b,c的值这是和直接调用局部变量不同的。

static修饰变量

static int c = 30;
int main() {
    static const int a = 10;
    static int b = 20;
      void (^Block)(void) = ^{
        printf("a = %d, b = %d, c = %d\n",a, b, c);
    };
    b = 100;
    c = 100;
      Block();                 // 输出结果:a = 10, b = 100, c = 100
}

通过上面这个demo我们可以看出
Block对象不会捕获静态全局变量和静态局部变量

同样过一下源码

static int c = 30;

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

        printf("a = %d, b = %d, c = %d\n",(*a), (*b), c);
    }

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() {
    static const int a = 10;
    static int b = 20;
      void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a, &b));
    b = 100;
    c = 100;
      ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
  • 同样根据__main_block_impl_0结构体中,静态局部变量static int b以指针的形式添加为成员变量,而静态局部变量static const int aconst int *的形式添加为成员变量。而全局静态变量并没有添加为成员变量
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const int *a;
  int *b;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const int *_a, int *_b, int flags=0) : a(_a), b(_b) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 再看func_0结构体部分,静态全局变量直接访问
    静态局部变量和静态局部常量都是通过指针传递,但是const修饰的无法进行赋值操作
    在这里插入图片描述

我们为什么能获取static变量最新的值?

  1. static修饰的,均存储在全局存储区,该地址在程序运行过程中一直不会改变,所以能访问最新值。
  2. static在修饰后,全局变量直接访问,局部变量指针访问(只要含static变量)。

const修饰变量

const int c = 30;
int main() {
    const int a = 10;
    int b = 20;
      void (^Block)(void) = ^{
        printf("a = %d, b = %d, c = %d\n",a, b, c);
    };
      Block();                 // 输出结果:a = 10, b = 50, c = 60
}
const int c = 30;

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

        printf("a = %d, b = %d, c = %d\n",a, b, c);
    }

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() {
    const int a = 10;
    int b = 20;
      void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, b));
      ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}

可以看到

  • const全局变量仍然是直接访问的
  • const局部变量和正常的自动变量一样,依然是值传递

总结

全局变量: 不捕获
局部变量: 捕获值
静态全局变量: 不捕获
静态局部变量: 捕获指针
const修饰的和局部变量一样

Block截获对象

int main() {		
    id obj = [NSMutableArray array];
      void (^Block)(void) = ^{
          NSLog(@"%@",obj);
    };
    [obj addObject:@1];
      Block();
}
//结果为1
#import "Person.h"
int main() {
    Person *person = [[Person alloc]init];
    person.age = 20;
      void (^Block)(void) = ^{
          printf("%d",person.age);
    };
    person.age = 30;
      Block();
}
//结果输出为30

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


          NSLog((NSString *)&__NSConstantStringImpl__var_folders_pt_w7b9v92s197g97khtskqdnn40000gn_T_main_09fb31_mi_0,obj);
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 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};
int main() {
    id obj = ((NSMutableArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));


      void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));

    ((void (*)(id, SEL, ObjectType  _Nonnull __strong))(void *)objc_msgSend)((id)obj, sel_registerName("addObject:"), (id _Nonnull)((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1));
    
      ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
  • 我们看到__main_block_impl_0结构体中多了一个成员变量__strong id obj;,因为obj是自动变量,所以这里捕获了自动变量obj作为_main_block_impl_0结构体的成员变量。
  • fun0都是指针截获的(其实也不能这样说,应该说在结构体外面自动变量是什么类型,结构体里面成员变量也是什么类型,id和person都是指针类型,所以结构体里面也是指针类型),func_0也有__strong id obj = __cself->obj; Person *__strong person = __cself->person;这样赋值的操作
  • 同时还多出来两个函数指针
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

针对这两个函数指针

  • __main_block_copy_0作用就是调用_Block_object_assign,相当于retain,将对象赋值在对象类型的结构体变量__main_block_impl_0中。在栈上的Block复制到堆时会进行调用。
  • __main_block_dispose_0调用_Block_object_dispose,相当于release,释放赋值在对象类型的结构体变量中的对象。在堆上Block被废弃时会被调用。

__block修饰符

__block修饰局部变量

int main() {
    __block int a = 10, b = 20;
      void (^Block)(void) = ^{
          printf("%d \n%d \n", a, b);
    };
    a = 100;
    b = 100;
      Block();
}
//100 100
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};
struct __Block_byref_b_1 {
  void *__isa;
__Block_byref_b_1 *__forwarding;
 int __flags;
 int __size;
 int b;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __Block_byref_b_1 *b; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_b_1 *_b, int flags=0) : a(_a->__forwarding), b(_b->__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
  __Block_byref_b_1 *b = __cself->b; // bound by ref

          printf("%d \n%d \n", (a->__forwarding->a), (b->__forwarding->b));

    }
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*/);_Block_object_assign((void*)&dst->b, (void*)src->b, 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*/);_Block_object_dispose((void*)src->b, 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() {
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
__Block_byref_b_1 b = {(void*)0,(__Block_byref_b_1 *)&b, 0, sizeof(__Block_byref_b_1), 20};
      void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, (__Block_byref_b_1 *)&b, 570425344));
    (a.__forwarding->a) = 100;
    (b.__forwarding->b) = 100;
      ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}

上票!

就加了一个__block修饰符就成这了

看一下具体是咋实现的

一个道理,从__main_block_impl_0说起

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __Block_byref_b_1 *b; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_b_1 *_b, int flags=0) : a(_a->__forwarding), b(_b->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • __block int a __block int b分别变成了 __Block_byref_a_0 *a; __Block_byref_b_1 *b;结构体类型的指针 (为什么是结构体指针,因为通过__block修饰的变量可能不止一个?)对于用__block修饰的变量,不管使用了没,都会相应的生成一个结构体
    拿其中任何一个结构体举个例子(只有成员变量不一样奥别尬黑)
struct __Block_byref_a_0 {
  void *__isa; //标识对象类的isa实例变量
__Block_byref_a_0 *__forwarding; //传入变量的地址
 int __flags; //标志位
 int __size; //结构体大小
 int a; //存放a实际的值,和之前不加__block修饰符时一致
};

来看一下main函数中这两个的赋值情况

a = {
void *__isa = 0,//对于c语言基础数据类型会是0,因为他们不是对象,没有所属类
__Block_byref_a_0 *__forwarding = &a, 
 int __flags = 0,
 int __size = sizeof(__Block_byref_a_0),
 int a = 10;
};

可以看到__isa指针值传空,__forwarding指向了局部变量a本身的地址,__flags分配了0,__size为结构体大小,a赋值为10。
在这里插入图片描述
所以在其中,__forwarding其实就是局部变量a本身的地址。

我们可以看到main函数中,

    (a.__forwarding->a) = 100;
    (b.__forwarding->b) = 100;

所以通过__block来修饰的变量,在Block的主体部分中改变值的原理其实是通过指针传递的方式进行修改。

那么堆上和栈上都有我们的__block,我们如何找到我们需要的那个?

这就用到了__forwarding指针,它在没有复制的时候就是简单的指向自己,而当进行复制以后,就会指向堆上的那个__block变量

栈上不持有仅仅是使用,复制到堆上才持有 同时调用copy那个函数

请添加图片描述

__block修饰对象

int main() {
    __block Person *person = [[Person alloc]init];
    void (^Block)(void) = ^{
          person = [[Person alloc]init];
          NSLog(@"%@",person);
      };
    Block();
}
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 *__strong 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

          (person->__forwarding->person) = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
          NSLog((NSString *)&__NSConstantStringImpl__var_folders_pt_w7b9v92s197g97khtskqdnn40000gn_T_main_b381a2_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*);
} __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_person_0 person = {(void*)0,(__Block_byref_person_0 *)&person, 33554432, sizeof(__Block_byref_person_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"))};
    void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}

依旧是老样子 从__main_block_impl_0开始看

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

并无大碍,继续看一下person_0和对应的初始化

struct __Block_byref_person_0 {
  void *__isa = 0;
__Block_byref_person_0 *__forwarding = &person
 int __flags = 33554432
 int __size = sizeof(__Block_byref_person_0), 
 void (*__Block_byref_id_object_copy)(void*, void*) = __Block_byref_id_object_copy_131,
 void (*__Block_byref_id_object_dispose)(void*) = __Block_byref_id_object_dispose_131,
 Person *__strong person;
};

flags = 33554432 即二进制的 1 << 25
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler 译:compiler 含有copy_dispose助手【即拥有copy和dispose函数】
嗯…没懂
然后这里有两个函数 __Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131

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_object_assign_Block_object_release为什么要加40?

struct __Block_byref_person_0 {
  void *__isa; //8字节
__Block_byref_person_0 *__forwarding; //8字节
 int __flags; // 4字节
 int __size;  //4字节
 void (*__Block_byref_id_object_copy)(void*, void*);  //8字节
 void (*__Block_byref_id_object_dispose)(void*); //8字节
 Person *__strong person;
};

impson_0的地址和person的地址相差40字节,所以+40是为了找到person指针,通过person指针进行调用吗?嗯…有点道理

__block修饰局部变量,这个变量在block内外属于同一个地址上的变量,可以被block内部的代码修改。
请添加图片描述

那么这里具体的逻辑其实就是:
在这里插入图片描述

Block的内存管理

我们先回顾一下之前学习的变量/对象是怎么管理内存的

干预:程序员手动管理,本质还是要系统管理内存的分配和释放

  • 自动局部基本类型变量,因为是值传递,内存是跟随Block,不用干预
  • static局部基本类型变量,指针传递,由于分配在静态区,故不用干预
  • 全局变量,存储在数据去,不用干预
  • 局部对象变量,如果在栈上,不用干预。但Block在拷贝到堆时,对其retain,在Block对象销毁时,对齐release

我发现只要牵扯引用计数就不对劲了

几个问题
为啥是1 …
难道ARC和MRC截获__block对象时都不会调用assign方法吗…idon’tknow
在这里插入图片描述

还有一个问题
在这里插入图片描述
在这里插入图片描述
这个问题可能可以大概理解一下 栈上的Block不会持有对象,仅仅是使用,copy到堆上,持有了一次,变成2,如果加__block变量,还会是1,因为MRC环境下assign这个不会被调用。

对于这个3

对象持有一次
栈上的block没有持有 而是这个第四行成员变量这个指针持有一次
堆上copy持有一次
总共为3

循环引用的问题

  1. 什么时候会发生循环引用,如何解决?
    一个对象中强引用了block,在block中又使用了该对象,就会发生循环引用
    解决是:将该对象使用__weak或者__block修饰符修饰之后再在block中使用。
  2. 变量前加block和weak和strong的区别
  • __strong是为了防止block持有的对象提前释放,__weak和__block是解决循环引用
  • __block不管是ARC还是MRC都可以使用,可以修饰对象和基本数据类型
  • __weak只能在ARC模式下使用,也只能修饰对象,不能修饰基本数据类型
  • __block对象可以在Block中被重新赋值,__weak不可以
  1. 对于__block变量MRC如何解决循环引用?ARC如何解决?
  • MRC解决循环引用用__block,禁止Block对所引用的对象进行retain操作.
  • ARC时期__block并不能禁止Block对所引用的对象进行强引用。解决办法可以是在Block中将变量置空,因为需要在Block中对Block进行操作,所以还是需要__block修饰符
  • __block在MRC下有两个作用
    1. 允许在BLock中访问和修改局部变量
    1. 进制Block对所引用的对象进行retain操作
  • ARC下仅仅只有一个作用
    1. 允许在BLock中访问和修改局部变量

iOS开发“强弱共舞——weak和strong配套使用解决block循环引用问题

  • __weak是为了解决循环引用
  • __strong是为了防止block持有的对象提前释放
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值