Objective-C高级编程笔记二(Blocks)

示例代码下载

Blocks概要

Blocks是c语言的扩充功能:带有自动变量(局部变量)值的匿名函数。

语法

完整的语法形式:

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

省略返回值的语法形式:

^ (参数列表) {表达式}

省略参数的语法形式:

^ 返回值类型 {表达式}

^ int (int a, int b) { return a + b; };
^ (int a, int b) { printf(@"a + b = %i", a + b); };
^ { printf(@"block"); };

block变量完整语法:

返回值类型 (^变量名称) (参数列表)

int (^aBlock) (int, int) = ^ int (int a, int b) { return a + b; };
void (^bBlock) (int a, int b) = ^ (int a, int b) { NSLog(@"a + b = %i", a + b); };
void (^cBlock) (void) = ^ { NSLog(@"block"); };

typedef可申明Block类型变量:

typedef int (^ABlock) (int a, int b);
ABlock aBlock = ^int (int a, int b) {
    return a + b;
};

Bolocks的调用和c语言函数相同,别无二致。

截获自动变量值

Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值,所以在执行Block语法后,即使改写了Block中使用的自动变量的值也不会影响Block执行时的值——Block-带有自动变量值的匿名函数。

int a = 1;
void (^block) (void) = ^{
    NSLog(@"%i", a);
};
a = 2;
block();

打印如下:

2019-03-29 13:54:43.758298+0800 ProfessionalExample[2688:18267238] 1

自动变量截获只能保存执行Block语法瞬间的值,保存后就不能在Block中更改该值,否则产生编译错误。但是使用截获的值是没有问题的。

若想在Block语法表达式中将值Block语法外的自动变量,需要在该自动变量上附加__block说明符。

注意: 截获自动变量值没有对c语言数组的支持,如果使用会产生编译错误,需要使用指针代替可以解决这个问题。

int nums[4] = {1, 2, 3, 4};
int *p = nums;
void (^block) (void) = ^{
    NSLog(@"%i", *(p + 1));
};
block();

“截获自动变量值”,意味着执行Block语法时,Block表达式所使用的自动变量值被保存到Block的结构体实例(即Block自身)中。

因为Block不能改写截获的自动变量的值,当编译器检查到有赋值操作时便产生编译错误。这样就无法在Block中保存值了,极为不便。有两种方法解决这个问题:

  • 允许使用静态变量、静态全局变量、全局变量的值。
  • __block说明符,用来指定Block中要变更值的自动变量。

Blocks实现

Block本质是一个结构体,就相当于基于objc_object结构体的Objective-C类对象的结构体。也就是可以将Block作为Objective-C对象来处理。如下图:
[外链图片转存中…(img-D8GCnuJR-1696748840205)]

__block变量的本质是一个结构体变量,并不在Block结构体中。这样就可以多个Block使用同一个__block变量。如下图:
[外链图片转存中…(img-ACUfeN4x-1696748840206)]

Block与__block变量本质:

名称实质
BlockBlock结构体实例
__block变量__block变量结构体实例

Block的类有3种:

Block类对象存储域说明
_NSConcreteStackBlock在记述自动变量的地方使用Block语法生成的Block和不截获自动变量的Block
_NSConcreteGlobalBlock程序的数据区域(.data区)在记述全局变量的地方使用Block语法生成的Block
_NSConcreteMallocBlockBlocks提供将Block和__block变量从从栈复制到堆的方法

栈上的Block,__block变量也在栈上,当作用域结束,就会被废弃。将配置在栈上的Block复制到堆上,可以在作用域结束时堆上的Block还继续存在。copy方法能将Block从栈复制到堆,并修改Block的isa指针为_NSConcreteMallocBlock。

事实上ARC有效时,大多数情况下ARC会进行恰当的判断,自动生成Block从栈复制到堆上的代码。

向方法或函数的参数中传递Block时,编译器不会判定生成复制到堆上的代码。但是Cocoa框架中方法名含有usingBlock和GCD的API除外。

NSArray *array;
{
    int a = 2;
    array = [NSArray arrayWithObjects:[^{ NSLog(@"a + a = %i", a + a); } copy], ^{ NSLog(@"a * a = %i", a * a); }, nil];
}
typedef void (^Block) (void);
Block first = [array firstObject];
first();
Block类源对象存储域复制效果
_NSConcreteStackBlock从栈复制到堆
_NSConcreteGlobalBlock程序的数据区域(.data区)什么也不做
_NSConcreteMallocBlock引用计数增加

由此可见,不管Block在何处配置,用copy方法都不会引起任何问题,在不确定时调用copy方法即可。

复制Block时对__block变量的影响:

__block变量存储域Block重栈复制到堆
从栈复制到堆并被Block持有
被Block持有

__block变量结构体中有一个指向自身的__forwarding成员变量指针,当__block变量从栈复制到堆时,会将__forwarding成员变量的值替换为复制到堆上的__block变量结构体实例的指针。通过该功能,无论在Block语法中、语法外使用__block变量,还是__block变量配置在栈上、堆上,都可以顺利的访问同一个__block变量。如下图所示:
[外链图片转存中…(img-z31Z2oLQ-1696748840295)]

如下几种情况,Block会复制到堆:

  • 调用Block的copy实例方法
  • Block作为函数返回值
  • Block赋值给__strong修饰符的变量
  • 在方法名含有usingBlock的Cocoa框架方法和GCD的API中传递Block

复制到堆上的Block会持有__strong修饰符的自动变量和复制到堆上的__block变量,使其超出作用域而存在。如下示例:

            typedef void (^Block) (id);
            __unsafe_unretained Block block;
            Block block1;
            {
                NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];
                NSMutableArray *array1 = [NSMutableArray arrayWithCapacity:1];
                block = ^(id obj) {
                    [array addObject:obj];
                    NSLog(@"array: %@, array count: %li", array, array.count);
                };
                block1 = ^(id obj) {
                    [array1 addObject:obj];
                    NSLog(@"array1: %@, array1 count: %li", array1, array1.count);
                };
            }
            block([[NSObject alloc] init]);
            block1([[NSObject alloc] init]);

打印结果:

2019-04-02 11:42:30.155444+0800 ProfessionalExample[71676:22254773] array: (null), array count: 0
2019-04-02 11:42:30.155680+0800 ProfessionalExample[71676:22254773] array1: (
    "<NSObject: 0x600000d8e0f0>"
), array1 count: 1
  • __block变量为附有__strong修饰符的id类型和对象类型的自动变量,当__block变量从栈复制到堆时,持有赋值给__block变量的对象。当堆上的__block变量释放时,释放赋值给__block变量的对象。
  • __block变量为附有__weak修饰符的id类型和对象类型的自动变量,__block变量不持有对象。
  • __block变量不能为附有__autoreleasing修饰符的id类型和对象类型的自动变量。
  • __block变量为附有__unsafe_unretained修饰符的id类型和对象类型的自动变量,与指针相同。
typedef void (^Block) (id);
Block block;
{
    NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];
    __block NSMutableArray __weak *array1 = array;
    block = ^(id obj) {
        [array1 addObject:obj];
        NSLog(@"array1: %@, array1 count: %li", array1, array1.count);
    };
}
block([[NSObject alloc] init]);

打印结果:

2019-04-02 15:20:35.135112+0800 ProfessionalExample[74596:22578767] array1: (null), array1 count: 0

Block使用__strong修饰符的自动变量,当Block从栈复制到堆时该对象被Block持有,容易形成循环引用。

__weak修饰符、__unsafe_unretained修饰符(iOS4中替代__weak修饰符)可以避免循环引用。

另外__block变量也可以避免循环引用,例如如下对象:

@interface BlockObject : NSObject

@property (strong, nonatomic) void (^block)(void);

@end

@implementation BlockObject

- (void)dealloc {
    NSLog(@"%@销毁了", self);
}

@end

创建一个BlockObject对象,并设置其block属性的值。在执行block之前,__block变量obj持有BlockObject对象,BlockObject对象持有block,block持有__block变量,形成了循环引用。执行block时,向__block变量obj赋值nil,至此__block变量不再持有object对象,打破循环引用。如下所以:

__block BlockObject *obj = [[BlockObject alloc] init];
obj.block = ^{
    NSLog(@"%@", obj);
    obj = nil;
};
obj.block();

使用__block变量与__weak修饰符、__unsafe_unretained修饰符(iOS4中替代__weak修饰符)避免循环引用的比较:

  • 通过__block变量可控制对象的持有时间
  • 在不能使用__weak修饰符、__unsafe_unretained修饰符(iOS4中替代__weak修饰符)的环境下,如此__block变量能够赋值
  • 使用__block变量避免循环引用,必须执行Block并手动释放__block变量对对象的引用(如置空或其它值)

ARC无效时使用Blocks注意事项

ARC无效时,一般需要手动用copy方法将Block从栈复制到堆,并手动用release方法来释放。

__block说明符用来避免循环引用。这是由于当Block从栈复制到堆时,若Block使用__block说明符的id类型或对象类型的自动变量,不会被retain;若没有__block说明符,则会被retain。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值