Objective-C 学习笔记 | Block 对象

Objective-C 学习笔记 | Block 对象

Block 对象类似于匿名函数,没有函数名,有返回类型和实参类型,用一个^标识。

请添加图片描述

编写并使用 Block 对象

enumerateObjectsUsingBlock: 方法要求传入的 Block 对象的三个实参类型是固定的。第一个实参是对象指针,指向当前(枚举)的对象。该指针的类型是 id,所以无论数组包含的是什么类型的对象,都可以将地址赋给该指针。第二个实参的类型是 NSUInteger,其值是 当前对象在数组中的索引。第三个实参是指向 BOOL 变量的指针,默认为 NO,如果设为 YES,那么数组对象会在执行完当前的 Block 对象后终止枚举过程。

#import <Foundation/Foundation.h>
// 使用 typedef 定义 Block 对象的类型
typedef void (^ArrayEnumerationBlock)(id, NSUInteger, BOOL *);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSArray *originalStrings = @[@"Sauerkraut", @"Raygun", @"Big Nerd Ranch", @"Mississippi"];
        NSLog(@"original strings: %@", originalStrings);
        NSMutableArray *devowelizedStrings = [NSMutableArray array];
        NSArray *vowels = @[@"a", @"e", @"i", @"o", @"u"];
        
        // 声明 Block 变量
        // void (^devowelizer)(id, NSUInteger, BOOL *);
        ArrayEnumerationBlock devowelizer;
        // 将 Block 对象赋给变量
        devowelizer = ^(id string, NSUInteger i, BOOL *stop)
        {
            /**
            NSRange yRange = [string rangeOfString:@"y" options:NSCaseInsensitiveSearch];
            if (yRange.location != NSNotFound)
            {
                *stop = YES; // 执行完当前的 Block 对象后终止枚举过程
                return; // 结束当前正在执行的 Block 对象
            }
            */
            NSMutableString *newString = [NSMutableString stringWithString:string];
            // 枚举数组中的字符串,将所有出现的元音字符替换成空字符串
            for (NSString *vowel in vowels)
            {
                NSRange fullRange = NSMakeRange(0, [newString length]);
                [newString replaceOccurrencesOfString:vowel withString:@"" options:NSCaseInsensitiveSearch range:fullRange];
            }
            [devowelizedStrings addObject:newString];
        }; // Block 变量赋值结束
        
        [originalStrings enumerateObjectsUsingBlock:devowelizer];
        NSLog(@"new strings: %@", devowelizedStrings);
    }
    return 0;
}

本程序编写的 Block 对象会复制 originalStrings 数组的字符串,并移除其中所有的元音字母,将修改后的字符串保存到 devowelizedStrings 数组。

Block 对象的返回值

可以像调用函数一样调用 Block 对象,获得返回值:

// 声明 divBlock 变量
double (^divBlock)(double, double);
// 将 Block 对象赋给变量
divBlock = ^(double dividend, double divisor)
{
    return dividend / divisor;
}
double res = divBlock(3.0, 1.5);

匿名 Block 对象

匿名的 Block 对象是可以传递给方法的 Block 对象的,而不需要先赋值给变量。

外部变量

Block 对象通常会使用外部变量(在其代码外部创建的变量)。当执行 Block 对象时,为了确保其下的外部变量一直存在,相应的 Block 对象会捕获这些变量。

对基本类型的变量,捕获意味着程序会拷贝变量的值,并用 Block 对象内的局部变量保存;对指针类型的变量,Block 对象会使用强引用。这意味着直到 Block 对象释放前,其使用的外部对象都不会被释放,这也是 Block 对象和函数的区别。

在 Block 对象中使用 self

如果需要写一个使用 self 的 Block 对象,那么需要考虑强引用循环的问题。为了解决这个问题,可以在 Block 对象外声明一个 __weak 指针,将这个指针指向 Block 对象使用的 self,最后在 Block 对象里使用弱指针。然而,由于是弱引用,所以 self 指向的对象在 Block 执行时可能被释放,导致出错。为了避免这种情况,可以在 Block 对象中创建一个对 self 的局部强引用,其生命周期和 Block 对象相同,强引用循环也只会在 Block 对象执行时出现:

__weak BNREmployee *weakSelf = self; // 弱引用
myBlock = ^{
    BNREmployee *innerSelf = weakSelf; // 局部强引用
    NSLog(@"Employee: %@", innerSelf);
};

在 Block 对象中无意使用 self

如果直接在 Block 对象中使用实例变量,那么block 会捕获 self,而不会捕获实例变量。

以下代码直接存取一个实例变量:

_weak BNREmployee *weakSelf = self;
myBlock = ^{
    BNREmployee*innerSelf=weakSelf;// 局部强引用
  	NSLog (@"Employee: %@", innerSelf);
    NSLog (@"Employee ID: %d", _employeeID);
};

编译器在遇到 _employeeID 时,会将其看成 self->_employeeID,self 就被 Block 对象无意地捕获了,这样又造成了强引用循环。

解决方法:不要直接存取实例变量,使用存取方法!

_weak BNREmployee *weakSelf = self;
myBlock = ^{
    BNREmployee*innerSelf=weakSelf;// 局部强引用
  	NSLog (@"Employee: %@", innerSelf);
    NSLog (@"Employee ID: %d", innerSelf.employeeID);
};

修改外部变量

在 Block 对象中,被捕获的变量不可被修改。如果需要在 Block 对象内修改某个外部变量,则可以在声明该外部变量时加上 __block 关键字。

例如,下面的代码可以在 Block 对象内将外部变量 counter 值加 1:

__block int counter = 0;
void (^counterBlock)()= ^{ counter++; };
counterBlock(); // counter = 1
counterBlock(); // counter = 2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值