iOS block的值捕获与指针捕获详解

目录
  • 指针与指针变量
  • block捕获变量方式
    • 值捕获
    • 指针捕获
    • __block修饰的变量
  • 关于block延伸的知识点
  • 总结

指针与指针变量

通俗的理解:

指针:内存地址
指针变量:存放内存地址的变量
指针变量的指针:指针变量自身的内存地址

Person *p = [Person new]

右边isa为:对象的内存地址 - 指针

p为:指针变量

左边isa为:指针变量的内存地址 - 指针变量的指针

block捕获变量方式

对局部变量捕获有两种形式:1、值捕获(局部自动变量) 2、指针捕获(局部静态变量);全局变量无需捕获,可直接进行访问。

clang -rewrite-objc **.m -o **.cpp 不同场景下转换成C++代码结果如下(嫌代码长不想看的直接看代码下面的结论)

值捕获

指针变量的捕获

block内部用一个新的指针变量来接收原指针变量。接收后,两个指针变量里面存储的值都是对象的内存地址,所以也可以说是值的捕获。

局部自动变量:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [Person new];
        void (^block)(void) = ^{
            NSLog(@"%@",p);
        };
        block();
    }
    return 0;
}
struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *p;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_p, int flags=0) : p(_p) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

  Person *p = __cself->p; // bound by copy
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_7w_wgxxl_655s9g6tms_7z44s6w0000gn_T_main_f76e59_mi_0,p);
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->p, 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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, 570425344));

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

    }
    return 0;
}

代码分析,生成的__main_block_impl_0结构体里面创建了一个指针变量p,main函数里面的__main_block_impl_0初始化时,传入的也是指针变量p。所以block对局部自动变量采用的捕获方式是指针变量的捕获,也就是值捕获。

指针捕获

对指针变量自身指针的捕获

block内部用一个新的指针来接收(指向)原指针变量自身的地址。

局部静态变量:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static Person *p = nil;
        p = [Person new];
        void (^block)(void) = ^{
            NSLog(@"%@",p);
        };
        block();
    }
    return 0;
}
struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
};

struct __main_block_impl_0 {

  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person **p;

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person **_p, int flags=0) : p(_p) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

  Person **p = __cself->p; // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_7w_wgxxl_655s9g6tms_7z44s6w0000gn_T_main_bd39c2_mi_0,(*p));
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->p, 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[]) {

    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

        static Person *p = __null;
        p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &p, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

代码分析,生成的__main_block_impl_0结构体里面创建了一个指针*p,main函数里面的__main_block_impl_0初始化时,传入的是指针变量p的地址&p。所以block对局部静态变量采用的捕获方式是指针变量自身地址的捕获,也就是指针捕获。

__block修饰的变量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block Person *p = [Person new];
        void (^block)(void) = ^{
            NSLog(@"%@",p);
        };
        block();
    }
    return 0;
}
struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
};

struct __Block_byref_p_0 {
  void *__isa;
__Block_byref_p_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 Person *p;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;

  __Block_byref_p_0 *p; // by ref

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_p_0 *_p, int flags=0) : p(_p->__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_p_0 *p = __cself->p; // bound by ref
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_7w_wgxxl_655s9g6tms_7z44s6w0000gn_T_main_6c171f_mi_0,(p->__forwarding->p));
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->p, (void*)src->p, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->p, 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[]) {

    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        __attribute__((__blocks__(byref))) __Block_byref_p_0 p = {(void*)0,(__Block_byref_p_0 *)&p, 33554432, sizeof(__Block_byref_p_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"))};

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_p_0 *)&p, 570425344));

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

    }
    return 0;
}

代码分析,使用__block修饰的指针变量p,会被转换为__Block_byref_p_0的结构体,结构体内持有p。main函数里面初始化__main_block_impl_0时传入的是__Block_byref_p_0的地址,访问p时,通过__Block_byref_p_0的__forwarding指针进行访问。其实就相当于block内部捕获了__Block_byref_p_0的指针,通过指针去访问__Block_byref_p_0持有的p。所以__block修饰的变量本质上也相当于是一种指针捕获,只不过不是直接捕获指针变量p的自身地址。

值捕获能否重新赋值? 进行值拷贝时,block内部同名指针变量如果执行重新赋值操作,相当于使内部的指针变量指向了一个新的对象,再对此对象进行任何操作都与原指针变量指向的原对象无关,所以不能进行重新赋值。

指针捕获能否重新赋值? block内部将block外部的指针变量的指针赋值给一个新的指针,block内部、外部的指针都指向的是同一个指针变量。如果进行赋值操作,操作的是同一个指针变量,所以可以进行重新赋值。

关于block延伸的知识点

如果文章看到了这里,相信对值捕获和指针捕获已经有了一个清晰的认识,那么可以自行思考以下几个问题,看是否真的理解了block,文章没有的答案放在评论区

  • 值捕获能否在block内被重新赋值?如果是静态变量呢?(文中已有)
  • 经__block修饰变量生成的持有变量的结构体里面__forwarding的意义在于什么?
  • 使用block有什么需要注意的点,如何去解决?

总结

到此这篇关于iOS block的值捕获与指针捕获的文章就介绍到这了,更多相关iOS block值捕获与指针捕获内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2022-02-06

iOS中block的定义与使用

概念 代码块block是苹果在iOS4开始引入的对C语言的扩展,用来实现匿名函数的特性,block是一种特殊的数据类型,其可以正常定义变量.作为参数.作为返回值,特殊地,block还可以保存一段代码,在需要的时候调用,目前block已经广泛应用于iOS开发中,常用于GCD.动画.排序及各类回调 block:我们称代码块,他类似一个方法.而每一个方法都是在被调用的时候从硬盘到内存,然后去执行,执行完就消失,所以,方法的内存不需要我们管理,也就是说,方法是在内存的栈区.所以,block不像OC中的类

iOS(闭包)block传值详解

在iOSAPP开发的过程中 我们会用到很多需要传值的地方 传值的方式也多种多样 有:代理传值.通知传值.KVC.KVO.block.单例 等.其中block 因为其简洁实用规范的代码 无疑是大牛们传值的不二选择 但对于初学者来说要理解并能运用 起初确实有些难以理解 以下我将细细的介绍下block 首先我总结了一下block的公式: 步骤1.block 的声明 返回值类型(^block 的名字)(参数列表); 步骤2.block 实现 block的名字 = ^(参数列表)(){}; 步骤3.blo

浅谈iOS 对于block的一点理解

block是对象,它封装了一段代码,这段代码可以在任何时候执行.block可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值.它和传统的函数指针很类似,但是有区别:block是inline的,并且它对局部变量是只读的. Block的定义: int (^myBlock) (int a,int b) = ^(int a,int b){ return a+b; }; 定义了一个名为myBlock的blocks对象,它带有两个int参数,返回int.等式右边就是blocks的具体实现,是

iOS中Block的回调使用和解析详解

Block 回调实现 先跟着我实现最简单的 Block 回调传参的使用,如果你能举一反三,基本上可以满足了 OC 中的开发需求.已经实现的同学可以跳到下一节. 首先解释一下我们例子要实现什么功能(其实是烂大街又最形象的例子): 有两个视图控制器 A 和 B,现在点击 A 上的按钮跳转到视图 B ,并在 B 中的textfield 输入字符串,点击 B 中的跳转按钮跳转回 A ,并将之前输入的字符串 显示在 A 中的 label 上.也就是说 A 视图中需要回调 B 视图中的数据. 想不明白的同学

iOS利用Block逆向传值的方式详解

前言 在iOS通过代理逆向传值的方式详解一文中,分析了如何利用代理模式来逆向传值,其实还有一些其他的方式,如通知.Block等,相比较代理,我个人认为反而要简单些,但是需要处理好细节问题,如Block循环引用.还是用前文的案例,本次使用Block来实现,Block的基本知识本文不再赘述. 一.书写规范 Block传值,需要注意的是,谁传值就需要定义Block,捕获方仅仅需要传递Block给传值方,并处理捕获的值. 传值方 1.定义Block用于传值 2.声明一个上述Block属性,这个属性的具体

iOS中block变量捕获原理详析

Block概述 Block它是C语言级别和运行时方面的一个特征.Block封装了一段代码逻辑,也用{}括起,和标准C语言中的函数/函数指针很相似,此外就是blokc能够对定义环境中的变量可以引用到.这一点和其它各种语言中所说的"闭包"是非常类似的概念.在iOS中,block有很多应用场景,比如对代码封装作为参数传递.这在使用dispatch并发(Operation中也有BlockOperation)和completion异步回调等处都广泛应用. Block是苹果官方特别推荐使用的数据类

Python中的变量和作用域详解

作用域介绍 python中的作用域分4种情况: L:local,局部作用域,即函数中定义的变量: E:enclosing,嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域,但不是全局的: G:globa,全局变量,就是模块级别定义的变量: B:built-in,系统固定模块里面的变量,比如int, bytearray等. 搜索变量的优先级顺序依次是:作用域局部>外层作用域>当前模块中的全局>python内置作用域,也就是LEGB. x = int(2.9) # int bu

Node.Js中实现端口重用原理详解

本文介绍了Node.Js中实现端口重用原理详解,分享给大家,具体如下: 起源,从官方实例中看多进程共用端口 const cluster = require('cluster'); const http = require('http'); const numCPUs = require('os').cpus().length; if (cluster.isMaster) { console.log(`Master ${process.pid} is running`); for (let i =

vue之组件内监控$store中定义变量的变化详解

// 1.采用计算属性来获取$store中的值 computed: { listenstage() { return this.$store.state.iShaveMsg; } }, // 2.通过watch来检查定义计算属性获取到的值的变化 watch:{ listenstage: function(ov,nv){ console.log('watch start--'); if(this.$store.state.iShaveMsg){ //业务处理 } } console.log('wa

对Tensorflow中的变量初始化函数详解

Tensorflow 提供了7种不同的初始化函数: tf.constant_initializer(value) #将变量初始化为给定的常量,初始化一切所提供的值. 假设在卷积层中,设置偏执项b为0,则写法为: 1. bias_initializer=tf.constant_initializer(0) 2. bias_initializer=tf.zeros_initializer(0) tf.random_normal_initializer(mean,stddev) #功能是将变量初始化为

关于iOS中属性变量setter与getter方法的理解

关于成员变量.实例变量.属性变量 成员变量 @implementation ViewController { UILabel *textLabel; int count; } 成员变量是用于类的内部,无需与外界接触的变量.成员变量默认是protected,因为成员变量不会生成set.get方法,所以无法和外界接触,从上面代码可以看出来,成员变量是定义在{ }中的变量,如果变量的数据类型是一个类,就如:UILabel *textLabel;那么称这个变量为实例变量.所以实例变量也是成员变量的一种特

iOS中的应用启动原理以及嵌套模型开发示例详解

程序启动原理和UIApplication   一.UIApplication 1.简单介绍 (1)UIApplication对象是应用程序的象征,一个UIApplication对象就代表一个应用程序. (2)每一个应用都有自己的UIApplication对象,而且是单例的,如果试图在程序中新建一个UIApplication对象,那么将报错提示. (3)通过[UIApplicationsharedApplication]可以获得这个单例对象 (4) 一个iOS程序启动后创建的第一个对象就是UIAp

php中的ini配置原理详解

使用php的同学都知道php.ini配置的生效会贯穿整个SAPI的生命周期.在一段php脚本的执行过程中,如果手动修改ini配置,是不会启作用的.此时如果无法重启apache或者nginx等,那么就只能显式的在php代码中调用ini_set接口.ini_set是php向我们提供的一个动态修改配置的函数,需要注意的是,利用ini_set所设置的配置与ini文件中设置的配置,其生效的时间范围并不相同.在php脚本执行结束之后,ini_set的设置便会随即失效. 因此本文打算分两篇,第一篇阐述php.