全文分两部分(认认真真看完,肯定有收获)
1.Block定义主要是介绍一下block的使用
2.详解Block,深入了解Block的用法
一.Block定义
1.Block格式
//等号左侧是声明一个block,右侧是定义一个block
(1).定义block块必须以^开头,声明块以返回值类型开头
(2).定义块的返回值类型可以省略,而且经常会省略,声明不能省略
(3).定义块无需指定名字,声明块必须得有
(4).如果块没有参数,参数部分可以省略但是参数部分的括号不能省略,
但是内部可以留空,通常建议使用void作为占位符
returnType(^blockName)(parameterTypes) = ^(parameters) {
statements
};
void(^blockName)(NSString *) = ^void(NSString*str){
NSLog(@"%@",str);
};
blockName(@"这是一个简单的无返回值的block");
//^告诉计算机这是一个block
NSString *(^blockParam)(NSString *) = ^NSString*(NSString*n){
NSLog(@"%@",n);
return @"123456789";
};
NSString * str = blockParam(@"这是一个简单的带返回值的block");
str = @"123456789";
2.Block作为属性
@property(nonatomic,copy) void (^propertyBlock)(NSString*);
3.typedef定义Block
typedef void (^typedefBlock)(NSString *);
@property(nonatomic, copy)typedefBlock block;
4.Block作为参数不带返回值
- (void)viewDidLoad{
[self blockAsParam:^(NSString *name) {
NSLog(@"%@",name);//123123123
}];
}
- (void)blockAsParam:(void (^)(NSString * _Nullable))paramBlock{
paramBlock(@"123123123");
}
5.Block作为参数带返回值
- (void)viewDidLoad{
[self blockAsParamReturnValue:^NSString*(NSString *name){
NSLog(@"%@",name);
return @"123123123123";
}];
}
- (void)blockAsParamReturnValue:(NSString *(^)(NSString *str))returnValue{
NSString *s = returnValue(@"123123123");
NSLog(@"%@",s);//123123123123
}
6.Block不带返回值,作为返回值
- (void)viewDidLoad{
void(^blockNoReturn)(NSString*) = [self blockNoReturnValue:@"11111"];
blockNoReturn(@"000000");
}
- (void(^)(NSString *))blockNoReturnValue:(NSString*)value{
return ^(NSString *name){
NSLog(@"%@,%@",name,value);//000000,11111
};
}
7.Block带返回值,作为返回值
- (void)viewDidLoad{
NSString *(^haveReturnValue)(NSString *) = [self blockHaveReturnValue:@"2222"];
NSString *s = haveReturnValue(@"123");
NSLog(@"%@",s);//11111111111111111111
}
- (NSString *(^)(NSString*))blockHaveReturnValue:(NSString*)value{
return ^NSString *(NSString*n){
NSLog(@"%@,%@",n,value);//123,2222
return @"11111111111111111111"
};
}
二.Block详解(2020.03.18更新)
1.使用copy和strong修饰block有什么区别吗?
block有三种存储类型__NSGlobalBlock__,__NSStackBlock__, __NSMallocBlock__.
经测试在MRC环境下,任何位置定义的block,未引用外部变量的情况下为NSGlobalBlock类型.
引用了外部静态变量(全局/局部)的为NSGlobalBlock,引用了外部非静态变量(全局/局部)的,
如果使用copy修饰了属性并且使用了self.语法调用的为NSMallocBlock,否则是NSStackBlock类型.
(
举例
@property (nonatomic ,copy) void (^copyPropertyBlock)(NSString * _Nullable);
self.copyPropertyBlock = ^(NSString *c){
int d = globaln + 9;
};//globaln为非静态变量,这个时候copyPropertyBlock类型是NSMallocBlock
)
在以上的基础上对所有的block执行copy操作,NSGlobalBlock的block保持原状,
NSStackBlock类型的block变为NSMallocBlock,拷贝到了堆上.所以在MRC中需要使用copy修饰
否则在栈中会随时释放
在ARC环境下任何位置定义的block,未引用外部变量的情况下为NSGlobalBlock类型,
引用了外部静态变量(全局/局部)的为NSGlobalBlock,引用了外部非静态变量(全局/局部)的为NSMallocBlock
在以上的基础上对所有的block执行copy操作,NSGlobalBlock的block保持原状,
NSMallocBlock的类型未变化(地址也未发生变化)
结论
在MRC中访问外部非静态变量的block必须需要使用copy对block进行修饰,将其拷贝到堆中.
ARC中系统已经帮我们将其从栈拷贝到堆了,所以我们不需要在进行copy处理
,使用copy或者strong修饰都可以.
网上一搜好多关于这方面的讲解,但是大多数都是复制的一篇文章,内容都一样,
而且还是比较老的文章,没有涉及ARC和MRC的对比,当然也有好的文章,建议大家自己试一下才会知道结果.
2.__weak和__block
__weak仅仅是将该对象赋值给weak修饰的对象,__weak还有一个作用就是将被修饰的对象加入缓存池
中,当缓存池释放时,会对其内部的对象release一次,因为对象是weak修饰的弱引用,所以会被释放.
不会引起循环引用的问题
__block
修饰的局部变量会包装成一个结构体对象,这个结构体对象里面包含着被修饰的对象,被block持有,
这个时候__block修饰的值是可以修改的.
3.block捕获变量
源码查看方式
#include "DDBlockStudy.h"
//全局的
int w = 23;
static int e = 10;
int f[] = {11,22,33,44};
void buildBlock(){
int m = 1;
static int n = 3;
int arr[] = {1,2,3,4,5,6};
void (^testBlock)(int)= ^(int d){
printf("\n%d,%d,%d,%d,%d\n", w,e,*f,m,n);
//打印的结果是12,14,32,1,10
};
w = 12;
e = 14;
*f = 32;
m = 4;
n = 10;
testBlock(3);
}
上面的内容出现的结果可以总结一下
(1).静态变量(全局和局部)和全局变量并未捕获任何内容,用的时候直接取得,可以修改值
(2).属性值和成员变量捕获的是对象的持有者(self),可以修改值
(3).对于局部变量block捕获的是值.
理由见 static void __buildBlock_block_func_0(struct __buildBlock_block_impl_0 *__cself, int d);函数
(补充,更确切的说是在__buildBlock_block_impl_0((void *)__buildBlock_block_func_0, &__buildBlock_block_desc_0_DATA, m, &n)函数中可以看出,详细见下面的👇Block修改变量有解说)
//通过源码分析
int w = 23;
static int e = 10;
int f[] = {11,22,33,44};
//这是block的定义来自__buildBlock_block_impl_0((void *)__buildBlock_block_func_0, &__buildBlock_block_desc_0_DATA, m, &n)
struct __buildBlock_block_impl_0 {
struct __block_impl impl;
struct __buildBlock_block_desc_0* Desc;
//下面的内容是被捕获的参数
int m;
int *n;
//void *fp 对应 (void *)__buildBlock_block_func_0 方法的指针
//struct __buildBlock_block_desc_0 *desc 对应 &__buildBlock_block_desc_0_DATA 这个block的一些参数
//impl.isa = &_NSConcreteStackBlock;可以得出当前的block在栈中
//m(_m)将_m的值赋给m,n同理
//从这里看出(int _m, int *_n)这个时候m和n被捕获了,但是m只是值被持有了,n被持有的是指针.
__buildBlock_block_impl_0(void *fp, struct __buildBlock_block_desc_0 *desc, int _m, int *_n, int flags=0) : m(_m), n(_n) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//下面的这个函数存储的是block执行的代码
static void __buildBlock_block_func_0(struct __buildBlock_block_impl_0 *__cself, int d) {
int m = __cself->m; // bound by copy
int *n = __cself->n; // bound by copy
printf("\n%d,%d,%d,%d,%d\n", w,e,*f,m,(*n));
}
static struct __buildBlock_block_desc_0 {
size_t reserved;
size_t Block_size;
} __buildBlock_block_desc_0_DATA = { 0, sizeof(struct __buildBlock_block_impl_0)};
void buildBlock(){
int m = 1;
static int n = 3;
int arr[] = {1,2,3,4,5,6};
void (*testBlock)(int) = ((void (*)(int))&__buildBlock_block_impl_0((void *)__buildBlock_block_func_0, &__buildBlock_block_desc_0_DATA, m, &n));
w = 12;
e = 14;
*f = 32;
m = 4;
n = 10;
//这里是调用了这个block
((void (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 3);
}
4.Block修改变量
先上代码
#import "DDBlockController.h"
@interface DDBlockController ()
@property (nonatomic, assign) int a;
@property (nonatomic, copy) NSString *str;
@end
static int b = 11;
int c = 10;
NSString *str1 = @"aaaaaa";
static NSString *str2 = @"bbbbbb";
@implementation DDBlockController
{
int d;
NSString *str3;
void (^testTwo)(void);
}
- (void)viewDidLoad {
[super viewDidLoad];
self.a = 123;
self.str = @"eeeeeeeee";
d = 4444444;
str3 = @"rrrrrrrrrrrrrrrrr";
[self blockTest];
// Do any additional setup after loading the view.
}
- (void)blockTest{
//如果修改局部非静态变量需要使用__block修饰.
int e = 10;
static int f = 12;
NSString * str4= [[NSString alloc]init];;
str4 = @"123123";
static NSString * str5= @"1234asdads";
void (^testOne)(void) = ^(void){
NSLog(@"%d",_a);
NSLog(@"%d",b);
NSLog(@"%d",c);
NSLog(@"%d",d);
NSLog(@"%d",e);
NSLog(@"%d",f);
NSLog(@"%@",_str);
NSLog(@"%@",str1);
NSLog(@"%@",str3);
NSLog(@"%@",str4);
NSLog(@"%@",str5);
};
testOne();
}
- (void)dealloc{
NSLog(@"aaaaaaaaaaaaaa");
}
@end
//*desc和flags之间的是被捕获的内容,self(_self)将_self赋值给self, DDBlockController *self = __cself->self; // bound by copy,从__DDBlockController__blockTest_block_impl_0中取出self的值(C++->取值符号)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {
将block中的打印依次打开,从源码中可以看出(注捕获的意思就是持有的意思)
(1).使用当前类属性的时候self.a时,会将持有a的对象self捕获,可以修改值,但是注意循环引用(DDBlockController *_self)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {
(2).当使用全局静态变量b的时候未捕获任何值,用的时候直接使用,可以修改值
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int flags=0) {
(3).当使用全局变量c的时候未捕获任何值,用的时候直接取,可以修改值
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int flags=0) {
(4).当使用成员变量d的是,捕获的是对其持有的self,可以修改其值,但是注意循环引用问题(DDBlockController *_self)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {
(5).当使用局部变量e的时候会捕获其值,未捕获其指针地址,不可修改值除非使用__block修饰(int _e)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int _e, int flags=0) : e(_e) {
(6).当使用局部静态变量f的时候,捕获的是f的指针,可以进行修改(int *_f,)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int *_f, int flags=0) : f(_f) {
(7).当使用全局的属性self.str时,捕获的是持有str的对象self,可以修改值,但是注意循环引用(DDBlockController *_self)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {
(8).当使用全局变量str1的时候,未捕获任何值,因此可以直接使用且可以修改值
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, int flags=0) {
(9).当使用成员变量d的是,捕获的是对其持有的self,可以修改其值,但是注意循环引用问题(DDBlockController *_self)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, DDBlockController *_self, int flags=0) : self(_self) {
(10).当使用局部变量str4的时候,捕获的是str4的值并没有捕获其指针地址,所以不可修改,除非使用__block修饰(NSString *_str4)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, NSString *_str4, int flags=0) : str4(_str4) {
(11).当使用局部静态变量str5的时候,捕获的是str5的地址,所以可以修改(NSString **_str5)
__DDBlockController__blockTest_block_impl_0(void *fp, struct __DDBlockController__blockTest_block_desc_0 *desc, NSString **_str5, int flags=0) : str5(_str5) {
总结
//auto变量(局部非静态变量)捕获的是值所以不能操作修改值.
//注意因为当前的block是局部的并未被self持有,所以使用self不会引起循环引用
//__block的作用是将修饰的变量包装成一个结构体对象
下面的这个是__block修饰的int e;被block捕获的后,系统为其创建的一个结构体
struct __Block_byref_e_0 {
void *__isa;
__Block_byref_e_0 *__forwarding;//__forwarding指向的是__Block_byref_e_0自己,自己指向了自己
int __flags;
int __size;
int e;
};
//对于可变变量(NSMutableArray,NSMutableString等)即使是局部变量也可以进行增删改查的操作,
//因为可变内容在初始话的时候分配了一块内存,操作的时候都是在这块内存操作的,只要是不改变该变量的指针对应的内容即可
总结一下就是对于局部非静态变量而言,可以修改局部变量的属性等操作,但是不能改变其指向的内存的地址,否则就会报错,
使用__block之后就是block持有了这个对象,没有使用__block的话只是copy了一份,但是只有只读权限,不能改变对象,但是可以改变对象的属性.
意思就是秘书来了权限不够不好使,只能动一部分内容.老板亲自来了权限就大了可以做的事就更多了.
5.Block循环引用,并不是所有的block都会循环引用
6.Block作为形参的时候不会引起循环引用,这也就解释了为什么GCD或者UIView动画这些操作不会引起循环引用
7.Block存储域