【iOS】Blocks学习
Blocks学习
- Blocks概要
- Blocks模式
- Blocks语法
- Blocks类型变量
- 截获自动变量值
- __block说明符
- 截获的自动变量
- Blocks的实现
- Blocks的实质
- 截获自动变量值
- __block说明符
- Block存储域
- _block变量存储域
- 截获对象
- __block变量和对象
- 总结
Blocks概要
Blocks
是C语言的扩充功能,简单来说:带有自动变量(局部变量)的匿名函数(不带有名称的函数)。
Blocks模式
Blocks语法
我们先来了解一下Blocks
的BN范式:
这里先给出一个标准的Blocks
语法的格式:
^void (int count) {return count + 1;
}
在Blocks
语法中,可以省略好几个项目,首先可以省略返回值类型:
^(int count) {return count + 1;
}
当我们省略返回值类型的时候,如果表达式中有return语句即使用return语句的返回值类型,如果没有就使用void类型。当有多个return的时候,所有return的返回值必须相同。
当我们不使用参数的时候,还可以省略参数列表,如下所示:
^ {return count + 1;
}
Blocks类型变量
在定义C语言函数的时候,可以将所定义的函数的地址赋值给函数指针类型变量:
int func(int count) {return count + 1;
}int (*funcptr)(int) = &func;
同样的,在Block语法下,可将Blocks
语法赋值给声明为Blocks类型的变量中。即源代码中一旦使用Blocks语法就相当于生成了可赋值给Blocks类型的变量的“值”。
下面举一个声明Blocks
类型变量的示例:
int (^blk((int);
这个Blocks
类型的变量和一般C语言变量完全相同,可以作为以下用途使用:
- 自动变量
- 函数参数
- 静态变量
- 静态全局变量
- 全局变量
这里给出一个使用Blocks
语法将Blocks
赋值为Blocks
类型变量:
int (^blk)(int) = ^(int count) {return count + 1;
}
由“^"开始的Block被赋值给变量blk中。因为与通常的变量相同,所以也可以由Block类型变量向Block类型变量赋值:
int (^blk)(int) = blk;
int (^blk2)(int);
blk2 = blk1;
在函数参数中使用Block
类型变量可以向函数传递Block
:
void (^func()(int)) {return count + 1;
}
在函数返回值中指定Block
类型,可以将Block
作为函数的返回值返回。
int (^func()(int)) {return ^(int count) {return count + 1;}
}
在函数参数和返回值中使用Block
类型变量的时候,记述的方式过于复杂,这个时候我们可以使用typedef
来解决该问题:
typedef int (^blk_t)(int);
这样让我们的代码更加直观,更方便我们去理解这种类似的代码
截获自动变量值
在上述讲解中,我们已经明白了什么叫做匿名函数,而“带有自动变量值”是什么意思呢?在Blocks
中表现为“截取自动变量值”,下面举例说明:
int dmy = 256;int val = 250;const char* fmt = "Hyt is %d\n";void (^blk)(void) = ^{printf(fmt, val);};val = 2;fmt = "val = %d\n";blk();return 0;
下面我们看一下这段代码的执行结果:
那么为什么会是这样的结果呢?这是由于该Block
语法执行的时候,字符串指针“Hyt is %d\n"被赋值到自动变量fmt中,int值250被赋值到自动变量val中,因此这些值被保存,从而在执行时使用。
这就是自动变量值的截获。
__block说明符
实际上,自动变量值截获只能保存执行Block
语法瞬间的值。保存后就不能改写该值。如果想在Block语法的表达式中将值赋给Block语法外声明的自动变量,需要在该自动变量上附加上__block说明符:
__block int val = 0;void (^blk)(void) = ^{val = 1;};blk();printf("val = %d\n", val);
这是我们将该变量成为__block变量
截获的自动变量
如果将值赋值给Block中截获的自动变量,就会产生变异错误。那么在截获OC
对象,调用变更该对象的方法也会产生编译错误吗?
这样是没有问题的,而向截获的变量array赋值则会产生编译错误。这种情况下,我们应该给截获的自动变量附加上__block说明符:
注意的是,在使用C语言数组的时候必须小心使用指针,在block中不能使用字符数组,这是由于截获自动变量的方法并没有对C语言数组的截获。
Blocks的实现
Blocks的实质
这不过是概念上的问题,在实际编译时无法转换成我们能够理解的源代码,但 clang (LLVM编译器)具有转换为我们可读源代码的功能。通过-rewrite-objc
选项就能将含有 Block 语法的源代码变换为C++的源代码。说是C++,其实也仅是使用了struct结构,其本质是C语言源代码。
这里我们将Block语法:
int main() {void (^blk)(void) = ^{printf("Block\n");};blk();return 0;
}
该源代码通过clang可变换为以下形式:
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) {printf("Block\n");}static struct __main_block_desc_0 {size_t reserved;size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main() {void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
下面我们将这段代码分步来理解:
首先来看最初源代码中的Block语法:
^{printf("Block\n");};
在转化后的源代码中也有相同的表达式:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf("Block\n");}
这里我们可以看出,通过Block
使用的匿名函数实际上被作为简单的C语言函数来处理。同时,我们可以通过Block语法所属的函数名可以看出:struct __main_block_impl_0 *__cself
中的main
表示Block语法所属的函数名为main,顺序值为0(在此处)来给劲clang变换的函数命名。
这里的参数_cself
相当于OC中的self
, 即指向Block值的变量。
下面来看一下struct __main_block_impl
结构体,该结构体声明如下:
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;
}
再来看一下_block-impl
结构题的声明:
struct __block_impl {void *isa; //如同对象类型的Class isa,将Block作为Objc对象是,关于该对象类的信息放置于isa指向的对象中int Flags; //某些标志int Reserved; //保留区域void *FuncPtr; //函数指针
}
第二个成员变量为Desc
指针,下面为_main_block_desc_0
结构体的声明:
static struct __main_block_desc_0 {unsigned long reserved;unsigned long Block_size;
}
下面再来看看初始化含有这些结构体的__main_block_impl_0
结构体的构造函数:
__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;
}
下面来看看该构造函数的调用:
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//对应的最初源代码
void (^blk)(void) = ^{ printf("Block\n"); };
总结来说,这段代码将栈上生成的__main_block_impl_0
结构体实例的指针赋值给__main_block_impl_0
结构体指针类型的变量blk。
在__main_block_impl_0
结构体实例构造参数:_main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
中,第一个参数为由Block语法转化的C语言函数指针,第二个参数是作为静态全局变量初始化的__main_block_desc_0
结构体实例指针。下面为__main_block_desc_0
结构体实例初始化部分代码:
__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
该源代码使用Block,进行初始化,下面展示__main_block_impl_0
结构体的构造函数:
__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;}
这时候我们来看一下blk()转换源代码为:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
去掉转换部分:
((__block_impl *)blk);
这就是简单的时候函数指针调用函数。
截获自动变量值
这里我们先将截获自动变量值的源代码通过clang进行转换:
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;const char *fmt;int val;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {const char *fmt = __cself->fmt; // bound by copyint val = __cself->val; // bound by copyprintf(fmt, val);}static struct __main_block_desc_0 {size_t reserved;size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main() {int dmy = 256;int val = 250;const char* fmt = "Hyt is %d\n";void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));val = 2;fmt = "val = %d\n";((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
这里不难看出,block语法中使用的自动变量被作为成员变量追加到__main_block_impl_0
结构体中:
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;const char *fmt;int val;}
注意:block语法表达时中没有使用的自动变量不会被追加,Blocks的自动变量截获只针对Block中使用的自动变量。
下面来看看那初始化该结构体实例的构造函数的差异:
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
在初始化结构体实例的时候,根据传递给构造函数的参数对由自动变量追加的成员变量进行初始化。以下通过构造函数调用确认其参数:
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
在使用Block匿名函数的实现,最初源代码的Block语法如下所示:
^{printf(fmt, val);};
将其转换为以下函数:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {const char *fmt = __cself->fmt; // bound by copyint val = __cself->val; // bound by copyprintf(fmt, val);}
由此可以看出,原来代码表达时无需改动便可以使用截获的自动变量值执行。
总的来说,所谓“截获自动变量值”意味着在执行Block语法时,Block语法表达时所使用的自动变量值被保存到Block得结构体实例中。说明了获取的是瞬间值。
__block说明符
这里我们也将上文中使用__block说明符的源代码进行转化:
struct __Block_byref_val_0 {void *__isa;
__Block_byref_val_0 *__forwarding;int __flags;int __size;int val;
};struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__Block_byref_val_0 *val; // by ref__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__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_val_0 *val = __cself->val; // bound by ref(val->__forwarding->val) = 1;}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}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(void) {__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);printf("val = %d", (val.__forwarding->val));return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
我们可以发现代码量急速的增加了,那么__block int val = 10
这段代码是如何转化过去的呢?这里来看看:
__Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10
};
这里我们可以发现__block变量也变成了一个__Block_byref_val_0
的结构体类型的自动变量,该变量的初始化为10,并且这个值也出现在结构体实例的初始化中,意味着该结构体持有相当于原自动变量的成员变量。
该结构体的声明如下:
struct __Block_byref_val_0 {void *__isa;
__Block_byref_val_0 *__forwarding;int __flags;int __size;int val;
};
不难看出,结构体最后的val相当于原自动变量的成员变量,下面来看看给__block变量赋值(^{val = 1;}
)的代码:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_val_0 *val = __cself->val; // bound by ref(val->__forwarding->val) = 1;}
这段代码相比于之前在Block中向静态变量赋值更为复杂。Block的__main_block_impl_0
结构体实例持有指向__block
变量的__Block_byref_val_0
结构体实例的指针。
__Block_byref_val_0
结构体中的成员变量__forwarding
持有指向该实例自身的指针,然后我们可以通过__forwarding
(__forwarding
持有指向该实例自身的指针)来访问原成员变量val
。如图所示:
__block变量的__Block_byref_val_0
结构体并不在Block用__main_block_impl_0
结构体中,这样是为了在多个Block中使用__block变量。例如:
这样我们可以看出,两个Block都使用了__Block_byref_val_0
结构体实例val指针,这样的话,既可以在多个Block中使用同一个__block变量,也可以在一个Block中使用多个__block变量。
Block存储域
通过我们前面的源码可以得知,Block转换为Block结构体类型变量__main_block_impl_0
,__block转换为block变量的结构体类型变量__Block_byref_val_0
所谓结构体类型的自动变量,就是栈上生成的结构体的实例。
通过之前的说明可知Block也是OC对象,将Block当作OC对象来看的时候,该Block的类为_NSConcreteStackBlock
。虽然该类并没有出现在已变换的源代码中,但是有很多类似的类:
_ NSConcreteStackBlock(分布在栈上)
_NSConcreteGlobalBlock(分布在程序的数据区的位置)
_NSConcreteMallocBlock(分布在堆的位置)
下面图片说明:
在前期出现的Block例子中均是_NSConcreteStackBlock
类,并且都设置在栈上。但是实际上并不都是这样,在记述全局变量的地方使用Block语法时,生成的Block为_NSConcreteGlobalBlock
类对象。
那么将Block配置在堆上的_NSConcreteMallocBlock
类在何时使用呢?这是两个问题的答案:
- Block超出变量作用域可存在的原因
- __block变量用结构体成员变量__forwarding存在的原因
Blocks提供了将Block和__block变量从栈上复制到堆上的方法来解决这个问题,还配置栈上的Block复制到堆上,这样的话,哪怕Block语法记述的变量作用域结束,堆上的Block还可以继续存在。
_block变量存储域
使用__block变量的Block从栈上复制到堆上,__block变量也会收到影响,如图:
在一个Block中使用__block变量的时候:
在多个Block中使用__block变量的时候,因为最先会将所有的Block配置在栈上,所以__block变量的配置也会在栈上。在任何一个Block从栈上复制到堆上的时候,__block变量也会一并从栈上复制到堆并被该Block所持有,当剩下的Block从栈上复制到堆上,被复制的Block持有__block变量,并增加__block变量的引用计数:
如果配置在堆上的Block被废弃,那么他使用的__block变量也就被释放了;
由此我们可以知道__block的思考方式与OC引用计数管理完全相同
同时通过Block从栈上复制到堆上之后,原来的栈上的__block变量的__forwarding指针从指向自身变味指向堆上的__block结构体,由此不管__block变量配置在栈上还是堆上都可以顺利访问同一个__block变量
截获对象
我们先来看看这段OC代码:
int main(int argc, const char * argv[]) {NSMutableArray* array = [NSMutableArray array];void (^blk)(id object) = [^(id object) {[array addObject:object];NSLog(@"%ld", array.count);} copy];blk([[NSMutableArray alloc] init]);blk([[NSMutableArray alloc] init]);blk([[NSMutableArray alloc] init]);return 0;
}
运行结果
这个结果说明赋值给变量array的NSMutableArray
类的对象在该源代码最后Block的执行部分超出其变量作用域而存在,将其转换后的源代码为:
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;NSMutableArray *array;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, int flags=0) : array(_array) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id object) {NSMutableArray *array = __cself->array; // bound by copy((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)object);NSLog((NSString *)&__NSConstantStringImpl__var_folders_07_67glw_2n3csgg41v0tztm7w00000gn_T_main_07e131_mi_0, ((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}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[]) {NSMutableArray* array = ((NSMutableArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));void (*blk)(id object) = (void (*)(id))((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init")));((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init")));((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init")));return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
这里我们可以看到这里的Block的结构体:
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;NSMutableArray *__strong array;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, int flags=0) : array(_array) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
- 按照1.3.4节,在OC中,C语言结构体不能含有附_strong修饰符的变量。因为编译器不知道应何时进行C语言结构体的初始化和废弃操作,不能很好的管理内存。
- 但是 Objective-C 的运行时库能够准确把握Block 从栈复制到堆以及堆上的Block 被废弃的时机,因此 Block 用结构体中即使含有附有__strong 修饰符或__weak 修饰符的变量,也可以恰当地进行初始化和废弃。为此需要使用在__main_block _dese_0结构体中增加的成员变量
copy
和dispose
,以及作为指针赋值给该成员变量的_main_block copy_0
函数和__main_block_dispose 0
函数。 - 由于在该源代码的Block 用结构体中,含有附有__strong 修饰符的对象类型变量 array,所以需要恰当管理赋值给变量 array 的对象。因此__main_block_copy_0函数使用
_Block_object_assign
函数将对象类型对象赋值给 Block 用结构体的成员变量 array 中并持有该对象。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
_Block_object_assign这个方法其实相当于调用retain方法,将对象赋值在对象类型的结构体中,同时有一个释放内存的函数:
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
这个方法其实是Block在废弃后会调用的函数,用来释放内存。这些函数分别会在Block复制到堆上被调用,和Block被废弃的时候调用:
在下面这几种情况下Block会被复制到堆上:
- 调用 Block 的 copy 实例方法时
- Block 作为函数返回值返回时
- 将 Block 赋值给附有 __strong 修饰符 id 类型的类或 Block 类型成员变量时
- 在方法名中含有
usingBlock
的Cocoa 框架方法或Grand Central Dispatch
的API 中传递Block
如果Block配置在栈上,那么就会从栈上复制到堆上。Block 作为函数返回值返回时、将Block 赋值给附有__strong 修饰符id类型的类或Block 类型成员变量时,编译器自动地将对象的Block作为参数并调用_Block_copy 函数,这与调用Block的 copy 实例方法的效果相同。在方法名中含有 usingBlock 的 Cocoa 框架方法或 Grand CentralDispatch 的API 中传递 Block 时,在该方法或函数内部对传递过来的Block 调用 Block 的 copy 实列方法或者_Block _copy 函数。
在Block中使用对象类型自动变量的时候,除以下情形外,推荐调用Block的copy实例方法:
- Block作为函数返回时的时候
- 将 Block 赋值给附有 __strong 修饰符 id 类型的类或 Block 类型成员变量时
- 在方法名中含有
usingBlock
的Cocoa 框架方法或Grand Central Dispatch
的API 中传递Block
__block变量和对象
我们知道在ARC中会给id类型变量自动加上__strong修饰符,只有使用__strong修饰符的变量才会在block从栈复制到堆时使用_Block_object_assign
来持有__block变量。如果使用__weak修饰符就当作用与结束时__block变量也会自动被释放。
由此我们可以知道只有自动变量用__strong进行修饰时才会被block持有,且不随作用域结束而销毁。同时在blk被定义的时候blk就已经捕获了自动变量,而不是在调用blk时才进行捕获
blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));
总结
本篇博客主要是笔者对于自己寒假学习中内容的一个总结,但是笔者还是感觉自己对于这个部分的学习并不是很好,后期笔者学习后如果遇到问题还会再次总结相关知识,本篇博客就到这里,期待笔者的新博客吧。
相关文章:
【iOS】Blocks学习
Blocks学习 Blocks概要Blocks模式Blocks语法Blocks类型变量截获自动变量值__block说明符截获的自动变量 Blocks的实现Blocks的实质截获自动变量值__block说明符Block存储域_block变量存储域截获对象__block变量和对象 总结 Blocks概要 Blocks是C语言的扩充功能,简单…...
Spring MVC DispatcherServlet 的作用是什么? 它在整个请求处理流程中扮演了什么角色?为什么它是核心?
DispatcherServlet 是 Spring MVC 框架的绝对核心和灵魂。它扮演着前端控制器(Front Controller)的角色,是所有进入 Spring MVC 应用程序的 HTTP 请求的统一入口点和中央调度枢纽。 一、 DispatcherServlet 的核心作用和职责: 请…...
QT 5.15 程序打包
说明: windeployqt 是 Qt 提供的一个工具,用于自动收集并复制运行 Qt 应用程序所需的动态链接库(.dll 文件)及其他资源(如插件、QML 模块等)到可执行文件所在的目录。这样你就可以将应用程序和这些依赖项一…...
PyCharm 初级教程:从安装到第一个 Python 项目
作为 Python 程序员,无论是刚入门还是工作多年,PyCharm 都是一个绕不开的开发工具。它是 JetBrains 出品的一款强大的 Python IDE,有自动补全、调试、虚拟环境支持、代码检查等等功能,体验比命令行 记事本舒服一百倍。 今天这篇…...
【Linux】进程替换与自定义 Shell:原理与实战
目录 一、进程程序替换 1、替换原理 2、替换函数 (1)函数解释 ① filename / pathname ② 参数表传递 ③ 环境变量表传递 (2)命名理解 二、自定义shell命令行解释器 1、实现原理 2、实现代码 (1)获…...
【AI提示词】数据分析专家
提示说明 数据分析师专家致力于通过深入分析和解读数据,帮助用户发现数据背后的模式和趋势。他们通常在商业智能、市场研究、社会科学等领域发挥重要作用,为决策提供数据支持。 提示词 # 角色 数据分析师专家## 注意 1. 数据分析师专家需要具备高度的…...
Lucky配置反向代理+Https安全访问AxureCloud服务(解决证书续签问题)
前言 之前用AxureCloud配置了SSL证书,发现ssl证书3个月就过期了,还需要手动续证书,更改配置文件,重启服务才能正常使用,太过于麻烦。也暴露了过多不安全的端口在公网,操作过于麻烦。另外暴露了过多不安全的…...
vscode使用remote ssh插件连接服务器的问题
本人今天发现自己的vscode使用remote ssh连接不上服务器了,表现是:始终在初始化 解决方法: 参考链接:vscode remote-ssh 连接失败的基本原理和优雅的解决方案 原因 vscode 的 SSH 之所以能够拥有比传统 SSH 更加强大的功能&a…...
WWW和WWWForm类
WWW类 WWW类是什么 //WWW是Unity提供的简单的访问网页的类 //我们可以通过该类上传和下载一些资源 //在使用http是,默认的请求类型是get,如果想要用post上传需要配合WWWFrom类使用 //它主要支持的协议: //…...
利用课程编辑器创新教学,提升竞争力
(一)快速创建优质教学内容 对于教育机构来说,教学内容的质量是吸引学员的关键因素之一。而课程编辑器就像是一位得力的助手,帮助教师快速创建出优质的教学内容。课程编辑器通常具有简洁易用的界面,教师即使没有专业的…...
spark与hadoop的区别
一.概述 二.处理速度 三.编程模型 四:实时性处理 五.spark内置模块 六.spark的运行模式...
【项目日记(三)】
目录 SERVER服务器模块实现: 1、Buffer模块:缓冲区模块 2、套接字Socket类实现: 3、事件管理Channel类实现: 4、 描述符事件监控Poller类实现: 5、定时任务管理TimerWheel类实现: eventfd 6、Reac…...
【图片转PDF工具】如何批量将文件夹里的图片以文件夹为单位批量合并PDF文档,基于WPF实现步骤及总结
应用场景 在实际工作和生活中,我们可能会遇到需要将一个文件夹内的多张图片合并成一个 PDF 文档的情况。例如,设计师可能会将一个项目的所有设计稿图片整理在一个文件夹中,然后合并成一个 PDF 方便交付给客户;摄影师可能会将一次拍摄的所有照片按拍摄主题存放在不同文件夹…...
深度解析算法之位运算
33.常见位运算 1.基础位运算 << 左移操作符 > >右移操作符号 ~取反 &按位与:有0就是0 |按位或:有1就是1 ^按位异或:相同为0,不用的话就是1 /无进位相加 0 1 0 0 1 1 0 1 0 按位与结果 0 1 1 按位或结果 0 0 1 …...
深入探索Qt异步编程--从信号槽到Future
概述 在现代软件开发中,应用程序的响应速度和用户体验是至关重要的。尤其是在图形用户界面(GUI)应用中,长时间运行的任务如果直接在主线程执行会导致界面冻结,严重影响用户体验。 Qt提供了一系列工具和技术来帮助开发者实现异步编程,从而避免这些问题。本文将深入探讨Qt…...
【KWDB 创作者计划】_本地化部署与使用KWDB 深度实践
引言 KWDB 是一款面向 AIoT 场景的分布式多模数据库,由开放原子开源基金会孵化及运营。它能在同一实例同时建立时序库和关系库,融合处理多模数据,具备强大的数据处理能力,可实现千万级设备接入、百万级数据秒级写入、亿级数据秒级…...
基于XC7V690T的在轨抗单粒子翻转系统设计
本文介绍一种基于XC7V690T 的在轨抗单粒子翻转系统架构;其硬件架构主要由XC7V690TSRAM 型FPGA芯片、AX500反熔丝型FPGA 芯片以及多片FLASH 组成;软件架构主要包括AX500反熔丝型FPGA对XC7V690T进行配置管理及监控管理,对XC7V690T进行在轨重构管理,XC7V690T通过调用内部SEMIP核实…...
机器学习 Day13 Boosting集成学习方法: Adaboosting和GBDT
大多数优化算法可以分解为三个主要部分: 模型函数:如何组合特征进行预测(如线性加法) 损失函数:衡量预测与真实值的差距(如交叉熵、平方损失) 优化方法:如何最小化损失函数&#x…...
Floyd算法求解最短路径问题——从零开始的图论讲解(3)
目录 前言 Djikstra算法的缺陷 为什么无法解决负权图 模拟流程 什么是Floyd算法 Floyd算法的核心思想 状态表示 状态转移方程 边界设置 代码实现 逻辑解释 举例说明 Floyd算法的特点 结尾 前言 这是笔者图论系列的第三篇博客 第一篇: 图的概念,图的存储,图的…...
spark和hadoop的区别与联系
区别 1. 数据处理模型 Hadoop:主要依赖 MapReduce 模型,计算分 Map(映射)和 Reduce(归约)两个阶段,中间结果常需写入磁盘,磁盘 I/O 操作频繁,数据处理速度相对受限&#…...
XMLXXE 安全无回显方案OOB 盲注DTD 外部实体黑白盒挖掘
# 详细点: XML 被设计为传输和存储数据, XML 文档结构包括 XML 声明、 DTD 文档类型定义(可 选)、文档元素,其焦点是数据的内容,其把数据从 HTML 分离,是独立于软件和硬件的 信息传输…...
C# .NET如何自动实现依赖注入(DI)
为解决重复性的工作,自动实现依赖注入(DI) 示例代码如下 namespace DialysisSOPSystem.Infrastructure {public static class ServiceCollectionExtensions{/// <summary>/// 批量注入服务/// </summary>/// <param name&qu…...
FastGPT Docker Compose本地部署与硅基流动免费AI接口集成指南
本文参考:https://doc.tryfastgpt.ai/docs/development/ 一、背景与技术优势 FastGPT是基于LLM的知识库问答系统,支持自定义数据训练与多模型接入。硅基流动(SiliconFlow)作为AI基础设施平台,提供高性能大模型推理引…...
AI对话高效输入指令攻略(三):使用大忌——“AI味”
免责声明: 1.本文所提供的所有 AI 使用示例及提示词,仅用于学术写作技巧交流与 AI 功能探索测试,无任何唆使或鼓励利用 AI 抄袭作业、学术造假的意图。 2.文章中提及的内容旨在帮助读者提升与 AI 交互的能力,合理运用 AI 辅助学…...
算法 | 成长优化算法(Growth Optimizer,GO)原理,公式,应用,算法改进研究综述,matlab代码
===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== 成长优化算法 一、算法原理二、核心公式三、应用领域四、算法改进研究五…...
生产环境问题排查:日志分析与性能瓶颈定位(一)
引言 在当今数字化时代,各类应用系统如潮水般涌现,支撑着我们生活和工作的方方面面。从日常使用的电商平台、社交网络,到企业内部复杂的业务系统,它们的稳定运行和高效性能至关重要。而在生产环境中,日志分析与性能瓶…...
go语言的八股文
1.go语言触发异常的场景有哪些 运行时错误 1.空指针解引用:尝试访问一个未初始化的指针指向的内存,会导致程序崩溃并触发异常。 2.数组越界访问:试图访问数组中不存在的索引,比如数组长度为5,却尝试访问索引为10的元素…...
Office文件内容提取 | 获取Word文件内容 |Javascript提取PDF文字内容 |PPT文档文字内容提取
关于Office系列文件文字内容的提取 本文主要通过接口的方式获取Office文件和PDF、OFD文件的文字内容。适用于需要获取Word、OFD、PDF、PPT等文件内容的提取实现。例如在线文字统计以及论文文字内容的提取。 一、提取Word及WPS文档的文字内容。 支持以下文件格式: …...
组态软件工业化自动领域的可视化配置利器
组态软件是工业自动化领域的可视化配置利器,在工业生产中发挥着至关重要的作用,以下从定义、特点、功能、应用场景、市场现状及发展趋势等方面进行详细介绍: 定义 组态软件,又称组态监控系统软件,是用于数据采集和过程…...
Ansys electronics安装多版本simulink打开s-function冲突解决方法
安装了Ansys Electronics 2022 R1和2024 R1,想通过simplorer和simulink中的S-function进行联合仿真,结果注册表一直是2024 R1,修改方法如下: 1. WINR打开cmd,注意要用管理员权限打开 2. 输入 "D:\ANSYS\AnsysE…...
ubuntu--安装双系统
教程 BIOS设置 启动盘生成和ubuntu安装 boot option #1设置USB为第一启动项 rufus下载 官网: 链接 点击“链接”下面的按钮,即可下载。(注意查看自己的电脑是x64还是x84) 网盘下载: 链接...
快速搭建 Cpolar 内网穿透(Mac 系统)
1、Cpolar快速入门教程(官方) 链接地址:Cpolar 快速入门 2、官方教程详解 本地安装homebrew /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"这个是从 git 上拉取的&#x…...
【pytorch】torch.nn.Unfold操作
说明 一个代码里涉及到了unfold的操作,看了半天官网都没整明白维度怎么变化的,参考这个链接搞明白了: https://blog.csdn.net/ViatorSun/article/details/119940759 https://zhuanlan.zhihu.com/p/361140988 维度计算 输入( N,…...
使用PyTorch实现图像增广与模型训练实战
本文通过完整代码示例演示如何利用PyTorch和torchvision实现常用图像增广方法,并在CIFAR-10数据集上训练ResNet-18模型。我们将从基础图像变换到复杂数据增强策略逐步讲解,最终实现一个完整的训练流程。 一、图像增广基础操作 1.1 准备工作 #matplotli…...
PyTorch实现糖尿病预测的CNN模型:从数据加载到模型部署全解析【N折交叉验证、文末免费下载】
本文将详细介绍如何使用PyTorch框架构建一个卷积神经网络(CNN)来预测糖尿病,包含完整的代码实现、技术细节和可视化分析。 1. 项目概述 本项目使用经典的Pima Indians Diabetes数据集,通过5折交叉验证训练一个1D CNN模型,最终实现糖尿病预测…...
红队专题-漏洞挖掘-代码审计-反序列化
漏洞挖掘-代码审计-反序列化 加固/防御命令执行相关日志Tools-JNDIExploitJNDI Java Naming and Directory Interface Java命名目录接口注入原理payload参数渗透测试-php命令执行-RCE+Struts2拿webshell普通权限 命令执行 拿 webshellCMD echo 写入一句话 php文件菜刀连接Strut…...
【2025软考高级架构师】——计算机系统基础(7)
摘要 本文主要介绍了计算机系统的组成,包括硬件和软件两大部分。硬件由处理器、存储器、总线、接口和外部设备等组成,软件则涵盖系统软件和应用软件。文章还详细阐述了冯诺依曼计算机的组成结构,包括 CPU、主存储器、外存等,并解…...
【网络原理】TCP协议如何实现可靠传输(确认应答和超时重传机制)
目录 一. TCP协议 二. 确定应答 三. 超时重传 一. TCP协议 1)端口号 源端口号:发送方端口号目的端口号:接收方端口号 16位(2字节)端口号,可以表示的范围(0~65535) 源端口和目的…...
Java synchroinzed和ReentrantLock
synchronized —— JVM亲儿子的暗黑兵法 核心思想:“锁即对象,对象即锁!” 底层三板斧 对象头里的锁密码 每个Java对象头里藏了两个骚东西: Mark Word:32/64位的比特修罗场,存哈希码、GC年龄࿰…...
【Linux】vim配置----超详细
目录 一、插件管理器准备 二、目录准备 三、安装插件 一、插件管理器准备 Vim-plug 是一个Vim插件管理器,利用异步并行可以快速地安装、更新和卸载插件。它的安装和配置都非常简单,而且在操作过程中会给出很多易读的反馈信息,是一个自由、…...
驱动开发硬核特训 · Day 15:电源管理核心知识与实战解析
在嵌入式系统中,电源管理(Power Management)并不是“可选项”,而是实际部署中影响系统稳定性、功耗、安全性的重要一环。今天我们将以 Linux 电源管理框架 为基础,从理论结构、内核架构,再到典型驱动实战&a…...
如何使用人工智能大模型,免费快速写工作计划?
如何使用人工智能大模型,免费快速写工作计划? 具体视频教程https://edu.csdn.net/learn/40406/666579...
延长(暂停)Windows更新
延长(暂停)Windows更新 因为不关闭更新有时候就会出现驱动或者软硬件不兼容,导致蓝屏出现。 注:为什么选择延长更新而不是用软件暂停更新,因为使用软件暂停更新会出现一下问题,比如微软商店打不开等等 键…...
QT实现串口透传的功能
在一些产品的开发的时候,需要将一个串口的数据发送给另外一个串口进行转发。 具体的代码如下: #include "mainwindow.h" #include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::Ma…...
分布类相关的可视化图像
目录 一、直方图(Histogram) 1.定义 2.特点 3.局限性 4.类型 5.应用场景 6.使用Python实现 二、密度图(Density Plot) 1.定义 2.特点 3.局限性 4.类型 5.应用场景 6.使用Python实现 三、箱线图(Box Plo…...
【android bluetooth 框架分析 02】【Module详解 12】【 BidiQueue、BidiQueueEnd、Queue介绍】
1. BidiQueue 和 BidiQueueEnd 蓝牙协议栈里面有很多 BidiQueue ,本节就专门来梳理这块内容。 2. BidiQueue 介绍 BidiQueue,是 Host 与 Controller 层通信的中枢之一, acl_queue_、sco_queue_、iso_queue_ 都是 BidiQueue 类型。让我们一起看一下这个…...
c++通讯录管理系统
通讯录是一个可以记录亲人,好友的信息工具。 功能包括: 1,添加联系人:向通讯录添加新人,包括(姓名,性别年龄,联系电话,家庭住址) 2,显示联系人…...
React 打包
路由懒加载 原本的加载方式 #使用lazy()函数声明的路由页面 使用Suspense组件进行加载 使用CDN优化...
day1 python训练营
变量与输出 print(1,2,3,sep\n,endsep用来区分两个变量,end会紧跟最后一个变量) print(1,2,3,sepaaa,endsep用来区分两个变量,3后面不会再输出aaa) 格式化字符串 变量名值 print(f"变量名{变量名}") 变量的基础运算 ,-*,/ 注意*不要忘写。比如2j就不…...
C语言状态字与库函数详解:概念辨析与应用实践
C语言状态字与库函数详解:概念辨析与应用实践 一、状态字与库函数的核心概念区分 在C语言系统编程中,"状态字"和"库函数"是两个经常被混淆但本质完全不同的概念,理解它们的区别是掌握系统编程的基础。 1. 状态字&…...