iOS 栈对象、堆对象和Block

栈对象、堆对象

是一块保存局部变量或函数参数值的内存区域。在现代的操作系统中,每个线程都有一个对应的栈。当函数调用时,一个栈帧Stack Frame会被放入栈内。栈帧保存了这个函数涉及的参数、局部变量、返回地址等相关信息。当函数返回后这个栈帧就会被销毁。而这一切都是系统自动完成的,程序员无需关心。

同样是一块内存区域。在这里内存可以随时分配和销毁,但需要我们自己去请求,而不是系统帮我们完成。不像Stack里面的变量会随着函数执行而销毁,堆上面的东西在函数执行之外依然能够存活。

关于栈对象和堆对象,首先要搞清楚什么是对象。事实上对象就是一段连续的内存空间,它有固定的布局。对象可以放在栈上也可以放在堆上。对象被放在栈上就是栈对象,被放在堆上就是堆对象。但是在Object-C中,对象都是在堆上面创建的,比如执行以下代码:

NSObject *obj = [[NSObject alloc] init];

obj这个指针变量本身是保存在栈上面的,它的本质就是int类型,但是它指向的对象是保存在堆上面的。[NSObject alloc]就是请求在堆上面分配内存。

那Object-C有没有办法在栈上面创建对象呢?Object-C没有提供直接的方法,但是还是可以通过以下方式来创建一个栈对象:

struct {
   Class isa;
} fakeNSObject;
fakeNSObject.isa = [NSObject class];
    
NSObject *obj = (__bridge NSObject *)&fakeNSObject;
NSLog(@"%@", [obj description]);

fakeNSObject这个结构体是保存在栈上面的,但是它有isa,所以根据对象的定义它也是对象,所以它就成了一个栈对象。

事实上有些语言是支持在栈上面创建对象的,比如C++。在C++中类的对象建立分为两种,一种是静态建立,如A a;另一种是动态建立,如A* p=new A(),A*p=(A*)malloc()

  • 静态建立一个类对象,是由编译器为对象在栈空间中分配内存,通过直接移动栈顶指针挪出适当的空间,然后在这片内存空间上调用构造函数形成一个栈对象。

  • 动态建立类对象,是使用new运算符将对象建立在堆空间中,在栈中只保留了指向该对象的指针

栈对象有好处也有不好的地方。好处是内存分配很快,而且它有确切的生命周期,不会内存泄漏,因为函数执行完变量会被自动销毁。但它的生命周期也是它的劣势之一,因为函数执行完就被销毁了,没办法在函数之外再使用它。

那为什么Object-C总是在堆上面创建对象呢?

  • 1、因为Object-C使用引用计数来管理内存。这种方式的好处是一个对象可以有多个Owner,而且只有当这个对象的Owner个数为0时它才会被销毁。而栈对象有一个固有的Owner,就是它所在的函数。当我们把一个栈对象传给另一段代码时,即使是引用它也没办法阻止它在原有函数执行完毕后被销毁,当它被销毁后再去访问它就会发生错误。简单而言,就是栈对象的生命周期不适合Objective-C的引用计数内存管理方法。
  • 2、栈对象不够灵活,它在创建时长度就已经是固定的。而Runtime的灵活性都是基于堆对象的。

但是Objective-C也不是完全不存在栈对象,Block就是一个特例。

Block

Block的本质

先创建一个简单的Block:

int blockStudy() {
    void (^blk)(void) = ^{
        int a = 0;
    };
    blk();
    return 0;
}

将该代码转为C语言源码:

// MRC
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc BlockStudy.m

// ARC
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.3.0 BlockStudy.m
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __blockStudy_block_impl_0 {
  struct __block_impl impl;
  struct __blockStudy_block_desc_0* Desc;

  // 构造函数
  __blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __blockStudy_block_func_0(struct __blockStudy_block_impl_0 *__cself) {
    int a = 0;
}

static struct __blockStudy_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __blockStudy_block_desc_0_DATA = { 0, sizeof(struct __blockStudy_block_impl_0)};

int blockStudy() {
    void (*blk)(void) = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

可以看到

void (^blk)(void) = ^{int a = 0;};

被转换成了

void (*blk)(void) = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA));

去掉类型转换的部分,就变成了

void (*blk)(void) = &__blockStudy_block_impl_0(__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA);

其实就是调用__blockStudy_block_impl_0的构造函数在栈区创建了一个结构体。

首先看__blockStudy_block_impl_0的定义:

struct __blockStudy_block_impl_0 {
  struct __block_impl impl;
  struct __blockStudy_block_desc_0* Desc;

  // 构造函数
  __blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

调用构造函数时的第一个参数是一个函数的指针:

static void __blockStudy_block_func_0(struct __blockStudy_block_impl_0 *__cself) {
    int a = 0;
}

这个函数里面的代码就是我们定义Block时的执行函数,这个函数指针被保存在impl.FuncPtr。这个函数接收的参数__cself其实就是Block本身的地址,相当于我们平时在函数里面用的self。

第二个参数是一个静态全局变量__blockStudy_block_desc_0_DATA

static struct __blockStudy_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __blockStudy_block_desc_0_DATA = { 0, sizeof(struct __blockStudy_block_impl_0)};

里面保存了Block的大小

执行Block的代码

blk();

被转换为:

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

去掉类型转换的部分

(blk->FuncPtr)(blk);

就是简单的使用函数指针调用函数。

这里有个地方需要注意,就是__blockStudy_block_impl_0被强转换为了__block_impl,这在C语言是可行的,因为__block_impl位于__blockStudy_block_impl_0的最顶部,就相当于__block_impl的变量直接排列在__blockStudy_block_impl_0的顶部。

再看__block_impl的定义:

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

它有一个isa变量,而且这里它被赋值为&_NSConcreteStackBlock,结合前面我们定义Object-C中的栈对象,我们可以知道Block其实就是一个栈对象,它的类就是_NSConcreteStackBlock

自由变量的截取

一般我们创建的局部变量都是自由变量,因为系统会自动在前面加上auto

int age = 10;

auto int age = 10;
int global_val = 1;
static int static_global_val = 1;

int blockStudy() {
    int var = 10; // auto 变量
    static int static_val = 2; // static 变量
    void (^blk)(void) = ^{
        NSLog(@"%@ %@ %@ %@", global_val, static_global_val, var, static_val);
    };
    blk();
    return 0;
}

同样把上面的截取局部变量的代码转为C语言源码:

int global_val = 1;
static int static_global_val = 1;


struct __blockStudy_block_impl_0 {
    struct __block_impl impl;
    struct __blockStudy_block_desc_0* Desc;
    int var;
    int *static_val;
    __blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, int _var, int *_static_val, int flags=0) : var(_var), static_val(_static_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __blockStudy_block_func_0(struct __blockStudy_block_impl_0 *__cself) {
    int var = __cself->var; // bound by copy
    int *static_val = __cself->static_val; // bound by copy
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2__6smntvs6ng1ww18kf8713tc0000gn_T_BlockStudy_7f4b92_mi_0, global_val, static_global_val, var, (*static_val));
}

static struct __blockStudy_block_desc_0 {
    size_t reserved;
    size_t Block_size;
} __blockStudy_block_desc_0_DATA = { 0, sizeof(struct __blockStudy_block_impl_0)};

int blockStudy() {
    int var = 10;
    static int static_val = 2;
    void (*blk)(void) = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA, var, &static_val));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

可以看到我们在Block里面使用到的局部变量被作为成员变量追加到了__blockStudy_block_impl_0结构体中(值传递),静态局部变量也是(指针传递),而全局变量和未用到的局部变量则不会。

结论:我们创建一个block时,实际上是创建了一个结构体,这个结构体保存了block的执行函数的地址以及这个函数里面使用到的局部变量(自由变量),一般情况下这些局部变量是被拷贝进去的。

结合前面Block的源码可以知道Block的内存布局如下:

再看block执行函数的实现:

static void __blockStudy_block_func_0(struct __blockStudy_block_impl_0 *__cself) {
    const char *fmt = __cself->fmt; // bound by copy
    int var = __cself->var; // bound by copy
    int *static_val = __cself->static_val; // bound by copy

    const char *a = fmt;
    int b = var;
    int c = global_val;
    int d = static_global_val;
    int e = (*static_val);
}

自动生成的注释已经说明了: bound by copy,block对它引用的局部变量做了只读拷贝,也就是说block引用的是局部变量的副本。所以在执行block语法后,即使改写block中使用的局部变量的值也不会影响block执行时局部变量的值。这也就是为什么在block里面修改局部变量的值时要额外加上__block修饰符。

__block

我们都知道要在Block内修改外面的局部变量就得给局部变量加上__block修饰符,不然编译器会报错。接下来看看__block的本质。

先定义一个简单的__block变量:

void blockStudy() {
    __block int myNumber = 10;
}

转换为源码:

struct __Block_byref_myNumber_0 {
  void *__isa;
__Block_byref_myNumber_0 *__forwarding;
 int __flags;
 int __size;
 int myNumber;
};

void blockStudy() {
    __attribute__((__blocks__(byref))) __Block_byref_myNumber_0 myNumber = {
    (void*)0,(__Block_byref_myNumber_0 *)&myNumber, 
    0, 
    sizeof(__Block_byref_myNumber_0), 
    10};
}

可以看到__block变量实际是生成了一个结构体的实例,而这个实例最后的一个成员变量才是我们定义自由变量,而且还多了一个特别的__forwarding变量。再看在block里面引用这个__block变量的情况:

int blockStudy() {
    __block int number = 10;
    
    void (^blk)(void) = ^{
        number = 5;
    };
    blk();
    return 0;
}
struct __Block_byref_myNumber_0 {
  void *__isa;
__Block_byref_myNumber_0 *__forwarding;
 int __flags;
 int __size;
 int myNumber;
};

struct __blockStudy_block_impl_0 {
  struct __block_impl impl;
  struct __blockStudy_block_desc_0* Desc;
  __Block_byref_myNumber_0 *myNumber; // by ref
    
  __blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, __Block_byref_myNumber_0 *_myNumber, int flags=0) : myNumber(_myNumber->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __blockStudy_block_func_0(struct __blockStudy_block_impl_0 *__cself) {
    __Block_byref_myNumber_0 *myNumber = __cself->myNumber; // bound by ref
    (myNumber->__forwarding->myNumber) = 5;
}

static void __blockStudy_block_copy_0(struct __blockStudy_block_impl_0*dst, struct __blockStudy_block_impl_0*src) {
    _Block_object_assign((void*)&dst->myNumber, (void*)src->myNumber, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __blockStudy_block_dispose_0(struct __blockStudy_block_impl_0*src) {
    _Block_object_dispose((void*)src->myNumber, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static struct __blockStudy_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __blockStudy_block_impl_0*, struct __blockStudy_block_impl_0*);
  void (*dispose)(struct __blockStudy_block_impl_0*);
} __blockStudy_block_desc_0_DATA = { 0, sizeof(struct __blockStudy_block_impl_0), __blockStudy_block_copy_0, __blockStudy_block_dispose_0};

int blockStudy() {
    __attribute__((__blocks__(byref))) __Block_byref_myNumber_0 myNumber = {
        (void*)0,
        (__Block_byref_myNumber_0 *)&myNumber,
        0,
        sizeof(__Block_byref_myNumber_0),
        10
    };

    void (*blk)(void) = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA, (__Block_byref_myNumber_0 *)&myNumber, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

同样的__block变量被block捕获了,但这次是引用传递:

__Block_byref_myNumber_0 *myNumber; // by ref

给__block变量赋值的执行函数被转换为:

static void __blockStudy_block_func_0(struct __blockStudy_block_impl_0 *__cself) {
    __Block_byref_myNumber_0 *myNumber = __cself->myNumber; // bound by ref
    (myNumber->__forwarding->myNumber) = 5;
}

这里的注释变为了bound by ref,说明是引用传递。而且这里是通过myNumber的__forwarding去修改值的,而不是直接通过myNumber->myNumber来修改,这里就涉及到block的存储域问题了。

Block存储域

我们前面创建的Block都是存在栈上面的,而且它的类是_NSConcreteStackBlock。但除此之外还有另外两种Block:_NSConcreteGlobalBlock_NSConcreteMallocBlock

_NSConcreteGlobalBlock和全局变量一样他是存在程序的数据区域的,这种Block不捕获任何自由变量,相当于代码片段。

void (^blk)(void) = ^{};

int blockStudy() {
    return 0;
}

// C语言源码
struct __blk_block_impl_0 {
  struct __block_impl impl;
  struct __blk_block_desc_0* Desc;
  __blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

_NSConcreteMallocBlock是保存在堆上的。

为什么需要_NSConcreteMallocBlock呢?因为栈上的Block会随着其所属的变量作用域结束而被废弃。这时候就需要把栈上的Block拷贝到堆上面,使得Block语法记述的变量的作用域结束后堆上的Block依旧可以继续存在。这也是为什么要把block的属性声明为copy的缘故。

但是在ARC之后block会被自动复制到堆上,即使你声明的修饰符是strong,实际上效果是与声明为copy一样的,也就不强求声明为copy了,但官方仍然建议我们使用copy以显示相关拷贝行为。

大部分的情况编译器会自动帮我们把block复制到堆上面,比如以下代码:

typedef int (^blk_t)(int);

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

// 转换后的源码
blk_t func(int rate) {
    blk_t tmp = &__func_block_impl_0(
        __func_block_func_0, &__func_block_desc_0_DATA, rate);

    tmp = objc_retainBlock(tmp);

    return objc_autoreleaseReturnValue(tmp);
}

再比如我们平常调用接口的方法:

- (void)loadData {
    __block id myData;
    [self getMyData:^(id result, NSError *error) {
        myData = result;
    }];
}

- (void)getMyData:(void(^)(id result, NSError *error))completionBlock {
    if (completionBlock) { // __NSStackBlock__
    }
    [@[@"0"] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        completionBlock(nil, nil); // __NSStackBlock__
    }];
    dispatch_sync(dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL), ^{
        completionBlock(nil, nil); // __NSStackBlock__
    });
    dispatch_async(dispatch_get_main_queue(), ^{
        completionBlock(nil, nil); // __NSMallocBlock__
    });
}

当执行到dispatch_async时block就被复制到堆上面了,如果还在栈上面,那getMyData函数执行完成后这个block就会被释放,等异步回来执行时就会出错。

那block被拷贝到堆上后,__block变量会发生什么呢?

当Block被复制到堆上面后,它所使用的__block变量也会被复制到堆上面。除此之外还有一个__forwarding变量需要注意。一开始__block变量在栈上面创建时,它的__forwarding是指向它自己的,当它被复制到堆上面后,原本的栈上面的__block变量__forwarding会指向被复制出来的堆上面的__block变量,而被复制到堆上面的__block变量__forwarding则会指向它自己。

void blockStudy()
{
    dispatch_block_t myBlock;
    {
        __block int a = 0;
        myBlock = ^() {
            a = 1;
        };
        a = 2;
    }
}

把上面这段代码转成C语言源码后:

void blockStudy()
{
    dispatch_block_t myBlock;
    {
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0};
        myBlock = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
        (a.__forwarding->a) = 2;
    }
}

可以看到原本的a = 2;被转成了(a.__forwarding->a) = 2;,而a其实是个结构体__Block_byref_a_0。创建__Block_byref_a_0时的第二个参数就是__forwarding,这里传进去的值是(__Block_byref_a_0 *)&a,也就是它自己本身。

小结:当用__block修饰一个局部变量时,实际上是在栈上创建了一个包含该变量的结构体A1,该结构体包含一个变量__forwarding,此时栈上的A1的__forwarding指向A1本身。block通过引用传递捕获__block变量,当block被拷贝到堆上时,__block变量也被拷贝到堆上面有了A2,这时就会同时存在A1和A2,读取和修改就会有同步问题,这时__forwarding变量就起作用了。复制到堆上面后,A1和A2的__forwarding变量都会指向A2,外部统一通过__forwarding变量来读取和修改变量就能保持一致了。

block捕获对象类型的自由变量

先看以下ARC环境下的简单例子:

当超出实例的作用域后它就会被销毁,如果我们用一个block来对这个变量进行引用会发生什么呢?

这个时候myObject变量不会被销毁,而且这个时候block已经被复制到堆上了:

我们可以理解为堆上的block对myObject进行了引用,所以myObject不会被销毁。

ARC环境下,一旦Block赋值就会触发copy,__block就会copy到堆上,Block也是__NSMallocBlock。

把ARC改为MRC:

myObject又被销毁了,而且block没有复制到堆上:

我们手动进行copy:

myObject又被持有了。

结论:栈上的block不会持有对象类型的自由变量,而堆上的block则会。

返回ARC再对改代码进行一些修改:

用__weak来修饰myObject,可以看到myObject在走到NSLog时会被回收,所以block内部并没有强引用myObject。

查看源码:

struct __blockStudy_block_impl_0 {
  struct __block_impl impl;
  struct __blockStudy_block_desc_0* Desc;
  MyObject *__weak weakMyObject;
    
  __blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, MyObject *__weak _weakMyObject, int flags=0) : weakMyObject(_weakMyObject) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

block内部同样用__weak来修饰。

为什么栈上的block不会持有对象型的自由变量呢?

回到前面的代码:

void blockStudy() {
    dispatch_block_t myBlock;
    {
        MyObject *myObject = [MyObject new];
        myBlock = ^() {
            NSLog(@"%@", NSStringFromClass(myObject.class));
        };
    }
    NSLog(@"End");
}

转为源码:

struct __blockStudy_block_impl_0 {
    struct __block_impl impl;
    struct __blockStudy_block_desc_0* Desc;
    MyObject *myObject;
    
    __blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, MyObject *_myObject, int flags=0) : myObject(_myObject) {
        impl.isa = &_NSConcreteStackBlock; // 栈上面的Block
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __blockStudy_block_func_0(struct __blockStudy_block_impl_0 *__cself) {
    MyObject *myObject = __cself->myObject; // bound by copy
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2__6smntvs6ng1ww18kf8713tc0000gn_T_BlockStudy_f6b706_mi_0, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)myObject, sel_registerName("class"))));
}
static void __blockStudy_block_copy_0(struct __blockStudy_block_impl_0*dst, struct __blockStudy_block_impl_0*src) {
    _Block_object_assign((void*)&dst->myObject, (void*)src->myObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __blockStudy_block_dispose_0(struct __blockStudy_block_impl_0*src) {
    _Block_object_dispose((void*)src->myObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static struct __blockStudy_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __blockStudy_block_impl_0*, struct __blockStudy_block_impl_0*);
    void (*dispose)(struct __blockStudy_block_impl_0*);
} __blockStudy_block_desc_0_DATA = { 0, sizeof(struct __blockStudy_block_impl_0), __blockStudy_block_copy_0, __blockStudy_block_dispose_0};

void blockStudy() {
    dispatch_block_t myBlock;
    {
        MyObject *myObject = ((MyObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyObject"), sel_registerName("new"));
        myBlock = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA, myObject, 570425344));
    }
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_j2__6smntvs6ng1ww18kf8713tc0000gn_T_BlockStudy_f6b706_mi_1);
}

创建block的时候把myObject传了进去

myBlock = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA, myObject, 570425344));

保存在了__blockStudy_block_impl_0中:

struct __blockStudy_block_impl_0 {
    struct __block_impl impl;
    struct __blockStudy_block_desc_0* Desc;
    MyObject *myObject;
    
    __blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, MyObject *_myObject, int flags=0) : myObject(_myObject) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

这时候并没有对myObject进行持有,也就是没有增加它的引用计数。只有当block被复制到堆上面时,这个时候__blockStudy_block_copy_0函数会被调用,才会通过_Block_object_assign会根据自由变量的修饰符(__strong__weak__unretained)进行相应的操作,形成强引用(retain)或者弱引用。__blockStudy_block_dispose_0则在block被销毁时进行调用。这就是捕获对象型自由变量时__blockStudy_block_desc_0会多出两个函数指针的原因了。

static void __blockStudy_block_copy_0(struct __blockStudy_block_impl_0*dst, struct __blockStudy_block_impl_0*src) {
    _Block_object_assign((void*)&dst->myObject, (void*)src->myObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __blockStudy_block_dispose_0(struct __blockStudy_block_impl_0*src) {
    _Block_object_dispose((void*)src->myObject, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static struct __blockStudy_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    
    void (*copy)(struct __blockStudy_block_impl_0*, struct __blockStudy_block_impl_0*);
    void (*dispose)(struct __blockStudy_block_impl_0*);
} __blockStudy_block_desc_0_DATA = { 0, sizeof(struct __blockStudy_block_impl_0), __blockStudy_block_copy_0, __blockStudy_block_dispose_0};

事实上捕获__block变量时也会有这两个函数指针,只是调用_Block_object_assign__blockStudy_block_dispose_0函数时第三个参数有所不同。它们在__block变量被复制到堆上面时会被调用。

static void __blockStudy_block_copy_0(struct __blockStudy_block_impl_0*dst, struct __blockStudy_block_impl_0*src) {
    _Block_object_assign((void*)&dst->myNumber, (void*)src->myNumber, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __blockStudy_block_dispose_0(struct __blockStudy_block_impl_0*src) {
    _Block_object_dispose((void*)src->myNumber, 8/*BLOCK_FIELD_IS_BYREF*/);
}

而__block变量在ARC和MRC下是不一样的。

  • MRC 环境下,block 截获外部用 __block 修饰的变量,不会增加对象的引用计数;
  • ARC 环境下,block 截获外部用 __block 修饰的变量,会增加对象的引用计数。

主要原因是_Block_object_assign方法,它根据第三个参数来决定是否需要增加对象的引用计数。所以MRC下可以利用 __block来打破循环引用,在 ARC 环境下,则需要用__weak 来打破循环引用。

小结:ARC环境下,block会强引用捕获到的局部变量,是因为block被拷贝到堆上面时,会执行内部的copy函数,而这个copy函数内部会调用_Block_object_assign,该函数会根据局部变量的修饰符(__strong__weak__unretained)来对该变量进行强引用或弱引用。栈上的block在拷贝到堆上面之前是不会强引用局部变量的。

总结:__block修饰的自由变量,不管是对象类型的还是非对象类型的,其实都是定义了一个结构体,我们原本想要定义的变量是被封装到这个结构体里面的,最后使用的都是这个结构体,通过__forwarding引用值。这个结构体可以被复制到堆上面,所以值可以被修改。当被复制到堆上时,__forwarding变量指向的是__block变量自己,而栈上的__forwarding也是指向堆上的__block变量,这就保证了读取和修改的同步。

如果不用__block修饰,block捕获非对象类型的自由变量时只是把它的值拷贝进来,自然就无法修改,修改了也不会影响到外面。如果是对象类型的,捕获的也是对象的指针,同样是拷贝这个地址就行了。但是block可能会增加对象类型的引用计数,是因为block被拷贝到堆上面时,会执行block的copy函数,也就是会执行_Block_object_assign这个方法,这个方法会根据自由变量的修饰符来决定对该变量进行强引用或弱引用,这里的引用当然是堆上的变量,和栈上的指针无关。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349