您的位置:首页 > 文旅 > 美景 > iOS ------ Block的相关问题

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

2024/12/28 10:15:06 来源:https://blog.csdn.net/m0_73974056/article/details/140646869  浏览:    关键词: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,直接返回 NULLif (!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// 就只将引用计数加 1latching_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);// 开辟失败,返回 NULLif (!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 部分的位全部清为 0result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed// 将 result 标记位在堆上,需要手动释放;并且引用计数初始化为 1result->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 指向 _NSConcreteMallocBlockresult->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 copyNSLog((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 copyNSLog((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 copyNSLog((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 refNSLog((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))); // 1void(^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 copyNSLog((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))); // 1void(^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()指向的对象已经被释放了,形成野指针,所以程序崩溃了。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com