读书笔记(二)

void (^blk) (void) = ^{pringf("block \n");};

这个block的其实就是编译成一个函数指针void (*blk) (void) = &impl_0(func_0, &desc_0)

impl_0 func_0 desc_0都是结构体,impl_0的构造函数是以func_0,desc_0型变量为参数的。

blk(); 使用block其实就是调用函数指针,(*blk->impl.FuncPtr)(blk); (func_0以impl_0为参数)

block如何截取局部变量的值的?

在编译的时候,如果block声明前有局部变量,并在block内被使用了,在impl_0的构造函数的参数列表就有这个局部变量,并且使用局部变量的值来初始化;在impl_0的成员变量也添加了这些成员变量。

在block声明前如果有静态局部变量,

static int static_val = 3;

相当于在impl_0内增加了int *static_val成员变量,使用该变量时即是使用它的指针进行访问。

为什么非静态的局部变量没有用这个方式来进行访问呢,如果这样就不存在block截获局部变量的值的情况了。因为如果局部变量被截获后(在block声明处被截获),如果该局部变量作用域结束,这个局部变量就被废弃了,block中就不能再对它访问了。

__block实现

__block int val = 0;

被编译成:(括号里面是赋的值)

struct __Block_byref_val_0{

    void *__isa;(0)

    __Block_byref_val_0 * __forwarding;(&val)

    int __flags;(0)

    int __size;(__Block_byref_val_0)

    int val;(10)

}

block内对val赋值:

^{val = 1;}

在func_0内被编译成,在impl_0中同样会添加成员变量

{

    __Block_byref_val_0 *val = __cself->val;

    val->__forwarding->val = 1;

}

Block存储域

一般block设置在上,_NSConcreteStackBlock。isa=_NSConcreteStackBlock

有两种情况在global数据区 即isa=_NSConcreteGlobalBlock:

1、block声明为全局变量

2、block中不截获局部变量的值(执行时不依赖局部变量的值,整个程序只需一个实例)

设置在栈上有个问题,就是声明block时的变量作用域结束了,栈也就没了,block和__block变量也都没了,所以arc时,大多数情况下编译器会自动生成将block从栈上复制到堆上的代码来解决这个问题。

比如下面这个将block作为函数返回值

blk_t func(int rate)

{

    return ^(int count){return rate * count;};

}

blk_t func(int rate)

{

    blk_t tmp = &_func_block_impl_0(func_0,desc_0,rate);

    tmp = _Block_copy(tmp);从栈复制到堆

    return objc_autoreleaseReturnValue(tmp); 注册到autoreleasePool并返回

}

有些情况编译器不能判断:

函数参数传递block(例外:函数里面已经复制了参数的就不用在函数前手动复制了,还有框架的某个方法名中有usingBlock,GCD的API里)

对block调用copy方法时,原来在栈上的block复制到堆上;原来在数据区的,什么也不做;原来在堆的,引用计数加1

那么block中使用的__block变量在block从栈复制到堆上又发生了什么?

同样是从栈复制到堆,并依然被该block所持有。

__block变量在从栈复制到堆上的时候,将__forwarding指向堆上的__block变量,所以不论是访问栈上的(在block外使用__block变量)还是访问堆上的(block内使用__block变量),都能通过val.__forwarding->val来访问。(访问的都是堆上的??)

block持有截获的局部变量(如果是weak类型呢??)

typedef void(^blk_t)(id obj);

blk_tblk;

{

id array = [[NSMutableArray alloc] init];

blk = [^(id b){

[array addObject:obj];

NSLog(@"array count %ld",[array count]);

}copy];

}

blk([[NSObject alloc] init]);

blk([[NSObject alloc] init]);

blk([[NSObject alloc] init]);


读书笔记(二)_第1张图片
strong

block在编译后成员变量添加了id __strong array;

由于[block copy]的调用,多了__main_block_copy_0和__main_block_dispose_0方法,在__main_block_copy方法中调用__Block_object_assign将对象赋值给array成员变量并持有该对象,同理,__main_block_dispose_0方法中调用_Block_object_dispose释放赋值给array的对象。

所以在大括号结束时,即便array已经超过变量作用域,堆上的block依然持有该对象,不能被废弃。

block从栈赋值到堆有四种情况:

1、[block copy]

2、return block; block作为函数返回值

3、id strong blk = block; / self.blk = block;

4、xxxusingBlock(blk_t blk) /  GCD的API用block做参数的


变成weak之后 发现block结构体中的成员变量也变成weak了


读书笔记(二)_第2张图片
改为weak
读书笔记(二)_第3张图片
持有weak变量

array2在出变量作用域之后设为nil,所以三次输出都是0

可用声明为weak来避免循环引用。


看一下block截获基础类型变量和对象类型变量的区别

__block int  val = 10;

该变量转换为结构体__Block_byref_val_0

同时转换后的文件头部多了___main_block_copy_0___main_block_dispose_0这两个函数定义,

这两个函数内部调用了__Block_object_assign和_Block_object_dispose方法,

__Block_object_assign方法中让Block持有__block变量

这两个方法有个参数block_field_is_byref(绿色的)代表是assign了__block变量,如果是id类型,但是没有__block修饰符,则这里的值是BLOCK_FIELD_IS_OBJECT。

具体的__Block_object_assign实现是这样的:


这两个函数在main中并没有被调用,那么是何时进行了调用呢?

当block从栈复制到堆上的时候,调用__main_block_copy_0,__block变量也从栈复制到堆上时,并被Block所持有。

读书笔记(二)_第4张图片

__block id obj = [[NSObject alloc] init];的情况

__Block_object_assign函数将__block变量赋值给block成员变量,strong类型变量持有对象,从而block持有该对象

只要__block变量在堆上存在,对象就会一直被持有。

读书笔记(二)_第5张图片

__block id __strong obj = [[NSObject alloc] init];  和不带__strong的情况一样

总结一下就是:

ARC下,Block中引用id类型的数据有没有__block都一样都是retain,而对于基础变量如int而言,没有的话无法修改变量值,有的话就是修改其结构体令其内部的forwarding指针指向拷贝后的地址达到值的修改。而MRC下,则都是拷贝一份指针。

你可能感兴趣的:(读书笔记(二))