【iOS】Block底层分析

目录

    • 前言
    • Block底层结构
    • Block捕获变量原理
      • 捕获局部变量(auto、static)
      • 全局变量
      • 捕获实例`self`
    • Block类型
    • Block的copy
      • Block作为返回值
      • 将Block赋值给__strong指针
      • Block作为Cocoa API中方法名含有usingBlock的方法参数
      • Block作为GCD API的方法参数
      • Block属性的写法
    • Block访问对象类型的auto变量
      • Block在栈上
      • Block被拷贝到堆上
      • Block从堆上移除
    • 修饰符__block
      • __block内存管理
      • __forwarding指针
      • __block修饰对象类型
    • Block循环引用
      • 解决办法
      • 强弱共舞
    • 总结


前言

Block是带有局部变量的匿名函数,函数实现就是代码块里的内容,同样有参数和非返回值,本质是一个封装了函数调用以及函数调用环境的OC对象,因为它内部有isa指针

Block的基本使用请看这两篇文章:

  • k
  • l

本篇文章着重探究Block这些特性的底层原理

Block底层结构

声明一个最简单的块并调用:

void (^block)(void) = ^{NSLog(@"Hello World!");
};
block();

使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m命令将OC代码转换成C++代码:

// 原本的代码有各种强制转换,目前不重要,先删去从简// 声明并实现一个block
// void (*block)(void) = ((void (*)(int, int))&__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);// 调用执行block
// ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
block->FuncPtr(block);
// __main_block_impl_0可以直接转换为__block_impl类型,是因为两个类型的结构体地址是一样的,而且相当于直接把__block_impl里的值都放到__main_block_impl_0里

这些穿插了许多下划线的符号实际上是不同的结构体变量,Block本质就是struct __main_block_impl_0类型的结构体,下图清晰地说明了block的底层结构:

在这里插入图片描述
__main_block_impl_0可以直接转换为__block_impl类型,是因为两个类型的结构体地址是一样的(相当于直接把__block_impl里的值都放到__main_block_impl_0里)
所以block.impl->FuncPtr(block)就相当于block->FuncPtr(block)

Block捕获变量原理

为了保证block内部能够正常访问外部的变量,block有个变量捕获机制

捕获局部变量(auto、static)

auto:自动变量,离开作用域就自动销毁,只存在于局部变量
static:静态局部变量

// 不加关键字默认是auto变量
/*auto*/ int age = 10;
static int height = 175;void (^block)(void) = ^{// age、height的值捕获进来(capture))NSLog(@"age is %d, height is %d", age, height);
};// 修改局部变量的值
age = 20;
height = 180;block();
NSLog(@"%d %d", age, height);

打印结果:

在这里插入图片描述

可以看到age仍为修改前的值,而height确确实实被修改了

将以上代码转换成C++代码来看一下:

struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;int age;int *height;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
  • Block结构体的变量多了两个,分别是ageheight,这说明外部的变量被捕获到了Block的内部
  • 构造函数后面的 : age(_age), height(_height)语法会自动将_age、_height赋值给int age、int* height来保存

声明实现Block调用析构函数:

int age = 10;
static int height = 175;block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &height));age = 20;
height = 180;

而后调用Block,实际调用__main_block_func_0

block->FunPtr(block)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int age = __cself->age; // bound by copyint *height = __cself->height; // bound by copyNSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age, (*height));
}

此时的age是值传递,打印的只是Block初始化时传进去的,后面age修改跟这个值无关;height是指针传递,打印的是height变量地址一直所指向那块内存的值

全局变量

int age_ = 10;
static int height_ = 175;int main(int argc, const char * argv[]) {@autoreleasepool {void (^block)(void) = ^{NSLog(@"age_ is %d, height_ is %d", age_, height_);};age_ = 20;height_ = 180;block();}return 0;
}

全局变量一直在内存中,打印的一直是最新的值,不用捕获

在这里插入图片描述

为什么会有这样的差异呢?

auto和static:因为作用域的问题,自动变量的内存随时可能被销毁,所以要捕获就赶紧把它的值拿进来,防止调用的时候访问不到;静态变量就不一样了,它一直在内存中(作用域仅限于定义它们的函数、它们不能在函数外访问),随时可以通过指针访问到最新的值

全局变量:在Block中访问局部变量相当于是跨函数访问,要先将变量存储在Block里(捕获),使用的时候再从Block中取出,而全局变量是直接访问

捕获实例self

- (void)testSelf {void (^block)(void) = ^{// NSLog(@"--------%p -- %p -- %p -- %p", self, _name, self->_name, self.name);NSLog(@"--------%p", self);/*NSLog(@"--------%p", self->_name);相当于NSLog(@"--------%p", _name);也会捕获进去*/};block();
}

看了它的C++实现后,发现self也会被捕获进去

实际上OC方法转换成C++函数后会发现前两个参数永远是方法调用者self、方法名_cmd

void testSelf(Person* self, SEL _cmd, ) {// ...
}

即然self是参数,参数也是局部变量,它被捕获进Block也就能解释得通了

Block类型

上面提到Block是OC对象,因为它有isa指针,对象的isa指向它的类型,那么Block都有什么类型呢?

首先运行以下代码:

void (^block)(void) = ^{NSLog(@"Hello!");
};
NSLog(@"%@ %@", block, [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
/*__NSGlobalBlock__NSBlockNSObject*/

可以看到Block类型的根类是NSObject,也能说明Block是一个OC对象

不同操作对应的Block类型不同

// Global:没有访问auto变量,跟static变量无关
void (^block1)(void) = ^{NSLog(@"Hello");
};// 函数调用栈:要调用一个函数的时候,就会指定一块栈区空间给这个函数用
// 一旦函数调用完毕后,栈区的这块空间就会回收,变成垃圾数据,会被其他数据覆盖// Stack:访问了auto变量
int age = 21;
void (^block2)(void) = ^{NSLog(@"Hello - %d", age);
};
// ARC下打印Malloc?MRC下确实是StackNSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{NSLog(@"%d", age);
} class]); // 打印结果:__NSGlobalBlock__ __NSStackBlock__ __NSStackBlock__// 编译完成后isa指向是_NSConcreteStackBlock、_NSConcreteMallocBlock、_NSConcreteGlobalBlock
// 首先肯定以运行时的结果为准,Block确实有三种类型,可能会通过Runtime动态修改类型
  • 没有访问自动变量的Block类型是__NSGlobalBlock__,存储在数据段
    其实Global不常用,既然不访问变量,那么将代码块封装成函数一行直接调用才显得更为简洁

  • 访问了自动变量的Block类型是__NSStackBlock__,存储在栈区
    以上代码是在MRC下运行的

  • __NSStackBlock__的Block调用了copy后类型会变为__NSMallocBlock__,存储在堆区
    若是在ARC下运行,即使不用copy修饰编译器也会自动对__NSStackBlock__进行copy操作,block2的类型将会是Malloc类型

手动对每种类型的Block调用copy后的结果如下图所示

请添加图片描述

Block的copy

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上
放到堆上的目的是方便我们来控制他的生命周期,可以更有效的进行内存管理

Block作为返回值

typedef void(^BBlock)(void);BBlock myBlock(void) {int age = 21;return ^{NSLog(@"----------%d", age);};
}BBlock bblock = myBlock();
bblock();
NSLog(@"%@", [bblock class]); // __NSMallocBlock__
//BBlock myBlock(void) {
//    return [^{
//        NSLog(@"----------");
//    } copy];
//}

由于Block在栈区,所以函数调用完毕后Block内存就被销毁了,再去调用它就很危险,如果在MRC下运行上述代码,编译器会提示报错:

在这里插入图片描述

ARC下不必担心此问题,编译器会自动对返回的Block进行copy操作(如注释所写),返回拷贝到堆上的Block

将Block赋值给__strong指针

int age = 21;
/*__strong*/ BBlock bblock = ^{NSLog(@"--------%d", age);
};
NSLog(@"%@", [bblock class]);  // ARC:__NSMallocBlock__// 没有被强指针指着
NSLog(@"%@", [^{NSLog(@"--------%d", age);
} class]); // __NSStackBlock__

Block作为Cocoa API中方法名含有usingBlock的方法参数

NSArray* array = @[@"one", @2, @{@"seven" : @7}];
// 遍历数组并调用Block
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {NSLog(@"%@ --- %lu", obj, (unsigned long)idx);
}];

在这里插入图片描述

Block作为GCD API的方法参数

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{});

Block属性的写法

因为编译器会自动视情况进行copy操作,所以两种写法都没问题,只是为了统一规范建议使用copy来修饰属性

@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);

Block访问对象类型的auto变量

Block在栈上

只要Block存在栈上,无论访问外部变量是用强指针还是弱指针,都不会对外部auto变量产生强引用

Block被拷贝到堆上

如果Block被拷贝到堆上,会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作

BBlock bblock;
{__strong Person* person = [[Person alloc] init];// __weak Person* person = [[Person alloc] init];person.age = 21;bblock = ^{// 在ARC环境下block会自动拷贝到堆区间,切换修饰符__strong和__weak,person分别会不释放和释放NSLog(@"-%d-", person.age);};// MRC环境下block是在栈区间的,所以不会对age进行强引用,person会随着作用域结束而释放//[bblock release];
}
NSLog(@"--------------");

将上面代码文件转换成C++文件:

struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;Person *__strong person;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};static struct __main_block_desc_0 {size_t reserved;size_t Block_size;void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

Block内部的__main_block_desc_0结构体会调用copy函数,copy函数内部会调用_Block_object_assign函数,而_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

Block从堆上移除

如果Block从堆上移除,会调用Block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动release引用的auto变量

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

注:

  • 只有在引用对象类型的变量时,才会生成copydispose函数
  • 如果引用的是static修饰的对象类型,那么捕获的变量在C++代码中将会是Person *__strong *person;
  • 代码里有__weak,转换C++文件可能会报错cannot create __weak reference in file using manual reference,可以指定支持ARC、指定运行时系统版本xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

使用GCD API验证Block对外部变量的强弱引用(Github Demo):

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {Person* person = [[Person alloc] init];__weak Person* weakPerson = person;// 强引用了,Block调用完毕释放了person才会释放
//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//        NSLog(@"---%@", person);
//    });// 弱引用,调用Block之前person已经释放
//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//        NSLog(@"---%@", weakPerson);
//    });// 编译器已经检查到会有强引用
//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//        NSLog(@"---1%@", weakPerson);
//        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//            NSLog(@"---2%@", person);
//        });
//    });// 不会等到弱引用就释放了dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"---1%@", person);dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"---2%@", weakPerson);});});NSLog(@"Screen Touched");
}

修饰符__block

如果在Block内部修改捕获的auto变量值,编译器将会报错:

int age = 21;
BBlock block = ^{age = 20;NSLog(@"%d", age);
};
block();

在这里插入图片描述

从底层可看出在这里修改变量的值,实际上是通过改变__main_block_fun_0函数里的局部变量达到改变main函数里的变量,这是两个独立的函数,显然不可能

1. 使用static修饰变量

static来修饰age属性,底层用指针访问,block内部引用的是age的地址值,函数间会传递变量的地址,可以根据地址去修改age的值,修改的就是同一块内存
但不好的是age属性会一直存放在内存中不销毁,造成多余的内存占用,而且会改变age属性的性质,不再是一个auto变量

2. 使用__block修饰变量

__block来修饰属性,底层会生成__Block_byref_age_0类型的结构体对象,里面存储着age的真实值

在这里插入图片描述

转换成C++文件来查看内部结构,经__block修饰后,会根据__main_block_impl_0里生成的age对象来修改内部的成员变量age而且在外面打印的age属性的地址值也是__Block_byref_age_0结构体里的成员变量age的地址,目的就是不需要知道内部的真实实现,所看到的就是打印出来的值

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 struct __main_block_desc_0 {size_t reserved;size_t Block_size;void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; // 传进去的是age的地址__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, (__Block_byref_age_0 *)&age, 570425344));((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);}return 0;
}

总结

  • __block可以用于解决block内部无法修改auto变量值的问题
  • 编译器会将__block变量包装成一个对象
  • 其实修改的变量是__block生成的对象里面存储的变量的值,而不是外面的auto变量,但是内部生成的相同的变量的地址和外面的auto变量地址值是一样的,所以修改了内部的变量也会修改了外面的auto变量
  • __block不能修饰全局变量、静态变量(static)

__block内存管理

程序编译时,block__block都是在栈中的,这时并不会对__block变量产生强引用

因为__block也会包装成 OC对象,所以block底层也会生成copy函数dispose函数

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复制到堆上

blockcopy到堆时,会调用block内部的copy函数copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对__block变量形成强引用(retain)
实际上,这时__block修饰的变量因为被包装成了OC对象,所以也会被拷贝到堆上,如果再有block强引用__block,由于__block变量已经拷贝到堆上了,就不会再拷贝了

在这里插入图片描述

Block从堆上移除

block从堆中移除时,会调用block内部的dispose函数dispose函数内部会调用_Block_object_dispose函数,_Block_object_dispose函数会自动释放引用的__block变量(release)

如果有多个block同时持有着__block变量,那么只有所有的block都从堆中移除了,__block变量才会被释放

在这里插入图片描述

__block和OC对象在block中的区别

__block生成的对象就是强引用,而NSObject对象会根据修饰符__strong或者__weak来区分是否要进行retain操作

注意:__weak不能修饰基本数据类型,编译器会报__weak' only applies to Objective-C object or block pointer types; type here is 'int'警告

__forwarding指针

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;
}
  • 在栈中,__block中的__forwarding指针指向自己的内存地址
  • 复制到堆中之后,__forwarding指针指向堆中的__block,堆中的__forwarding指向堆中的__block
  • 这样的目的都是为了不论访问的__block是在栈上还是在堆上,都可以通过__forwarding指针找到存储在堆中的auto变量

在这里插入图片描述

保证20被存储在堆中Block所引用的变量

__block修饰对象类型

情况类似于Block捕获对象类型的auto变量,__block包装的对象结构体里的对象变量会有__strong__weak修饰

__block对象在栈上时,不会对指向的对象产生强引用

__block对象被copy到堆上时,也会生成一个新的结构体对象,并且只会被block进行强引用,会根据不同的修饰符__strong__weak来对应着该对象类型成员变量是被强引用(retain)或弱引用

struct __Block_byref_weakPerson_0 {void __isa;__Block_byref_weakPerson_0 __forwarding;int __flags;int __size;void (__Block_byref_id_object_copy)(void, void);void (__Block_byref_id_object_dispose)(void*);Person *__weak weakPerson;
};static void __Block_byref_id_object_copy_131(void *dst, void src) {
_Block_object_assign((char)dst + 40, *(void * ) ((char)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void src) {
_Block_object_dispose((void * ) ((char)src + 40), 131);// __Block_byref_weakPerson_0 weakPerson = {0, &weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};
__attribute__((__blocks__(byref))) __Block_byref_weakPerson_0 weakPerson = {(void*)0,(__Block_byref_weakPerson_0 *)&weakPerson, 33554432, sizeof(__Block_byref_weakPerson_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, person};

注:在MRC环境下即使用__block修饰,对于结构体对象的成员变量,__block内部只会对auto变量进行弱引用,无论加不加__weak,block还没有释放,__block修饰的变量就已经释放了,这点和在ARC环境下不同

Block循环引用

两个对象相互强引用,导致谁的引用计数都不会归零,谁都不会释放

int main(int argc, const char * argv[]) {@autoreleasepool {Person* person = [[Person alloc] init];person.age = 21;person.block = ^{NSLog(@"%d", person.age);};}NSLog(@"111111111111");return 0;
}

结果就是person对象不会释放,因为没有调用dealloc方法

person对象里面的block属性强引用着block对象,而block对象内部也会有一个person的成员变量指向这个Person对象,这样就会造成循环引用,谁也无法释放

@implementation Person- (void)test {self.block = ^{NSLog(@"%d", self.age);};
}- (void)dealloc
{NSLog(@"%s", __func__);
}@endint main(int argc, const char * argv[]) {@autoreleasepool {Person* person = [[Person alloc] init];person.age = 21;[person test];}return 0;NSLog(@"111111111111");
}

block引用(捕获,之前提到self就是函数的第一个参数,参数也是局部变量)selfself又持有block,同样会造成循环引用

在这里插入图片描述

解决办法

  • 使用__weak__unsafe_unretainedBlock指向对象的引用变为弱引用

    在这里插入图片描述

    //    __unsafe_unretained typeof(self)weakSelf = self;
    __weak typeof(self)weakSelf = self;self.block = ^{NSLog(@"%d", weakSelf.age);
    };
    
  • __block解决,用__block修饰对象会造成三者相互引用造成循环引用,需要手动调用block

    在这里插入图片描述

    __block Person* person = [[Person alloc] init];
    person.age = 21;
    person.block = ^{NSLog(@"%d", person.age);person = nil;
    };
    person.block();
    

    block内部也需要手动将person置空,这个person__block内部生成的指向Person对象的变量

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

    // Person.m
    self.block = ^(Person * _Nonnull person) {NSLog(@"%d", person.age);
    };
    self.block(self);
    
  • MRC下不支持__weak,只能使用__unsafe_unretained
    MRC下直接使用__block即可解决循环引用,上面提到了MRC环境下__block修饰的变量只会被弱引用,已达成效果:

    __block Person *person = [[Person alloc] init];
    person.age = 10;person.block = [^{NSLog(@"age is %d", person.age);
    } copy];[person release];
    

强弱共舞

这种情况虽没有引起循环引用,但block延迟执行2秒,等person释放后,就无法获取其age,很不合理

__weak typeof(person) weakPerson = person;
person.block = ^{dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"%d", weakPerson.age);});
};
person.block();

改进一下:

__weak typeof(person) weakPerson = person;
person.block = ^{__strong __typeof(weakPerson)strongPerson = weakPerson;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"%d", strongPerson.age);});
};
person.block();

通过运行结果发现,完全解决了以上self中途被释放的问题,这是为什么呢?分析如下:

  • 在完成block中的操作之后,才调用了dealloc方法。添加strongWeak之后,持有关系为:self -> block -> strongWeak -> weakSelf -> self
  • weakSelf被强引用了就不会自动释放,因为strongWeak只是一个临时变量,它的声明周期只在block内部,block执行完毕后,strongWeak就会释放,而弱引用weakSelf也会自动释放

总结

Block在iOS开发中极为重要,非常适合处理异步操作、回调、集合操作等场景,重点学习Block的内存管理、变量捕获和循环引用解决方案

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

光性能 -- 边模抑制比眼图

最小边模抑制比 在最坏的发射条件下&#xff0c;主模的平均光功率与最显著边模的光功率之比即最小边模抑制比。 什么是边模 理想情况下&#xff0c;光模块发射的信号应当只是有特定波长的光信号。但实际情况下不仅有一个特定波长的主信号&#xff0c;还有一些其他波长的存在&…

Java学习笔记(02)接口的使用

1、接口关键字&#xff1a;interface 2、接口内部结构的说明&#xff1a; 可以声明抽象方法&#xff0c;属性由public static final修饰&#xff0c;但都会默认。 不可以声明&#xff1a;构造器&#xff0c;代码块等。 3、格式&#xff1a;class A extends SuperA implements…

开放式耳机别人能听到吗?现在开放式耳机用防漏音效果越来越好!

回答&#xff1a; 开放式耳机的通透的设计允许一部分声音泄露出来&#xff0c;因此站在您旁边的人确实有可能听到您耳机中的声音&#xff0c;尤其是当音量设置得比较高时。开放式耳机通常提供更为自然和宽敞的听感&#xff0c;但牺牲了一定的隔音效果和隐私性。如果您需要在公…

探索提示工程 Prompt Engineering的奥妙

一、探索提示工程 1. 介绍通用人工智能和专用人工智能 人工智能&#xff08;AI&#xff09;可以分为通用人工智能&#xff08;AGI&#xff09;和专用人工智能&#xff08;Narrow AI&#xff09;。AGI是一种能够理解、学习和执行任何人类可以完成的任务的智能。与此相对&#x…

《深入浅出多模态》(八)多模态经典模型:MiniGPT4

&#x1f389;AI学习星球推荐&#xff1a; GoAI的学习社区 知识星球是一个致力于提供《机器学习 | 深度学习 | CV | NLP | 大模型 | 多模态 | AIGC 》各个最新AI方向综述、论文等成体系的学习资料&#xff0c;配有全面而有深度的专栏内容&#xff0c;包括不限于 前沿论文解读、…

【Qt】输入类控件QComboBox

目录 输入类控件QComboBox 例子&#xff1a;使用下拉框模拟点餐 例子&#xff1a;从文件中加载下拉框的选项 输入类控件QComboBox QComboBox表示下拉框 核心属性 属性说明 currentText 当前选中的⽂本 currentIndex 当前选中的条⽬下标. 从 0 开始计算. 如果当前没有条…

Deepin【2】:Deepin系统盘扩容

Deepin【2】&#xff1a;Deepin系统盘扩容 1、进入live系统1.1、live系统入步骤 2、连接网络3、新增系统仓库4、安装gparted应用5、使用gparted进行扩容操作5.1、观察当前分区5.2、压缩data分区5.3、Rootb分区合并空闲空间5.4、Rootb分区压缩空间5.5、Roota合并空闲空间5.6、核…

高速服务区公共厕所为什么要升级做智慧公厕?@卓振思众

高速服务区智慧公厕的建设&#xff0c;将为公共卫生设施带来了全新的变革&#xff0c;可实现以下功能和效益&#xff1a; 一、服务区智慧公厕功能精准厕位引导&#xff1a;高速服务区人流量大&#xff0c;尤其是在节假日等高峰时期&#xff0c;厕所常常人满为患。智慧公厕系统可…

【论文阅读】A Closer Look at Parameter-Efficient Tuning in Diffusion Models

Abstract 大规模扩散模型功能强大&#xff0c;但微调定制这些模型&#xff0c;内存和时间效率都很低。 本文通过向大规模扩散模型中插入小的学习器(称为adapters)&#xff0c;实现有效的参数微调。 特别地&#xff0c;将适配器的设计空间分解为输入位置、输出位置、函数形式的…

基于Ubuntu22.04 安装SSH服务

安全外壳协议&#xff08;Secure Shell&#xff0c;简称 SSH&#xff09;是一种在不安全网络上用于安全远程登录和其他安全网络服务的协议。 SSH 由 IETF 的网络小组&#xff08;Network Working Group&#xff09;所制定&#xff0c;SSH 为建立在应用层基础上的安全协议。SSH…

Excel“取消工作表保护”忘记密码并恢复原始密码

文章目录 1.前言2.破解步骤3. 最终效果4.参考文献 1.前言 有时候别人发来的Excel中有些表格不能编辑&#xff0c;提示如下&#xff0c;但是又不知道原始密码 2.破解步骤 1、打开您需要破解保护密码的Excel文件&#xff1b; 2、依次点击菜单栏上的视图—宏----录制宏&#xf…

音频格式转换方法有哪些?学会这3个方法让音频转换从未如此简单

在音乐的世界里&#xff0c;我们常常遇到各种格式的音频文件&#xff0c;它们就像是五线谱上的音符&#xff0c;需要不同的乐器来演绎。 热爱音乐的我&#xff0c;经常需要将这些音频文件转换成适合我设备播放的格式。在线音频格式转换工具&#xff0c;就像是我的音乐小助手&a…

【传输层协议】UDP协议 {端口号的范围划分;UDP数据报格式;UDP协议的特点;UDP的缓冲区;基于UDP的应用层协议}

一、再谈端口号 1.1 端口号标识网络进程 如何通过端口号找到主机上的网络进程&#xff1f; 在socket编程中bind绑定是最为重要的一步&#xff1a;他将套接字与指定的本地 IP 地址和端口号关联起来&#xff0c;这意味着指定的套接字可以接收来自指定 IP 地址和端口号的数据包…

收银系统源码助力零售门店数字化升级

一、国内零售业数字化转型迈入深水区 近年来&#xff0c;我国零售业数字化进程显著加速&#xff0c;从线上电商到新零售模式&#xff0c;再到利用大数据、人工智能等技术优化供应链、提升体验&#xff0c;每一步都见证了行业的深刻变革。随着零售行业进入存量市场竞争&#xf…

微服务设计原则——高性能:存储设计

文章目录 1.读写分离2.分库分表3.动静分离4.冷热分离5.重写轻读6.数据异构参考文献 任何一个系统&#xff0c;从单机到分布式&#xff0c;从前端到后台&#xff0c;功能和逻辑各不相同&#xff0c;但干的只有两件事&#xff1a;读和写。而每个系统的业务特性可能都不一样&#…

LangChain框架深度解析:对Chains组件的全方位探索与实战案例

文章目录 前言一、Chains二、LLMChain⭐1.LLMChain介绍2.LLMChain案例 三、SimpleSequentialChain⭐1.SimpleSequentialChain介绍2.SimpleSequentialChain案例 四、SequentialChain⭐1.SequentialChain介绍2.SequentialChain案例 五、RouterChain⭐1.RouterChain介绍2.RouterCh…

leetcode:2520. 统计能整除数字的位数(python3解法)

难度&#xff1a;简单 给你一个整数 num &#xff0c;返回 num 中能整除 num 的数位的数目。 如果满足 nums % val 0 &#xff0c;则认为整数 val 可以整除 nums 。 示例 1&#xff1a; 输入&#xff1a;num 7 输出&#xff1a;1 解释&#xff1a;7 被自己整除&#xff0c;因…

大模型概念入门:探索这一AI技术的奥秘

一、引言 ChatGPT、Open AI、大模型、提示词工程、Token、幻觉等人工智能的黑话&#xff0c;在2023年这个普通却又神奇的年份里&#xff0c;反复的冲刷着大家的认知。让一部分人彻底躺平的同时&#xff0c;让另外一部分人开始焦虑起来&#xff0c;生怕在这个人工智能的奇迹之年…

JRE和JDK概念区分

1.JRE Java Runtime Environment&#xff1a;java运行环境。JVMJava类库。开发好的java程序&#xff0c;直接运行&#xff0c;可只安装JRE。 2.JDK Java Development Kit&#xff1a;java软件开发工具包。JREJava开发工具。编译、运行java代码。 3.总结 JRE就是运行Java字…

跨界融合,《黑神话:悟空》这把火,能否为实景三维再造商机?

8月20号&#xff0c;国产3A游戏《黑神话&#xff1a;悟空》正式上线&#xff0c;全球发售 这几天&#xff0c;国产游戏《黑神话:悟空》终于面世&#xff0c;迅速引爆了全球游戏市场。 《黑神话&#xff1a;悟空》作为一款国产3A游戏&#xff0c;不仅在游戏设计和玩法上实现了…