iOS ------ Block的相关问题

Block的定义

Block可以截获局部变量的匿名函数, 是将函数及其执行上下文封装起来的对象。

Block的实现

通过Clang将以下的OC代码转化为C++代码

objectivec 复制代码
// Clang
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
objectivec 复制代码
//main.m
#import <Foundation/Foundation.h>
 
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^{
            NSLog(@"我是 block");
        };
        block();
    }
    return 0;
}
objectivec 复制代码
//参数结构体
struct __main_block_impl_0 {
  struct __block_impl impl;// block 结构体
  struct __main_block_desc_0* Desc;// block 的描述对象
/*
block 的构造函数
 ** 返回值:__main_block_impl_0 结构体
 ** 参数一:__main_block_func_0 结构体
 ** 参数二:__main_block_desc_0 结构体的地址
 ** 参数三:flags 标识位
*/
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;// &_NSConcreteStackBlock 表示存储在栈上
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
 
//block 结构体
struct __block_impl {
  void *isa;//block 的类型
  int Flags;
  int Reserved;
  void *FuncPtr;// block的执行函数指针,指向__main_block_func_0
};
 
//封装了 block 中的代码
//参数是__main_block_impl_0结构体的指针
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_03dcda_mi_0);
        }
 
 
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;// block 所占的内存空间
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
/*
         ** void(^block)(void) = ^{
                NSLog(@"调用了block");
            };
         ** 定义block的本质:
         ** 调用__main_block_impl_0()构造函数
         ** 并且给它传了两个参数 __main_block_func_0 和 &__main_block_desc_0_DATA
         ** __main_block_func_0 封装了block里的代码
         ** 拿到函数的返回值,再取返回值的地址 &__main_block_impl_0,
         ** 把这个地址赋值给 block
         */
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
/*
         ** block();
         ** 调用block的本质:
         ** 通过 __main_block_impl_0 中的 __block_impl 中的 FuncPtr 拿到函数地址,直接调用
         */    
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
objectivec 复制代码
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

定义block的本质:调用__main_block_impl_0()构造函数并且给它传了两个参数 __main_block_func_0&__main_block_desc_0_DATA,__main_block_func_0 封装了block里的代码,拿到函数的返回值,再取返回值的地址 &__main_block_impl_0, 把这个地址赋值给 block

__main_block_impl_0

objectivec 复制代码
struct __main_block_impl_0 {
  struct __block_impl impl;// block 结构体
  struct __main_block_desc_0* Desc;// block 的描述对象
/*
block 的构造函数
 ** 返回值:__main_block_impl_0 结构体
 ** 参数一:__main_block_func_0 结构体
 ** 参数二:__main_block_desc_0 结构体的地址
 ** 参数三:flags 标识位
*/
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;// &_NSConcreteStackBlock 表示存储在栈上
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__block_impl

objectivec 复制代码
//block 结构体
struct __block_impl {
  void *isa;//block 的类型
  int Flags;
  int Reserved;
  void *FuncPtr;// block的执行函数指针,指向__main_block_func_0
};

调用block的本质:

通过 __main_block_impl_0 中的 __block_impl 中的 FuncPtr 拿到函数__main_block_func_0的地址,直接调用。

objectivec 复制代码
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

__main_block_func_0

封装了 block 中的代码,参数是__main_block_impl_0结构体的指针

objectivec 复制代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_03dcda_mi_0);
        }

总结:

先定义block,通过__main_block_impl_0构造函数初始化__block_impl的block结构体,其中包括执行block执行函数 __main_block_func_0的地址FuncPtr。 再调用Block,转换 Block 类型为 __block_impl,通过 FuncPtr 获取执行函数地址,调用执行函数 __main_block_func_0

Block的种类

block的分类主要分为以下的三种:

NSGlobalBlock 全局block
NSStackBlock 栈区block
NSMallocBlock 堆区block

objectivec 复制代码
int b = 20;

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    static int a = 10;
    int c = 30;
    NSObject* obj = [[NSObject alloc] init];

    void (^Block0)(void) = ^{
       NSLog(@"Block");
    };
    NSLog(@"Block0  %@",Block0);
    
    void (^Block1)(void) = ^{
       NSLog(@"a = %d  b = %d",a, b);
    };
    NSLog(@"Block1  %@",Block1);
    
    void (^Block2)(void) = ^{
       NSLog(@"c = %d",c);
    };
    NSLog(@"Block2  %@",Block2);
    
    void (^Block3)(void) = ^{
       NSLog(@"%@",obj);
    };
    NSLog(@"Block3  %@",Block3);
    
    void (^__weak Block4)(void) = ^{
       NSLog(@"c = %d",c);
    };
    NSLog(@"Block4  %@",Block4);
    
    
    void(^block)(void);
    block = [Block4 copy];
    NSLog(@"block  %@",block);
    
}

运行结果:

objectivec 复制代码
2024-07-23 21:45:38.640468+0800 Block总结[91605:3226729] Block0  <__NSGlobalBlock__: 0x104ae40d0>
2024-07-23 21:45:38.640558+0800 Block总结[91605:3226729] Block1  <__NSGlobalBlock__: 0x104ae40f0>
2024-07-23 21:45:38.640606+0800 Block总结[91605:3226729] Block2  <__NSMallocBlock__: 0x60000355a670>
2024-07-23 21:45:38.640659+0800 Block总结[91605:3226729] Block3  <__NSMallocBlock__: 0x60000357c690>
2024-07-23 21:45:38.640705+0800 Block总结[91605:3226729] Block4  <__NSStackBlock__: 0x16b31b930>
2024-07-23 21:45:38.640750+0800 Block总结[91605:3226729] block  <__NSMallocBlock__: 0x60000357c8a0>

从上面可以总结出:

GlobalBlock:

  • 位于全局变量
  • 在Block的内部不使用外部变量,或者只使用静态变量或全局变量

NSMallocBlock:

  • 在Block内部使用局部变量或者OC的属,并且赋值给强引用或者Copy修饰的变量

NSStackBlock

  • 位于栈区
  • 与mallocBlock一样,可以在内部使用局部变量或者OC属性
  • 但是不能赋值给强引用或者Copy修饰的变量

三种类型的Block的底层

  • 全局block在运行时调用_Block_copy方法后,仍然是全局block
  • 堆区block是由编译时的栈区block在运行时调用_Block_copy方法,生成新的堆区block
  • 栈区blcok不会进行_Block_copy的操作
  • 结论:如果block赋值给强引用或者copy修饰的变量,那么block会进行(objc_retainBlock中的_Block_copy操作,如果是赋值给__weak修饰的变量则不会进行_Block_copy的操作

_Block_copy方法

objectivec 复制代码
// Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
// 拷贝 block,
// 如果原来就在堆上,就将引用计数加 1;
// 如果原来在栈上,会拷贝到堆上,引用计数初始化为 1,并且会调用 copy helper 方法(如果存在的话);
// 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身
// 参数 arg 就是 Block_layout 对象,
// 返回值是拷贝后的 block 的地址
// 运行?stack -》malloc
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    // 如果 arg 为 NULL,直接返回 NULL

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    // 强转为 Block_layout 类型
    aBlock = (struct Block_layout *)arg;
    const char *signature = _Block_descriptor_3(aBlock)->signature;
    
    // 如果现在已经在堆上
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        // 就只将引用计数加 1
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // Its a stack block.  Make a copy.
        // block 现在在栈上,现在需要将其拷贝到堆上
        // 在堆上重新开辟一块和 aBlock 相同大小的内存
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        // 开辟失败,返回 NULL
        if (!result) return NULL;
        // 将 aBlock 内存上的数据全部复制新开辟的 result 上
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // reset refcount
        // 将 flags 中的 BLOCK_REFCOUNT_MASK 和 BLOCK_DEALLOCATING 部分的位全部清为 0
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        // 将 result 标记位在堆上,需要手动释放;并且引用计数初始化为 1
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        // copy 方法中会调用做拷贝成员变量的工作
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        // isa 指向 _NSConcreteMallocBlock
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

在 ARC 环境下,编译器会根据情况,自动将 Block 从栈上 copy 到堆上。具体会进行 copy 的情况有如下 4 种

  • block作为函数的返回值
  • block赋值给__strong指针,或者赋值给block类型的成员变量时
  • block作为Cocoa API方法中国呢含有using Block的方法参数
  • block作为GCDAPI的方法参数时

__block变量

在使用__block变量的Block从栈复制到堆上时,__block变量也会受到影响

按照oc的内存管理机制来管理,此时两者的关系就从block使用__block变成了block持有__block(__Block_byref_age_0结构体对象)

讲过的使用__block变量用结构体成员变量__forwarding的原因。"不管__block变量配置在栈上还是堆上,都能够正确访问该变量"

objectivec 复制代码
__block int val = 0;
        void (^blk) (void) = [^{++val;
            NSLog(@"%d", val);
            NSLog(@"%p", &val);
        } copy];
        ++val;
        blk();
        NSLog(@"%d", val);
        NSLog(@"%p", &val);

输出结果:

objectivec 复制代码
2024-07-25 15:26:02.624764+0800 block捕获OC对象[13116:500565] 2
2024-07-25 15:26:02.625028+0800 block捕获OC对象[13116:500565] 0x600000203398
2024-07-25 15:26:02.625045+0800 block捕获OC对象[13116:500565] 2
2024-07-25 15:26:02.625055+0800 block捕获OC对象[13116:500565] 0x600000203398

在Block语法的函数中,该变量val为复制到堆上的_block变量,而使用Block语法外的与Block无关的变量val,为复制前栈上的__block变量用结构体实例。

但是栈上的__block变量用结构体实例在__block变量从栈复制到堆上 时,会将成员变量__forwarding的值替换为复制目标堆上的__block变量用结构体实例的地址。

无论在Block语法中,Block语法外使用__block变量,还是__block变量配置在堆上或栈上,都可以顺利访问堆上同一个__block变量。

Block循环引用

循环引用的案例

objectivec 复制代码
typedef void(^TBlock)(void);

@interface ViewController ()
@property (nonatomic, strong) TBlock block;
@property (nonatomic, copy) NSString *name;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    // 循环引用
    self.name = @"Hello";
    self.block = ^(){
        NSLog(@"%@", self.name);
    };
    self.block();
}

这里self持有block,block也持有了self。两者的释放都要等待对方先释放。就会造成循环引用。造成内存泄露。

解决方法

  • weak的使用
objectivec 复制代码
    // 循环引用
    self.name = @"Hello";

    __weak typeof(self) weakSelf = self;
    self.block = ^(){
        NSLog(@"%@", weakSelf.name);
    };
    self.block();

此时self持有block,block弱引用self,强持有中断,所以不会引起循环引用。

  • 强弱共舞
objectivec 复制代码
self.name = @"Hello";

    __weak typeof(self) weakSelf = self;
    self.block = ^(){
        __strong __typeof(weakSelf)strongWeak = weakSelf;
        NSLog(@"%@", strongWeak.name);
    };
    self.block();

__strong的作用是防止block内部引用的外部weak变量被提前释放,进而在Block内部无法获取weak变量进而继续使用的情况。这样就保证了在block作用域结束之前,block内部都持有一个strongSelf对象可供使用。

  • 手动中断持有关系
objectivec 复制代码
    self.name = @"Hello";

    __block ViewController * ctrl = self;
    self.block = ^(){
         NSLog(@"%@", ctrl.name);
         ctrl = nil;
    };
    self.block();

ctrl在block使用完成后,被指为nil,block对self持有关系就解除了,不构成循环引用。

  • block传参
objectivec 复制代码
    // 循环引用
    self.name = @"Hello";
    self.block = ^(ViewController * ctrl){
        NSLog(@"%@", ctrl.name);
    };
    self.block(self);

self作为参数传入block,进行指针拷贝,并没有对self进行持有

Block变量截获

  • 对于全局变量,不会捕获到block内部,访问方为直接访问
  • 对于auto类型的局部变量,会捕获到block内部,block内部会自动生成一个成员变量,访问方式为值传递
  • 对于static类型的局部变量,会捕获到block内部,block内部会自动生成一个成员变量,访问方式为指针传递
  • 对于对象类型的局部变量,block会连同修饰符一起捕获

auto自动变量

objectivec 复制代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void(^block)(void) = ^{
            NSLog(@"%d",age);
        };
        block();
    }
    return 0;
}

将以下 OC 代码转换为 C++ 代码

objectivec 复制代码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;//生成的变量
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
 
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_40c716_mi_0,age);
       }
  • 在__main_block_impl_0结构体中会自动生成一个相同类型的age变量
  • 构造函数__main_block_impl_0中多出了一个age参数,用来捕获外部的变量

由于传递方式为值传递,所以我们在block外部修饰age变量时,不会影响到block中的age变量

static类型的局部变量

objectivec 复制代码
    static int age = 10;
    void(^block)(void) = ^{
        NSLog(@"%d",age);
    };
    age = 20;
    block();
    // 20

将以下OC代码转化为C++代码

objectivec 复制代码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *age;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *age = __cself->age; // bound by copy
 
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_cb7943_mi_0,(*age));
        }
  • __main_block_impl_0结构体中生成了一个相同类型的age变量指针
  • __main_block_impl_0构造函数多了个参数,用来捕获外部的age变量的地址

由于传递方式是指针传递,所以修改局部变量age时,age的值会随之变化

全局变量

objectivec 复制代码
int height = 10;
static int age = 20;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^{
            NSLog(@"%d,%d",height,age);
        };
        block();
    }
    return 0;
}

将以下OC代码转化为C++代码

objectivec 复制代码
int height = 10;
static int age = 20;
 
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_7a340f_mi_0,height,age);
        }

__main_block_impl_0结构体中,并没有自动生成age和height全局变量,也就是说没有将变量捕获到block内部

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

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

OC对象

objectivec 复制代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject* obj = [[NSObject alloc] init];
        void(^block)(void) = ^{
            NSLog(@"%@", obj);
        };
        block();
    }
    return 0;
}

将以下OC代码转化为C++代码

objectivec 复制代码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *obj;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSObject *obj = __cself->obj; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_46_qzlmlhgd0xd2xjp590bfb5_00000gn_T_main_81a5a1_mi_0, obj);
        }
  • __main_block_impl_0结构体中生成了一个NSObject对象指针
  • __main_block_impl_0构造函数多了个参数,用来捕获外部的NSObject对象的地址(值拷贝)

_block修饰的变量

  • _block同于解决block内部无法修改auto变量值的问题;
  • _block不能修饰全局变量,静态变量;
  • 编译器会将_block变量包装成一个对象(struct __Block_byref_age_0(byref:按地址传递));
  • 加_block修饰不会改变变量的性质,他还是auto变量;
  • 一般情况,对捕获变量进行赋值(赋值!=使用)操作需要添加_block修饰符,比如给数组添加或删除对象,就不用加_bolck修饰符;
objectivec 复制代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int age = 10;
        void(^block)(void) = ^{
            age = 20;
            NSLog(@"block-%d",age);
        };
        block();
        NSLog(@"%d",age);
    }
    return 0;
}
block修饰局部变量
objectivec 复制代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int age = 10;
        void(^block)(void) = ^{
            age = 20;
            NSLog(@"block-%d",age);
        };
        block();
        NSLog(@"%d",age);
    }
    return 0;
}

将以下OC代码转化为C++代码

objectivec 复制代码
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;//持有指向该实例自身的指针
 int __flags;
 int __size;
 int age;
};
 
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref
 
            (age->__forwarding->age) = 20;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_75529b_mi_0,(age->__forwarding->age));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
 
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
  • 编译器会将_block修饰的变量包装成一个__Block_byref_age_0结构体对象
  • 以上age = 20 的赋值过程:通过block的__main_block_imp_0结构体实例中( __Block_byref_age_0)类型的age指针,找到 __Block_byref_age_0结构体的对象, __Block_byref_age_0结构体对象持有指向实例本身的__forwarding指针,通过成员变量_forwarding访问 __Block_byref_age_0结构体里的age变量,并将值改为20;
__block修饰对象
objectivec 复制代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block NSObject* obj = [[NSObject alloc] init];
        NSLog(@"%@", obj);
        void(^block)(void) = ^{
            NSLog(@"%@", obj);
            obj  =  [[NSObject alloc] init];
            NSLog(@"%@", obj);
        };
        block();
    }
    return 0;
}

将以下OC代码转化为C++代码

objectivec 复制代码
struct __Block_byref_obj_0 {
  void *__isa;
__Block_byref_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *obj;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_obj_0 *obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_obj_0 *obj = __cself->obj; // bound by ref

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_46_qzlmlhgd0xd2xjp590bfb5_00000gn_T_main_155262_mi_1, (obj->__forwarding->obj));
            (obj->__forwarding->obj) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_46_qzlmlhgd0xd2xjp590bfb5_00000gn_T_main_155262_mi_2, (obj->__forwarding->obj));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}

void (__Block_byref_id_object_copy)(void, void*);

这是一个函数指针,指向一个用于复制 Block 中引用的对象的函数。当 Block 被复制到堆上(例如,通过 Block_copy)时,这个函数将被调用以确保被捕获的对象也被正确地复制。
调用后Block就持有了该对象,在栈上的时候只是使用并没有持有注意这里的对象是__Block_byref_age_0结构体对象,对于obj对象并没有持有

void (__Block_byref_id_object_dispose)(void);

这是另一个函数指针,指向一个用于释放或清理 Block 中引用的对象的函数。当 Block 被释放(例如,通过 Block_release)时,这个函数将被调用以确保被捕获的对象也被正确地释放。

总结如图

Block的内存管理

对外部变量的引用计数

  • 自动局部基本类型变量,因为是值传递,内存是跟随Block,不用干预
  • static局部基本类型变量,指针传递,由于分配在静态区,故不用干预
  • 全局变量,存储在数据去,不用干预
  • 局部对象变量,如果在栈上,只是使用,不用干预。但Block在拷贝到堆时,对其retain,在Block对象销毁时,对其release

示例一

objectivec 复制代码
  NSObject *objc = [NSObject new];
       NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 1

       void(^strongBlock)(void) = ^{
           NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
       };
       strongBlock();

       void(^__weak weakBlock)(void) = ^{
           NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
       };
       weakBlock();
       
       void(^mallocBlock)(void) = [weakBlock copy];
       mallocBlock();  

输出结果:

objectivec 复制代码
2024-07-24 09:46:10.696422+0800 Block总结[5902:3676907] 1
2024-07-24 09:46:10.696469+0800 Block总结[5902:3676907] ---3
2024-07-24 09:46:10.696501+0800 Block总结[5902:3676907] ---4
2024-07-24 09:46:10.696542+0800 Block总结[5902:3676907] ---5

为什么第二个打印3

我们可以看一看这段源码

objectivec 复制代码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *obj;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSObject *obj = __cself->obj; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_46_qzlmlhgd0xd2xjp590bfb5_00000gn_T_main_81a5a1_mi_0, obj);
        }

编译时这段直接捕获局部OC对象,将obj指针拷贝过来并且赋值,引用计数加一。运行时block还会从栈区拷贝到堆区,obj的引用计数还会加一

示例二

objectivec 复制代码
 __block NSObject *objc = [[NSObject alloc] init];
        NSLog(@"%@",objc);
        NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 1
        void(^strongBlock)(void) = ^{
            NSLog(@"%@",objc);
            NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
            NSObject* objc1 = objc;
            NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
        };
        strongBlock();
        NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));

输出结果

objectivec 复制代码
2024-07-24 20:59:00.137761+0800 block捕获OC对象[8663:256819] <NSObject: 0x600000008050>
2024-07-24 20:59:00.137770+0800 block捕获OC对象[8663:256819] 1
2024-07-24 20:59:00.137780+0800 block捕获OC对象[8663:256819] <NSObject: 0x600000008050>
2024-07-24 20:59:00.137788+0800 block捕获OC对象[8663:256819] ---1
2024-07-24 20:59:00.137795+0800 block捕获OC对象[8663:256819] ---2
2024-07-24 20:59:00.137802+0800 block捕获OC对象[8663:256819] ---1

为什么第二个输出1?

先看一下源码

objectivec 复制代码
struct __Block_byref_obj_0 {
  void *__isa;
__Block_byref_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *obj;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_obj_0 *obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_obj_0 *obj = __cself->obj; // bound by ref
  }

编译器会将_block修饰的变量包装成一个__Block_byref_age_0结构体对象

objectivec 复制代码
__Block_byref_obj_0 *obj = __cself->obj;

当块捕获 __block 变量时,它实际上捕获的是 __Block_byref 结构体的指针,而不是变量本身。这意味着块捕获的是指向 __block 变量的间接引用,而不是直接引用

__block 修饰符时,编译器会对该变量进行特殊处理:

__block 变量在栈上分配内存。

当块(block)被复制到堆上时,__block 变量的内存也会被复制到堆上。

Block堆栈释放差异

objectivec 复制代码
#pragma mark - block 堆栈释放差异
- (void)jpreno {
    
    int reno = 10;
    void(^__weak weakBlock)(void) = nil;
    {
        // 栈区
        void(^__weak strongBlock)(void) = ^{
            NSLog(@"jp1:---%d", reno);
        };
        weakBlock = strongBlock;
        NSLog(@"jp2:--%@--%@",weakBlock,strongBlock);
    }
    weakBlock();

}

输出结果

objectivec 复制代码
2024-07-24 11:08:23.993541+0800 Block总结[7484:3735630] jp2:--<__NSStackBlock__: 0x16b9939b0>--<__NSStackBlock__: 0x16b9939b0>
2024-07-24 11:08:23.993625+0800 Block总结[7484:3735630] jp1:---10

通过赋值weakBlock = strongBlock操作,两个block同时指向一个NSStackBlock,代码块运行结束后不会被释放,能够正常运行 weakBlock();

objectivec 复制代码
        int reno = 10;
        void(^__weak weakBlock)(void) = nil;
        {
            // 栈区
            void(^__strong strongBlock)(void) = ^{
                NSLog(@"jp1:---%d", reno);
            };
            weakBlock = strongBlock;
            NSLog(@"jp2:--%@--%@",weakBlock,strongBlock);
        }
        weakBlock();

由于weakBlock不能强引用NSMallocBlock,代码块运行结束就会被释放。此时调用weakBlock()指向的对象已经被释放了,形成野指针,所以程序崩溃了。

相关推荐
crasowas5 小时前
iOS - 超好用的隐私清单修复脚本(持续更新)
ios·app store
ii_best6 小时前
ios按键精灵脚本开发:ios悬浮窗命令
ios
Code&Ocean11 小时前
iOS从Matter的设备认证证书中获取VID和PID
ios·matter·chip
/**书香门第*/11 小时前
Laya ios接入goole广告,开始接入 2
ios
恋猫de小郭1 天前
什么?Flutter 可能会被 SwiftUI/ArkUI 化?全新的 Flutter Roadmap
flutter·ios·swiftui
网安墨雨1 天前
iOS应用网络安全之HTTPS
web安全·ios·https
福大大架构师每日一题1 天前
37.1 prometheus管理接口源码讲解
ios·iphone·prometheus
BangRaJun2 天前
LNCollectionView-替换幂率流体
算法·ios·设计
刘小哈哈哈2 天前
iOS 多个输入框弹出键盘处理
macos·ios·cocoa
靴子学长2 天前
iOS + watchOS Tourism App(含源码可简单复现)
mysql·ios·swiftui