Block 的官方定义是这样的:Block块是封装工作单元的对象,是可以在任何时间执行的代码段,其本质是可移植的匿名函数,可以作为方法和函数的参数传入,可以从方法和函数中返回。
在iOS4以后,越来越多的系统级的API在使用Block。苹果对于Block的使用主要集中在如下几个方面:
- 完成处理–Completion Handlers
- 通知处理–Notification Handlers
- 错误处理–Error Handlers
- 枚举–Enumeration
- 动画与形变–View Animation and Transitions
- 分类–Sorting
- 线程管理:GCD/NSOperation
第二部分:操作Block外部的变量
Q:访问Block之外的变量?
int age=10;
void (^Block)(void) = ^{
NSLog(@"age:%d",age);
};
Block();
age = 20;
Block();
输出值为 age:10
输出值为 age:10
原因:创建block的时候,已经把age的值存储在里面了。
注意此时觉得不能对block里面age的值修改,会报错
Q:下列代码输出值分别为多少?
auto int age = 10;
static int num = 25;
void (^Block)(void) = ^{
NSLog(@"age:%d,num:%d",age,num);
};
age = 20;
num = 11;
Block();
输出结果为:age:10,num:11
愿意:auto变量block访问方式是值传递,static变量block访问方式是指针传递
源码证明
int age = __cself->age; // bound by copy
int *num = __cself->num; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_d2875b_mi_0, age, (*num));
int age = 10;
static int num = 25;
block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &num));
age = 20;
num = 11;
上述代码可查看 static修饰的变量,是根据指针访问的
Q:为什么block对auto和static变量捕获有差异?
auto自动变量可能会销毁的,内存可能会消失,不采用指针访问;static变量一直保存在内存中,指针访问即可
Q:修改block之外的变量
在block中假如需要修改block之外定义的变量时,那么在定义变量时必须加上__block关键字这个比较常用
self.shadowView.alpha = 0.0f;
__block typeof(self) bself = self;
[UIView animateWithDuration:0.2f animations:^{
bself.shadowView.alpha = 1.0f;
} completion:^(BOOL finished){
}];
或者
改变 result的结果
__block BOOL result = NO;
[self.dataBaseQueue inDatabase:^(FMDatabase *db){
result = [db executeUpdate:sql];
}];
Q:block在修改NSMutableArray,需不需要添加__block?
不需要。
NSMutableArray *array = [NSMutableArray array];
void(^block)(void) = ^{
[array addObject:@123];
};
Block();
这里 对 array 只是一个使用,而不是赋值,所以不需要 _ _block 进行修饰
错误的例子
NSMutableArray *array = nil;
void(^block)(void) = ^{
array = [NSMutableArray array];
};
Block();
这里就需要在array的声明处添加__block修饰符,不然编译器会报错
总结下,对变量进行赋值的时候,下面这些不需要__block修饰符
Q:block能否修改变量值?
auto修饰变量,block无法修改,因为block使用的时候是内部创建了变量来保存外部的变量的值,block只有修改内部自己变量的权限,无法修改外部变量的权限。
static修饰变量,block可以修改,因为block把外部static修饰变量的指针存入,block直接修改指针指向变量值,即可修改外部变量值。
全局变量值,全局变量无论哪里都可以修改,当然block内部也可以修改。
Q:__block 修饰符作用?
Q:block的属性修饰词为什么是copy?
block一旦没有进行copy操作,就不会在堆上
block在堆上,程序员就可以对block做内存管理等操作,可以控制block的生命周期
@property (copy, nonatomic) CMBCAddressUIBlock finishCompletion;
block的类型,取决于isa指针,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
__NSGlobalBlock __ ( _NSConcreteGlobalBlock )
__NSStackBlock __ ( _NSConcreteStackBlock )
__NSMallocBlock __ ( _NSConcreteMallocBlock )
Q:当block内部访问了对象类型的auto变量时,是否会强引用?
答案:分情况讨论,分为栈block和堆block
栈block
a) 如果block是在栈上,将不会对auto变量产生强引用
b) 栈上的block随时会被销毁,也没必要去强引用其他对象
堆block
1.如果block被拷贝到堆上:
a) 会调用block内部的copy函数
b) copy函数内部会调用_Block_object_assign函数
c) _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
2.如果block从堆上移除
a) 会调用block内部的dispose函数
b) dispose函数内部会调用_Block_object_dispose函数
c) _Block_object_dispose函数会自动释放引用的auto变量(release)
正确答案:
如果block在栈空间,不管外部变量是强引用还是弱引用,block都会弱引用访问对象
如果block在堆空间,如果外部强引用,block内部也是强引用;如果外部弱引用,block内部也是弱引用
Q:__weak 在使用clang转换OC为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
Q1:gcd的block中引用 Person对象什么时候销毁?
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
Person *person = [[Person alloc] init];
person.age = 10;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"age:%d",person.age);
});
NSLog(@"touchesBegan");
}
原因:gcd的block默认会做copy操作,即dispatch_after的block是堆block,block会对Person强引用,block销毁时候Person才会被释放。
Q2:上述代码如果换成__weak,Person什么时候释放?
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
Person *person = [[Person alloc] init];
person.age = 10;
__weak Person *weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"age:%p",weakPerson);
});
NSLog(@"touchesBegan");
}
原因:使用__weak修饰过后的对象,堆block会采用弱引用,无法延时Person的寿命,所以在touchesBegan函数结束后,Person就会被释放,gcd就无法捕捉到Person。
Q4:如果gcd内部先强引用后弱引用,Person什么时候释放?
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
Person *person = [[Person alloc] init];
person.age = 10;
__weak Person *weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2-----age:%p",weakPerson);
});
NSLog(@"1-----age:%p",person);
});
NSLog(@"touchesBegan");
}
原因:Person会等待强引用执行完毕后释放,只要强引用执行完,就不会等待后执行的弱引用,会直接释放的,所以Person释放时间为4秒。
Block的循环引用原理和解决方法大家都比较熟悉,此处将结合上文的介绍,介绍一种不常用的解决Block循环引用的方法和一种借助Block参数解决该问题的方法。
Block循环引用原因:一个对象A有Block类型的属性,从而持有这个Block,如果Block的代码块中使用到这个对象A,或者仅仅是用用到A对象的属性,会使Block也持有A对象,导致两者互相持有,不能在作用域结束后正常释放。
解决原理:对象A照常持有Block,但Block不能强引用持有对象A以打破循环。
__unsafe_unretained Person *person = [[Person alloc] init];
person.block = ^{
NSLog(@"age is %d", weakPerson.age);
};
__block的作用:能够对外部的变量操作和修改
__block XXController *blkSelf = self;
self.blk = ^{
NSLog(@"In Block : %@",blkSelf);
};
注意上述代码仍存在内存泄露,因为:
__block XXController *blkSelf = self;
self.blk = ^{
NSLog(@"In Block : %@",blkSelf);
blkSelf = nil;//不能省略
};
self.blk();//该block必须执行一次,否则还是内存泄露
在block代码块内,使用完使用完__block变量后将其设为nil,并且该block必须至少执行一次后,不存在内存泄露,因为此时:
第二种使用__block打破循环的方法,优点是:
其缺点也明显:
因此,还是避免使用第二种不常用方式,直接使用__weak打破Block循环引用
__weak 加入弱引用表示同一个指针地址,weakSelf 是否对引用计数处理
将在Block内要使用到的对象(一般为self对象),以Block参数的形式传入,Block就不会捕获该对象,而将其作为参数使用,其生命周期系统的栈自动管理,不造成内存泄露
__weak typeof(self) weakSelf = self;
self.blk = ^{
__strong typeof(self) strongSelf = weakSelf;
NSLog(@"Use Property:%@", strongSelf.name);
//……
};
self.blk();
为什么 weakSelf 需要配合 strongSelf 使用
__weak typeof(self) weakSelf = self;
[self doSomeBackgroundJob:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
...
}
}];
在 block 中先写一个 strongSelf,其实是为了避免在 block 的执行过程中,突然出现 self 被释放的尴尬情况。通常情况下,如果不这么做的话,还是很容易出现一些奇怪的逻辑,甚至闪退。
strongSelf 释放时机?
strongSelf 的作用域在block里面,也就是block执行完毕后就会被释放掉。
比如下面这样
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[weakSelf doSomething];
[weakSelf doOtherThing];
});
在 doSomething 内,weakSelf 不会被释放.可是在执行完第一个方法后 ,weakSelf可能就已经释放掉,再去执行 doOtherThing,会引起 一些奇怪的逻辑,甚至闪退。
所以需要这么写
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong __typeof(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doOtherThing];
});
在比如
__weak typeof(self) weakSelf = self;
[self.collectionView performBatchUpdates:^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf.collectionView deleteItemsAtIndexPaths:@[ previousIndexPath ]];
[strongSelf.collectionView insertItemsAtIndexPaths:@[ newIndexPath ]];
}
} completion:^(BOOL finished) {
__strong typeof(self) strongSelf = weakSelf;
if ([strongSelf.dataSource respondsToSelector:@selector(collectionView:itemAtIndexPath:didMoveToIndexPath:)]) {
[strongSelf.dataSource collectionView:strongSelf.collectionView itemAtIndexPath:previousIndexPath didMoveToIndexPath:newIndexPath];
}
}];
优点:
依赖于终中介者模式,自己不方便nil就交给别人来处理
self.block = ^(ViewController *vc) {
NSLog(@"%@",vc.name)
};
分析
self 对block 持有,但是vc)没有对block 持有,所以不造成循环引用。它只是作为一种临时变量压栈进去。
self.timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(test1) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
-(void)test1{
NSLog(@"%s",__FUNCTION__);
}
1.timer的timerWithTimeInterval这个方法里面的参数target,接收的是viewController的内存地址,而在该方法内部,会形成一个对viewController 的强引用;
2.而恰巧 timer 是viewController的一个强指针属性,这就造成了强循环引用
因此我们可以设法在timer和viewController之间加入一个中间人。你timer不是谁把地址传给你你就强引用谁吗?好嘛,你本来是要强引用viewController的,现在你不必了,中间人的地址传给你timer,你timer要调用viewController的什么方法,你先告知中间人,中间人因为这种弱引用了viewController,所以可以把你timer发过来的消息转发给viewController。这样,即解决了消息传递问题,又能将强循环引用斩断,一举两得。
struct __mian_block_impl_0 {
struct __block_impl impl;
struct __mian_block_desc_0* Desc;
__mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
总结block 是一个结构体,所以block能够打印“%@”
如图所示
void(*block)(void) = ((void (*)())&__mian_block_impl_0((void *)__mian_block_func_0, &__mian_block_desc_0_DATA));
这两个方法是有两个参数:
1.__mian_block_func_0
2.&__mian_block_desc_0_DATA
static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
printf("赵苗苗");
}
在结构体里面传给了 impl.FuncPtr = fp
__mian_block_impl_0(void *fp, struct __mian_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp; // __mian_block_func_0 这个函数给了fp 手法:函数式:(以函数作为参数传进来)
Desc = desc;
}
小知识点:以函数式作为参数就是函数式编程
总结结构体里面有匿名函数:({})
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
简洁化
// 为什么参数是block
// block 里面包含了所有参数,隐藏参数
// 函数里面拿不到block,所以把block传进来
block->FuncPtr(block);
因为函数的声明和具体的实现
static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("赵苗苗 --- %d",a);
}
分析
1.a 在编译时就自动生成了相应的变量
2. int a = __cself->a 是值拷贝
3. 值拷贝:内存地址与原来的内存地址不一样,所以只能读,不能修改
分析
__Block_byref_a_0 a = {
// a 是外部变量的地址
(void*)0,(__Block_byref_a_0 *)&a,
0, sizeof(__Block_byref_a_0),
// 值
10
};
1.把原来的地址和值封装成对象传给了结构体
static void __mian_block_func_0(struct __mian_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref // 指针拷贝
// 所以对block 里面的++,就是对外面的++
(a->__forwarding->a)++;
printf("赵苗苗 --- %d",(a->__forwarding->a));
}
__Block_byref_a_0 *a = __cself->a 它是一个指针拷贝,
所以对block 里面的++,就是对外面的++
解读过的源码