IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> block底层探索 -> 正文阅读

[移动开发]block底层探索

今天我们继续探索block底层原理,为什么会造成循环引用,block的原理,block的结构等。

一. block的底层

1、block的循环引用

-(instancetype)init {
? ? if (self = [super init]) {
? ? ? ? void(^block)(void) = ^{
? ? ? ? ? ? _name;
? ? ? ? };
? ? }
? ? return self;
}
复制代码

这段代码会循环引用吗?答应是:会。为什么呢?
现在我们用clang来看看源码:

NSObject *objc = [NSObject new];
? ? void (^block)(void) = ^ {
? ? ? ? NSLog(@"%@",objc);
? ? };
复制代码

clang -rewrite-objc main.m后查看源码: 第一个问题得到答应:_name在block捕获中又变成了self。所以还是会循环引用。
那要如何解决循环引用呢?

? ? self.block = ^{
? ? ? ? weakself.name = @"lg";
? ? };
复制代码

还有一个小细节,如果在block中使用延迟操作:

__weak typeof(self) weakself = self;
? ? self.name = @"lg";
? ? self.block = ^{
? ? ? ? dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
? ? ? ? ? ? NSLog(@"%@",weakself.name);
? ? ? ? });
? ? };
**2022-06-19 17:41:31.215393+0800 Block的循环引用[13967:532378] -[LGViewController dealloc]**
**2022-06-19 17:41:32.146640+0800 Block的循环引用[13967:532378] (null)**
复制代码

最后打印结果为null,name被提前释放了?怎么办和我们想要的效果不一样啊。为解决这个问题使用了__strong在block在强引一次。

self.name = @"lg";
? ? __weak typeof(self) weakSelf = self;
? ? self.block = ^{
? ? ? ? __strong typeof(weakSelf) strongSelf = weakSelf;
? ? ? ? NSLog(@"%p",&strongSelf);
? ? ? ? dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
? ? ? ? ? ? NSLog(@"%@",strongSelf.name);
? ? ? ? });
? ? };
? ? self.block();
**2022-06-19 17:45:21.113499+0800 Block的循环引用[14067:535530] 0x7ff7b0cdaf78**
**2022-06-19 17:45:24.113992+0800 Block的循环引用[14067:535530] lg**
**2022-06-19 17:45:24.114181+0800 Block的循环引用[14067:535530] -[LGViewController dealloc]**
复制代码

发现打印先打印了lg,在执行dealloc释放。还有其他方法可以打破循环引用吗?当然有:使用__block

self.name = @"lg";
? ? __block LGViewController *vc = self;
? ? self.block = ^{
? ? ? ? dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
? ? ? ? ? ? NSLog(@"%@",vc.name);
? ? ? ? ? ? vc = nil; //手动释放
? ? ? ? });
? ? };
? ? self.block();
**2022-06-19 17:55:33.469556+0800 Block的循环引用[14313:543068] lg**
**2022-06-19 17:55:33.469761+0800 Block的循环引用[14313:543068] -[LGViewController dealloc]**
复制代码

还有一种block传参的方式:

    self.name = @"lg";
? ? self.block = ^(LGViewController *vc){
? ? ? ? dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
? ? ? ? ? ? NSLog(@"%@",vc.name);
? ? ? ? });
? ? };
? ? self.block(self);
**2022-06-19 17:59:17.416274+0800 Block的循环引用[14413:546339] lg**
**2022-06-19 17:59:17.416459+0800 Block的循环引用[14413:546339] -[LGViewController dealloc]**
复制代码

block不会捕获传入的参数。只会捕获外部的变量。

1.__strong 2.__block 3.block传参数中方式都能解决block循环引用问题。
现在看几个例题:(1.下面代码会循环引用吗)

static LGViewController *_staticSelf;
-(void)test1 {
? ? __weak typeof(self) weakSelf = self;
? ? _staticSelf = weakSelf;
}
复制代码

答案是:会 ,为什么呢?因为weakSelf 指向了self的内存地址,然后_staticSelf静态的变量又指向了weakSelf=self;_staticSelf静态的变量的生命周期是整个程序的生命周期所以self被_staticSelf所持有,无法在当前的controller的生命周期释放。
(2.下面代码会循环引用吗)

__weak typeof(self) weakSelf = self;
? ? self.block1 = ^{
? ? ? ? __strong typeof(weakSelf) strongSelf = weakSelf;
? ? ? ? NSLog(@"%p", &strongSelf);
? ? ? ? weakSelf.block2 = ^{
? ? ? ? ? ? NSLog(@"%@", strongSelf);
? ? ? ? };
? ? ? ? weakSelf.block2();
? ? };
? ? self.block1();
复制代码

答案是:会 ,为什么呢?因为在weakSelf.block2是强引用又在捕获了一次self的地址。虽然在外部block1中会对weakSelf进行一次计数--,但是block2已经又累加了一次。所以self无法释放。

2、block的原理

1、block的本质

    int a = 10;
? ? void (^block)(void) = ^{
? ? ? ? NSLog(@"%d",a);
? ? };
? ? block();
复制代码

同上面一样用clang -rewrite-objc main.m后查看源码:

去找第一个参数:__main_block_func_0->__main_block_impl_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
? int a = __cself->a; // bound by copy
? ? ? ? NSLog((NSString *)&__NSConstantStringImpl__var_folders_v9_ry9xdp0n3mb9mfz00s2ckzt80000gn_T_main_93da1c_mi_0,a);

? ? }
复制代码

最后需要用block()来调用FuncPtr的代码块
那么继续分析代码:

int b = 20;
static int c = 30;
int main(int argc, const char * argv[]) {
? ? static int a = 10;
? ? __block NSObject *objc = [NSObject new];
? ? NSLog(@"%@",objc);
? ? void(^block)(void) = ^{
? ? ? ? a ++;
? ? ? ? b ++;
? ? ? ? c ++;
? ? ? ? NSLog(@"%d %d %d %@",a,b,c,objc);
? ? };
? ? NSLog(@"%@",block);
? ? block();

? ? return NSApplicationMain(argc, argv);
}
**2022-06-19 20:54:45.564308+0800 Block[18008:647433] <NSObject: 0x600001664280>**
**2022-06-19 20:54:45.564446+0800 Block[18008:647433] <__NSMallocBlock__: 0x600001a389f0>**
**2022-06-19 20:54:45.564509+0800 Block[18008:647433] 11 21 31 <NSObject: 0x600001664280>**
复制代码

block为什么可以修改,全局和静态变量的值呢? clang -rewrite-objc main.m后查看源码: 由此可见,static int a 和 int a 捕获的方式不一样,static a是捕获地址指针, a 完全是一个新的a和内存地址。__block不可以?于修饰静态变量和全局变量。

struct __Block_byref_objc_0 {
? void *__isa;
__Block_byref_objc_0 *__forwarding;
?int __flags;
?int __size;
?void (*__Block_byref_id_object_copy)(void*, void*);
?void (*__Block_byref_id_object_dispose)(void*);
?NSObject *objc;
};
复制代码

从源码看到:__attribute__((__blocks__(byref))) __Block_byref_objc_0 objc = 创建的block 进行赋值给__Block_byref_objc_0 ;

查看源码可知:

case BLOCK_FIELD_IS_BYREF:
? ? ? ? /******
?? ? ? ? // copy the onstack __block container to the heap
?? ? ? ? // Note this __weak is old GC-weak/MRC-unretained.
?? ? ? ? // ARC-style __weak is handled by the copy helper directly.
?? ? ? ? __block ... x;
?? ? ? ? __weak __block ... x;
?? ? ? ? [^{ x; } copy];
?? ? ? ? ********/
?? ? ? ? // 使 dest 指向的拷贝到堆上的byref
? ? ? ? *dest = _Block_byref_copy(object);
? ? ? ? break;
复制代码

如果参数是__block 修饰的参数就会执行_Block_byref_copy

block初始化的时候在栈上, 当copy到堆上时,整个block结构体包括捕获的对象进行拷贝到堆上。原来的 __forwording也会指向堆上的 byref。dest 二级指针,最终指向堆上的byrefBlock_layout 类型的结构体: 小结: block的本质是?个 Block_layout 类型的结构体copy和dispose函数是?来对block内部的对象进?内存管理的,block拷?到堆上会调?copy函数,在block从堆上释放的时候会调?dispose函数。

2、block的底层原理

进入源码:

// 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;
? ? }
}
复制代码

通过下面代码:

    int a = 7;
? ? void (^block)(void) = ^{
? ? ? ? NSLog(@"%d",a);
? ? };
? ? block();
复制代码

读取寄存器打印: 添加符号断点:objc_retainBlock->_Block_copy

NSObject *objc = [NSObject new];
? ? void (^block)(void) = ^{
? ? ? ? NSLog(@"%@",objc);
? ? };
? ? block();
复制代码

重复上面操作(查看寄存器):

_Block_copy只会在捕获到外部变量(对象类型的变量)的时候才会有copy和dispose函数。 看下Block_descriptor_2是如何获取的:

// 取得 block 中的 Block_descriptor_2,它藏在 descriptor 列表中
// 调用者:_Block_call_copy_helper() / _Block_call_dispose_helper
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
? ? // Block_descriptor_2 中存的是 copy/dispose 方法,如果没有指定有 copy / dispose 方法,则返回 NULL
? ? if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
? ? // 先取得 Block_descriptor_1 的地址
? ? uint8_t *desc = (uint8_t *)aBlock->descriptor;
? ? // 偏移 Block_descriptor_1 的大小,就是 Block_descriptor_2 的起始地址
? ? desc += sizeof(struct Block_descriptor_1);
? ? return (struct Block_descriptor_2 *)desc;
}
复制代码

偏移 Block_descriptor_1 的大小,就是 Block_descriptor_2 的起始地址
同理Block_descriptor_3的获取就是 偏移 Block_descriptor_2 的大小,就是Block_descriptor_3的起始地址了。

通过一个小例子(巩固知识体系):

- (void)blockDemo1{
? ? int a = 1;
? ? void(^ __weak weakBlock)(void) = ^{
? ? ? ? NSLog(@"-----%d", a);
? ? };
? ? struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
? ? void(^strongBlock)(void) = weakBlock;
? ? blc->invoke = nil;
? ? strongBlock();
}

//自定义的Block结构
struct _LGBlock {
? ? void *isa;
? ? volatile int32_t flags; // contains ref count
? ? int32_t reserved;
? ? // 函数指针
? ? LGBlockInvokeFunction invoke;
? ? struct _LGBlockDescriptor1 *descriptor;
};
复制代码

分析:栈weakBlock 打印了a,然后赋值给了strongBlock 堆block指针,在把栈weakBlock的invoke = nil; 调用strongBlock() 应该是会崩溃报错。(因为strongBlock其实指向的还是weakBlock的内存地址,调用也是weakBlock->invoke 这时候invoke已经被赋值为nil了)

3、总结

1.block的本质:
block的本质是?个 Block_layout 类型的结构体。copy和dispose函数是?来对block内部的对象进?内存管理的,block拷?到堆上会调?copy函 数,在block从堆上释放的时候会调?dispose函数。

2.block的底层原理:
?__block修饰的变量在编译过后会变成 __Block_byref__XXX 类型的结构体,在结构体内部有?个 __forwarding 的结构体指针,指向结构体本身

block创建的时候是在栈上的,在将栈block拷?到堆上的时候,同时也会将block中捕获的对象拷?到堆上,然后就会将栈上的__block修饰对象的__forwarding指针指向堆上的拷?之后的对象。这样我们在block内部修改的时候虽然是修改堆上的对象的值,但是因为栈上的对象的__forwarding指针将堆和栈的对象链接起来。因此就可以达到修改的?的。

expression指令:简写为 e ,能够在调试时,动态的修改变量的值,同时打印出结果,还可以动态调?函数。它会实时的真正的执?后?的代码。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-08-06 10:55:38  更:2022-08-06 10:56:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 4:38:16-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码