OC中Block的原理、实现及注意事项

Block

在Objc中,GCC编译器的更新引入了Block语法,这为objc语言提供了良好的闭包的功能,并为 Mac OS 与 iOS 的多个系统API所使用。
它的基本语法如下

//语法形式
return_type (^block_name)(parameters)
//实现
int addtional = 5;
int (^addBlock)(int a,int b) = ^(int a, int b){
    return a + b + addtional;
}

当声明与实现一个Block时,创建的闭包会捕获在它的域中的任何涉及的变量,通过在内存中持有他们,能够在block的实现中对其进行访问。在默认情况下,任何在block的域中被捕获的变量都不能被修改,除非这个变量已被给予了__block的标志。如上面的代码中,如果我们在block中修改addtionnal的值,编译器就会报错。我们需要做出以下修改,才能使addtionnal变量能在blo这为objc语言提供了良好的闭包的功能 ck中修改。

__block int addtional = 5;

当block捕获了一个对象时,它会对其进行retain操作,并在block代码执行完毕完release对象,这样才能保证在block执行过程中,对象不会因引用计数为0而被释放掉。我们需要理解的是,block本身就是一个对象,它对其他对象的引用与一般的对象引用类似,都是需要对引用对象进行retainrelease

Block的实现

我们所需要知道的是 block 就是一个对象,在它所在的内存中,保存着block自身的实现函数,可在调用block时用block自身的代码替代,同时保持着一个Block描述,标志着block的内存size与持有对象的指针。

Block的类型

stack block

看看下面这段代码,当block被定义时,block会被分配在stack(堆)中的一块内存中,这意味着这个block仅在自己所声明的域中生效,因此,这份代码是会出错的

void (^block)();
if(/*true*/)
{
    block = ^{
        NSLog(@"AAAA");
    };
}
else
{
    block = ^{
        NSLog(@"BBBB");
    };
}
block();

因为声明的block只在所属的域中生效,因为调用block()时,定义的两个block实现已经失效了,内存已经被释放了,在stack中推出,这就是 stack block.

heap block

为了解决这个问题,我们可以通过copy将block由stack copy至 heap,这样block就能够在它所属域之外被引用。当block保存在stack中时,系统机制会在调用完毕后自动清理它,相比之下,当在heap中时,block就与其他变量类似,接受引用计数管理,当block没必有再进行持有时,需要对其进行release操作(在ARC中,会自动插入release代码),没有对象持有它时,就会对其heap中的内存进行释放。如以下代码

 void (^block)();
if(/*true*/)
{
    block = [^{
        NSLog(@"AAAA");
    } copy];
}
else
{
    block = [^{
        NSLog(@"BBBB");
    } copy];
}
block();

这样的话这段代码是正确的,当然如果在非ARC环境下,需要对block执行release操作。

grobal block

全局 blcok与之前的stack block 、 heap block 不同,当一个block在闭包中不捕获程序的任何上下文(如各种程序中的变量)时,编译器在编译阶段就能够知道这个block执行的所需要的所有信息,这时,block会当做全局变量保存在全局内存中,以相当于单例的形式存在,它将不会收到任何release消息。这是编译器的一个优化点,减少了当block被copy或销毁时的多余操作。

用typedef来定义block类型

之前提及了block的声明语法

return_type (^block_name)(parameters)

我们可以看到,在block的声明中,我们规定了block的输入参数,以及返回的类型。这种声明的语法与objc其他对象(或函数)的声明语法有着很大的区别,这大大增加了我们对这种语法的记忆与理解的难度,特别是需要用block作为参数来声明与定义类的函数。为了定制更好的API外部接口,我们可以对经常使用的block的声明做类型定义

typedef int(^SomeBlock)(BOOL flag,int value);

这样的话,我们在类型系统中添加了一种新的类型SomeBlock,当我们需要使用这个block类型时,我们只需要简单使用这个新类型

SomeBlock block = ^(BOOL flag,int value){
    //do something
}

这样子,代码会变得更简洁,更有可读性,就如同我们在声明一个普通的objc对象一样。

使用Block减少代码分离

在Block语法出现之前,我们使用delegate委托方式,通过异步任务实现与委托方法的实现分离,处理异步任务完成后的回调工作。然而,当一个类作为一个相同的委托类成为了多个异步任务的委托时,我们就需要在这个类的委托方法实现中区分开来,并在同一个委托方法的实现中对不同异步任务的回调进行不同的处理,这样会使委托函数十分冗长,可读性变差,如下

- (void)tableView:(UITableView*)tableView didSelectedAtIndexPath:(NSIndexPath*)indexPath
{
    if(tableView == _tableViewA)
    {
        //do something
    }
    else if (tableView == _tableViewB)
    {
        //do otherThing
    }
}

这样子不仅我们需要在同一个函数做不同的处理,而且我们需要持有多个异步任务执行者的指针,在委托方法一一比对来区分调用委托者。而在block语法中,我们无需区分调用者,我们在声明调用者时,也声明了它回调的委托处理方法

//假设tableView支持block语法
UITableView *tableViewA = [[UITableView alloc] init];
[tableViewA startWorkWithSelectionHandler:^(NSIndexPath *indexPath){
    //do something with indexPath
}];

UITableView *tableViewB = [[UITableView alloc] init];
[tableViewB startWorkWithSelectionHandler:^(NSIndexPath *indexPath){
    //do something with indexPath
}];

这样使业务处理代码在一处一起实现,代码更具备可读性。

避免Block 的 retain cycle

在Block的使用中,我们如果不考虑仔细,就非常容易引人retain cycle引用循环的问题,如以下代码

//执行类
typedef void(^IFNetWortFetcherCompletionHandler)(NSData *data);
- (void)startWithCompletionHandler:(IFNetWortFetcherCompletionHandler)completion
{
    self.completion = completion;
    // 开始请求,当请求完成后,我们调用completion委托block
}

//委托类
- (void)download
{
    _networkFetcher = [[IFNetWortFetcher alloc] initWithUrl:_url];
    [[_networkFetcher startWithCompletionHandler:^(NSData *data)
    {
        _fetcherData = data;
    }];
}

这是一个很常见的retain cycle,在委托类中,我们持有了 networkFetcher ,而在 networkFetcher中, 我们持有了block,而在block中,我们通过捕获了_fetcherData,持有了委托类的self对象,这就造成了引用循环。
这个retain cycle能够通过打断任意一条链条来解决,而不是简单的一句__block __weak IFNetWortFetcher *wself = self能够解决的。

在委托类中,我们可以选择不持有 networkFetcher ,来打破这个链条,这在普遍的网络库中实行,在编写请求数据的代码中,一般不建议持有数据请求器

- (void)download
{
    IFNetWortFetcher *networkFetcher = [[IFNetWortFetcher alloc] initWithUrl:_url];
    [[networkFetcher startWithCompletionHandler:^(NSData *data)
    {
        _fetcherData = data;
    }];
}

当然,我们不能将打破引用循环的任务主观地交给调用者,我们需要在内部解决这个问题,当执行类对象异步任务完成后,我们要主动去释放持有的block

- (void)startWithCompletionHandler:(IFNetWortFetcherCompletionHandler)completion
{
    self.completion = completion;
    // 开始请求,当请求完成后,我们调用completion委托block
    self.completion = nil;
}

全文完~

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OC(Objective-C)是一种面向对象的编程语言,它支持在类使用方法和block。类方法是在类定义的方法,可以直接通过类名来调用,而不需要实例化对象。而block是一种闭包,可以在代码定义并传递给方法或函数,用于延迟执行特定的逻辑。 在OC,可以使用类方法来创建和操作类的实例,例如通过一个工厂方法创建对象,或者在类方法实现一些与类相关的逻辑。类方法通常使用“+”符号进行声明和实现。 而block可以在方法作为参数传递,也可以在方法定义和使用。block可以捕获其所在作用域的变量,可以在方法内部延迟执行一段代码,也可以用于实现回调等功能。block的定义和使用使用“^(){}”语法。 类方法和block可以结合使用,例如可以在类方法接受一个block作为参数,并在合适的时机调用该block,以实现一些灵活的逻辑。通过类方法和block的组合,可以在OC实现更加灵活和强大的功能,例如在异步操作使用block来回调结果,或者在工厂方法使用block来定制对象的初始化逻辑等。 总而言之,类方法和blockOC的两个重要特性,它们可以分别用于类的操作和延迟执行逻辑,也可以结合使用以实现更加灵活的功能。在实际的OC开发,类方法和block通常会被广泛使用,可以帮助开发者更加简洁和灵活地实现代码逻辑。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值