美文网首页
iOS Block 部分三

iOS Block 部分三

作者: 飞不越疯人院 | 来源:发表于2020-06-30 15:57 被阅读0次

主要讲解 Block内修改外部变量, 内存管理, 以及循环引用;

Block部分一
Block部分二
Block部分三
Block知识点总结

以下内容的测试主要针对ARC环境; MRC下直接贴出测试结果, 不再贴出测试代码, 具体请自行测试MRC环境;

1. __block的用法(基础类型)

block内部需要修改外部变量时()
全局变量, 全局静态变量可以直接进行修改, 因为block内部不对他们进行捕获;
静态局部变量也可以直接在block内部修改, 因为�block捕获了它的地址;
auto变量则不能直接在block内部修改; 需要用__block修饰才行;


先从底层结构代码看下为什么auto变量为什么不能直接在block内部修改;
首先我们知道int c = 3整个变量的作用域就是viewDidLoad这个方法, 出了这个方法后就不能再访问;
底层代码如下:
///viewDidLoad的底层代码如下
static void _I_ViewController3_viewDidLoad(ViewController3 * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController3"))}, sel_registerName("viewDidLoad"));
     int c = 3;
    static int d = 4;
    void(*Case3Block)(void) = ((void (*)())&__ViewController3__viewDidLoad_block_impl_0((void *)__ViewController3__viewDidLoad_block_func_0, &__ViewController3__viewDidLoad_block_desc_0_DATA, c, &d));

    ((void (*)(__block_impl *))((__block_impl *)Case3Block)->FuncPtr)((__block_impl *)Case3Block);
}

block内部执行的代码是封装在这个函数中, 从明面即可得知, 这个函数中并不能访问另一个函数的auto变量, 这个函数内部里面的变量c; 是block底层的结构体重新创建的变量 int c = __cself->c;

static void __ViewController3__viewDidLoad_block_func_0(struct __ViewController3__viewDidLoad_block_impl_0 *__cself) {
  int c = __cself->c; // bound by copy
  int *d = __cself->d; // bound by copy
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController3_2637aa_mi_0, a,b,c,(*d));
    }

为什么用__block修饰后的auto变量就可以在block内部修改了呢?

下面代码, 通过__block修饰后就可以在block内部修改;

@implementation ViewController3
- (void)viewDidLoad {
    [super viewDidLoad];
  __block    int XXXX = 3;
    void(^Case3Block)(void) =  ^{
        XXXX = 300;
    };
    NSLog(@"XXXX = %d", XXXX);
    Case3Block();
    NSLog(@"XXXX = %d", XXXX);
}
@end

2020-06-27 15:27:21.694208+0800 BlockMore2[31764:1243071] XXXX = 3
2020-06-27 15:27:21.694316+0800 BlockMore2[31764:1243071] XXXX = 300

底层的结构为

///viewDidLoad的底层
static void _I_ViewController3_viewDidLoad(ViewController3 * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController3"))}, sel_registerName("viewDidLoad"));
  ///变量XXXX被封装成了一个对象
  __attribute__((__blocks__(byref))) __Block_byref_XXXX_0 XXXX = {(void*)0,(__Block_byref_XXXX_0 *)&XXXX, 0, sizeof(__Block_byref_XXXX_0), 3};
   ///block的底层
 void(*Case3Block)(void) = ((void (*)())&__ViewController3__viewDidLoad_block_impl_0((void *)__ViewController3__viewDidLoad_block_func_0, &__ViewController3__viewDidLoad_block_desc_0_DATA, (__Block_byref_XXXX_0 *)&XXXX, 570425344));
     ///NSlog删掉了
    ///block的调用
    ((void (*)(__block_impl *))((__block_impl *)Case3Block)->FuncPtr)((__block_impl *)Case3Block);
     ///NSlog删掉了
}
===>
///block的底层结构
struct __ViewController3__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController3__viewDidLoad_block_desc_0* Desc;
  ///将XXXX变量封装成一个对象
  __Block_byref_XXXX_0 *XXXX; // by ref
  __ViewController3__viewDidLoad_block_impl_0(void *fp, struct __ViewController3__viewDidLoad_block_desc_0 *desc, __Block_byref_XXXX_0 *_XXXX, int flags=0) : XXXX(_XXXX->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
===>
///变量XXXX的封装对象, 底层也是一个结构体
struct __Block_byref_XXXX_0 {
///isa指针
  void *__isa;
///指向自身的一个指针
__Block_byref_XXXX_0 *__forwarding;
///flags: 他的作用就是传入不同的值进行不同的操作;
 int __flags;
///结构体的size
 int __size;
///变量XXXX
 int XXXX;
};

===>
///封了block内部执行代码块的函数
static void __ViewController3__viewDidLoad_block_func_0(struct __ViewController3__viewDidLoad_block_impl_0 *__cself) {
  __Block_byref_XXXX_0 *XXXX = __cself->XXXX; // bound by ref
        ///通过__forwarding指针访问XXXX变量
        (XXXX->__forwarding->XXXX) = 300;
    }
===>
注意:
///block的描述信息的函数发生了变化, 多了两个函数
static struct __ViewController3__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  ///ARC下栈block会被拷贝到堆上, 这个过程会降封装的变量也进行拷贝;
  void (*copy)(struct __ViewController3__viewDidLoad_block_impl_0*, struct __ViewController3__viewDidLoad_block_impl_0*);
///当block执行完毕从堆上移出时,会调用dispose函数对所持有的对象进行类似release操作;
  void (*dispose)(struct __ViewController3__viewDidLoad_block_impl_0*);
} __ViewController3__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController3__viewDidLoad_block_impl_0), __ViewController3__viewDidLoad_block_copy_0, __ViewController3__viewDidLoad_block_dispose_0};

===>
///copy函数
static void __ViewController3__viewDidLoad_block_copy_0(struct __ViewController3__viewDidLoad_block_impl_0*dst, struct __ViewController3__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->XXXX, (void*)src->XXXX, 8/*BLOCK_FIELD_IS_BYREF*/);}
===>
///dispose函数
static void __ViewController3__viewDidLoad_block_dispose_0(struct __ViewController3__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->XXXX, 8/*BLOCK_FIELD_IS_BYREF*/);}

总结:当用__block修饰的auto变量(基础类型)使用block的过程为:

  • 首先被__block修饰的变量在底层会被封装成__Block_byref_XXXX_0的对象变量;
  • block被拷贝大堆上时会调用内部的copy函数, copy函数会通过_Block_object_assign函数对封装的对象进行强引用;
  • block执行完/从堆上移出时, 会调用block内部的dispose函数, 其内部调用_Block_object_dispose函数对结构体持有的对象进行类似release的释放操作;

2. __block的用法(对象类型)

首先弄清楚下面两个操作的不同之处;为什么第一个会报错第二个不报错呢;



因为那里的arr操作是访问它的方法或者属性, 并不是修改它; 如果arr = nil也是会报错的;
首先看下方代码
- (void)viewDidLoad {
    [super viewDidLoad];
    __block TestObject *obj = [[TestObject alloc] init];
    void(^Case4Block)(void) = ^ {
        obj = nil;
    };
    NSLog(@"obc = %@", obj);
    Case4Block();
    NSLog(@"obc = %@", obj);
}

2020-06-27 16:29:03.310124+0800 BlockMore2[32512:1277607] obc = <TestObject: 0x600002e5e920>
2020-06-27 16:29:03.310238+0800 BlockMore2[32512:1277607] obc = (null)

通过指令后获得底层代码

///__Block_byref_obj_0封装对象结构
struct __Block_byref_obj_0 {
  void *__isa;
__Block_byref_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
///对obj强引用
 TestObject *__strong obj;
};
///block结构
struct __ViewController4__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController4__viewDidLoad_block_desc_0* Desc;
  ///obj对象被封装成__Block_byref_obj_0对象, block对其强引用
  __Block_byref_obj_0 *obj; // by ref
  __ViewController4__viewDidLoad_block_impl_0(void *fp, struct __ViewController4__viewDidLoad_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

弱引用代码示例:

    ///弱引用
    TestObject *obj = [[TestObject alloc] init];
    __block   __weak TestObject *weakObj = obj;
    void(^Case4Block)(void) = ^ {
        NSLog(@"weakObj = %@", weakObj);
    };
    NSLog(@"obc = %@", obj);
    Case4Block();

底层代码实现为

///将obj封装成__Block_byref_weakObj_0对象
struct __Block_byref_weakObj_0 {
  void *__isa;
__Block_byref_weakObj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
///对obj对象弱引用
 TestObject *__weak weakObj;
};
///block底层结构
struct __ViewController4__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController4__viewDidLoad_block_desc_0* Desc;
///对obj封装成的__Block_byref_weakObj_0强引用;
  __Block_byref_weakObj_0 *weakObj; // by ref
  __ViewController4__viewDidLoad_block_impl_0(void *fp, struct __ViewController4__viewDidLoad_block_desc_0 *desc, __Block_byref_weakObj_0 *_weakObj, int flags=0) : weakObj(_weakObj->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

总结:当用__block修饰的对象类型时使用block整个过程为:

  • 首先被__block修饰的变量在底层会被封装成__Block_byref_XXXX_0的对象变量;
  • block被拷贝大堆上时会调用内部的copy函数, copy函数调用_Block_object_assign函数, 会对对象的修饰符__strong, __weak, unsafe_unretained做出相应的操作(强引用或者弱引用);
  • block执行完/从堆上移出时, 会调用block内部的dispose函数, 其内部调用_Block_object_dispose函数对结构体持有的对象进行类似release的释放操作;
总结: __block在MRC和ARC下的不同
  • MRC下: 自动变量对象都是被浅拷贝, 不会强引用(不会增加引用计数); 因为MRC环境下, block放在栈区, 所以不会对对象强引用, 如果对block进行拷贝到堆区操作则是强引用;
  • ARC下: block访问局部变量后会自动进行拷贝操作到堆区, 所以会自动变量对象封装后的__Block_byref_XXX对象强引用(引用计数会增加);__Block_byref_XXX对变量根据实际的修饰符进行强/弱引用;

3. 循环引用问题

首先看下方代码, 我们都知道产生了循环引用, 但是为什么会循环引用, 通过底层代码查看下原因

#import "ViewController4.h"
typedef void(^Block)(void);
@interface ViewController4 ()
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy)  Block  Block1;
@property (nonatomic, copy)  Block  Block2;
@end
@implementation ViewController4
- (void)viewDidLoad {
    [super viewDidLoad];
    ///产生循环引用
    self.Block1 =    ^{
        NSLog(@"name = %@", self.name);
    };
     self.Block1();
    ///weak 修饰不产生循环引用
    __weak typeof(self) weakSelf = self;
    self.Block2 =    ^{
        NSLog(@"name = %@", weakSelf.name);
    };
    self.Block2();
}
@end

底层代码:

#Block0的底层结构
struct __ViewController4__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController4__viewDidLoad_block_desc_0* Desc;
  ///对 self 进行强引用
  ViewController4 *const __strong self;
  __ViewController4__viewDidLoad_block_impl_0(void *fp, struct __ViewController4__viewDidLoad_block_desc_0 *desc, ViewController4 *const __strong _self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

#Block1的底层结构
struct __ViewController4__viewDidLoad_block_impl_1 {
  struct __block_impl impl;
  struct __ViewController4__viewDidLoad_block_desc_1* Desc;
  ///对 self 进行弱引用
  ViewController4 *const __weak weakSelf;
  __ViewController4__viewDidLoad_block_impl_1(void *fp, struct __ViewController4__viewDidLoad_block_desc_1 *desc, ViewController4 *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

引用block结构中对self指针有强引用, 而self又对block有强引用, 所以造成循环引用;

ARC解决循环引用方案:

  • __weak修饰, 这样block底层对self的引用变成了XXX *__weak weakSelf; 推荐使用;
  • __unsafe_unretained修饰, 具体效果跟__weak类似, 但是需要注意__unsafe_unretained不会将使用完的对象置为nil; 不推荐使用;

MRC解决循环引用方案:

  • __block修饰, 将对象封装为__Block_byref_XX_0形式, bock__Block_byref_XX_0是强引用, 但是__Block_byref_XX_0对其结构体内的变量不是强引用;
  • __unsafe_unretained修饰, 具体效果跟__weak类似, 但是需要注意__unsafe_unretained不会将使用完的对象置为nil;


参考文章和下载链接
文中测试代码
iOS clang指令报错问题总结
Apple 一些源码的下载地址
auto关键字是什么
C++中结构体的构造函数
全局变量、静态全局变量、静态局部变量和普通局部变量的区别

相关文章

  • iOS Block 部分三

    主要讲解 Block内修改外部变量, 内存管理, 以及循环引用; Block部分一Block部分二Block部分三...

  • iOS Block 部分二

    主要讲解 Block 的分类和变量捕获的强弱引用; Block部分一Block部分二Block部分三Block知识...

  • iOS Block 部分一

    主要讲解 Block 的底层实现原理; Block部分一Block部分二Block部分三Block知识点总结 基础...

  • iOS 题目详解 部分三

    主要讲解Block 内部使用strongSelf的理由和用法 iOS 题目详解 部分一iOS 题目详解 部分二...

  • iOS-2 Block

    block块 系列文章: iOS Block浅浅析 - 简书 iOS Block实现原理 iOS Block __...

  • OC--Block总结

    参考 Block编译代码解读:block没那么难(一、二、三)iOS进阶——iOS(Objective-C) 内存...

  • iOS Block存储域及循环引用

    系列文章:iOS Block概念、语法及基本使用iOS Block实现原理iOS Block __block说明符...

  • iOS Block实现原理

    系列文章:iOS Block概念、语法及基本使用iOS Block __block说明符iOS Block存储域及...

  • ios block使用

    iOS Block的使用一 .最简单的block使用******使用block的三个步骤:1.定义block变量 ...

  • iOS-Block本质

    参考篇:iOS-Block浅谈 前言:本文简述Block本质,如有错误请留言指正。 第一部分:Block本质 Q:...

网友评论

      本文标题:iOS Block 部分三

      本文链接:https://www.haomeiwen.com/subject/dkuxqktx.html