iOS-基础与底层iOS学习iOS学习笔记

深入研究Block实现原理

2016-12-08  本文已影响477人  CholMay

摘要

Blocks是C语言的扩充功能, iOS 4中引入了这个新功能“Blocks”,那么block到底是什么东西呢。其实它就是一个闭包,一个带有自动变量(局部变量)的匿名函数。很多语言也实现自己的闭包,比如C#的lamda表达式。这篇文章将从分析源码的角度来分析下block到底是什么鬼。

研究工具:clang

为了研究编译器的实现原理,我们使用clang(LLVM编译器,和GCC类似),通过命令clang -rewrite-objc main.m,解析main.m,这样我们就会得到对应的cpp文件main.cpp,就能看到block内部实现代码(后面有源码),借此可以研究 block 中各个特性的源码实现方式。

一、 block捕获外部变量

说到block怎么捕获外部变量,我们要知道c语言中的5种变量:

今天主要对除函数参数变量之外的四种变量的捕获情况进行研究
根据这四种变量写出测试代码如下:


测试代码出错,原因是变量d没有加__block修饰,由于__block稍微复杂,我们后边再讲解,现在我们先对静态变量、静态全局变量、全局变量进行分析,代码:
#import <Foundation/Foundation.h>
int global_a = 1;
static int static_global_b = 2;
int main(int argc, const char * argv[]) {
    
    static int static_c = 3;
    int d = 4;
    void(^TestBlock)() = ^{
        NSLog(@"block内部:global_a = %d, static_global_b = %d, static_c = %d, d = %d", global_a, static_global_b, static_c, d);
    };
    global_a ++;
    static_global_b ++;
    static_c ++;
    d ++;
    NSLog(@"block外部:global_a = %d, static_global_b = %d, static_c = %d, d = %d", global_a, static_global_b, static_c, d);
    TestBlock();
    
    return 0;
}

运行结果:

 block外部:global_a = 2, static_global_b = 3, static_c = 4, d = 5
block内部:global_a = 2, static_global_b = 3, static_c = 4, d = 4```
在这里有两个问题:
1、为何不加__block就不能修改自动变量的值?
2、为何自动变量的值没有增加,其他变量的值增加?自动变量什么情况下才能在block中增加修改?
为了弄清楚以上两个疑问,我们用clang转换一下源码分析:

int global_a = 1;
static int static_global_b = 2;

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

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_4acb3a_i_0, global_a, static_global_b, (*static_c), d);
}

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[]) {

static int static_c = 3;
int d = 4;
void(*TestBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_c, d));
global_a ++;
static_global_b ++;
static_c ++;
d ++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_4acb3a_i_1, global_a, static_global_b, static_c, d);
((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);

return 0;

}

我们先简单解释下三个概念:`__main_block_impl_0`、`__main_block_func_0`、 `__main_block_desc_0`

- ######__main_block_func_0
block内部的实现函数,其中__block_impl中的FuncPtr指向这个函数

- ######__main_block_desc_0
1、`reserved`:保留字段默认为0
2、`Block_size`:为`sizeof(struct __main_block_impl_0)`,用来表示block所占内存大小。因为没有持有变量,block大小为impl的大小加上Desc指针大小
3、`__main_block_desc_0_DATA`:`__main_block_desc_0`的一个结构体实例
这个结构体,用来描述block的大小等信息。如果持有可修改的捕获变量时(即加__block),会增加两个函数(copy和dispose),我们后面会分析
- __main_block_impl_0
我们可以看到__main_block_impl_0结构体中包含`__block_impl` 、`__main_block_desc_0`两个类型的结构体变量,其中`__block_impl`的内部实现源码如下:

struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};```
1、isa指针,如果我们对runtime了解的话,就明白isa指向Class的指针。
2、Flags,当block被copy时,应该执行的操作
3、Reserved为保留字段
4、FuncPtr指针,指向block内的函数实现
__block_impl保存block的类型isa(如&_NSConcreteStackBlock),标识(当block发生copy时,会用到),block的方法。
接下来我们看下在main函数中block实现代码:

void(*TestBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_c, d));```
去掉一些类型转换代码:

void(*TestBlock)() = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &static_c, d));```
调用block时的代码:

((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);```
去掉类型转换之后代码:

TestBlock->FuncPtr(TestBlock);```
可以看到以上代码是调用__main_block_impl_0结构体中的构造函数,将变量传入结构体内部保存,之后将这个结构体作为参数传给FuncPtr指向的函数即__main_block_func_0, 其中静态变量static_c传入block内部的是地址,自动变量传入的是值,而且在block外部执行d++之前已经将d的值捕获进入block内部, 这也就能说明为何block内部不能改变静态变量的值的原因
最终在block内部实现结果:

impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
static_c = 3;
d = 4;```

到此,__main_block_impl_0结构体就是这样把自动变量捕获进来的。也就是说,在执行Block语法的时候,Block语法表达式所使用的自动变量的值是被保存进了Block的结构体实例中,也就是Block自身中。

这里值得说明的一点是,如果Block外面还有很多自动变量,静态变量,等等,这些变量在Block里面并不会被使用到。那么这些变量并不会被Block捕获进来,也就是说并不会在构造函数里面传入它们的值。

Block捕获外部变量仅仅只捕获Block闭包里面会用到的值,其他用不到的值,它并不会去捕获。
我们再来看一下__main_block_func_0函数

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_c = __cself->static_c; // bound by copy
int d = __cself->d; // bound by copy

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_4acb3a_i_0, global_a, static_global_b, (*static_c), d);

}```
可以看到,在函数内部通过__cself->static_c__cself->d来获取static_cd的值,由于结构体中捕获了变量的值,因此__main_block_impl_0类型__cself能够获取到内部保存的变量值,但是在函数内部只能修改捕获地址值的static_c变量,不能修改传入值变量的d的值。

到此为止,上面提出的二个问题就解开答案了。首先全局变量global_a和静态全局变量static_global_b的值增加,以及它们被Block捕获进去,这一点很好理解,因为是全局的,作用域很广,所以Block捕获了它们进去之后,在Block里面进行++操作,就像局部函数一样,可以成功修改全局变量的值,Block结束之后,它们的值依旧可以得以保存下来。自动变量是以值传递方式传递到Block的构造函数里面去的。Block只捕获Block中会用到的变量。由于只捕获了自动变量的值,并非内存地址,所以Block内部不能改变自动变量的值。Block捕获的外部变量可以改变值的是静态变量,静态全局变量,全局变量。

总结一下:在Block中改变变量值有2种方式,一是传递内存地址指针到Block中,二是改变存储区方式(__block)。

我们先试一下第一种方式:传递内存地址,代码:

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {

    NSMutableString *str = [NSMutableString stringWithString:@"123"];
    void(^TestBlock)() = ^{
        [str appendString:@" 456"];
        NSLog(@"block中 %@", str);
    };
    NSLog(@"block前 %@", str);
    TestBlock();
    
    return 0;
}```
控制台输出:

block前 123
block中 123 456

内部实现源码:

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

    ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)str, sel_registerName("appendString:"), (NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_fde5a9_mi_1);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_fde5a9_mi_2, str);
}

static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) {_Block_object_assign((void)&dst->str, (void)src->str, 3/BLOCK_FIELD_IS_OBJECT/);}

static void __main_block_dispose_0(struct __main_block_impl_0src) {_Block_object_dispose((void)src->str, 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(int argc, const char * argv[]) {

NSMutableString *str = ((NSMutableString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)objc_getClass("NSMutableString"), sel_registerName("stringWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_fde5a9_mi_0);
void(*TestBlock)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, str, 570425344));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_fde5a9_mi_3, str);
((void (*)(__block_impl *))((__block_impl *)TestBlock)->FuncPtr)((__block_impl *)TestBlock);

return 0;

}

__main_block_impl_0构造函数

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableString *str, int flags=0) : str```
可以看出传递的是NSMutableString *类型,即传递的地址,进而可以改变str的值。
上边代码中我们可以看到__main_block_copy_0
_main_block_dispose_0概念,接下来对这两个概念进行初步讲解.

二、Block的copy和dispose


OC中,一般Block就分为以下3种,_NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock
先来说明一下3者的区别。

1.从捕获外部变量的角度上来看

只用到外部局部变量、成员属性变量,且没有强指针引用的block都是StackBlock。
StackBlock的生命周期由系统控制的,一旦返回之后,就被系统销毁了。

有强指针引用或copy修饰的成员属性引用的block会被复制一份到堆中成为MallocBlock,没有强指针引用即销毁,生命周期由程序员控制

没有用到外界变量或只用到全局变量、静态变量的block为_NSConcreteGlobalBlock,生命周期从创建到应用程序结束。

没有用到外部变量肯定是_NSConcreteGlobalBlock,这点很好理解。不过只用到全局变量、静态变量的block也是_NSConcreteGlobalBlock。举例如下:

#import <Foundation/Foundation.h>
int global_a = 1;
static int static_global_b = 2;

int main(int argc, const char * argv[]) {
    
    static int static_c = 3;
    void (^myBlock)(void) = ^{
        NSLog(@"Block中 变量 = %d %d %d",static_global_b ,static_c, global_a);
    };
    
    NSLog(@"%@",myBlock);
    myBlock();
    
    return 0;
}

控制台结果:

<__NSMallocBlock__: 0x100203980>
Block中 变量 = 2 3 1```
可见,只用到全局变量、静态变量的block也可以是_NSConcreteGlobalBlock。

所以在ARC环境下,3种类型都可以捕获外部变量。

##### 2. 从持有对象的角度上来看:
- ######_NSConcreteStackBlock是不持有对象的

import <Foundation/Foundation.h>

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

NSObject *obj = [[NSObject alloc]init];
void (^myBlock)(void) = ^{
     NSLog(@"block中 %ld",obj.retainCount);
};

NSLog(@"block外 %ld",obj.retainCount);
myBlock();

return 0;

}```
输出结果:

block外 1
block中 1```
- ######_NSConcreteMallocBlock是持有对象的

//以下是在MRC下执行的

import <Foundation/Foundation.h>

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

NSObject *obj = [[NSObject alloc]init];
NSLog(@"block-1 %ld",obj.retainCount);
void (^myBlock)(void) = [^{
     NSLog(@"block-3 %ld",obj.retainCount);
} copy];

NSLog(@"block-2 %ld",obj.retainCount);
myBlock();

return 0;

}```
输出结果:

block-1 1
block-1 2
block-1 2
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    
    void (^myBlock)(void) = ^{
         NSObject *obj = [[NSObject alloc]init];
         NSLog(@"block %ld",obj.retainCount);
    };
    myBlock();
    
    return 0;
}```
输出结果:

block 1

由于_NSConcreteStackBlock所属的变量域一旦结束,那么该Block就会被销毁。在ARC环境下,编译器会自动的判断,把Block自动的从栈copy到堆。比如当Block作为函数返回值的时候,肯定会copy到堆上。
- 手动调用copy
- Block是函数的返回值
- Block被强引用,Block被赋值给__strong或者id类型
- 调用系统API入参中含有usingBlcok的方法

以上4种情况,系统都会默认调用copy方法把Block赋复制

但是当Block为函数参数的时候,就需要我们手动的copy一份到堆上了。这里除去系统的API我们不需要管,比如GCD等方法中本身带usingBlock的方法,其他我们自定义的方法传递Block为参数的时候都需要手动copy一份到堆上。

copy函数把Block从栈上拷贝到堆上,dispose函数是把堆上的函数在废弃的时候销毁掉。

define Block_copy(...) ((__typeof(VA_ARGS))_Block_copy((const void *)(VA_ARGS)))

define Block_release(...) _Block_release((const void *)(VA_ARGS))

// Create a heap based copy of a Block or simply add a reference to an existing one.
// This must be paired with Block_release to recover memory, even when running
// under Objective-C Garbage Collection.
BLOCK_EXPORT void *_Block_copy(const void *aBlock)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Lose the reference, and if heap based and last reference, recover the memory
BLOCK_EXPORT void _Block_release(const void *aBlock)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

// Used by the compiler. Do not call this function yourself.
BLOCK_EXPORT void _Block_object_dispose(const void *, const int)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);```
上面是源码中2个常用的宏定义和4个常用的方法,一会我们就会看到这4个方法。

static void *_Block_copy_internal(const void *arg, const int flags) { 
   struct Block_layout *aBlock; 
   const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE; 
   if (!arg) return NULL;
   aBlock = (struct Block_layout *)arg;
   if (aBlock->flags & BLOCK_NEEDS_FREE) {
     latching_incr_int(&aBlock->flags); 
      return aBlock; 
    } else if (aBlock->flags & BLOCK_IS_GLOBAL) { 
      return aBlock; 
    } 
   struct Block_layout *result = malloc(aBlock->descriptor->size);
   if (!result) return (void *)0;
   memmove(result, aBlock, aBlock->descriptor->size);
   result->flags &= ~(BLOCK_REFCOUNT_MASK);
  result->flags |= BLOCK_NEEDS_FREE | 1; 
   result->isa = _NSConcreteMallocBlock; 
   if (result->flags & BLOCK_HAS_COPY_DISPOSE) { 
    (*aBlock->descriptor->copy)(result, aBlock); 
   }
   return result;
}```
上面这一段是Block_copy的一个实现,实现了从_NSConcreteStackBlock复制到_NSConcreteMallocBlock的过程.

void _Block_release(void *arg) {
struct Block_layout *aBlock = (struct Block_layout )arg;
if (!aBlock) return;
int32_t newCount;
newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;
if (newCount > 0) return;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(
aBlock->descriptor->dispose)(aBlock);
_Block_deallocator(aBlock);
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
;
}
else {
printf("Block_release called upon a stack Block: %p, ignored\\n", (void *)aBlock);
}
}

上面这一段是Block_release的一个实现,实现了怎么释放一个Block。
因为在C语言的结构体中,编译器没法很好的进行初始化和销毁操作。这样对内存管理来说是很不方便的。所以就在 `__main_block_desc_0`结构体中间增加成员变量` void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*)`和`void (*dispose)(struct __main_block_impl_0*)`,利用OC的Runtime进行内存管理。

相应的增加了2个方法。

static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) {_Block_object_assign((void)&dst->str, (void)src->str, 3/BLOCK_FIELD_IS_OBJECT/);}

static void __main_block_dispose_0(struct __main_block_impl_0src) {_Block_object_dispose((void)src->str, 3/BLOCK_FIELD_IS_OBJECT/);}```
这里的_Block_object_assign_Block_object_dispose就对应着retain和release方法。

三.Block中__block实现原理

1.普通非对象的变量

先来看看普通变量的情况

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
   
    __block int a = 0;
    void (^myBlock)(void) = ^{
        a ++;
    };
    myBlock();
    
    return 0;
}```
转化后的代码:

struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};

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

    (a->__forwarding->a) ++;
}

static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) {_Block_object_assign((void)&dst->a, (void)src->a, 8/BLOCK_FIELD_IS_BYREF/);}

static void __main_block_dispose_0(struct __main_block_impl_0src) {_Block_object_dispose((void)src->a, 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(int argc, const char * argv[]) {

__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0};
void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

return 0;

}```
从以上代码看出

ARC环境下,一旦Block赋值就会触发copy,__block就会copy到堆上,Block也是__NSMallocBlock。ARC环境下也是存在__NSStackBlock的时候,这种情况下,__block就在栈上。

MRC环境下,只有copy,__block才会被复制到堆上,否则,__block一直都在栈上,block也只是__NSStackBlock,这个时候__forwarding指针就只指向自己了。

2.对象的变量
//ARC环境下
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    
    __block NSObject *block_obj = [[NSObject alloc] init];
    NSObject *obj = [[NSObject alloc] init];
    NSLog(@"block外 block_obj:%p;  obj:%p", &block_obj, &obj);
    void (^myBlock)(void) = ^{
        NSLog(@"block中 block_obj:%p;  obj:%p", &block_obj, &obj);
    };
    myBlock();
    NSLog(@"%@", myBlock);
    return 0;
}```
结果输出:

block外 block_obj:0x7fff5fbff758; obj:0x7fff5fbff728
block中 block_obj:0x100300578; obj:0x1003001e0
<NSMallocBlock: 0x1002004d0>```
以上代码转换后:

struct __Block_byref_block_obj_0 {
  void *__isa;
__Block_byref_block_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *block_obj;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *obj;
  __Block_byref_block_obj_0 *block_obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, __Block_byref_block_obj_0 *_block_obj, int flags=0) : obj(_obj), block_obj(_block_obj->__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_block_obj_0 *block_obj = __cself->block_obj; // bound by ref
  NSObject *obj = __cself->obj; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_0486e2_mi_1, &(block_obj->__forwarding->block_obj), &obj);
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->block_obj, (void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_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->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_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(int argc, const char * argv[]) {

    __attribute__((__blocks__(byref))) __Block_byref_block_obj_0 block_obj = {(void*)0,(__Block_byref_block_obj_0 *)&block_obj, 33554432, sizeof(__Block_byref_block_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
    NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_nb_9b7x604975q0vcgs2w0_h7xm0000gq_T_main_0486e2_mi_0, &(block_obj.__forwarding->block_obj), &obj);
    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, (__Block_byref_block_obj_0 *)&block_obj, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

    return 0;
}```
首先需要说明的一点是对象在OC中,默认声明自带__strong所有权修饰符的,所以main开头我们声明的

__block NSObject *block_obj = [[NSObject alloc] init];
NSObject *obj = [[NSObject alloc] init];```
等价于:

__block __strong  NSObject *block_obj = [[NSObject alloc] init];
__strong NSObject *obj = [[NSObject alloc] init];```
从以上转化代码看出:
- 被__block 修饰的变量block_obj转化成`__Block_byref_block_obj_0`的变量,名称相同
- `__main_block_impl_0`构造方法中将`__Block_byref_block_obj_0`类型的`block_obj`变量的地址传入给结构体的`__Block_byref_block_obj_0 类型`block_obj`变量,将变量obj的地址传入给结构体的变量obj
-  在`__main_block_func_0`函数中通过`__cself->block_obj`获取`__Block_byref_block_obj_0`类型变量`block_obj`,再通过`block_obj->__forwarding->block_obj`获取外部捕获的变量;通过`__cself->obj`获取obj变量
- Block捕获了__block,并且强引用了,因为在__Block_byref_block_obj_0结构体中,有一个变量是id block_obj,这个默认也是带__strong所有权修饰符的
- ARC环境下,Block捕获外部对象变量,是都会copy一份的,地址都不同。只不过带有__block修饰符的变量会被捕获到Block内部持有

我们再来看看MRC环境下的情况,还是将上述代码的例子运行在MRC中:

block外 block_obj:0x7fff5fbff758; obj:0x7fff5fbff728
block中 block_obj:0x7fff5fbff758; obj:0x7fff5fbff700
<NSStackBlock: 0x7fff5fbff6e0>```

这个时候block在栈上,NSStackBlock,可以打印出来retainCount值都是1。当把这个block copy一下,就变成NSMallocBlock,对象的retainCount值就会变成2了。

总结:

在MRC环境下,__block根本不会对指针所指向的对象执行copy操作,而只是把指针进行的复制。
而在ARC环境下,对于声明为__block的外部对象,在block内部会进行retain,以至于在block环境内能安全的引用外部对象,所以才会产生循环引用的问题!

最后

在ARC环境下,Block也是存在__NSStackBlock的时候的,平时见到最多的是_NSConcreteMallocBlock,是因为我们会对Block有赋值操作,所以ARC下,block 类型通过=进行传递时,会导致调用objc_retainBlock->_Block_copy->_Block_copy_internal方法链。并导致 __NSStackBlock__类型的 block 转换为__NSMallocBlock__类型。

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {

    __block int temp = 10;
    NSLog(@"%@",^{NSLog(@"*******%d %p",temp ++,&temp);});
    return 0;
}
<__NSStackBlock__: 0x7fff5fbff768>```


参考:
http://www.jianshu.com/p/ca6ac0ae93ad
http://www.jianshu.com/p/ee9756f3d5f6
上一篇下一篇

猜你喜欢

热点阅读