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

Block的定义

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

Block的实现

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

// Clang
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
//main.m
#import <Foundation/Foundation.h>
 
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^block)(void) = ^{
            NSLog(@"我是 block");
        };
        block();
    }
    return 0;
}
//参数结构体
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;
}
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

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

//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的地址,直接调用。

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)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);
        }

总结:
先定义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

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

运行结果:

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方法

// 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变量配置在栈上还是堆上,都能够正确访问该变量

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

输出结果:

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循环引用

循环引用的案例

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的使用
    // 循环引用
    self.name = @"Hello";

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

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

  • 强弱共舞
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对象可供使用。

  • 手动中断持有关系
    self.name = @"Hello";

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

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

  • block传参
    // 循环引用
    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自动变量

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

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

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类型的局部变量

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

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

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的值会随之变化

全局变量

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++代码

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对象

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

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

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修饰符;
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修饰局部变量
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++代码

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修饰对象
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++代码

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

示例一

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

输出结果:

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
我们可以看一看这段源码

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的引用计数还会加一

示例二

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

输出结果

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?
先看一下源码

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结构体对象

__Block_byref_obj_0 *obj = __cself->obj;

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

__block 修饰符时,编译器会对该变量进行特殊处理:
__block 变量在栈上分配内存。
当块(block)被复制到堆上时,__block 变量的内存也会被复制到堆上。

Block堆栈释放差异

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

}

输出结果

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

        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()指向的对象已经被释放了,形成野指针,所以程序崩溃了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/870148.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

60个常见的 Linux 指令

1.ssh 登录到计算机主机 ssh -p port usernamehostnameusername&#xff1a; 远程计算机上的用户账户名。 hostname&#xff1a; 远程计算机的 IP 地址或主机名。 -p 选项指定端口号。 2.ls 列出目录内容 ls ls -l # 显示详细列表 ls -a # 显示包括隐藏文件在内的所有内…

封装和桥接Unity 协程体系

简介 协程&#xff08;Coroutine&#xff09;在C#中是一种特殊的函数&#xff0c;它允许开发者编写可以暂停执行并在未来某个时刻恢复执行的代码块。协程通常用于实现异步操作&#xff0c;如延时执行、等待某个事件发生、或者分段执行复杂的任务。在Unity游戏引擎中&#xff0c…

Conda和Pip有什么区别?

conda和pip是Python中两种常用的包管理工具&#xff0c;它们在用途、包来源以及环境管理等方面存在区别。以下是具体分析&#xff1a; 用途 conda&#xff1a;conda是Anaconda发行版中的包管理工具&#xff0c;可以管理包括非Python软件包在内的各种包。它是一个全面的环境管理…

【数据结构】建堆算法复杂度分析及TOP-K问题

【数据结构】建堆算法复杂度分析及TOP-K问题 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;数据结构 文章目录 【数据结构】建堆算法复杂度分析及TOP-K问题前言一.复杂度分析1.1向下建堆复杂度1.2向上建堆复杂度1.3堆排序复杂度 二.TOP-K问…

Leetcode—769. 最多能完成排序的块【中等】

2024每日刷题&#xff08;149&#xff09; Leetcode—769. 最多能完成排序的块 实现代码 class Solution { public:int maxChunksToSorted(vector<int>& arr) {int ans 0;int mx INT_MIN;for(int i 0; i < arr.size(); i) {mx max(arr[i], mx);if(mx i) {a…

数据库安全:MySQL安全配置,MySQL安全基线检查加固

「作者简介」:冬奥会网络安全中国代表队,CSDN Top100,就职奇安信多年,以实战工作为基础著作 《网络安全自学教程》,适合基础薄弱的同学系统化的学习网络安全,用最短的时间掌握最核心的技术。 这一章节我们需要知道MySQL的安全基线标准和加固方式。 MySQL基线检查 1、更新…

Cannot access org.springframework.context.ConfigurableApplicationContext

Cannot access org.springframework.context.ConfigurableApplicationContext SpringApplication.run曝红 解决方案&#xff1a; File -> Invalidate Cache and Restart 如果对你有用就点个赞&#xff01;

项目实战——外挂开发(30小时精通C++和外挂实战)

项目实战——外挂开发&#xff08;30小时精通C和外挂实战&#xff09; 外挂开发1-监控游戏外挂开发2-秒杀僵尸外挂开发3-阳光地址分析外挂开发4-模拟阳光外挂开发5-无限阳光 外挂开发1-监控游戏 外挂的本质 有两种方式 1&#xff0c;修改内存中的数据 2&#xff0c;更改内存中…

【Stable Diffusion】AI生成新玩法:图像风格迁移

【Stable Diffusion】 AI生成新玩法&#xff1a;图像风格迁移 1 背景导入 你是否曾梦想过让自己融入梵高的星空之中 或是将一幅风景画赋予毕加索的立体主义之魂 还是把人物送进宫崎骏的动画世界&#xff1f; 下面让我们来看看如何通过 Stable Diffusion 实现在图像中玩…

Java面试八股之什么是声明式事务管理,spring怎么实现声明式事务管理?

什么是声明式事务管理&#xff0c;spring怎么实现声明式事务管理&#xff1f; 声明式事务管理是一种编程范式&#xff0c;它允许开发人员通过声明性的配置或注解&#xff0c;而不是硬编码事务处理逻辑&#xff0c;来指定哪些方法或类应该在其上下文中执行事务。这种方法将事务…

【Gin】深度解析:在Gin框架中优化应用程序流程的责任链设计模式(上)

【Gin】深度解析&#xff1a;在Gin框架中优化应用程序流程的责任链设计模式(上) 大家好 我是寸铁&#x1f44a; 【Gin】深度解析&#xff1a;在Gin框架中优化应用程序流程的责任链设计模式(上)✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 本次文章分为上下两部分&#xf…

学习React(描述 UI)

React 是一个用于构建用户界面&#xff08;UI&#xff09;的 JavaScript 库&#xff0c;用户界面由按钮、文本和图像等小单元内容构建而成。React 帮助你把它们组合成可重用、可嵌套的 组件。从 web 端网站到移动端应用&#xff0c;屏幕上的所有内容都可以被分解成组件。在本章…

MongoDB教程(二十一):MongoDB大文件存储GridFS

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、GridFS…

Windows版MySQL5.7解压直用(如何卸载更换位置重新安装)

文章目录 停止mysql进程及服务迁移整个mysql文件夹删除data重启计算机重新安装 停止mysql进程及服务 net stop mysql mysqld -remove mysql迁移整个mysql文件夹 删除data 重启计算机 shutdown -r -t 0重新安装 https://blog.csdn.net/xzzteach/article/details/137723185

H3CNE(vlan的基础配置)

目录 9.1 传统以太网的问题 9.2 VLAN基础实现的原理 示例一&#xff08;vlan配置的基础实现&#xff09;&#xff1a; 示例二&#xff08;交换机间配置trunk&#xff09;&#xff1a; 9.3 hybrid接口类型与打标签的原理 示例三&#xff08;配置hybrid接口&#xff09;&#x…

【深度学习】LLaMA-Factory 大模型微调工具, 大模型GLM-4-9B Chat ,微调与部署 (2)

文章目录 数据准备chat评估模型导出模型部署总结 资料&#xff1a; https://github.com/hiyouga/LLaMA-Factory/blob/main/README_zh.md https://www.53ai.com/news/qianyanjishu/2015.html 代码拉取&#xff1a; git clone https://github.com/hiyouga/LLaMA-Factory.git cd …

STM32串口(串口基础)

串口整个东西可以说但凡你要碰单片机&#xff0c;想做点上点档次的东西的话那你就包用它的。32的串口配置并不难&#xff0c;哪怕是比起51其实也难不到哪去。 目录 一.通信基础 1.通信方式 2.通信速率 二.串口基础 1.串口的数据帧结构&#xff08;协议&#xff09; 2.ST…

【Godot4.2】GodotXML插件 - 解析和生成XML

概述 近期在研究Godot的XML和SVG解析&#xff0c;Godot提供了XMLParser类型&#xff0c;但本身只提供了低级的XML解析接口和功能&#xff0c;想要完成完整的XML文档解析和存储可能还需要在其基础上编写类或功能&#xff0c;就像我在昨天&#xff08;2024年7月20日&#xff09;…

Web开发:图片九宫格与非九宫格动态切换效果(HTML、CSS、JavaScript)

目录 一、业务需求 二、实现思路 三、实现过程 1、基础页面 2、图片大小调整 3、图片位置调整 4、鼠标控制切换 5、添加过渡 四、完整代码 一、业务需求 默认显示基础图片&#xff1b; 当鼠标移入&#xff0c;使用九宫格效果展示图片&#xff1b; 当鼠标离开&#…

基于Cobbler实现多版本系统批量部署

一、实验题目 基于Cobbler实现多版本系统批量部署 二、实验目的 通过Cobbler&#xff0c;实验旨在实现无需人工干预即可自动安装多个版本的操作系统。这可以大大提高机房设备或服务器集群的部署效率&#xff0c;减少人力成本和操作错误。 三、实验环境 centos7.9并安装Cob…