Block的深究

Block基础

1. 什么是Block

Block是对象,它封装和保存了一段代码,这段代码可以在任何时候执行。block可以作为函数参数或函数的返回值,本身又可作为参数或返回值。

2. Block和函数的相似性

1) 可以保存代码
2) 有返回值
3) 有形参
4) 调用方式一样

3. Block的语法

1) block作为本地变量

 void(^block)() = ^{
          		NSLog(@"block本地变量");
           };
           block();

2) block作为类的成员属性

@property (nonatomic ,copy)  void(^block)(NSString *param);

3) block作为函数参数

-(void)testWithBlock:(void(^block)(NSString *param))block;

4)使用typedef定义block类型

typedef void(^block)(NSString *param);
block blockName = ^(NSString *param) {
	NSLog(%@,param);
};

4. Block可以访问局部变量,但是不能修改局部变量,如果要修改就要将局部变量加上关键字__block。

// 这种情况会报错
int num = 0;
int (^addBlock)(int a,int b) = ^(int a,int b){
	num = a+b;
	return num;
};

// 这种情况是正确的
__block int num = 0;
int (^addBlock)(int a,int b) = ^(int a,int b){
	num = a+b;
	return num;
};
// 

将此代码在main方法里运行,注意:main.m文件中不能出现import UIKit和Applegate,要改为#include <stdio.h>

#include <stdio.h>

int main(int argc, char * argv[]) {
   @autoreleasepool {
       int num = 20;
       void (^testBlock)(void) = ^(){
           printf("%d",num);
       };
       num = 30;
       testBlock();
   }
}

打开mian.m文件所在的文件夹,终端运行clang -rewrite-objc main.m,用clang编译器对源文件main.m中的objective-c代码转换成C代码放在main.cpp文件中,会在此文件夹下生成main.cpp文件,双击打开。

在mian方法中,num的定义与OC里相同。

1.不带__block修饰的变量

1)__block_imp 结构体

__block_imp 结构体是编译器生成的结构体,每一个block都会用到这个结构体

struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr; // 函数指针,指向编译器生成的__main_block_func_0
};
2)__main_block_impl_0

__main_block_impl_0是一个函数名,有三个参数,又是一个结构体,结构体如下:

struct __main_block_impl_0 {
 	struct __block_impl impl;  // __block_impl变量impl
 	struct __main_block_desc_0* Desc;  //__main_block_desc_0 指针,指向编译器给我们生成的结构体变量
 	int num;   // 
 	// 构造体的构造函数
 	__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, int 		flags=0) : num(_num) {  
		 impl.isa = &_NSConcreteStackBlock;
 	 	 impl.Flags = flags;
		 impl.FuncPtr = fp;
 		 Desc = desc;
 	}
};

其中,

num(_num):

在 c++ 里面 指定_num(形参) 将来赋值给num 这个实参,也就是这个__main_block_impl_0 结构体中的 int num;在这里 int num = 20

impl.FuncPtr = fp;

将fp赋值给了 impl 结构体的 FuncPtr 参数

3)__main_block_func_0

编译器根据block代码生成的全局态函数,会被赋值给impl.FuncPtr

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  	int num = __cself->num; // 结构体访问自己的属性,访问num=20

  	printf("%d",num);
}
4) __main_block_desc_0

编译器根据block代码生成的block描述,主要是记录下__main_block_impl_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)}; // //这里就生成了__main_block_desc_0的变量__main_block_desc_0_DATA
5) void (*testBlock)(void)
void (^testBlock)(void) = ^(){
            printf("%d",num);
        };

对应于

void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));

__main_block_impl_0((void )__main_block_func_0, &__main_block_desc_0_DATA, num)是创建了一个__main_block_impl_0结构体。 前面加了&,是取了这个实例的地址。最后(void ()())将它强转为一个函数地址。

整句话就是定义一个函数指针指向一个新创建的__main_block_impl_0实例的地址。这个实例的两个参数正是编译器生成的静态函数__main_block_func_0和__main_block_desc_0的变量__main_block_desc_0_DATA。

6)testBlock()
testBlock();

对应着

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

整句话就是通过函数指针testBlock调用函数FuncPtr,传的参数为指针testBlock本身。

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int num = 20;
        void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
        num = 30;
        ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    }
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

调用testBlock的时候,虽然num变为30,但是打印的是FuncPtr存放的是num=20。
总的来说,当我们声明一个block变量a并为它赋值时,其实就是创建一个函数指针ptrA,再根据block a赋值的代码生成一个静态函数,而指针ptrA就指向这个静态函数。block a调用时就是使用函数指针ptrA调用生成的静态函数。

__block修饰的变量

#include <stdio.h>

int main(int argc, char * argv[]) {
   @autoreleasepool {
       int num = 20;
       void (^testBlock)(void) = ^(){
           printf("%d",num);
       };
       num = 30;
       testBlock();
   }
}

打印的是30.

1)__Block_byref_num_0

根据带__block修饰的变量num,编译器生成的结构体

struct __Block_byref_num_0 {
  void *__isa;
__Block_byref_num_0 *__forwarding; // //指向被创建出来的__Block_byref_num_0实例
 int __flags;
 int __size;
 int num;
};
2) __main_block_impl_0
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_num_0 *num; // 保存__Block_byref_num_0变量
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
3) __main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_num_0 *num = __cself->num; // bound by ref

            printf("%d",(num->__forwarding->num));
        }
4) __main_block_copy_0和__main_block_dispose_0

这两个函数分别会在block被拷贝到堆和释构时调用的,作用是对__Block_byref_num_0实例的内存进行管理

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

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}
5)__main_block_desc_0
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*);// //回调函数指针,会被赋值为__main_block_copy_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};
6) main方法
int main(int argc, char * argv[]) {
    { __AtAutoreleasePool __autoreleasepool; 
    // 我们定义的__block int num转换后并不是一个简单的栈变量,而会是新建的__Block_byref_num_0堆变量*/
        __attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 20};
        void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
        (num.__forwarding->num) = 30;
        ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    }
}

使用了__block 所以创建了一个block 类型的结构体,取值的时候,拿到的是结构体的地址,只要把地址传递过去,就有了最高的操作权限,到时候再去取值就可以取到内存中最新的值。
最后的(num.__forwarding->num) = 30;拿到结构体里面的地址去修改num的值为30。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值