iOS 关于block

关于iOS的block的使用网上有很多文章讲的很详细,这里不再做相应的阐述,我们在这里主要针对以下几点和大家进行探讨,欢迎指正。

一、block的声明和定义

block的一般声明形式

block变量的声明格式为:返回值类型 (^block名字)(参数列表)

// 声明一个无返回值,参数为两个字符串对象,叫做aBlock的Block
void(^aBlock)(NSString *x, NSString *y);

// 形参变量名称可以省略,只留有变量类型即可
void(^aBlock)(NSString *, NSString *);

复制代码

我们一般使用typedef定义block类型

// 定义一种无返回值无参数列表的Block类型
typedef void(^SayHello)();

// 我们可以像OC中声明变量一样使用Block类型SayHello来声明变量
SayHello hello = ^(){
    NSLog(@"hello");
};

// 调用后控制台输出"hello"
hello();
复制代码

二、block的内存管理

block的三种类型

  • 全局块(_NSConcreteGlobalBlock)
  • 栈块(_NSConcreteStackBlock)
  • 堆块(_NSConcreteMallocBlock)

block是怎么在内存中进行存放的

  • block不访问外界变量

block既不在栈中也不在堆中,此时为全局块,ARC和MRC下都是如此

  • block访问外界变量

MRC环境下:访问外接变量的block默认存在栈中。

ARC环境下:访问外界变量的block默认存放在堆中,实际是先放在栈区,在ARC情况下自动拷贝到栈区,又自动释放。

block使用copy修饰符

  • block使用copy修饰符的目的

使用copy修饰符的作用就是将block从栈区拷贝到堆区

  • 为什么要这么做

上面是官方文档截图,里面解释了block为什么这样做的目的。复制到堆区的主要目的就是保存block的状态,延长器生命周期。因为如果block在栈上的话,其所书的变量族谱用于结束,该block就会被释放掉,block中的__block变量也同时被释放掉。为了解决栈块在其变量作用域结束之后被释放掉的问题,所以将block复制到堆中。

  • 不同类型的block的拷贝方法效果

block使用__block修饰符

  • __block有什么作用
/// 函数1
- (void)testMethod {
    int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is : %i", anInteger);
    };
 
    anInteger = 50;
 
    testBlock();
}
//调用结果输出42
复制代码
/// 函数2
- (void)testMethod {
    __block int anInteger = 42;
 
    void (^testBlock)(void) = ^{
        NSLog(@"Integer is : %i", anInteger);
    };
 
    anInteger = 50;
 
    testBlock();
}
//输出结果为50
复制代码

上面的例子可以看出,使用__block修饰的变量,可以block中进行访问,变量修改后,block可以获取修改后的值。

  • 为什么使用__block之后就可以修改block外部变量

在第一个函数中,block会把anInterger变量复制为自己私有的const变量,在anInteger = 50的操作的时候,block已经将其复制未自己私有变量,所以修改变量值,对block中的anInteger不会造成任何影响。

在第二个函数中,aninteger是一个局部变量,存储在栈区。给anInteger加入__block修饰符所起到的作用就是只要观察到该变量被block所持有,就将该变量在栈中的内存地址放到堆中,此时不管block外部还是内部anInterger的内存地址都是一样的,进而不管在block外部还是内部都可以修改anInterger变量的值,所以anInteger = 50之后,在block输出的值就是50了。

三、循环引用

  • 循环引用的情况以及解决办法

循环引用的情况

self.someBlock = ^(Type var){
       [self dosomething];
};`
复制代码

解决办法 (1)ARC下:使用__weak

__weak typeof(self) weakSelf = self;
self.someBlock = ^(Type var){
      [weakSelf dosomething];
};
复制代码

(2)MRC下:使用__block

__block typeof(self) blockSelf = self;
self.someBlock = ^(Type var){
      [blockSelf dosomething];
};
复制代码
  • 异步并发执行的时候使用__strong关键字

先看一段代码

- (void)configureBlock {
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        [weakSelf doSomething]; // weakSelf != nil
        // preemption(抢占) weakSelf turned nil
        [weakSelf doAnotherThing];
    };
}
复制代码

这段代码看似正常,但是在并发执行的时候,block的执行是可以抢占的,而且对weakSelf指针的调用时序不同可以导致不同的结果,比如在一个特定的时序下weakSelf可能会变成nil,这个时候在执行doAnotherThing就会造成程序的崩溃。为了避免出现这样的问题,采用__strong的方式来进行避免,更改后的代码如下:

- (void)configureBlock {
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        [strongSelf doSomething]; // strongSelf != nil
        // 在抢占的时候,strongSelf还是非nil的。
        [strongSelf doAnotherThing];
    };
}
复制代码
  • 使用系统API 使用系统的block api 不会产生循环引用,例如:
[UIView animateWithDuration:0.5 animations:^{
    [self doSomething];
}];
复制代码

在这种情况下是不需要考虑循环引用的,因为这里只有block对self进行了一次强引用,属于单向的强引用,没有形成循环引用。

  • 避免循环引用总结
  1. 在block不是作为一个property的时候,可以在block里面直接使用self,比如UIView的animation动画block。
  2. 当block被声明为一个property的时候,需要在block里面使用weakSelf,来解决循环引用的问题。
  3. 当和并发执行相关的时候,当涉及异步的服务的时候,block可以在之后被执行,并且不会发生关于self是否存在的问题。

四、参考文章

iOS进阶(一)block与property

一篇文章看懂iOS代码块Block

iOS开发-由浅至深学习block

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值