未设置对象变量或with block变量_BLOCK介绍及常见问题

本文详细介绍了Objective-C中的Block原理,包括Block的定义、实现方式以及其作为匿名函数的特性。通过示例代码展示了Block如何捕获和修改外部变量,并深入探讨了Block导致的循环引用问题及解决方案。在实践中,Block的使用需注意防止内存泄漏,特别是避免Block与强引用对象间的循环引用,可以使用__weak关键字来打破这种循环。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

这段时间小编在整理开发代码问题时发现开发同学在使用block时经常出现一些BUG,其中还有一些隐藏的很深的问题,这里小编就为大家介绍一下block的原理,简单用法和常见问题。

fccf78e001ff584947464e9d1298abf7.png

Block概要

Block:带有自动变量的匿名函数。

匿名函数:没有函数名的函数,一对{}包裹的内容是匿名函数的作用域。

Block表达式语法:^ 返回值类型 (参数列表) {表达式}

3a04c7a3bf3620d2f1d295ae2e642f2d.png

返回类型为空:

8ea55c0ca5ea19e049c7a38e68eff03c.png

参数列表为空:

60ca002d2617670d7736a5675705a58b.png

声明Block类型变量语法:返回值类型 (^变量名)(参数列表) = Block表达式

声明一个变量名为blk的Block:

ae79385d4fc56ca994cb372172e79785.png

Block实现原理

Block实际上是作为极普通的C语言源码来处理的:含有Block语法的源码首先被转换成C语言编译器能处理的源码,再作为普通的C源代码进行编译。首先我们先写一个简单的block。

23f03640b73cb2387e3116fedbf0c2ea.png

利用终端编译生成C++代码:

clang -rewrite-objc main.m

static void __main_block_func_0( struct __main_block_impl_0 *__cself) { int count = __cself->count; // bound by copy   NSLog((NSString *)&__NSConstantStringImpl__var_folders_64_vf2p_jz52yz7x4xtcx55yv0r0000gn_T_main_d2f8d2_mi_0, count); }

这是一个函数的实现,对应Block中{}内的内容,这些内容被当做了C语言函数来处理,函数参数中的__cself相当于Objective-C中的self。

struct __main_block_impl_0 {   struct __block_impl impl;   struct __main_block_desc_0* Desc; //描述Block大小、版本等信息   int count;   //构造函数函数   __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _count, int flags=0) : count(_count) {     impl.isa = &_NSConcreteStackBlock;     //在函数栈上声明,则为_NSConcreteStackBlock     impl.Flags = flags;     impl.FuncPtr = fp;     Desc = desc;   } };

__main_block_impl_0即为main()函数栈上的Block结构体,其中的__block_impl结构体声明如下:

struct __block_impl {  void *isa;//指明对象的Class  int Flags;  int Reserved;  void *FuncPtr;};

__block_impl结构体,即为Block的结构体,可理解为Block的类结构。

再看下main()函数翻译的内容:

int main() {   int count = 10;   void (* blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, count));   ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk); }

由此,可以看出,Block也是Objective-C中的对象。

Block常见问题

问题1. Block对外部变量进行修改,最后没有生效导致出现BUG

首先,为什么Block不能修改外部自动变量?

自动变量存于栈中,在当前方法执行完,会释放掉。 一般来说,在 block 中用的变量值是被复制过来的,自动变量是值类型复制,新开辟栈空间,所以对于新复制变量的修改并不会影响这个变量的真实值(也称只读拷贝)。 大多情况下,block是作为参数传递以供后续回调执行的。 所以在你想要在block中修改此自动变量时,变量可能已被释放,所以不允许block进行修改是合理的。 对于 static 变量,全局变量,在 block中是有读写权限的,因为此变量存于全局数据区(非栈区),不会随时释放掉,也不会新开辟内存创建变量, block 拷贝的是指向这些变量的指针,可以修改原变量。

那么怎么让自动变量不被释放,还能被修改呢?

__block修饰符把所修饰的变量包装成一个 结构体对象,即可完美解决。Block既可以强引用此结构体对象,使之不会自动释放,也可以拷贝到指向该结构体对象的指针,通过指针修改结构体的变量,也就是__block所修饰的自动变量。

问题2. Block在使用过程中出现循环引用

在测试过程中,我们经常遇到内存泄漏问题,这里提到的循环引用就是引起内存泄漏的元凶之一,而且Block的循环引用很难被开发同学察觉,因此也需要我们重点注意。

举例说明:

//DemoObj.m@interface DemoObj ()@property (nonatomic, strong) NSMutableArray *myBlocks;@end#pragma mark 将代码改为调用self的方法-(NSMutableArray *)myBlocks{    if (_myBlocks == nil) {        _myBlocks = [NSMutableArray array];    }        return _myBlocks;}- (instancetype)init{    self = [super init];    if (self) {        int(^sum)(int, int) = ^(int x, int y) {            return [self sum:x y:y];        };        [self.myBlocks addObject:sum];    }        return self;}-(int)sum:(int) x y:(int)y{    return x + y;}#pragma mark 对象被释放时自动调用- (void)dealloc{    NSLog(@"DemoObj被释放");}

大家阅读完上述代码,请问创建的对象可以被正常销毁吗?

答案是否定的,产生问题的原因就是

int(^sum)(int, int) = ^(int x, int y) {    return [self sum:x y:y];};

此时sum的block对self强引用,在加上self对myBlocks强引用:

@property (nonatomic, strong) NSMutableArray *myBlocks;

以及sum block被添加到数组时,会被数组强引用:

[self.myBlocks addObject:sum];

这三个引用之间形成了循环引用,如下图:

14589da626cddf1ff2690b3eab96c921.png

那我们如何解除循环引用呢?

1. 在block代码中不要引用self以及其他局部变量

int(^sum)(int, int) = ^(int x, int y) {    return x + y;};

2. 使用__weak关键字,可以将局部变量声明为弱引用

- (instancetype)init{    self = [super init];    if (self) {        __weak DemoObj *weakSelf = self;        int(^sum)(int, int) = ^(int x, int y) {            return [weakSelf sum:x y:y];        };        [self.myBlocks addObject:sum];    }    return self;}

结语

在开发代码中,Block的使用特别的频繁,因此我们在做代码分析时也要重点关注其代码中的常见问题,以免将这类问题遗漏到测试末期,造成产品delay或产生更大的工作量。

### 关于用友U8订单对接中的错误解决方案 当在用友U8系统中进行订单对接时,如果遇到“设置对象变量With block对象变量”的错误,通常是因为脚本程序中某些对象实例化不完全者引用的对象为空所致。以下是针对该问题的具体分析和解决方法: #### 1. **检查对象初始化** 确保所有涉及的操作对象都已正确定义并初始化。例如,在SQL Server中使用游标操作时,需确认`DECLARE CURSOR`语句以及后续的`OPEN`, `FETCH NEXT`等命令均按顺序执行无误[^1]。 对于VBA其他编程环境下的接口调用,应验证如下几点: - 所有COM组件是否注册成功。 - 对象创建前是否有对应的`Set obj = CreateObject("TypeName")`语句来显式声明对象实例。 #### 2. **调试与日志记录** 增加详细的错误捕获机制可以帮助定位具体发生异常的位置。通过启用事务处理将每一步骤的结果写入日志文件,可以更清晰地了解哪一部分代码能正常运行。例如,在VBScript/VBA环境中可加入以下形式的日志输出功能: ```vbscript On Error Resume Next Dim fso, logFile Set fso = CreateObject("Scripting.FileSystemObject") Set logFile = fso.OpenTextFile("C:\Logs\ErrorLog.txt", 8, True) logFile.WriteLine Now & ": Starting process..." If Err.Number <> 0 Then logFile.WriteLine "Error: " & Err.Description End If ``` 此段代码片段展示了如何捕捉潜在错误并将它们保存到指定路径下的文本文件中以便进一步审查[^2]。 #### 3. **权限配置核查** 有时此类错误也可能源于访问数据库表单字段时缺乏足够的权限。因此需要重新审视当前用户的账户权限设定,保证其拥有读取、更新等相关资源所需的最低限度授权级别。 另外还需注意连接字符串内的参数准确性,比如服务器名称、端口号、认证模式等等因素都会影响最终能否顺利建立链接关系。 #### 4. **版本兼容性考量** 最后一点不可忽视的是不同版本之间可能存在API变更情况,所以务必查阅官方文档核实目标函数签名是否存在差异,并据此调整源码逻辑适应最新标准需求。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值