美文网首页
iOS Block基本使用(三)

iOS Block基本使用(三)

作者: 学不来的凡人 | 来源:发表于2021-03-18 09:57 被阅读0次

问题:block内部捕获到的局部变量是局部变量的值,所以在block上修改不了局部变量。
但是局部的静态变量捕获是指针,所以block内部可以修改值。全局变量和全局静态变量没有捕获,可以直接获取到(不存在作用域的问题)
怎么在block内部修改局部变量???
方法一:改成静态变量!!!
方法二:__block修饰局部变量

void test(){
    __block int a = 10;
    MyBlock myBlock = ^{
        a = 11;
        NSLog(@"myBlock---------%d", a);//11
    };
    myBlock();
}

转成底层代码

typedef void(*MyBlock)(void);
//在block内部捕获的时候把&a传进来包装成了__Block_byref_a_0对象
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __test_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __test_block_impl_0(void *fp, struct __test_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 __test_block_func_0(struct __test_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

        (a->__forwarding->a) = 11;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_r0_rjxg_z6540vblvmqqv4klgsm0000gn_T_main_cd5b88_mi_0, (a->__forwarding->a));
    }
static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __test_block_dispose_0(struct __test_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
  void (*dispose)(struct __test_block_impl_0*);
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0), __test_block_copy_0, __test_block_dispose_0};
void test(){
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
    MyBlock myBlock = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}

可以看到__block修饰的局部变量a,在block内部捕获的时候 变成了一个 __Block_byref_a_0 *a; // by ref 类型的对象 Blcok内部有个指向这个对象的指针 而这个对象拥有a变量(而且这个对象拥有的内存地址和我们声明的a的内存地址是一样的) 被执行Block的时候 通过被包装对象的__forwarding对象 访问到a 进行修改


image.png

__block的内存管理

当block在栈上的时候 并不会对__block变量产生强引用(ARC)或者retain操作(MRC)

当block被拷贝到堆上时:
1.会调用Block内部copy函数 这个函数内部会调用_Block_object_assign函数
2._Block_object_assign函数会对__block变量形成强引用(retain) (__block修饰的变量也会被拷贝到堆上)
3.只要是被__block修饰的变量 copy后都是强引用 如果没被__block修饰的对象 会根据对象的修饰符来确定是强引用还是弱引用

当Block从堆中移除的时候:
1.会调用Block内部的dispose函数 这个函数内部会调用_Block_object_dispose函数
2._Block_object_dispose 这个函数会断开对__block修饰的变量的强引用(realse)
block内部使用了被__block修饰的对象的话 那么block内部的 Desc 描述指针也会多两个函数 这两个函数就是用于内存管理的copy函数和dispose函数

不使用__block修饰对象类型

void test(){
    NSObject *objc = [NSObject new];
    MyBlock myBlock = ^{
        NSLog(@"myBlock---------%@", objc);//11
    };
    myBlock();
}
typedef void(*MyBlock)(void);

struct __test_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
  NSObject *objc;
  __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, NSObject *_objc, int flags=0) : objc(_objc) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
  NSObject *objc = __cself->objc; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_r0_rjxg_z6540vblvmqqv4klgsm0000gn_T_main_b0ec24_mi_0, objc);
    }
static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src) {_Block_object_assign((void*)&dst->objc, (void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __test_block_dispose_0(struct __test_block_impl_0*src) {_Block_object_dispose((void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
  void (*dispose)(struct __test_block_impl_0*);
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0), __test_block_copy_0, __test_block_dispose_0};
void test(){
    NSObject *objc = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
    MyBlock myBlock = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, objc, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}

__block修饰对象类型

void test(){
    __block NSObject *objc = [NSObject new];
    MyBlock myBlock = ^{
        NSLog(@"myBlock---------%@", objc);//11
    };
    myBlock();
}

转成底层代码

typedef void(*MyBlock)(void);
struct __Block_byref_objc_0 {
  void *__isa;
__Block_byref_objc_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *objc;
};

struct __test_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
  __Block_byref_objc_0 *objc; // by ref
  __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, __Block_byref_objc_0 *_objc, int flags=0) : objc(_objc->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
  __Block_byref_objc_0 *objc = __cself->objc; // bound by ref

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_r0_rjxg_z6540vblvmqqv4klgsm0000gn_T_main_c53779_mi_0, (objc->__forwarding->objc));
    }
static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src) {_Block_object_assign((void*)&dst->objc, (void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __test_block_dispose_0(struct __test_block_impl_0*src) {_Block_object_dispose((void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
  void (*dispose)(struct __test_block_impl_0*);
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0), __test_block_copy_0, __test_block_dispose_0};
void test(){
    __attribute__((__blocks__(byref))) __Block_byref_objc_0 objc = {(void*)0,(__Block_byref_objc_0 *)&objc, 33554432, sizeof(__Block_byref_objc_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))};
    MyBlock myBlock = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, (__Block_byref_objc_0 *)&objc, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}

可以看到和__block修饰的普通变量差不多 但是包装成的对象多了两个函数 copy函数和dispose函数 这两个和Block描述中的copy函数和dispose函数还不一样,当Block拷贝的到堆上的时候 被__block修饰的对象(objc)转化的对象(__Block_byref_objc_0)也会被拷贝到堆上,这个时候会触发被包装对象的copy函数 而这个函数会根据外部__block修饰对象(objc)的引用计数修饰符来决定对外部对象(objc)是强引用还是弱引用。当Block从堆中移除的时候 会调用内部的dispose函数 这个函数会断开对转化对象(__Block_byref_objc_0)的引用关系 如果计数为0 就直接释放 释放后会调用自己的dispose函数 对自己内部对象(objc) 断开引用关系 如果此时objc的引用计数为0 就会被释放掉。(转化对象持有的objc和我们外部声明的objc对象有同一片内存地址 是同一个对象)

注意:上面的分析 都是ARC的情况下。如果是MRC转化对象(_Block_byref objc_0) 对外部对象(objc) 一直都是弱引用 不管是什么情况。这也是为什么在MRC下我们使用__block来解决循环引用的原因

Block与循环引用
1.在ARC的情况下 我们常常使用__weak __unsafe__unretained来解决

我们使用__weak 和 __unsafe__unretained 都有一个共同的作用 就是使Block持有的对象指向我们声明的对象的指针式弱引用,那么我们外部指向实例对象的指针一旦被释放 实例对象就会被释放 那么Block就会被释放 从而解决循环引用。区别就是如果我们使用的是__weak 一旦我们的实例对象被释放,block内部持有的指向我们实例对象的指针会被指nil 不会产生野指针,但是__unsafe__unretained 不会有这一步操作 所有如果使用__unsafe__unretained block内部持有的指向我们实例对象的指针会还会指向我们已经释放的实例对象的地址 产生野指针。这也是它不安全的原因。所以我们一般使用__weak来解决循环引用

2.在MRC的情况下 我们使用__unsafe__unretained __block来解决 因为不支持weak指针

这两个都可以使Block内部的指向我们实例对象的指针进行retain操作

1.Block的原理是怎样的?本质是什么?

Block是封装了函数调用以及函数调用环境的OC对象 本质上也是一个OC对象

Block内部包含了一个__block_imp1 这个指针内部包含了一个isa指针(也证明了本质是个对象) 和 block内部封装的代码块的执行地址 以及捕获的变量的指针

和一个__main_block_desc_0 里面包含了block的大小等 如果内部使用了实例对象或者__block修饰的变量 会包含一个copy函数和dispose函数.

2.__block的作用是什么?有什么使用注意点?

被__block修饰的变量 在block内部可以被改变,因为在block内部被__block修饰的变量会被包装成一个__Block_byref_xxx_0的对象,这个对象内部有一个指向我们声明的变量的指针 指向的地址和我们声明的变量是同一个地址,修改的时候也是通过这个对象拿到指针做修改。block内部对这个对象是强引用的。而如果被修饰的变量是实例对象 那么这个对象内部会添加一个copy函数和dispose函数,在被copy到堆上时,这个对象的copy函数会根据被修饰实例对象的关键字来确定对实例对象是强引用还是弱引用。再block释放的时候,会调用dispose函数 来断开对实例对象的引用关系。需要注意的是__block只能修饰实例对象 还有一些内存管理问题 比如说修饰实例对象且该实例对象拥有该Block 会造成循环引用。而且在MRC下使用__block修饰的实例对象 不会被__Block_byref_xxx_0的对象retain操作。对象可能会被提前释放。

3.Block的属性修饰词为什么是copy? 使用Block有哪些注意?

如果block不被copy 就是存储在栈空间上,我们就控制不了block的生命周期,可能我们使用的时候已经被释放了或者我们使用的时候 其内部捕获的变量已经释放了 导致程序错误。而拷贝到堆上,我们可以方便的控制其生命周期。虽然增加了管理内存的一些成本。但是可以减少错误。在ARC的情况下 如果有一个强引用指向Block 内部也会copy到堆上。使用strong也行。但是我们习惯使用copy。 需要注意的是不能引起循环引用。我们可以使用__weak来避免这个问题

4.Block修改NSMutableArray 需不需要添加__block?

不需要,如果是对数组进行增删改查 我们不需要对其添加__block 因为不是改变数组的指针指向 只是操作数组。添加__block反而会增加开销。

Block的copy

_NSConcreteStackBlock copy 变成 _NSConcreteMallocBlock
_NSConcreteGlobalBlock copy 还是全局block 什么也不做
_NSConcreteMallocBlock copy 引用计数加1

在ARC环境下 编译器会根据情况自动将栈上的Block复制到堆上 相当于对栈上的Block进行copy操作。
1.Blcok 作为函数返回值的时候 会自动copy
2.将Block赋值给__strong强指针的时候 也会自动做copy操作
3.Block作为GCD的参数时 也会被copy到堆上
4.Foundation框架下 block作为参数且方法名含有usingBlock时 会被自动copy

相关文章

  • iOS Block存储域及循环引用

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

  • iOS Block实现原理

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

  • iOS Block __block说明符

    系列文章:iOS Block概念、语法及基本使用iOS Block实现原理iOS Block存储域及循环引用 上一...

  • iOS Block基本使用(三)

    问题:block内部捕获到的局部变量是局部变量的值,所以在block上修改不了局部变量。但是局部的静态变量捕获是指...

  • block的使用和注意

    iOS学习-(1)block的使用和注意 本文简介 本文主要介绍1.block的基本使用 ...

  • ios block使用

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

  • iOS block基本使用

    block的感觉是越用越喜欢,下面就简单说说她的基本使用,block基本有下面四种情况。 1.无参数无返回值 2....

  • iOS - block基本使用

    block的概念 OC在C的基础之上新增了一些数据类型。 BOOLBoolenclassnilSELidblock...

  • block使用及其底层原理

    一 block基本使用 二 block底层结构 三 block变量捕获 四 block的类型 五 block对象类...

  • iOS开发 多视角抛析匿名函数—— Block

    一.Block的基本使用1.概念在iOS开发中Block的使用随处可见,使用场景也非常多,例如在网络中进行网络请求...

网友评论

      本文标题:iOS Block基本使用(三)

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