赞
踩
Block完整格式:声明变量+定义
//block声明
void (^blockName) (int a, int b);
//block实现
int ^(int a, int b){
};
int (^blockName)(int numA, int numB) = ^int (int a, int b){
return a+b;
};
Block变量类似于函数指针。
用途:自动变量(局部变量)
截获自动变量:带有自动变量的值在Block中表现为“截获自动变量”。
值得注意是 :在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获。
Block判空:
在iOS中对一个 Block 进行判空实际上是在检查这个 Block变量是否有指向创建出来Block对象。
正如下面的例子:
void (^myBlock)(void);
if (myBlock) {
NSLog(@"2");
} else {
NSLog(@"1");
}
NSLog(@"%p", myBlock);
运行结果:说明该Block为空
这个是Block实际函数指针的结构体:
struct __block_impl {
void *isa;//用于保存Block结构体的实例指针
int Flags;//标志位
int Reserved;//今后版本升级所需的区域大小
void *FuncPtr;//函数指针,FuncPtr指针指向Block的主体部分,也就是Block对应OC代码中的^{…}的部分
};
而对于判空的底层代码逻辑则要跟更详细一点:
// 判空并执行 Block
if (myBlock != NULL && myBlock->FuncPtr != NULL) {
myBlock->FuncPtr();
} else {
printf("Block is NULL!\n");
}
因此判断Block是否为空有两个步骤:检查 Block 变量是否为 nil
,检查 Block 中的函数指针是否为 NULL
。
block可以截获变量,但是在block内部不能修改变量的值。
因此使用__block修饰符修饰变量,对需要在block内部赋值的变量,使用修饰符,确保可以对变量进行修饰。
id tyarray = @[@"blk", @"123", @"234"];
id __block arr = [[NSMutableArray alloc] init];
void (^blk) (void) = ^{
arr = tyarray;
NSLog(@"%@", arr);
};
我们都知道block捕获了带有__block修饰符的变量时可以修改变量的值,但是具体是怎么做到的?
以下面的变量为例:
__block int b = 10;
void (^block2)(void) = ^{
NSLog(@"%d",b);
};
b = 100;
block2();
原理:首先被__block
修饰的变量b,声明变为b的__Block_byref_b_0
结构体,加上__block
修饰符的话捕获到的block内的变量为__Block_byref_b_0
类型的结构体。
接下来看一下**__Block_byref_val_0
**结构体的底层:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
__isa
指针:Block_byref_age_0中也有isa指针也就是说__Block_byref_b_0
本质也一个对象。__forwarding
:__forwarding
是__Block_byref_b_0**
结构体类型的,并且__forwarding
存储的值为(__Block_byref_age_0
)&b,即结构体自己的内存地址。__flags
:0__size
:sizeof(__Block_byref_b_0
)即__Block_byref_b_0
所占用的内存空间。接着将 __Block_byref_b_0
结构体b存入__main_block_impl_0
结构体中,并赋值给__Block_byref_b_0 *age
之后调用block,首先取出__main_block_impl_0
中的b,通过b结构体拿到__forwarding
指针,__forwarding
中保存的就是__Block_byref_b_0
结构体本身,这里也就是b(__Block_byref_b_0
),在通过__forwarding
拿到结构体中的b(10)变量并修改其值。
总结:block为什么能够修改变量的值?因为block把变量包装成了一个带有指针的对象,然后把b封装在结构体里面,block内部存储的变量为结构体指针,也可以通过指针找到内存地址修改变量的值。
Block的存储域
三种类型对应不同的区域:
一个Block没有访问外部的局部变量,或者访问的是全局变量,或者是静态局部变量。此时的Block是全局Block,数据存储在全局区。
//block1没有引用到局部变量
int a = 10;
void (^block1)(void) = ^{
NSLog(@"hello world");
};
NSLog(@"block1:%@", block1);
// block2中引入的是静态变量
static int a1 = 20;
void (^block2)(void) = ^{
NSLog(@"hello - %d",a1);
};
NSLog(@"block2:%@", block2);
运行结果如下:
在捕获了局部变量之后,Block就会变成NSStackBlock,数据存储在栈中。
int a1 = 20;
void (^block2)(void) = ^{
NSLog(@"hello - %d",a1);
};
NSLog(@"\nblock2:%@", block2);
运行结果如下:但是刚开始打印的时候,并不是NSStackBlock,原因是:**在ARC环境下系统会自动将block进行拷贝操作。**只要换成MRC就行了。
什么时候栈上的 Block 会复制到堆呢?
int a = 10;
void (^block1)(void) = ^{
NSLog(@"%d",a);
};
NSLog(@"block1:%@", [block1 copy]);
__block int b = 10;
void (^block2)(void) = ^{
NSLog(@"%d",b);
};
NSLog(@"block2:%@", [block2 copy]);
运行结果如下:
简单来说,没有捕获自动变量的就是数据区,捕获了自动变量但是没有进行copy操作就是栈区,copy之后就变成了堆区。
初始化部分就是Block结构体
//Block结构体
struct __main_block_impl_0 {
struct __block_impl impl;//impl:Block的实际函数指针,就是指向包含Block主体部分的__main_block_func_0结构体
struct __main_block_desc_0* Desc;//Desc指针,指向包含Block附加信息的__main_block_desc_0()结构体
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {//__main_block_impl_0:Block构造函数(可以看到都是对上方两个成员变量的赋值操作)
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__main_block_impl_0结构体也就是Block结构体包含了三个部分:
struct __block_impl结构:包含Block实际函数指针的结构体
struct __block_impl {
void *isa;//用于保存Block结构体的实例指针
int Flags;//标志位
int Reserved;//今后版本升级所需的区域大小
void *FuncPtr;//函数指针
};
struct __main_block_desc_0结构:
static struct __main_block_desc_0 {
size_t reserved;//今后版本升级所需区域大小
size_t Block_size;//Block大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
Block构造函数__main_block_impl_0 :
作为构造函数注意和Block结构体是一个名字。
负责初始化__main_block_impl_0结构体(也就是Block结构体struct __block_impl
)的成员变量
//可以看到里面都是一些赋值操作
__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 (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
逐步解析这段代码:
((__block_impl *)myBlock)->FuncPtr
:这部分将 myBlock 转换为 __block_impl 指针类型,并访问 FuncPtr 成员。它获取了块实现内部存储的函数指针。((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)
:在这里,函数指针被转换为一个函数类型,该函数接受一个类型为 __block_impl* 的参数,并返回 void。它将函数指针转换为可以调用的实际函数类型。((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock)
:最后,使用 myBlock 作为参数,调用了所得到的函数指针。它使用块实现对象调用该函数。 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {//__main_block_impl_0:Block构造函数(可以看到都是对上方两个成员变量的赋值操作)
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
impl.isa = &_NSConcreteStackBlock;
_NSConcreteStackBlock相当于该block实例的父类.将Block作为OC对象调用时,关于该类的信息放置于_NSConcretestackBlock中,这也证明了 block出生就是在栈上。
BLOCK 可以捕获对象,其中需要知道两个方法。
在捕获对象的时候代码出现了_main_block_copy_0
和 _main_block_depose_0
。
捕获外部变量引用计数的变化
NSObject *objc = [NSObject new];
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 1
void(^strongBlock)(void) = ^{
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
strongBlock();
void(^__weak weakBlock)(void) = ^{
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
weakBlock();
void(^mallocBlock)(void) = [weakBlock copy];
mallocBlock();
运行结果:
对象持有导致对象不能及时的正常释放,容易造成内存泄漏。
#import "ViewController.h" typedef void (^TBlock)(void); @interface ViewController () @property (nonatomic, strong) NSString *name; @property (nonatomic, strong) TBlock block; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.name = @"View"; self.block = ^() { NSLog(@"%@", self.name); }; self.block(); } @end
self持有了block,block持有了self,导致循环引用。 编译器也会提示:Capturing 'self' strongly in this block is likely to lead to a retain cycle
如果单方面取消一方的持有即可取消循环。
// 不会引起循环引用
void(^blk1)(void);
blk1 = ^() {
NSLog(@"%@", self.name);
};
blk1();
这个案例就没有出现循环引用是因为当前self
,也就是ViewController
并没有对block
进行强持有,block
的生命周期只在viewDidLoad
方法内,viewDidLoad
方法执行完,block就会释放。
之前提到过weak,可以解决循环引用问题。
weak的使用
__weak typeof(self) weakSelf = self;
typeof(self)
:typeof
是一个运算符,用于获取表达式的类型。在这种情况下,表达式是 self,它代表当前对象的引用。
__weak typeof(self) weakSelf = self;
self.block = ^(){
NSLog(@"%@", weakSelf.name);
} ;
self.block();
此时self持有block
,block
弱引用self
,弱引用会自动变为nil
,强持有中断,所以不会引起循环引用。
之前学习GCD的时候将其他线程麻烦的操作执行完之后回到主线程,如果在执行其他线程的时ViewController被销毁,就会导致内部的函数来不及打印,导致想打印的数值为空。
。。。。。。。 __weak typeof(self) weakSelf = self; self.block = ^(){ // 延迟2秒钟 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)( 2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"%@", weakSelf.name); }); }; self.block(); [self fakeDealloc]; } - (void)fakeDealloc { NSLog(@"调用了dealloc 模拟ViewController模拟销毁"); // 模拟viewController被销毁 self.name = nil; }
运行结果:
为了解决上面的问题,由此引出了强弱共舞
#import "ViewController.h" @interface ViewController () @property (nonatomic, strong) NSString *name; @property (nonatomic, strong) void (^block)(void); @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.name = @"View"; __weak typeof (self) weakself = self; self.block = ^{ __strong __typeof(weakself) strongself = weakself; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"Block executed after 2 seconds. Name: %@", strongself.name); }); }; self.block(); [self dealloc1]; } - (void)dealloc1 { NSLog(@"Object is deallocating."); } @end
则一切就会正常打印。
因为__weak会自动置为nil,所以这里使用__strong(strong-weak-dance)暂时延长 self的生命周期,使得可以正常打印。
为什么强弱共舞能够避免循环引用,不是也调用了self? 因为这里strongself是一个临时的变量,出了作用域也跟着释放了,所以不会出现循环引用
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。