iOS进阶笔记(四) Block - 基础篇

📣iOS进阶笔记目录


前言:

Block的使用与C语言的函数指针比较类似。 为方便理解,我们先看看C语言函数指针的使用。


一、C语言函数指针

C 语言函数指针使用步骤拆解:

  • 函数指针定义
    returnType (*func_ptr)(parameterTypes)
  • 指针赋值:将函数指针指向函数首地址
    func_ptr = func_name;
  • 指针函数调用:
    func_ptr(argumentTypes);

具体示例如下:

int (*ptr)(int,int);

int func_name(int a, int b) {
    return a+b;
}
int main(void) {
    // 指针赋值:函数指针ptr指向了addFunc函数首地址
    ptr = func_name;
    // 函数指针调用
    int result = ptr(1,2);

    printf(“ %d”,result);// 3

    return 0;
}

二、Block定义和使用

1. Block定义

Block,在其他语言中也称作Closure(闭包),它封装了一段代码块,在需要执行的时候调用。
Block可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。


Block定义经典配图

Block使用步骤拆解:

  • block定义:
    returnType (^blockName)(parameterTypes,…);
  • block赋值:block的实现赋值给block变量:
    blockName = ^returnType(argumentTyps) {...};
  • block调用:
    blockName(argumentTyps);

简单示例:

int (^myBlock)(int a, int b);

int main(void) {
    // block赋值:将block的实现赋值给myBlock变量
    myBlock = ^(int a, int b){
        return a + b;
    };
    // block调用
    int result = myBlock(1,2);
    printf(“%d”,result);// 3

    return 0;
}

2. Block的用法

Block的用法
作为局部变量 returnType (^blockName)(parameterTypes) = ^returnType(argumentTyps) {...};
作为属性 @property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);
作为方法形参 - (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;
作为方法调用实参 [someObject someMethodThatTakesABlock:^returnType (argumentTyps) {...}];
作为C函数的形参 void SomeFunctionThatTakesABlock(returnType (^blockName)(parameterTypes));
typedef用法 typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(argumentTyps) {...};
Tips: 注意文中的函数(方法)参数表示,parameter表示形参,argument表示实参。

3. __block使用

通过__block可以捕获auto变量,再次修改auto变量的值,block内部会同时被修改。
__block详细分析请查看《iOS进阶笔记》Block原理篇中__block捕获变量原理分析小节

int a = 10;
__block int b = 20;
void (^myBlock)(void) = ^ {
    NSLog(@“a:%d b:%d”,a,b);
};    
    
a = 11;
b = 21;
myBlock();// 结果 a=10,b= 21

4. 通过block实现链式编程

示例如下:

  • Cat类结构
// Cat.h
@interface Cat : NSObject
- (Cat *)sleep:(int)hours;
- (Cat *)eat:(NSString *)foodName;

- (Cat *(^)(int))sleep;
- (Cat *(^)(NSString *))eat;
@end
// Cat.m
@implementation Cat
- (Cat *)sleep:(int)hours
{
    NSLog(@"sleep %d",hours);
    return self;
}
- (Cat *)eat:(NSString *)foodName
{
    NSLog(@"eat %@",foodName);
    return self;
}

- (Cat *(^)(int))sleep{
    return ^(int hours){
        NSLog(@"sleep %d hours",hours);
        [self like];
        return self;
    };
}

- (Cat *(^)(NSString *))eat{
    return ^(NSString *foodName){
        NSLog(@"eat %@",foodName);
        return self;
    };
}
@end
  • Cat对象使用
Cat *cat = [[Cat alloc] init];
// 普通链式
[[cat sleep:10] eat:@"fish"];
// block链式
cat.sleep(3).eat(@"fish");

链式的本质就是利用返回值为当前方法调用者,可再持续调用方法的特性。


三、Block循环引用问题

block 循环引用一般分两种,一种是block和对象相互直接持有,另一种是间接持有。


1、相互直接持有举例


self指针指向viewController控制器对象内存,myBlock指针又指向了block对象内存;
viewController的内存又持有myBlock对象的指针,block又持有self指针。加之二者都是强指针(strong),这样形成了相互持有谁也无法释放,因此造成相互直接持有的循环引用。这种情况下,编译器也会有相应的提示。


2、间接持有举例


self指针指向viewController控制器对象内存,person指针指向了Person对象内存;myBlock指针又指向了block对象内存;

viewController对象内存持有person指针,person对象又持有blcok指针,而block对象又持有self指针;加之三者都是强指针,造成三者间循环持有,谁都无法释放,因此造成间接持有循环引用。


3、打破循环引用措施

对于循环引用,只要将中间的强引用(strong)关系变为弱引用(weak)关系即可。

  • 相互直接持有解决办法


相关代码段:

__weak typeof(self) weakSelf = self;
    self.myBlock = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"%@",strongSelf);
    };

相当于将self指针由strong变为weak,切断了循环引用。这样即使weak指针指向viewController,当viewController没有其它对想引用时,引用计数依然会清零,不影响viewController的释放。


  • 间接相互持有解决办法


相关代码段:

self.person = [[Person alloc] init];
    __weak typeof(self) weakSelf = self;
    self.person.myBlock = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"%@",strongSelf);
    };
}

相当于将self指针由strong变为weak,切断了循环引用。这样即使weak指针指向 viewController,当viewController没有其它对想引用时,引用计数依然会清零,不影响viewController的释放。viewController在释放之前,其持有的person对象会被释放,person持有的block也会被释放,不会造成僵尸对象的存在。


Tips:block内部使用 __strong typeof(weakSelf) strongSelf = weakSelf 目的是,在block内部可能还存在self调用其它函数的情况,若只使用weakSelf,可能在没调用该函数self已经被释放了。因此使用__strong声明一个strongSelf的局部强指针,确保在block执行完之前self指针依然存在。


四、Block的内存语义

Block本身是像对象。

在MRC下,一样可以retain,和release。block的retain行为默认是用copy的行为实现的(编译器的优化)。block在创建的时候,它的内存是分配在栈上的,而不是在堆上。而栈区的特点就是创建的对象在离开当前作用域随时可能被销毁。一旦被销毁,后续再次调用就会导致程序异常。在对block进行copy后,就在堆区生成一个block对象的副本,并可由block指针所引用。因此在使用block时,需要将其属性声明为copy。

在ARC下,使用strong也是可以的,但是为了显式表示block的内存语义,建议使用copy。Apple文档《Working with Blocks》Objects Use Properties to Keep Track of Blocks 小节说的也很清楚。

Note: You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior.



以上(完)

----------End------------

posted @ 2021-08-04 20:56  ITRyan  阅读(43)  评论(0编辑  收藏  举报