返回顶部
首页 > 资讯 > 移动开发 >详解ObjectiveC中Block如何捕获外部值
  • 321
分享到

详解ObjectiveC中Block如何捕获外部值

2024-04-02 19:04:59 321人浏览 安东尼
摘要

目录引言自动变量静态变量、静态全局变量与全局变量带 __block 的自动变量捕获对象__block 对象类型的捕获引言 Block 本质上也是一个 Objective-C 对象,它

引言

Block 本质上也是一个 Objective-C 对象,它内部也有个 isa指针。Block 是封装了函数调用以及函数调用环境的 Objective-C 对象。Block 的底层结构如下图所示:

Block 对于不同类型的值会有不同的捕获方式,本文将通过代码展示其对于各种场景下的外部值是如何进行捕获的。

自动变量

首先展示源代码:

int main(int arGC, const char * argv[]) {
    @autoreleasepool {
        NSInteger value = 0;
        void(^block)(void) = ^{
            NSLog(@"%zd", value);
        };
        block();
    }
    return 0;
}

经过 clang -rewrite-objc 之后,得到的代码如下,可以看到,对于自动变量的捕获,是会在 Block 结构体中生成一个对应类型的成员变量来实现捕获的能力,这也解释了为什么在 Block 中修改捕获的值的内容,无法对 Block 外的值产生影响。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSInteger value; // 捕获的 NSInteger value
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSInteger _value, int flags=0) : value(_value) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSInteger value = __cself->value; // bound by copy
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_zyxvpxvq6csfxvn_n0000000000000_T_main_e3ca95_mi_0, value);
        }
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[]) {
     { __AtAutoreleasePool __autoreleasepool; 
        NSInteger value = 0;
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, value));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

静态变量、静态全局变量与全局变量

对于静态变量、静态全局变量与全局变量的捕获,会稍有不同,其中:

  • 全局变量与静态全局变量:直接使用,因为地址一直是可以直接获取的。
  • 静态变量:捕获地址使用,因为 block 有可能会传递出创建时的作用域。
NSInteger globalValue = 1;
static NSInteger staticGlobalValue = 2;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static NSInteger staticValue = 3;
        void(^block)(void) = ^{
            globalValue += 1;
            staticGlobalValue += 2;
            staticValue += 3;
        };
        block();
    }
    return 0;
}
NSInteger globalValue = 1;
static NSInteger staticGlobalValue = 2;
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSInteger *staticValue;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSInteger *_staticValue, int flags=0) : staticValue(_staticValue) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSInteger *staticValue = __cself->staticValue; // bound by copy
            globalValue += 1;
            staticGlobalValue += 2;
            (*staticValue) += 3;
        }
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[]) {
     { __AtAutoreleasePool __autoreleasepool; 
        static NSInteger staticValue = 3;
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &staticValue));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

带 __block 的自动变量

__block 修饰的自动变量,可以在 Block 内部对其外部的值进行修改:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block NSInteger value = 0;
        void(^block)(void) = ^{
            value = 10;
        };
        block();
        NSLog(@"%zd", value);
    }
    return 0;
}

这次生成的代码复杂了一些,不过只关注 value 部分的话可以发现,Block 为了捕获 __block 类型的自动变量,会生成 __Block_byref_value_0 结构体,并通过该结构体来实现对外部 __block 自动变量的捕获。

struct __Block_byref_value_0 { // 为捕获 __block 的自动变量,生成的结构体。为的是方便多个 Block 同时捕获一个自动变量时使用。
  void *__isa; // isa 指针
__Block_byref_value_0 *__forwarding; // 在 Block 单纯在栈上是,指向的是自己,拷贝到堆上后,指向的是在堆上的 Block。之所以需要这样的指针是因为当 Block 拷贝到堆上时,调用方式是统一的。
 int __flags;
 int __size;
 NSInteger value; // 具体的值
};
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_value_0 *value; // 通过引用的方式捕获 value,其中变量类型为 __Block_byref_value_0
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_value_0 *_value, int flags=0) : value(_value->__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_value_0 *value = __cself->value; // bound by ref
            (value->__forwarding->value) = 10; // 赋值代码
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->value, (void*)src->value, 8);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->value, 8);}
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[]) {
     { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_value_0 value = {(void*)0,(__Block_byref_value_0 *)&value, 0, sizeof(__Block_byref_value_0), 0};
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_value_0 *)&value, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_zyxvpxvq6csfxvn_n0000000000000_T_main_6bf1c6_mi_0, (value.__forwarding->value));
    }
    return 0;
}

__block 可以用于解决 block 内部无法修改 auto 变量值的问题,__block 不能修饰全局变量、静态变量(static),编译器会将 __block 变量包装成一个对象。

block 在栈上时,并不会对 __block 变量产生强引用。

blockcopy 到堆时,会调用 block 内部的 copy 函数,copy 函数内部会调用 _Block_object_assign 函数,_Block_object_assign 函数会对 __block 变量形成强引用(retain)。

block 从堆中移除时,会调用 block 内部的 dispose 函数,dispose 函数内部会调用 _Block_object_dispose 函数,_Block_object_dispose 函数会自动释放引用的 __block 变量(release)。

捕获对象

在探究完对标量类型的捕获之后,让我们看一下对对象类型的捕获:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray *array = [NSArray array];
        void(^block)(void) = ^{
            NSLog(@"%@", array);
        };
        block();
    }
    return 0;
}

通过转译的代码可以看出,因为对象类型本身已经是存储在堆上的值了,所以直接获取其地址即可,同时其新增了两个函数 __main_block_copy_0__main_block_dispose_0,这两个函数是用来将对象拷贝到堆上和被从堆上移除时调用的,其内部又分别调用了 _Block_object_assign_Block_object_dispose 用来对捕获的对象进行引用计数的增加和减少。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSArray *array;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSArray *_array, int flags=0) : array(_array) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSArray *array = __cself->array; // bound by copy
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_zyxvpxvq6csfxvn_n0000000000000_T_main_8ba4f7_mi_0, array);
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3);}
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[]) {
     { __AtAutoreleasePool __autoreleasepool; 
        NSArray *array = ((NSArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_reGISterName("array"));
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

Block 对象本身分为三种类型:

  • NSGlobalBlock:没有访问 auto 变量,调用 copy 方法之后不会发生变化。
  • NSStackBlock:访问了 auto 变量,调用 copy 方法之后存储位置从栈变为堆。
  • NSMallocBlock__NSStackBlock__ 调用了 copy 方法之后,引用计数增加。

ARC 环境下,编译器会根据情况自动将栈上的 block 复制到堆上,比如以下情况:

  • Block 作为函数返回值时
  • Block 赋值给 __strong 指针时
  • Block 作为 Cocoa api 中方法名含有 usingBlock 的方法参数时
  • Block 作为 GCD API 的方法参数时

所以,当 Block 内部访问了对象类型的 auto 变量时。如果 Block 是在栈上,将不会对 auto 变量产生强引用。

如果 Block 被拷贝到堆上,会调用 Block 内部的 copy 函数,copy 函数内部会调用 _Block_object_assign 函数,_Block_object_assign 函数会根据 auto 变量的修饰符(__strong__weak__unsafe_unretained)做出相应的操作,形成强引用或者弱引用。

如果 Block 从堆上移除,会调用 Block 内部的 dispose 函数,dispose 函数内部会调用 _Block_object_dispose 函数。_Block_object_dispose 函数会自动释放引用的 auto 变量(release)。

__block 对象类型的捕获

如果想在 Block 中,对捕获的对象的指针指向进行修改,则需要添加 __block 关键字:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block NSArray *array = [NSArray array];
        void(^block)(void) = ^{
            array = [NSArray array];
            NSLog(@"%@", array);
        };
        block();
    }
    return 0;
}

通过转译我们可以看出,跟 __block 修饰的标量类型相似,同样会生成 __Block_byref_array_0 结构体来捕获对象类型。同时其内部生成了 __Block_byref_id_object_copy__Block_byref_id_object_dispose 两个函数指针,用于对被结构体包装的对象进行内存管理。

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
struct __Block_byref_array_0 {
  void *__isa;
__Block_byref_array_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSArray *array;
};
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_array_0 *array; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_array_0 *_array, int flags=0) : array(_array->__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_array_0 *array = __cself->array; // bound by ref
            (array->__forwarding->array) = ((NSArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("array"));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_zz_zyxvpxvq6csfxvn_n0000000000000_T_main_3593f0_mi_0, (array->__forwarding->array));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 8);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 8);}
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[]) {
     { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_array_0 array = {(void*)0,(__Block_byref_array_0 *)&array, 33554432, sizeof(__Block_byref_array_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("array"))};
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_array_0 *)&array, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

block 在栈上时,对它们都不会产生强引用。

block 拷贝到堆上时,都会通过 copy 函数来处理它们,__block 变量(假设变量名叫做 a):

_Block_object_assign((void*)&dst->a, (void*)src->a, 8);

对象类型的 auto 变量(假设变量名叫做 p):

_Block_object_assign((void*)&dst->p, (void*)src->p, 3);

block 从堆上移除时,都会通过 dispose 函数来释放它们,__block 变量(假设变量名叫做 a):

_Block_object_dispose((void*)src->a, 8);

对象类型的 auto 变量(假设变量名叫做 p):

_Block_object_dispose((void*)src->p, 3);

以上就是详解Objective C 中Block如何捕获外部值的详细内容,更多关于Objective C Block捕获外部值的资料请关注编程网其它相关文章!

--结束END--

本文标题: 详解ObjectiveC中Block如何捕获外部值

本文链接: https://lsjlt.com/news/166812.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 详解ObjectiveC中Block如何捕获外部值
    目录引言自动变量静态变量、静态全局变量与全局变量带 __block 的自动变量捕获对象__block 对象类型的捕获引言 Block 本质上也是一个 Objective-C 对象,它...
    99+
    2024-04-02
  • iOS block的值捕获与指针捕获详解
    目录指针与指针变量block捕获变量方式值捕获指针捕获__block修饰的变量关于block延伸的知识点总结指针与指针变量 通俗的理解: 指针:内存地址指针变量:存放内存地址的变量指...
    99+
    2024-04-02
  • lambda 表达式如何捕获外部变量?
    lambda 表达式通过创建一个外部作用域中变量的引用来捕获外部变量,具体步骤包括:lambda 表达式使用外部变量时会捕获该变量。lambda 表达式只能捕获外部函数中的局部变量,不能...
    99+
    2024-04-25
    lambda 外部变量 python 作用域
  • C++ lambda 表达式如何捕获外部变量?
    在 c++++ 中捕获外部变量的 lambda 表达式有三种方法:按值捕获:创建一个变量副本。按引用捕获:获得变量引用。同时按值和引用捕获:允许捕获多个变量,按值或按引用。 C++ l...
    99+
    2024-04-17
    c++ lambda
  • 详细聊聊如何在C#循环中捕获局部变量
    目录问题:解答方案:总结问题: 我遇到了一个有趣的问题,它的代码大概是这样的。 List<Func<int>> actions = new List&l...
    99+
    2024-04-02
  • ES7中await如何优雅的捕获异常详解
    目录传统方式await-to-js安装使用源码测试用例结束语本篇文章介绍的是ES7中await语法糖在调用的时候出现异常如何捕获? 传统方式 try { const res =...
    99+
    2022-12-12
    await 捕获异常 await 异常处理 await怎么捕获异常
  • Vue中如何优雅的捕获Promise异常详解
    目录常规的异常捕获方式好一些的方式:await-to-js更好的方式:全局捕获常规的异常捕获方式 在 Promise 提供了一个 .catch 方法用来捕获异常,假设有很多异步请求,...
    99+
    2022-11-13
    Vue Promise 异常捕获 Vue Promise
  • C++ 函数中的 lambda 表达式如何通过参数传递捕获外部变量?
    c++++ lambda 表达式可以通过参数传递来捕获外部变量,具体步骤如下:定义一个接受 lambda 表达式作为参数的函数。在 lambda 表达式中捕获外部变量。将 lambda ...
    99+
    2024-04-25
    lambda 捕获外部变量 c++
  • ajax返回值给外部函数如何解决
    本篇文章给大家分享的是有关ajax返回值给外部函数如何解决,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。如下所示:function validateUser(mya)...
    99+
    2023-06-08
  • 详解Spark Sql在UDF中如何引用外部数据
    目录前言场景介绍方法一 Driver端加载方法二 Excutor端加载小结前言 Spark Sql可以通过UDF来对DataFrame的Column进行自定义操作。在特定场景下定义...
    99+
    2023-02-01
    Spark Sql UDF引用外部数据 Spark Sql UDF
  • Spring Boot如何读取自定义外部属性详解
    测试的环境:Spring Boot2 + Maven +lombok 准备需要用到的基础类: public class People { private String n...
    99+
    2024-04-02
  • cmd中pip不是内部或外部命令如何解决
    这篇文章将为大家详细讲解有关cmd中pip不是内部或外部命令如何解决,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。解决方法:首先,点击计算机,选择“属性”。然后,打开高级系统设置。再选择环境...
    99+
    2023-06-15
  • Android开发中如何使用外部应用获取SD卡状态
    小编给大家分享一下Android开发中如何使用外部应用获取SD卡状态,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!先来看看常规获取SD卡状态的方法if ...
    99+
    2023-05-30
    android sd卡
  • 详解如何在Elasticsearch中搜索空值
    目录引言选项 1:null_value 映射参数选项2:使用 MUST_NOT 查询引言 根据 Elasticsearch 文档,无法索引或搜索空值 null。 当一个字段设置为 ...
    99+
    2023-01-28
    Elasticsearch搜索空值 Elasticsearch 空值
  • nacos中使用@Value注解获取不到值如何解决
    这篇文章主要介绍了nacos中使用@Value注解获取不到值如何解决的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇nacos中使用@Value注解获取不到值如何解决文章都会有所收获,下面我们一起来看看吧。可能导...
    99+
    2023-07-05
  • QT中大部分部件如何使用举例详解
    目录【1】main.cpp【2】mainwindow.h【3】mainwindow.cpp【4】ui界面设计【5】代码运行效果图总结内容繁多,直接上代码 重点请看mainwindow...
    99+
    2024-04-02
  • php获取不到网址中的值该如何解决
    本篇文章为大家展示了php获取不到网址中的值该如何解决,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。php获取不到网址中的值的解决办法:1、查看url规则;2、将规则修改为“RewriteRule ...
    99+
    2023-06-26
  • 详解如何在Golang中修改变量的值
    Golang是当今最为流行的编程语言之一,它具有简单易用、高效、安全等优点,使得它在应用开发领域里得到越来越广泛的应用。在程序开发过程中,我们经常需要改变变量的值,本文将介绍如何在Golang中改变变量的值以及相关的语法细节。一、变量的声明...
    99+
    2023-05-14
  • 闭包如何解决jQuery中AJAX的外部变量问题
    这篇文章主要为大家展示了“闭包如何解决jQuery中AJAX的外部变量问题”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“闭包如何解决jQuery中AJAX的外部...
    99+
    2024-04-02
  • 详解Python如何获取列表(List)的中位数
    前言 中位数是一个可将数值集合划分为相等的上下两部分的一个数值。如果列表数据的个数是奇数,则列表中间那个数据就是列表数据的中位数;如果列表数据的个数是偶数,则列表中间那2个数据的算术平均值就是列表数据的中位...
    99+
    2022-06-04
    中位数 详解 列表
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作