本文介绍: Block (块),封装函数调用以及调用环境的 OC 对象,Objective-C闭包可以内部访问外部的值),相当于C语言函数指针,把一个函数写在一个函数内部,而OC并没有函数(方法嵌套这一语法作用域的原因,全局变量哪里都可以直接访问,所以不用捕获;局部变量,外部不能直接访问,所以需要捕获auto 类型的局部变量可能会销毁,其内存会消失,block 将来执行代码的时候不可能再去访问那块内存,所以捕获其值;static 变量会一直保存内存中, 所以捕获地址即可

Block基本使用

什么是Block

Block (块),封装了函数调用以及调用环境的 OC 对象,Objective-C闭包可以内部访问外部的值),相当于C语言的函数指针,把一个函数写在一个函数内部,而OC并没有函数(方法嵌套这一语法

Block声明

void(^blockName)();
int(^blockName2)(int a, int b, int c);

格式 返回值 (^block名称)(形参列表)
^代表块的符号

Block实现

  1. 参数返回
void(^blockName)(void) = ^{

};
  1. 参数返回
void(^blockName)(int a, int b) = ^(int a, int b){

};
  1. 参数有返回值
int(^blockName)(void) = ^int{
    return 3;
};

实现部分的返回值可以省略,像这样:

int(^blockName)(void) = ^{
    return 3;
};
  1. 参数有返回值
int(^blockName)(int a, int b) = ^int(int a, int b){
    return 3 + a * b;
};

实现部分的返回值int同样可以省略

Block的调用

//无参数无返回值
blockName();

//有参数有返回值
int result = blockName(7, 12);

现在已声明blockName代表一个块,那么调用这个块既可以通过blockName(7, 12);,也可以这样:

int result = ^(int a, int b) {
    return 3 + a + b;
}(7, 12);

Block作为形参使用

Block作为形式参数方法中的声明与上述格式略有不同(块的名称在外面)

现在Jaxon类和Jacky类中分别实现以下方法
Jaxon.h

- (void)askJackyForHelp: (void(^)(int num))blockName isOK: (void(^)(BOOL boolValue))completion;

Jaxon.m

- (void)askJackyForHelp:(void (^)(int))blockName isOK:(void (^)(BOOL))completion {
    blockName(3);

    //传入completion块的参数非1即0
    completion(arc4random() % 2);
}

Jacky.m

- (void)helpDoWith: (int)num {
    NSLog(@"帮忙做事%d次", num);
}

接下来main函数中调用:

Jaxon* jaxon = [[Jaxon alloc] init];
[jaxon askJackyForHelp:^(int num) {
            Jacky* jacky = [[Jacky alloc] init];
            [jacky helpDoWith: num];
        } isOK:^(BOOL boolValue) {

            //成功和失败概率各占一半
            if (boolValue) {
                NSLog(@"帮忙成功");
            } else {
                NSLog(@"帮忙失败");
            }
        }];

运行结果

请添加图片描述

这样是不是可以起到代理的作用,Jaxon委托Jacky帮忙做事,Jaxon实现不了的委托Jacky实现,因此Block块也可以用于界面传值或其他需要使用代理模式程序设计

Block作为属性使用

给Block起别名

文章开头也提到了块其实也是一种对象,可以将ta理解为一种数据类型

那么也可以用typedef关键字给Block起别名,看以下示例

typedef void(^Help)(int num);
typedef void(^Finish)(BOOL boolValue);

上面的方法也就可以这样声明

- (void)askJackyForHelp:(Help)blockName isOK:(Finish)completion;

块的属性关键字一般需要是是copy

@interface Jaxon : NSObject

//无别名
@property (nonatomic, copy)void(^helpBlock)(int num);
//有别名
//@property (nonatomic, copy)Help helpBlock;
- (void)askMyselfDo;

@end

@implementation Jaxon

- (void)askMyselfDo {
    self.helpBlock(5);
}

@end

main函数:

Jaxon* jaxon = [[Jaxon alloc] init];
jaxon.helpBlock = ^(int num) {
    NSLog(@"我自己做%@次", @(num));
};
[jaxon askMyselfDo];

运行结果
请添加图片描述

Block的copy

关于copy关键字,编者也简单了解一下,底层原理以后再加以详细的剖析:

ARC 环境下,编译器会根据情况自动上的 block 复制上,比如以下几种情况: 手动调用 block 的copy`方法时;

block 作为属性写法
ARC下写strong或者copy都会对 block 进行强引用,都会自动将 block 从栈 copy 到堆上;
建议都写成copy,这样 MRC 和 ARC 下一致。

Block刚创建存放在栈区,使用时copy堆区

Block的捕获机制

为保证Block内部能正常访问到外部的变量,Block有一种变量捕获机制

auto类型的局部变量

auto变量:正常定义出来的变量默认都是auto类型,只是省略了

auto int age = 20;

auto类型的局部变量会被捕获到block块内部,访问方式值传递

int age = 10;
NSLog(@"%d %p", age, &age);
void(^blockName)(void) = ^ {
    NSLog(@"%d %p", age, &age);
};
age = 20;
//可以打印出来,说明block块是可以访问到外部信息
blockName();
NSLog(@"%d %p", age, &age);

请添加图片描述
根据运行结果可以得出以下两点:

  • auto类型的局部变量被捕获到block块内部时,block内部会自动生成一个相同的成员变量用来存储这个变量的值,因此打印的block外部的age地址与内部age地址不一样
  • 由于值传递修改外部age变量的值,不会影响到block内部的变量

__block浅析

Block内部只能调用外部变量,不能修改

请添加图片描述

Block 默认情况下是使用被捕获的外部变量的只读拷贝,因此在 Block 内部无法直接修改外部变量的值

解决办法如下:

当变量被__block修饰时,block可以修改外部全局变量

__block int age = 10;
NSLog(@"%d %p", age, &age);
void(^blockName)(void) = ^ {
    age = 30;
    NSLog(@"%d %p", age, &age);
};
blockName();
NSLog(@"%d %p", age, &age);

请添加图片描述


static类型的局部变量

static类型的局部变量会被捕获到block内部,访问方式指针传递

static int age = 10;
NSLog(@"%d %p", age, &age);
void(^blockName)(void) = ^ {
    NSLog(@"%d %p", age, &age);
};
age = 20;
blockName();
NSLog(@"%d %p", age, &age);

请添加图片描述

  • static类型的局部变量被捕获到block内部时,block块内部会生成一个相同类型的指针,指向捕获到内部的age变量的地址
  • 由于指针传递修改外部的age变量的值,会影响block内部的age变量

全局变量

全局变量不会被捕获到block内部,访问方式直接访问

int _age = 10;
static int _height = 175;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSLog(@"%d %p", _age, &_age);
        void(^blockName)(void) = ^ {
            _age = 30;
            NSLog(@"%d %p", _age, &_age);
        };
        blockName();
        _age = 20;
        NSLog(@"%d %p", _age, &_age);
    }
    return 0;
}

请添加图片描述

其他问题

对于对象类型的局部变量,block会连同ta的所有权修饰符一起捕获

为什么局部变量需要捕获,全局变量不用捕获呢?

  • 作用域的原因,全局变量哪里都可以直接访问,所以不用捕获;
  • 局部变量,外部不能直接访问,所以需要捕获;
  • auto 类型的局部变量可能会销毁,其内存会消失,block 将来执行代码的时候不可能再去访问那块内存,所以捕获其值;
  • static 变量会一直保存在内存中, 所以捕获其地址即可

原文地址:https://blog.csdn.net/XY_Mckevince/article/details/133918732

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。

如若转载,请注明出处:http://www.7code.cn/show_10149.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注