13 - block的认识和使用

2021-11-09  本文已影响0人  iOS之文一

OC底层原理探索文档汇总

主要内容:

1、block的认识
2、block的基本使用
3、变量的捕获
4、循环引用问题

1、block的认识

1.1 定义

Block是一个里面存储了指向定义block时的代码块的函数指针,以及block外部上下文变量信息的结构体,简单说就是:带有自动变量的匿名函数。
我们通常使用block传递数据。

block是一个带自动变量的匿名函数,本质是一个函数。
但是在使用上更偏向于是一个引用类型。因为block可以作为参数、可以作为返回值,还可以作为一个属性供使用。

2 block的基本使用

block的使用有三种:1)作为参数;2)作为属性;3)作为返回值

我们在使用时既可以把他当做一个类即可,但是这个类只有一个函数。其他什么都没有。

1.3.1 block的定义和调用

定义: //返回值类型 (^block的变量名)(参数类型)

申请空间: ^返回值类型(参数列表)

//定义一个block
typedef void (^addBlock2)(int num1,int num2);

//block的定义和调用
- (void)blockTest{
    //1:定义block
    //返回值类型 (^block的变量名)(参数类型)
    //2:申请空间
    //^返回值类型(参数列表)
    void (^testBlock)(int num1,int num2) = ^void(int num1,int num2){
        NSLog(@"num1+num2=%d",num1+num2);
    };

    addBlock2 block2= ^void(int num1,int num2){
        NSLog(@"num1+num2=%d",num1+num2);
    };
    
    //3:调用
    testBlock(1,2);
    block2(3,4);
}

说明:

简写:

1.3.2 block作为属性

设置属性

//定义一个block
typedef int (^myBlock)(int num1,int num2);

/*
 block作为属性有两种,一种是先定义再设置,一种是直接定义到属性中
 */
@interface WYBlock : NSObject
@property (nonatomic,strong) NSString *name;
//作为属性,就和实例变量完全一样,block要使用copy修饰(虽然不用也行)
@property (nonatomic,copy) myBlock block1;

//也可以这么写,将block定义直接放到这里
@property (nonatomic,copy) void (^myBlock2)(int num1,int num2);

使用属性

- (void)test{
   addBlock2 block = [self blockTest3];
    block(10,10);
}

//block作为属性
- (void)blockTest4{
    WYBlock *block = [[WYBlock alloc] init];
    
    //定义
    block.block1 = ^int(int num1, int num2) {
        NSLog(@"num1+num2=%d",num1+num2);
        return num1+num2;
    };
    block.myBlock2 = ^(int num1, int num2) {
        NSLog(@"num1*num2=%d",num1*num2);
    };
    
    //调用
    block.block1(1, 2);
    block.myBlock2(3, 5);
}

说明:

1.3.3 block作为参数传递

block作为参数传递可以实现两个类的数据传递(因为实现和定义在不同的类中)

方法实现:

//block作为参数,提前定义好block
- (void)sumWithblock:(myBlock) block{
    NSLog(@"block的结果是%d",block(3,5));
}

//临时定义block
- (void)sumWithblock2:(int (^)(int num1,int num2)) block{
    NSLog(@"block2的结果是%d",block(3,5));
}

方法调用:

/*
 1、传递block的实现
 2、在被调用的方法里调用block
 */
- (void)blockTest2{
    WYBlock *block = [[WYBlock alloc] init];
    [block sumWithblock:^int(int num1, int num2) {
        NSLog(@"num1*num2:%d",num1*num2);
        return num1*num2;
    }];
    
    [block sumWithblock2:^int(int num1, int num2) {
        return num1+num2;
    }];
}

说明:

1.3.4 block作为返回值

typedef void (^addBlock2)(int num1,int num2);

//block作为返回值
- (addBlock2)blockTest3{
    return ^void(int num1,int num2){
        NSLog(@"num1+num2=%d",num1+num2);
    };
}

//调用
- (void)test{
   addBlock2 block = [self blockTest3];
    block(10,10);
}

说明:

2 block的认识

上面我们说block其实就是一个是一个带自动变量的匿名函数,这里就进行说明。
有两个需要考虑,一个是带自动变量,一个是匿名函数。

2.1 block的匿名函数认识

2.1.1 先看下函数是什么样子的

函数定义:

typedef int (*funcPtr)(int);

获取函数指针:

//C函数实现
int func(int arg) {
    return arg;
};
//C函数指针赋值
funcPtr ptr = *func;

函数指针调用:

int ret1 = ptr(10);

2.1.2 再看block的使用

block定义:

typedef int (^tmpBlock)(int arg);

获取block指针:

//block指针赋值
tmpBlock block = ^(int arg){
    return arg;
};

block调用

//block调用
int ret2 = block(10);

2.1.3 对比查看

经过对比,除了函数在实现时有自己的名称func,而block没有名称,需要直接赋给一个Block指针。这就是所谓的匿名函数。

因此block本质就是一个匿名函数。我们定义的block其实是一个函数指针。而block的实现就是函数实现。

为了更方便放到一块看看

//C函数实现
int func(int arg) {
    return arg;
};

typedef int (*funcPtr)(int);

typedef int (^tmpBlock)(int arg);

/*
 通过C函数和block的实现对比可以发现,C函数和block的声明定义基本一样,只是在实现block时没有名称,而函数是有名称的。
 */

void niminghanshu(int arg){
    //C函数指针赋值
    funcPtr ptr = *func;
    //C函数指针调用
    int ret1 = ptr(10);

    //block指针赋值
    tmpBlock block = ^(int arg){
        return arg;
    };
    //block调用
    int ret2 = block(10);
    NSLog(@"ret1:%d---ret2:%d",ret1,ret2);
}

2.2 block的自动变量认识

上面我们看到block本质就是一个匿名函数,但是还有一个区别于函数的特性就是自动变量。
自动变量的意思是可以捕获变量。接下来看看如何捕获变量。

普通函数使用外界的变量,变量仍然是外界的变量,并不是自己的。而block在使用外界的变量时,会将外界变量copy自己的函数中,作为自己函数的一个变量。这就是捕获变量。

- (void)testVariable{

    __block int a = 10;
    __block NSString *str = [[NSString alloc] init];
    str = @"wy11";
    NSLog(@"a1---%d---%p",a,&a);
    NSLog(@"str1--%@--%p",str,str);
    a = 100;
    NSLog(@"a2---%d---%p",a,&a);
    NSLog(@"str2--%@--%p",str,str);
    void (^block)(void) = ^{
        a = a+1;
        str = @"wy22";
        NSLog(@"a3--%d--%p",a,&a);
        NSLog(@"str3--%@--%p",str,str);
    };
    block();
    NSLog(@"a4--%d---%p",a,&a);
    NSLog(@"str4--%@--%p",str,str);
}

运行结果:

2021-11-07 19:14:03.228605+0800 Block的学习[5696:1734206] a1---10---0x7ff7b9551f08
2021-11-07 19:14:03.228697+0800 Block的学习[5696:1734206] str1--wy11--0x1069ae2e8
2021-11-07 19:14:03.228762+0800 Block的学习[5696:1734206] a2---100---0x7ff7b9551f08
2021-11-07 19:14:03.228818+0800 Block的学习[5696:1734206] str2--wy11--0x1069ae2e8
2021-11-07 19:14:03.228876+0800 Block的学习[5696:1734206] a3--101--0x600002ffc338
2021-11-07 19:14:03.228933+0800 Block的学习[5696:1734206] str3--wy22--0x1069ae3a8
2021-11-07 19:14:03.229006+0800 Block的学习[5696:1734206] a4--101---0x600002ffc338
2021-11-07 19:14:03.229075+0800 Block的学习[5696:1734206] str4--wy22--0x1069ae3a8

说明:

2.3 block的类型

block根据所在的不同区域,可以分为三种类型,存储在全局区的是全局block、存储在栈的block是栈block、存储在堆的block是对block。

2.3.1 全局block(NSGlobalBlock)

- (void)blockType{
    //不使用任何数据
    void (^globalBlock1)(void) = ^{
        NSLog(@"quanju:%d",quanju);
    };
    NSLog(@"wy:globalBlock1--%@",globalBlock1);
    //使用全局变量
    void (^globalBlock2)(void) = ^{
        NSLog(@"wy");
    };
    NSLog(@"wy:globalBlock2--%@",globalBlock2);
}

结果:

2021-11-07 19:26:20.011182+0800 Block的学习[6134:1745135] wy:globalBlock1--<__NSGlobalBlock__: 0x1085e9208>
2021-11-07 19:26:20.011280+0800 Block的学习[6134:1745135] wy:globalBlock2--<__NSGlobalBlock__: 0x1085e9228>

说明:

2.3.2 堆block(NSMallocBlock)

void (^mallocBlock1)(void) = ^{
        self->string = @"wy";
    };
    
    NSLog(@"wy:mallocBlock1--%@",mallocBlock1);
    int a;
    void (^mallocBlock2)(void) = ^{
        NSLog(@"a=%d",a);
    };
    NSLog(@"wy:mallocBlock2--%@",mallocBlock2);

运行结果:

2021-11-07 19:38:45.370101+0800 Block的学习[6586:1757682] wy:mallocBlock1--<__NSMallocBlock__: 0x60000220f9f0>
2021-11-07 19:38:45.370194+0800 Block的学习[6586:1757682] wy:mallocBlock2--<__NSMallocBlock__: 0x600002208ed0>

2.3.3 栈block(NSStackBlock)

默认情况下block是堆block,我们可以通过__weak不对block进行强持有,就是栈block,

int b = 10;
void (^ __weak stackBlock1)(void) = ^{
    NSLog(@"b=%d",b);
};
NSLog(@"wy:stackBlock1--%@",stackBlock1);

运行结果:

2021-11-07 19:38:45.370276+0800 Block的学习[6586:1757682] wy:stackBlock1--<__NSStackBlock__: 0x7ff7b9aede60>

3、循环引用问题

3.1 问题的出现:

block中可能会出现循环引用:

请看下这个代码有没有循环引用

/*
 会出现循环引用
 在block内部使用self会出现循环引用,因为self和block相互引用
 */
- (void)circularTest2{
    self.block1 = ^int(int num1, int num2) {
        self.name = @"zhang";
        return num1+num2;
    };
    self.block1(10,2);
}

说明:

请看下这个代码有没有循环引用

/*
 不会出现循环引用
 虽然block使用了self,但是这个block并没有被self持有,所以不会出现
 */

- (void)circularTest{
    [self sumWithblock:^int(int num1, int num2) {
        self.name = @"zhang";
        return num1+num2;
    }];
}

说明:

3.2 循环引用的解决

block的循环引用归根结底就是断开其中的一个持有,打破相互持有。共有四种方案可以实现

【方案一】:使用__weak
【方案二】:手动释放一个引用
【方案三】:将self作为参数
【方案四】:使用NSProxy虚拟类

3.2.1 给self使用__weak

/*
 循环引用解决1: __weak弱引用self
 将block持有self这一环断开
 */
- (void)circularTest3{
    __weak typeof(self) weakSelf = self;
    self.block1 = ^int(int num1, int num2) {
        weakSelf.name = @"zhang";
        return num1+num2;
    };
    self.block1(10,2);
}

注意:

3.2.2 手动释放对象

将self赋给一个变量,这样就是强引用,之后我们在block执行结束后主动设置为nil,也就是主动释放掉,这样就打破了block对self的引用

/*
 循环引用解决2:在block内将对象设置为nil
 通过wyBlock作为中介,给self增加一个引用,之后将wyBlock设置为nil就可以给self减少一个引用计数了
 */

- (void)circularTest4{
    __block WYBlock *wyBlock = self;
    self.block1 = ^int(int num1, int num2) {
        wyBlock.name = @"zhang";
        wyBlock = nil;
        return num1+num2;
    };
    self.block1(10,2);
}

3.2.3 将self作为参数传递

/*
 循环引用解决3:对象self作为参数
 wyBlock的生命周期仅在block内部,与self无关。block不持有self
 */
- (void)circularTest5{
    self.block11 = ^(WYBlock *wyBlock){
        wyBlock.name = @"wy";
        NSLog(@"wy--%@",wyBlock.name);
    };
    self.block11(self);
}

说明:

3.2.4 使用NSProxy虚基类实现

虚基类本质上是一个定义了消息转发功能的抽象类。也就是说他可以实现消息转发功能。
因此我们在这里可以通过虚基类来调用,避免了self的强引用。

详细的虚基类的认识可以查看博客:NSproxy虚基类实现代理和多继承以及多态

/*
 循环引用解决4:通过虚基类调用方法,不与self绑定
 */
- (void)circularTest6{
    WYProxy *proxy = [WYProxy alloc];
    [proxy transformObjc:self];
    self.block1 = ^int(int num1, int num2) {
        [proxy performSelector:@selector(eat)];
        return num1+num2;
    };
    self.block1(10,2);
}

- (void)eat{
    NSLog(@"eat");
}

运行结果:

2021-11-09 09:51:15.464526+0800 Block的学习[31084:410195] eat

说明:

上一篇下一篇

猜你喜欢

热点阅读