iOS开发~Block学习总结

本文介绍了Block的内部实现,包括结构体、类型、内存管理、__block修饰符的作用、循环引用以及在不同内存区域的影响。重点讨论了Block如何捕获和修改外部变量,以及在ARC和MRC下内存管理的差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、block的内部实现,结构体是什么样的?

block的结构体如下:

struct Block_literal_1 {
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 {
    unsigned long int reserved;         // NULL
        unsigned long int size;         // sizeof(struct Block_literal_1)
        // optional helper functions
        void (*copy_helper)(void *dst, void *src);     // IFF (1<<25)
        void (*dispose_helper)(void *src);             // IFF (1<<25)
        // required ABI.2010.3.16
        const char *signature;                         // IFF (1<<30)
    } *descriptor;
    // imported variables
};

isa:由此可知,block也是一个对象类型,具体类型包括_NSConcreteGlobalBlock、_NSConcreteStackBlock、_NSConcreteMallocBlock。

flags:block 的负载信息(引用计数和类型信息),按位存储,也可以获取block版本兼容的相关信息。以下是flags按bit位取与的所有可能值:

enum {
    // Set to true on blocks that have captures (and thus are not true
    // global blocks) but are known not to escape for various other
    // reasons. For backward compatibility with old runtimes, whenever
    // BLOCK_IS_NOESCAPE is set, BLOCK_IS_GLOBAL is set too. Copying a
    // non-escaping block returns the original block and releasing such a
    // block is a no-op, which is exactly how global blocks are handled.
    BLOCK_IS_NOESCAPE      =  (1 << 23),

    BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
    BLOCK_HAS_CTOR =          (1 << 26), // helpers have C++ code
    BLOCK_IS_GLOBAL =         (1 << 28),
    BLOCK_HAS_STRET =         (1 << 29), // IFF BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE =     (1 << 30),
};
switch (flags & (3<<29)) {
  case (0<<29):      10.6.ABI, no signature field available
  case (1<<29):      10.6.ABI, no signature field available
  case (2<<29): ABI.2010.3.16, regular calling convention, presence of signature field
  case (3<<29): ABI.2010.3.16, stret calling convention, presence of signature field,
}

由此可知:当flags & (3<<29) is BLOCK_HAS_COPY_DISPOSE的时候,才会有copy_helper和dispose_helper函数指针。

invoke:是block具体实现函数指针地址,可以通过此地址直接调用block。

Block_descriptor_1:block的描述文内容,它包括如下:

size:block所占的内存大小

copy_helper:copy函数指针(不同版本不一定存在)

dispose_helper:dispose函数指针(不同版本不一定存在)

signature:block的实现函数的签名(不同版本不一定存在),可以通过此指针获取block的参数内容描述、返回值内容描述等

获取block的方法签名,可以参考这篇文章

2、block是类吗,有哪些类型?

从block的结构体中可知,block同样也有一个isa指针,所以block也是一个类,它的类型包括:

  • _NSConcreteGlobalBlock
  • _NSConcreteStackBlock
  • _NSConcreteMallocBlock

3、一个int变量被 __block 修饰与否的区别?block的变量截获?

没有被__block修饰的int,block体中对这个变量的引用是值拷贝,在block中是不能被修改的。

通过__block修饰的int,block体中对这个变量的引用是指针拷贝,它会生成一个结构体,复制这个变量的指针引用,从而达到可以修改变量的作用。

关于block的变量截获:

block会将block体内引用外部变量的变量进行拷贝,将其拷贝到block的数据结构中,从而可以在block体内访问或修改外部变量。

外部变量未被__block修饰时,block数据结构中捕获的是外部变量的值,通过__block修饰时,则捕获的是对外部变量的指针引用。

注意:block内部访问全局变量时,全局变量不会被捕获到block数据结构中。

举个栗子:

未被__block修饰的情况

int param = 1;
int a = param; // 没用__block修饰的时候,block内部捕获的外部变量
[self updateInt:a];
NSLog(@"----:%@", @(param));// 这里输出:1

// 没用__block修饰的时候,block内部实现如下
- (void)updateInt:(int)a{
    a = 2;// 此时对外部变量修改是无效的
}

被__block修饰的情况

int param = 1;
int *a = &param; // 用__block修饰的时候,block内部捕获的外部变量,是外部变量的指针
[self updateInt:a];
NSLog(@"----:%@", @(param));// 这里输出:2


// 用__block修饰的时候,block内部实现如下
- (void)updateInt:(int *)a{
    *a = 2;// 此时对外部变量修改是有效的
}

参考这篇文章

4、block在修改NSMutableArray,需不需要添加__block?

  • 如果修改的是NSMutableArray的存储内容的话,是不需要添加__block修饰的。
  • 如果修改的是NSMutableArray对象的本身,那必须添加__block修饰。

参考block的变量捕获。

5、block怎么进行内存管理的?

block按照内存分布,分三种类型:全局内存中的block、栈内存中的block、堆内存中的block。

在MRC和ARC下block的分布情况不一样

MRC下:

当block内部引用全局变量或者不引用任何外部变量时,该block是在全局内存中的。

当block内部引用了外部的非全局变量的时候,该block是在栈内存中的。

当栈中的block进行copy操作时,会将block拷贝到堆内存中。

通过__block修饰的变量,不会对其应用计数+1,不会造成循环引用。

ARC下:

当block内部引用全局变量或者不引用任何外部变量时,该block是在全局内存中的。

当block内部引用了外部的非全局变量的时候,该block是在堆内存中的。

也就是说,ARC下只存在全局block和堆block。

通过__block修饰的变量,在block内部依然会对其引用计数+1,可能会造成循环引用。

通过__weak修饰的变量,在block内部不会对其引用计数+1,不会造成循环引用。

参考这篇文章

6、block可以用strong修饰吗?

在MRC环境中,是不可以的,strong修饰符会对修饰的变量进行retain操作,这样并不会将栈中的block拷贝到堆内存中,而执行的block是在堆内存中,所以用strong修饰的block会导致在执行的时候因为错误的内存地址,导致闪退。

在ARC环境中,是可以的,因为在ARC环境中的block只能在堆内存或全局内存中,因此不涉及到从栈拷贝到堆中的操作。

7、解决循环引用时为什么要用__strong、__weak修饰?

__weak修饰的变量,不会出现引用计数+1,也就不会造成block强持有外部变量,这样也就不会出现循环引用的问题了。

但是,我们的block内部执行的代码中,有可能是一个异步操作,或者延迟操作,此时引用的外部变量可能会变成nil,导致意想不到的问题,而我们在block内部通过__strong修饰这个变量时,block会在执行过程中强持有这个变量,此时这个变量也就不会出现nil的情况,当block执行完成后,这个变量也就会随之释放了。

8、block发生copy时机?

一般情况在ARC环境中,编译器将创建在栈中的block会自动拷贝到堆内存中,而block作为方法或函数的参数传递时,编译器不会做copy操作。

  • block作为方法或函数的返回值时,编译器会自动完成copy操作。
  • 当block赋值给通过strong或copy修饰的id或block类型的成员变量时。
  • 当 block 作为参数被传入方法名带有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 时。

9、Block访问对象类型的auto变量时,在ARC和MRC下有什么区别?

首先我们知道,在ARC下,栈区创建的block会自动copy到堆区;而MRC下,就不会自动拷贝了,需要我们手动调用copy函数。

我们再说说block的copy操作,当block从栈区copy到堆区的过程中,也会对block内部访问的外部变量进行处理,它会调用Block_object_assign函数对变量进行处理,根据外部变量是strong还会weak对block内部捕获的变量进行引用计数+1或-1,从而达到强引用或弱引用的作用。

因此

在ARC下,由于block被自动copy到了堆区,从而对外部的对象进行强引用,如果这个对象同样强引用这个block,就会形成循环引用。

在MRC下,由于访问的外部变量是auto修饰的,所以这个block属于栈区的,如果不对block手动进行copy操作,在运行完block的定义代码段后,block就会被释放,而由于没有进行copy操作,所以这个变量也不会经过Block_object_assign处理,也就不会对变量强引用。

简单说就是:

ARC下会对这个对象强引用,MRC下不会。

参考这篇文章

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zfpp25_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值