一 block种类
1 静态Block
初始化一个block并打印当前block,能直接用%@打印它是因为block就是一个对象.
void(^block)(void) = ^{
};
NSLog(@"%@",block);
控制台输出: <NSGlobalBlock: 0x1016610e8>
证明当前block在静态区.
2 堆Block
声明一个外部变量并在block作用域引用这个外部变量
int a = 0;
void(^block)(void) = ^{
NSLog(@"%d",a);
};
NSLog(@"%@",block);
控制台输出:<NSMallocBlock: 0x600000b29080>
说明当block引用外部变量时,它是在堆区的.
3 栈Block
直接打印等号右边的匿名函数
int a = 0;
NSLog(@"%@",^{
NSLog(@"%d",a);
});
控制台输出:<NSStackBlock: 0x7ffeec538e80>
可以看出这个引用了外部变量的最简单的block是在栈区的.
二 循环引用
声明一个block并引用
self.block = ^{
NSLog(@"%@",self.name);
};
self初始化了block,block作用域引用self两者互相持有造成了循环引用,使两者不能正常释放.
1. weak打破循环引用
用weak修饰的属性在被引用一次后会立刻被释放掉
__weak typeof(self)weakSelf = self;
self.block = ^{
__strong typeof(self)strongSelf = weakSelf;
NSLog(@"%@",strongSelf.name);
};
__strong修饰的作用是延长self的生命周期,防止block代码块延迟执行的时候会引用一个已经被释放掉的self.
2. __block拷贝
通过__block将self拷贝到vc2,在block作用域引用vc2,注意要在引用完后手动释放vc2并且还要保证让block执行此代码
__block ViewController2 *vc2 = self;
self.block = ^{
NSLog(@"%@",vc2.name);
vc2 = nil;
};
self.block();
4. 通过block参数传递引用者
将self作为一个临时变量通过block传递到block作用域,此时block引用的是自己的参数而不是self.不会存在循环引用
self.block = ^(ViewController2 *vc){
NSLog(@"%@",vc.name);
};
self.block(self);
三 原理初探
__block int a = 0;
NSLog(@"上===%p",&a);
void(^block)(void) = ^{
a++;
NSLog(@"中===%p",&a);
};
NSLog(@"下===%p",&a);
block();
a初始化在栈区,而block作用域是在堆区,如果想在block作用域改变a的值就要通过__block捕捉变量a,再拷贝到堆区.
控制台输出a的指针地址:
上===0x7ffee4c8fed8
下===0x6000000e02b8
中===0x6000000e02b8
在经过block作用后a指针的地址由0x7(高地址)栈区变为0x6(低地址)堆区.
block究竟对a做了什么操作?
- 打开终端
输入: vim bock.c
创建一个c文件
2.在c文件中写一个简单的block
#include "stdio.h"
int main(){
int a = 10;
void(^block)(void) = ^{
printf("%d",a);
};
block();
return 0;
}
3.打开block.cpp开始源码分析 image.png在终端输入: clang -rewrite-objc block.c
将我们的c代码转成c++
发现在block.c目录下多了一个block.cpp文件
如图通过函数名称可以不难看出c++与c语言对应的代码.
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
通过这几行代码可以看出block就是一个struct结构体
impl.isa = &_NSConcreteStackBlock;表示block初始化就是stackBlock,isa表示它是一个对象
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy 值拷贝
printf("%d",a);
}
再通过这几行代码可以看出block又声明了一个变量a把外部变量a的值拷贝给了a,也就是值拷贝.所以我们在这里是改变不了a的值的因为a的内存区域还在外面.
- 修改代码用__block修饰变量a
#include "stdio.h"
int main(){
__block int a = 10;
void(^block)(void) = ^{
printf("%d",a);
};
block();
return 0;
}
重新生成c++代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref 指针拷贝
printf("%d",(a->__forwarding->a));
}
通过 *a 可以看出这次拷贝的是指针,指针指向存放变量a的内存区域,我们可以在此修改变量a的值了
三 block拓展应用
- 链式编程
- (void)viewDidLoad {
[super viewDidLoad];
NSString *whereStr = self.run.where(@"block");
NSLog(@"%@",whereStr);
}
- (ViewController2 *)run{
return self;
}
- (NSString *(^)(NSString *string))where{
NSString *(^block)(NSString *string) = ^(NSString *string){
return [NSString stringWithFormat:@"hello! %@",string];
};
return block;
}
我们声明的无参数的方法都可以直接通过点语法调用原理和调用属性的getter方法一样,所以我们只要给其中一个方法返回self就可以实现连续调用.但是如果想要给方法传参数该怎么办呢?如图可以直接在方法中返回一个block,如果返回的block是有返回值的,我们还可以通过调用方法获取参数.这种做法让我想到了masonry中设置view的宽度高度等值时的写法.
- 函数式编程
- (void)viewDidLoad {
[super viewDidLoad];
[self run:^(NSString *string) {
NSLog(@"%@",string);
}];
}
- (void)run:(void(^)(NSString *))block{
block(@"hello block");
}
这种方法很常见,将block的匿名函数作为参数在方法中传递. 把函数当成参数传递给另一个函数.
网友评论