【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.mzph.cn/pingmian/51818.shtml

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

相关文章

使用QGraphicsView思想做一个简单图片查看器

使用QGraphicsView思想做一个简单图片查看器 如果要做一个图片查看器&#xff0c;支持放大、滚动操作&#xff0c;比较直接的方法是&#xff0c;使用QWidget来显示完整图片&#xff0c;将QWidget放入QScrollArea。缩放时调整QWidget的尺寸&#xff0c;QScrollArea会自动调整滚…

MBR20200FCT-ASEMI智能AI专用MBR20200FCT

编辑&#xff1a;ll MBR20200FCT-ASEMI智能AI专用MBR20200FCT 型号&#xff1a;MBR20200FCT 品牌&#xff1a;ASEMI 封装&#xff1a;TO-220F 批号&#xff1a;最新 最大平均正向电流&#xff08;IF&#xff09;&#xff1a;20A 最大循环峰值反向电压&#xff08;VRRM&a…

别再问了!微信小程序的那些事儿,一文搞定

微信小程序是一种无需下载安装即可使用的应用&#xff0c;它嵌入在微信生态中&#xff0c;用户通过微信扫一扫或搜索即可快速访问。 无论是购物、订餐、预约服务&#xff0c;还是玩个小游戏、看篇文章&#xff0c;都不需要下载额外的APP&#xff0c;直接就能在微信里搞定。不会…

联想电脑如何查看ip地址?详细介绍几种方法

随着互联网的普及和技术的飞速发展&#xff0c;IP地址已成为我们日常网络活动中不可或缺的一部分。无论是访问网站、远程办公还是进行网络游戏&#xff0c;IP地址都扮演着重要的角色。对于联想电脑用户来说&#xff0c;了解如何查看自己的IP地址是一项基本技能。虎观代理小二将…

JSON Web Token (JWT): 理解与应用

JWT&#xff08;JSON Web Token&#xff09;是一种开放标准&#xff08;RFC 7519&#xff09;&#xff0c;它定义了一种紧凑且自包含的方式&#xff0c;用于在各方之间以JSON对象的形式安全地传输信息。JWT通常用于身份验证和授权目的&#xff0c;因为它可以使用JSON对象在各方…

【向量数据库】Ubuntu编译安装FAISS

参考官方的安装指导&#xff1a;https://github.com/facebookresearch/faiss/blob/main/INSTALL.md&#xff0c;不需要安装的可以跳过 ~$ wget https://github.com/facebookresearch/faiss/archive/refs/tags/v1.8.0.tar.gz ~$ tar -zxvf v1.8.0.tar.gz ~$ cd faiss-1.8.0 ~$ …

易基因:RNA修饰N4-乙酰胞苷(ac4C)的调控机制、检测方法及其在癌症中的作用最新研究进展|新方向

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 N4-乙酰胞苷&#xff08;ac4C&#xff09;是一种高度保守的化学修饰&#xff0c;广泛存在于真核和原核生物RNA中&#xff0c;如tRNA、rRNA和mRNA。这种修饰与多种人类疾病显著相关&#…

vuex的原理和使用方法

简介 Vuex 是 Vue.js 应用的状态管理模式&#xff0c;它为应用内的所有组件提供集中式的状态&#xff08;数据&#xff09;管理。可以帮我们管理 Vue 通用的数据 (多组件共享的数据)。 Vuex的构成 state&#xff1a;state 是 Vuex 的数据中心&#xff0c;也就是说state是用来…

职业院校云计算实训室建设方案全景剖析

在信息化社会的今天&#xff0c;云计算作为一项关键技术&#xff0c;正在迅速改变着教育和培训的方式。本文旨在探讨如何通过"职业院校云计算实训室建设方案"&#xff0c;为学生提供一个现代化、高效的学习和研究环境&#xff0c;以适应云计算技术的发展和市场需求。…

软件测试---接口测试

一、接口及接口测试概念 &#xff08;1&#xff09;接口的类型 &#xff08;2&#xff09;接口测试的概念 &#xff08;3&#xff09;接口测试的原理 &#xff08;4&#xff09;接口测试的特点 &#xff08;5&#xff09;接口测试的实现方式 二、HTTP协议 &#xff08;1&#…

Qt 实现抽屉效果

1、实现效果和UI设计界面 2、工程目录 3、mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include <QToolButton> #include <QPushButton> #include <vector> using namespace std;QT_BEGIN_NAMESPACE namespace…

生成式:PolyGen: An Autoregressive Generative Model of 3D Meshes【附件】

论文:PolyGen: An Autoregressive Generative Model of 3D Meshes OBJ坐标变换: # Transpose so that z-axis is vertical.vertices = vertices[:, [2, 0, 1]]变换前: 对应数据:

C++模板(初阶)

1.引入 在之前的笔记中有提到&#xff1a;函数重载&#xff08;特别是交换函数&#xff08;Swap&#xff09;的实现&#xff09; void Swap(int& left, int& right) {int temp left;left right;right temp; } void Swap(double& left, double& right) {do…

小程序开发_02

一、项目的基本结构 二、小程序的页面组成部分 三、json配置文件 ① project.config.json文件 作用&#xff1a;项目的配置文件&#xff0c;用来记录对小程序开发工具所作的个性化配置 ② sitemap.json 作用&#xff1a;是否允许被微信引擎搜索,不希望被搜索dis ③ app.jso…

基于C51和OLED12864实现贪吃蛇小游戏

引言 在微电子技术飞速发展的今天&#xff0c;单片机作为智能控制的核心&#xff0c;广泛应用于各种电子设备中。C51系列单片机以其高效、稳定的特性&#xff0c;成为众多电子爱好者和工程师的首选平台。而OLED显示屏以其轻薄、低功耗、响应速度快等优点&#xff0c;在显示设备…

springcloud接入seata管理分布式事务

下载安装包 链接: seata 配置seata-server 文件上传Linux解压 压缩包我放在/usr/local/seata中 tar -zxvf seata-server-2.0.0.tar.gz修改配置文件 设置nacos为注册和配置中心 进入文件夹 cd /usr/local/seata/seata/conf修改application.yml文件 ...... ...... cons…

CST软件如何进行参数化扫描?

在用CST进行仿真设计的过程中&#xff0c;经常需要对某一些参数进行参数化设置&#xff0c;并对这些参数进行仿真对比。这一期&#xff0c;我们介绍下如何进行参数化扫描。 还是借用&#xff0c;之前已经对馈电位置、贴片的长和宽都进行了参数设置&#xff0c;如下图所示&…

计算机网络HTTP全讲解,让你透彻掌握HTTP协议(三)http长短连接/代理/网关/缓存/内容协商机制/断点续传

HTTP HTTP的长连接与短连接短链接长链接HTTP代理代理的作用HTTP网关web网关常见的网关类型HTTP缓存HTTP缓存头部字段HTTP缓存工作方式缓存改进方案cdn缓存工作方式浏览器操作对http缓存的影响HTTP内容协商机制客户端驱动服务器驱动请求首部集近似匹配透明协商断点续传和多线程下…

springboot投票管理系统-计算机毕业设计源码33128

摘 要 本文介绍了基于微信小程序和Spring Boot的投票管理系统的设计与实现。该系统结合了移动互联网技术和后端开发框架&#xff0c;旨在为各类组织或活动提供一个高效、便捷、用户友好的在线投票平台。 系统采用微信小程序作为前端展示与交互界面&#xff0c;用户无需下载安装…

学校如何筹办一场汉字听写大赛

汉字作为中国最宝贵的文化遗产&#xff0c;在五千年的历史长河里&#xff0c;汉字以其浩瀚广博抒写着华夏历史&#xff0c;以其灵秀展示着炎黄之精神。传承汉字文明是我们的使命和主责任。随着科技的发展&#xff0c;现在人们很少用笔书写汉字&#xff0c;导致汉字听写能力普遍…