【Objective-C】浅析Block及其捕获机制

这篇具有很好参考价值的文章主要介绍了【Objective-C】浅析Block及其捕获机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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(@"帮忙失败");
            }
        }];

运行结果:

【Objective-C】浅析Block及其捕获机制,objective-c,开发语言,macos,ios

这样是不是可以起到代理的作用,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];

运行结果:
【Objective-C】浅析Block及其捕获机制,objective-c,开发语言,macos,ios

Block的copy

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

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

  • block 作为函数返回值时(Masonry 框架中用很多);
  • 将 block 赋值给__strong指针时;
  • block 作为 Cocoa API 中方法名含有usingBlock的方法参数时;
  • block 作为 GCD API 的方法参数时。

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);

【Objective-C】浅析Block及其捕获机制,objective-c,开发语言,macos,ios
根据运行结果可以得出以下两点:

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

__block浅析

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

【Objective-C】浅析Block及其捕获机制,objective-c,开发语言,macos,ios

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

解决办法如下:

  • 变量用static修饰(原因:捕获static类型的局部变量是指针传递,可以访问到该变量的内存地址)
  • 全局变量
  • __block(我们只希望临时用一下这个变量临时改一下而已,而改为 static 变量和全局变量会一直在内存中)

当变量被__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);

【Objective-C】浅析Block及其捕获机制,objective-c,开发语言,macos,ios


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);

【Objective-C】浅析Block及其捕获机制,objective-c,开发语言,macos,ios

  • 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;
}

【Objective-C】浅析Block及其捕获机制,objective-c,开发语言,macos,ios

其他问题

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

为什么局部变量需要捕获,全局变量不用捕获呢?文章来源地址https://www.toymoban.com/news/detail-719731.html

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

到了这里,关于【Objective-C】浅析Block及其捕获机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Effective Objective - C】—— 熟悉Objective-C

    Objective-C通过一套全新语法,在C语言基础上添加了面向对象特性。Objective-C的语法中频繁使用方括号,而且不吝于写出极长的方法名,这通常令许多人觉得此语言较为冗长。其实这样写出来的代码十分易读,只是C++或Java程序员不太能适应。 Objective-C语言学起来很快,但有很

    2024年01月16日
    浏览(36)
  • 第一章 熟悉Objective-C

    Objective—C语言是由Smalltalk演化而来,后者是消息型语言的鼻祖,所以该语言使用的“消息结构”而非“函数调用”。 1. 消息和函数调用之间的区别 关键区别在于: 使用消息结构的语言,其运行所应执行的代码由运行环境来决定;而使用函数调用的语言,则由编译器决定。

    2024年01月18日
    浏览(30)
  • Effective Objective-C 学习(二)

    “属性”(property)是 Objective-C 的一项特性,用于封装对象中的数据。Objective-C 对象通常会把其所需的数据保存为各种实例变量。实例变量一般通过“存取方法”来访问。其中,“获取方法”(getter)用于读取变量值,而“设置方法”(setter)用于写入变量值。开发者可以令

    2024年02月20日
    浏览(33)
  • Objective-C日期NSDate使用

    2024年01月21日
    浏览(38)
  • Effective Objective-C 学习(三)

    Objective-C 使用引用计数来管理内存:每个对象都有个可以递增或递减的计数器。如果想使某个对象继续存活,那就递增其引用计数:用完了之后,就递减其计数。计数变为 0时,就可以把它销毁。 在ARC中,所有与引用计数有关的方法都无法编译(由于 ARC 会在编译时自动插入

    2024年02月22日
    浏览(36)
  • Effective Objective-C 学习(四)

    在执行后台任务时,GCD 并不一定是最佳方式。还有一种技术叫做 NSOperationQueue,它虽然与 GCD 不同,但是却与之相关,开发者可以把操作以 NSOperation 子类的形式放在队列中,而这些操作也能够并发执行。 GCD是纯C的API,而NSOperationQueue是Objective-C的对象。这意味着使用GCD时,任

    2024年02月19日
    浏览(30)
  • use gnustep objective-c

    专注于概念,而不是迷失在语言技术细节中 编程语言的目的是成为一个更好的程序员; 也就是说,在设计和实现新系统以及维护旧系统方面变得更加有效 header preprocess interface implementation method variable declare and expression comment basic integer set and float set enum type void type derive type incl

    2024年02月14日
    浏览(32)
  • 【KVC补充 Objective-C语言】

    2024年02月15日
    浏览(31)
  • Objective-C获取变量类型的方法

    在Objective-C中,要获取一个对象的类型,可以使用[object class]方法。这将返回一个Class对象,表示该对象的类型。 另外,typeid是C++中的,用于获取一个变量的类型信息。在Objective-C中,typeid并不适用于获取对象类型。相反,您应该使用[object class]方法来获取对象的类型。

    2024年02月13日
    浏览(33)
  • Effective Objective-C学习第一周

    OC是一种消息型语言,使用的是“消息结构”而非“函数调用”,由smalltalk演化而来。使用消息结构的语言运行时执行的代码由运行环境来决定,而使用函数调用的语言由编译器决定。 OC将堆内存管理抽象出来了。不需要使用malloc或者free来分配或释放对象所占的内存。OC运行

    2024年01月17日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包