当前位置: 首页 > news >正文

【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的内存管理、变量捕获和循环引用解决方案

相关文章:

【iOS】Block底层分析

目录 前言Block底层结构Block捕获变量原理捕获局部变量&#xff08;auto、static&#xff09;全局变量捕获实例self Block类型Block的copyBlock作为返回值将Block赋值给__strong指针Block作为Cocoa API中方法名含有usingBlock的方法参数Block作为GCD API的方法参数Block属性的写…...

复现dom破坏案例和靶场

文章目录 靶场网址第一个实验步骤和原理(代码为示例要根据自己的实验修改) 第二个实验步骤和原理(代码为示例要根据自己的实验修改) 靶场网址 注册后点击 第一个实验 此实验室包含一个 DOM 破坏漏洞。注释功能允许“安全”HTML。为了解决这个实验&#xff0c;请构造一个 HT…...

【高校科研前沿】南方科技大学冯炼教授等人在遥感顶刊RSE发文:全球人类改造的基塘系统制图

1.文章简介 论文名称&#xff1a;Global mapping of human-transformed dike-pond systems&#xff08;全球人类改造的基塘系统制图&#xff09; 第一作者及单位&#xff1a;Yang Xu&#xff08;南方科技大学环境学院&#xff09; 第一通讯作者及单位&#xff1a;冯炼&#x…...

How to run angular CICD on gitlab-runner of wsl?

前提文件 .gitlab-ci.yml, .dockerignore, ci-funcs.sh, Dockerfile, karma.conf.js, nginx.conf, nginx-custom.conf, sonar-project.properties 1.test.ts const context require.context(./app/pages, true, /\.spec\.ts$/); 2.sonar-project.properties sonar.sourcessrc/…...

搭建Java集成开发环境IntelliJ IDEA

搭建Java集成开发环境&#xff08;Integrated Development Environment&#xff0c;简称IDE&#xff09;IntelliJ IDEA是一个涉及多个步骤的过程&#xff0c;旨在帮助Java开发者高效、舒适地进行编程工作。IntelliJ IDEA由JetBrains公司开发&#xff0c;以其强大的代码自动补全…...

JS逆向浏览器脱环境专题:事件学习和编写、DOM和BOM结构、指纹验证排查、代理自吐环境通杀环境检测、脱环境框架、脱环境插件解决

&#x1f310;JS逆向浏览器脱环境专题&#xff1a;事件学习和编写、DOM和BOM结构、指纹验证排查、代理自吐环境通杀环境检测、脱环境框架、脱环境插件解决 &#x1f5a5;️ 浏览器事件学习和编写 浏览器事件是用户与网页交互的主要方式&#xff0c;了解并掌握这些事件的处理方…...

驾校预约学习系统--论文pf

TOC springboot373驾校预约学习系统--论文pf 第1章 绪论 1.1 课题背景 二十一世纪互联网的出现&#xff0c;改变了几千年以来人们的生活&#xff0c;不仅仅是生活物资的丰富&#xff0c;还有精神层次的丰富。在互联网诞生之前&#xff0c;地域位置往往是人们思想上不可跨域…...

交叉编译ARM平台的OpenCV1.0

首先,从http://www.opencv.org.cn下载1.0的源码包,然后解压出来,进入解压后的目录,再进行下面的修改: 将configure 文件下列内容注释掉(有两处)&#xff0c;只保留GTK_CFLAGS"" 、GTK_LIBS"" 、have_gtkno 三项内容&#xff08;如下黑体所示&#xff09;&…...

牛客周赛 Round 56 AK

背景 语言艺术 A题&#xff1a;面包店故事 题意 一块面包要x元&#xff0c;加培根要y元&#xff0c;有n元&#xff0c;问能否买到加培根的面包 思路 大水题&#xff0c;gpt秒了 代码 #include <bits/stdc.h> using namespace std; int main() {int x, y, n; cin …...

LeetCode 热题 HOT 100 (038/100)【宇宙最简单版】

【动态规划】No. 0337 打家劫舍III【中等】&#x1f449;力扣对应题目指路 希望对你有帮助呀&#xff01;&#xff01;&#x1f49c;&#x1f49c; 如有更好理解的思路&#xff0c;欢迎大家留言补充 ~ 一起加油叭 &#x1f4a6; 欢迎关注、订阅专栏 【力扣详解】谢谢你的支持&a…...

SQLALchemy ORM 的关联关系之 ORM 中的一对一

SQLALchemy ORM 的关联关系之 ORM 中的一对一 场景示例实现一对一关系使用 `relationship()` 和外键(FK)插入和查询数据总结在 SQLAlchemy ORM 中,一对一(One-to-One)关联关系是一种比较少见的模型关系,但它确实有其应用场景,特别是在你需要将一个对象与另一个对象紧密绑…...

模型部署 - docker

docker简介 Docker 是一种开源的容器化平台&#xff0c;允许开发者将应用程序及其依赖项打包到一个标准化的单元中&#xff0c;称为“容器”。这些容器可以在任何支持 Docker 的系统上运行&#xff0c;无需担心环境差异。 为什么需要 Docker&#xff1f; 在传统的开发中&…...

学懂C++(三十四):深入详解 C++ 高级多线程编程技术中的并发设计模式

引言 在现代软件开发中&#xff0c;多线程编程已成为提升性能和响应能力的重要手段。设计模式为解决并发问题提供了有效的解决方案。本文将探讨常见的并发设计模式&#xff0c;包括生产者-消费者模式、读者-写者模式、单例模式、帧-工作者模式以及Future-Task模式&#xff0c;并…...

大数据产业链图谱_产业链全景图_大数据行业市场分析

数据作为新型生产要素&#xff0c;是数字化、网络化、智能化的基础&#xff0c;已快速融入生产、分配、流通、消费和社会服务管理等各环节&#xff0c;影响着千行百业&#xff0c;推动着我国数字经济的蓬勃发展。 大数据又称巨量数据、海量数据&#xff0c;是由数量巨大、结构…...

photonserver 部署相关教程

Photon Server 是 Exit Games 开发的高性能、可扩展的多人游戏服务器框架。部署 Photon Server 需要一些基础的服务器管理知识和配置技巧。以下是一个基本的部署教程&#xff0c;帮助你将 Photon Server 部署在 Windows 服务器上。 目录 1. 下载并安装 Photon Server 2. 配置…...

GEE训练:sentinel-1数据的投影、显示和导出

函数 projection() Returns the default projection of an Image. Throws an error if the bands of the image dont all have the same projection. 返回图像的默认投影。如果图像带的投影不一致,则会抛出错误。 Arguments: this:image (Image): The image from which …...

后端学习笔记(七)--MyBatis参数传递

5.MyBatis参数传递 ​ *MyBatis接口方法中可以接收各种各样的参数&#xff0c;MyBatis底层对于这些参数进行不同的封装处理方式 ​ *单个参数&#xff1a; 1.POJO类型&#xff1a;直接使用&#xff0c;属性名和参数占位符名称一致 2.Map集合&#xff1a;直接使用&#xff0c;…...

uniapp 网络请求自动处理loading

文章目录 背景整理思路V1版本V2版本V3版本 背景 最近在写uniapp&#xff0c;发现执行网络请求的时候经常要处理Loading效果。 比如&#xff0c;在发送网络请求之前&#xff0c;触发Loadng&#xff1b;无论请求成功还是失败都要关闭Loading&#xff1b;请求失败的时候我们还要…...

【Solidity】函数的使用

构造函数 构造函数仅在部署合约时调用一次&#xff0c;它的作用主要是初始化一些状态变量。 contract Demo {address public owner;uint public num;constructor(uint _num) {owner msg.sender;num _num;} }函数装饰器 函数装饰器可以在函数执行之前或之后插入代码逻辑&am…...

详解golang内存管理

介绍 要搞明白 Go 语言的内存管理,就必须先理解操作系统以及机器硬件是如何管理内存的。因为 Go 语言的内部机制是建立在这个基础之上的,它的设计,本质上就是尽可能的会发挥操作系统层面的优势,而避开导致低效情况。 操作系统内存管理 其实现在计算机内存管理的方式都是…...

C++ 线程 一些同步方式

C 线程一些同步方式 1.互斥锁&#xff08;Mutex&#xff09;2. 读写锁&#xff08;Reader-Writer Lock&#xff09;3. 信号量&#xff08;Semaphore&#xff09;4. 原子操作&#xff08;Atomic&#xff09;5. 屏障&#xff08;Barrier&#xff09;6. 条件变量&#xff08;Condi…...

【开发语言】编译型语言和解释性语言有啥区别?

作为一名从业多年的程序员,对于编译型语言和解释型语言之间的区别有着深入的理解。这两种类型的编程语言在将源代码转换成可执行代码的过程中采用了不同的机制,这导致了它们在执行效率、跨平台性、安全性以及开发效率等方面存在一些差异。 编译型语言(Compiled Languages)…...

将A服务器上指定文件夹中的文件,批量同步到B服务器上

需求&#xff1a;最近有一个需求&#xff0c;需要定期将A服务器上的PDF文件&#xff0c;同步到B服务器上&#xff0c;于是便写个脚本记录一下&#xff01; 下面是使用Python3脚本实现的方法 import os import paramikodef copy_pdf_files(source_ip, source_user, source_pas…...

2024.8.17

130124202408171002 DATE #:20240817 ITEM #:DOC WEEK #:SATURDAY DAIL #:捌月拾肆 TAGS < BGM "快哉风 -- 黄金玉米王" > < theme oi-language > < theme oi-graph theory > < [空] > < [空] >取次花丛懒回顾&#xff0c;半缘修道…...

十分钟搭建一个RTMP服务器

使用SRS搭建RTMP服务器 如果您需要搭建一个RTMP服务器&#xff0c;您可以使用SRS&#xff08;Simple-RTMP-Server&#xff09;来完成此任务。SRS是一个开源的RTMP服务器下面是一个简单的步骤指南&#xff1a; 获取srs srs官⽹&#xff1a;https://github.com/ossrs/srs 码云…...

Spring Boot解决循环注入问题

Spring Boot解决循环依赖注入问题 代码问题回显启动错误日志解决方案&#xff1a;使用事件驱动或通过 ApplicationContext 手动获取 Bean1. 事件驱动设计2. 使用 ApplicationContext 手动获取 Bean3. 拆分逻辑 总结 代码问题回显 现有代码1 在InterestService中依赖MemberInte…...

《数据挖掘》期末考核重点

1.数据预处理的目的与形式 数据预处理的目的是提供干净&#xff0c;简洁&#xff0c;准确的数据&#xff0c;以达到简化模型和提高算法泛化能力的目的&#xff0c;使挖掘过程更有效&#xff0c;更容易&#xff0c;提高挖掘效率和准确性。 2.数据预处理的形式 数据清理&#…...

Golang | Leetcode Golang题解之第334题递增的三元子序列

题目&#xff1a; 题解&#xff1a; func increasingTriplet(nums []int) bool {n : len(nums)if n < 3 {return false}first, second : nums[0], math.MaxInt32for i : 1; i < n; i {num : nums[i]if num > second {return true} else if num > first {second n…...

HarmonyOs编写一个案例实现一个照片选择(阶段进阶 四种需求 逐一完善)

需求1. .实现照片选择 并将选择好的照片展示出来 import { GoodItem } from ../06/modules;Entry Component struct PhotoPage {State message: string 实现一个相册;State List: GoodItem[] [{goods_name: dsfjlsjkfsf,goods_price: 100,goods_img: https://img1.baidu.com…...

洗衣机洗衣服一些知识

01智能:按衣物多少自动调节合适水位的标准洗涤程序 (需要30分钟时间) 02:大物:较大,较厚的衣服洗涤 03:轻柔:毛织品或内衣洗涤 04:快速:少量清污衣服洗涤 (13分钟) 05:浸泡:先浸泡一段时间再洗涤 06:单洗:只洗衣不脱水 07:单脱:只脱水不洗衣 08:洁桶:清洁洗衣桶 准备工作: (1)…...

探索文件系统:高效、可靠的文件管理与访问机制

文件系统的功能规划 内存就像是一个书包&#xff0c;容量有限&#xff0c;只能带着一部分东西。而图书馆则是一个专门存储和管理文件的地方&#xff0c;拥有更大的容量&#xff0c;并且可以永久保存文件。为了能够快速找到需要的文件&#xff0c;我们需要有一个书单来记录每本…...

启程与远征Ⅸ--优化生成式人工智能以满足业务需求的框架

生成类似人类的文本和语音曾经只存在于科幻小说中。但 GPT-3 和 PaLM 等大型语言模型 (LLM) 的快速发展让这一愿景更接近现实&#xff0c;解锁了从聊天机器人到内容创作等一系列有前景的商业应用。 然而&#xff0c;通用基础模型往往无法满足行业用例的需求。企业对其生成式 A…...

canal数据同步工具介绍与应用

canal服务 canal介绍canal版本与环境canal 服务集canal应用场景&#xff1a; canal常见问题xml配置问题连接认证问题jar版本问题连接问题 canal介绍 ‌1、Canal是‌阿里巴巴开源的‌MySQL增量数据订阅和消费工具&#xff0c;通过模拟MySQL的‌slave与‌master交互&#xff0c;捕…...

ubuntu18.04 设置静态地址

修改配置文件 sudo vim /etc/netplan/01-network-manager-all.yaml 代码如下&#xff1a; network: version: 2 renderer: NetworkManager ethernets: ens33: # 配置的网卡名称&#xff0c;可以使用ifconfig -a查看本机的网卡 dhcp4: no # 关闭动态IP设置 …...

jira敏捷开发管理工具视频教程Confluence工作流协同开发(2024)

正文&#xff1a; 随着Jira敏捷开发方法论的普及&#xff0c;Jira已经成为全球软件开发团队管理项目、任务和问题的首选工具。为了帮助团队更好地掌握Jira的核心功能&#xff0c;精心准备了一套全面开发技术及案例视频教程——《Jira敏捷开发管理工具视频教程Confluenc…...

【网络】TCP回显服务器和客户端的构造,以及相关bug解决方法

文章目录 ServerSocket构造方法方法 Socket构造方法方法 回显服务器&#xff08;Echo Server&#xff09;1. 构造方法2. 建立连接processConnection 方法的创建1. 读取请求并解析2. 根据请求计算响应3. 把响应写回给客户端 3. 完整代码 客户端&#xff08;Echo Client&#xff…...

Python知识点:如何使用Boto3进行AWS服务管理

使用 boto3 来管理 AWS 服务是一个非常强大的方式&#xff0c;因为 boto3 是 AWS 提供的官方 Python SDK。下面是使用 boto3 管理 AWS 服务的基本步骤&#xff0c;包括设置、操作和常见的 AWS 服务示例。 1. 安装 boto3 首先&#xff0c;确保你已经安装了 boto3。可以使用 pi…...

Java - 正则表达式

Java 提供了 java.util.regex 包&#xff0c;它包含了 Pattern 和 Matcher 类&#xff0c;用于处理正则表达式的匹配操作。 正则表达式的模式 正则表达式的模式可以包括以下内容&#xff1a; 字面值字符&#xff1a;例如字母、数字、空格等&#xff0c;可以直接匹配它们自身。…...

Vue一款流行的JavaScript前端框架

1.Vue简介 Vue是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助你高效地开发用户界面。无论是简单还是复杂的界面&#xff0c;Vue 都可以胜任。 Vue所关注的核心是MVC…...

GPT-SoVITS

文章目录 model archS1 ModelS2 model model arch S1 model: AR model–ssl tokensS2 model: VITS&#xff0c;ssl 已经是mel 长度线性相关&#xff0c;MRTE(ssl_codes_embs, text, global_mel_emb)模块&#xff0c;将文本加强相关&#xff0c;学到一个参考结果 S1 Model cla…...

linux高级编程——文件IO(常用函数大全)

1.相关介绍及常用函数 Linux高级编程中的目录IO操作是文件系统编程的一个重要组成部分&#xff0c;主要涉及到目录的打开、读取、遍历和关闭等操作。以下是一些基本的目录IO操作和相关的系统调用函数 1.1 opendir函数 打开目录&#xff1a;使用opendir函数打开一个目录&#…...

matplotlib画图

Matplotlib 先写一个最简单的&#xff1a; import matplotlib.pyplot as plt plt.plot([1,4],[2,8]) #plot画折线图plt.show() 确定两个点画一条线 import matplotlib.pyplot as plt x[1,23,4,56,7,6] #x轴数据 y[22,44,56,67,43,2] #y轴数据 s[22,43,33,44,43,7] plt.p…...

Jetpack 各种框架简介

Jetpack是Google推出的一套为Android开发提供极大便利的组件、工具和指导集&#xff0c;旨在帮助开发者快速构建高质量的应用&#xff0c;并遵循最佳实践。 Jetpack不仅是一个提高开发效率的工具集&#xff0c;还是Android开发的未来方向。它通过整合各种组件和工具&#xff0…...

海康VisionMaster使用学习笔记5-开机自启动

开机自启动 在实际应用中&#xff0c;用户会希望机台上电开机后&#xff0c;软件能自启动避免现场人员误操作&#xff0c;减少机台重新上电时的操作步骤以提升效率。 设置 打开VM,点击设置,软件设置->开机自启动->勾选开机自启动->确定 默认运行界面 启动时以设定的…...

驾驭数据之序:SQL序列的奥秘与实现

标题&#xff1a;驾驭数据之序&#xff1a;SQL序列的奥秘与实现 摘要 在数据库管理中&#xff0c;保证数据的有序性和唯一性是至关重要的。SQL序列&#xff08;Sequence&#xff09;作为一种强大的数据库对象&#xff0c;为我们提供了一种在不同数据库系统中生成连续数字的手…...

【LeetCode】148. 排序链表

排序链表 题目描述&#xff1a; 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4]示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0] 输出&#xff1a;…...

阿里云-java调用短信服务,第三方接口的开启(傻瓜式教程)

第一步&#xff1a;在浏览器中&#xff0c;搜索阿里云 第二步&#xff1a;打开aly的主页 第三步&#xff1a;在最上方的导航栏中&#xff0c;找到云市场&#xff0c;注意不要点击&#xff0c;会自动有触发悬浮框出现&#xff0c;在悬浮框中找到 短信 第四步&#xff1a;点击 短…...

以node / link文件表征的道路网络-----基于南京公路公开数据做路径规划(下)------dijkstra算法的一些简单花样

在不改变dijkstra算法本身的情况下&#xff0c;完全可以从数据源的角度出发&#xff0c;解决我们的一些简单需求&#xff1a; 比较初级且粗暴的玩法&#xff0c;可以是强行赋予一些link极端的路段长度。 对于我们坚决不希望车辆行驶的道路、禁行区、或是危险区&#xff0c;就…...

计算机操作员中级理论知识试题

计算机操作员中级理论知识试题 一、单项选择题 在ASCII编码中,无法显示或打印的字符是()。 A.字符$,%,# B.运算符号*,.,/ C.空格 D.ASCII编码值在0-30间的控制符号将十进制数31.625转换成十六进制数是() A.115.10 B.If.a C.37.5 D.If.10在计算机中,同统一指挥和控制计…...

Redis主从同步配置

1&#xff1a; 安装Redis 参考 linux ubuntu安装redis_ubuntu离线安装redis7.2.5-CSDN博客 2:创建目录 到达redis 根目录 cd /usr/redis/# 创建主从工作目录 mkdir -p replication/6379 # master 节点 mkdir -p replication/6378 # 从节点 mkdir -p replication/6377 # 从节点…...