iOS block原理以及注意事项

iOS Block 原理

Block 是 iOS 开发中一种非常重要的特性,它本质上是 Objective-C 语言的扩展,类似于其他编程语言中的闭包(Closure)或 Lambda 表达式。Block 不仅可以封装代码,还可以携带执行环境(包括外部变量的引用),使得代码更加模块化和重用。

Block 的内部结构
  1. isa 指针:Block 本质上是一个 OC 对象,内部包含 isa 指针,指向其 Class 类。Block 有三种类型,都继承自 NSBlock,而 NSBlock 的基类是 NSObject。
  2. 调用信息:Block 内部包含了函数调用的地址和将来需要访问的变量等信息。
  3. 捕获机制:Block 能够捕获外部变量,根据变量的类型(局部变量、静态变量、全局变量)和存储位置(栈、堆)的不同,捕获的方式也有所不同。
Block 的类型
  • NSGlobalBlock:没有访问外部变量的 Block,通常存储在全局数据区。
  • NSStackBlock:访问了外部局部变量的 Block,默认存储在栈上。如果 Block 所在的函数返回,且没有对 Block 进行 copy 操作,该 Block 将被销毁。
  • NSMallocBlock:通过 copy 操作从栈上转移到堆上的 Block,由开发者管理内存。
ARC 环境下的自动处理

在 ARC(Automatic Reference Counting,自动引用计数)环境下,编译器会根据情况自动将栈上的 Block 拷贝到堆上,例如:

  • Block 作为函数返回值时。
  • 将 Block 赋值给强指针时。
  • Block 作为 Cocoa API 或 GCD API 的方法参数时。

注意事项

  1. 内存管理
    • 在 MRC(Manual Reference Counting,手动引用计数)环境下,需要手动管理 Block 的内存,避免内存泄漏或提前释放。
    • 在 ARC 环境下,虽然编译器会自动处理大部分内存管理问题,但仍需注意避免循环引用。
  2. 访问外部变量
    • Block 可以访问函数以外、词法作用域以内的外部变量的值,但默认情况下,这些变量在 Block 内部是只读的。
    • 如果需要在 Block 内部修改局部变量的值,应使用 __block 修饰符声明该变量。
  3. 循环引用
    • 当 Block 和一个对象相互强引用时,会造成循环引用,导致内存无法释放。此时,应使用 __weak 或 _unsafe_unretained 修饰符来修饰对象,以避免循环引用。但需注意 _unsafe_unretained 修饰符在对象被释放后不会将指针置为 nil,可能导致野指针问题。
  4. 属性修饰符
    • 当 Block 作为属性时,通常应使用 copy 修饰符,以确保 Block 被正确复制到堆上,避免在函数返回后被销毁。
  5. Block 的 copy、retain、release 操作
    • 不同于 NSObject 的内存管理操作,对 Block 的 copy、retain、release 操作通常不会改变其引用计数(retainCount 始终为 1)。
    • NSGlobalBlock 的 copy、retain、release 操作均无效。
    • NSStackBlock 的 retain、release 操作无效,必须 copy 到堆上才能有效使用。
    • NSMallocBlock 支持 retain、release 操作,但引用计数始终为 1,实际是通过内存管理器中的计数来管理内存。

综上所述,Block 是 iOS 开发中非常强大的工具,但使用时也需要注意内存管理、变量访问权限、循环引用等问题,以确保代码的健壮性和稳定性。

iOS block修改NSMutableArray,需要添加__block吗

在iOS开发中,当你想要在block内部修改一个外部定义的NSMutableArray(或任何可变集合,如NSMutableSetNSMutableDictionary等)时,通常不需要在声明这个数组时加上__block修饰符。这是因为NSMutableArray(以及其他可变集合)是对象类型,它们是通过指针来引用的。当你在block内部修改这些对象时,你实际上是在修改它们指向的实例,而不是修改指针本身。

__block修饰符主要用于在block内部修改在block外部定义的基本数据类型(如intfloat等)或对象指针(但当你只是想要修改指针指向的对象时,通常不需要__block)。使用__block可以让这些变量在block内部被修改,并且这些修改能够反映到block外部。

然而,对于对象类型(如NSMutableArray),即使你不使用__block,你也可以在block内部安全地修改它们的内容(比如添加、删除元素等),因为这些操作不会改变对象指针本身,只是改变了对象内部的状态。

下面是一个简单的例子,展示了如何在block内部修改NSMutableArray

NSMutableArray *mutableArray = [NSMutableArray arrayWithObjects:@"A", @"B", nil];  
  
void (^myBlock)(void) = ^{  
    // 在block内部添加元素到mutableArray  
    [mutableArray addObject:@"C"];  
      
    // 打印修改后的数组,以验证修改是否生效  
    NSLog(@"%@", mutableArray);  
};  
  
// 执行block  
myBlock();  
  
// 再次打印数组,以验证block外部的数组是否也被修改了  
NSLog(@"%@", mutableArray);

当然,让我们更详细地解释一下__block修饰符与对象类型(如NSMutableArray)之间的关系。

__block 修饰符的基本用途

__block修饰符主要用于解决block捕获变量时的“值捕获”问题。在默认情况下,当block捕获一个外部变量时,它会捕获该变量的一个拷贝(对于基本数据类型)或一个指向该变量的常量指针(对于对象类型)。这意味着在block内部,你不能修改基本数据类型的值(除非使用__block),也不能让对象指针指向一个新的对象(但你可以修改对象本身的状态)。

对象类型的特殊情况

然而,对于对象类型(如NSMutableArray),情况有些不同。因为对象是通过指针来引用的,所以即使你不使用__block,当你在block内部修改对象的内容时(比如调用addObject:removeObjectAtIndex:等方法),你实际上是在修改对象本身,而不是修改指向该对象的指针。这种修改是“就地”发生的,不需要改变指针的指向,因此它会自动反映到block外部。

为什么通常不需要__block来修改对象

当你想要在block内部修改一个对象(比如向NSMutableArray添加元素)时,你其实是在通过指针来操作该对象,而这个指针本身并没有被block捕获为常量。因此,你可以自由地修改对象的状态,而不需要担心这种修改不会被反映到block外部。

何时需要使用__block来修改对象

尽管在大多数情况下你不需要使用__block来修改对象,但如果你需要在block内部修改一个指向对象的指针本身(即让指针指向一个新的对象),那么你就需要使用__block了。然而,这种情况比较少见,并且通常与集合类型(如NSMutableArray)无关,而是与对象指针的重新赋值有关。

示例

这里有一个示例,展示了何时可能需要使用__block来修改对象指针:

__block SomeClass *someObject = [[SomeClass alloc] init];  
  
void (^myBlock)(void) = ^{  
    // 如果想要让someObject指向一个新的对象,就需要使用__block  
    someObject = [[SomeClass alloc] initWithNewData];  
      
    // 注意:这里的修改只会影响block内部的someObject副本(如果它不是__block的),  
    // 但因为我们使用了__block,所以修改会反映到block外部。  
};  
  
// 执行block  
myBlock();  
  
// 现在,block外部的someObject也指向了新创建的对象

__weak 和 __strong 是Objective-C(OC)语言在自动引用计数(ARC)环境下用于管理对象生命周期的修饰符。它们的主要作用在于解决循环引用问题,特别是在使用block时。下面分别解释这两个修饰符的原理:

1. __weak 修饰符的原理

__weak 修饰符的主要作用是防止循环引用。当一个对象被 __weak 修饰后,它不会增加被引用对象的引用计数。这意味着,如果 __weak 修饰的变量所指向的对象被释放,该变量会自动被设置为 nil,从而避免了野指针(dangling pointer)的问题。

在底层实现上,__weak 修饰符的变量会通过一个哈希表(weak table)来维护。当 __weak 变量被初始化时,会调用 objc_initWeak 函数,该函数内部会调用 objc_storeWeak,将对象的地址和 __weak 指针的地址作为键值对存储在 weak table 中。当对象被释放时,会遍历 weak table,将指向该对象的所有 __weak 指针置为 nil

2. __strong 修饰符的原理

__strong 是Objective-C中默认的修饰符,用于表示对对象的强引用。当一个对象被 __strong 修饰的变量引用时,该对象的引用计数会增加。当 __strong 修饰的变量超出其作用域或被显式设置为 nil 时,对象的引用计数会减少。如果引用计数变为0,则对象会被释放。

在block中使用 __strong 修饰符的场景通常是为了避免在block执行期间,原本用 __weak 修饰的变量所指向的对象被提前释放。这是因为block可能会异步执行,而在block执行期间,原作用域中的对象可能已经被释放。此时,在block内部使用 __strong 修饰符来重新引用 __weak 变量所指向的对象,可以确保在block执行期间该对象不会被释放。然而,需要注意的是,这种强引用是临时的,当block执行完毕后,__strong 修饰的变量也会超出其作用域,对象的引用计数会相应减少。

总结

  • __weak 修饰符用于防止循环引用,不增加对象的引用计数,当对象被释放时自动置为 nil
  • __strong 修饰符表示对对象的强引用,增加对象的引用计数,当变量超出作用域或被显式设置为 nil时,对象的引用计数减少。
  • 在block中使用 __weak 和 __strong 修饰符可以避免循环引用和野指针问题,确保block执行期间所需的对象不会被提前释放。
  • 14
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值