iOS中Block使用注意点及常见问题浅析
2017-08-31 18:14
232 查看
原写于简书Block使用注意点及常见问题浅析,现转在CSDN
本文将浅分析几个Block使用问题:
- 问题一:Block作为类变量属性时为啥用copy修饰?堆栈存储位置是怎样的?
- 问题二:为什么需要__block 修饰自动变量后,才能在Block中更改?
- 问题三:关于常见block中self的循环引用问题,可以用__weak打破强引用链;那么为什么AFN或像UIView动画不需要__weak修饰self?
Block类型:
内联Block(inline):说白了就是Block被嵌入到一个函数中,较常使用;
独立Block:即在方法外定义的。不能直接访问 self,只能通过将 self 当作参数传递到 Block 中才能使用,并且此时的 self 只能通过 setter 或 getter 方法访问其属性,不能使用句点式方法。但内联 Block 不受此限制.
Block作为类属性,要用copy修饰,把Block从栈拷贝到堆中,防止被释放掉;
不能修改外部自动变量问题:
Block里面能修改全局或静态的变量,但是不能修改在Block外并且定义在所在方法内的自动变量,这时需要__block符修饰此变量;
例:
(自动(automatic)变量,即局部变量:不作专门说明的局部变量,均是自动变量。自动变量也可用关键字auto作出说明,auto int c=3;/c为自动变量/。自动变量只有一种存储方式,就是存储在栈中。由于自动变量存储在栈中,所以自动变量的作用域只在函数内,其生命周期也只持续到函数调用的结束。)
循环引用问题:
Block在方法中被定义时,该方法执行完是需要被释放的,Block会强引用自己持有的对象,使其引用计数+1,如果所持有的对象也持有此Block时,需要__weak 在外修饰此对象,标识不+1,常见的是self或一些强类型的对象:
例如:(ARC下)
(MRC下,无__weak)
ARC与MRC下Block内存分配机制不一样,ARC中iOS5+用
__unsafe_unretained缺点是指针所引用的对象被回收后,自己不会置为nil,因此可能导致程序崩溃;而weak指针不同,对象被回收后,指针会被赋值为nil。一般来说,weak修饰符更加安全。
另一种写法:
有时候weakSelf会在Block未执行完就会释放掉成为nil,为了防止这种情况出现,在Block内需要__strong对weakSelf强引用一下(更高级写法见RAC的@weakify(self),@strongify(self))。
这样不会造成循环引用是因为strongSelf是在Block里面声明的一个指针,当Block执行完毕后,strongSelf会释放,这个时候将不再强引用weakSelf,所以self会正确的释放。
1._NSConcreteGlobalBlock 全局的静态block,不会访问任何外部变量。
2._NSConcreteStackBlock 保存在栈中的block,当函数返回时会被销毁。
3._NSConcreteMallocBlock 保存在堆中的block(从栈中copy过去的),当引用计数为0时会被销毁。
Block内捕获变量会改变自身存储位置,包括读取变量和__block这种写变量,两种方式(其实结果是一样的)。
【在MRC下】:存在栈、全局、堆这三种形式。
【在ARC下】:大部分情况下系统会把Block自动copy到堆上。
Block作为变量:
方法中声明一个 block 的时候是在栈上;
引用了外部局部变量或成员变量,并且有赋值操作(有名字),会被 copy 到堆上;
赋值给附有__strong修饰符的id类型的类或者Blcok类型成员变量时;
赋值给一个 weak 变量不会被 copy;
Block作为属性:
用 copy 修饰会被 copy;
Block作为函数参数:
作为参数传入函数不会被 copy,依然在栈上,方法执行完即释放;
作为函数的返回值会被 copy 到堆;
例如:
问题二:为什么
首先,为什么Block不能修改外部自动变量?
自动变量存于栈中,在当前方法执行完,会释放掉。一般来说,在 block 中用的变量值是被复制过来的,自动变量是值类型复制,新开辟栈空间,所以对于新复制变量的修改并不会影响这个变量的真实值(也称只读拷贝)。大多情况下,block是作为参数传递以供后续回调执行的。所以在你想要在block中修改此自动变量时,变量可能已被释放,所以不允许block进行修改是合理的。
对于 static 变量,全局变量,在 block中是有读写权限的,因为此变量存于全局数据区(非栈区),不会随时释放掉,也不会新开辟内存创建变量, block 拷贝的是指向这些变量的指针,可以修改原变量。
那么怎么让自动变量不被释放,还能被修改呢?
__block修饰符把所修饰的变量包装成一个
将下面main.m文件进行clang -rewrite-objc main.m命令查看编译时的c++代码:
如下:
在__main_block_func_0里,发现是通过
我们知道,ARC下Block很多情况会被自动拷贝到堆,而在栈上的__block变量被复制到堆上之后,会将结构体
解决循环引用问题主要有两个办法:
事前避免,我们在会产生循环引用的地方使用 weak 弱引用,以避免产生循环引用。
事后补救,我们明确知道会存在循环引用,但是我们在合理的位置主动断开环中的一个引用,使得对象得以回收。
有时候纳闷为什么AFN或像UIView的Block动画不需要weakSelf也可以自己释放掉。其实明白了无法释放的原理,也就明白了。虽然Block强引用了self,但是self不强引用这个Block呀。
举例说明(需要和不需要使用__weak打破循环引用):
本文将浅分析几个Block使用问题:
- 问题一:Block作为类变量属性时为啥用copy修饰?堆栈存储位置是怎样的?
- 问题二:为什么需要__block 修饰自动变量后,才能在Block中更改?
- 问题三:关于常见block中self的循环引用问题,可以用__weak打破强引用链;那么为什么AFN或像UIView动画不需要__weak修饰self?
Block类型:
内联Block(inline):说白了就是Block被嵌入到一个函数中,较常使用;
独立Block:即在方法外定义的。不能直接访问 self,只能通过将 self 当作参数传递到 Block 中才能使用,并且此时的 self 只能通过 setter 或 getter 方法访问其属性,不能使用句点式方法。但内联 Block 不受此限制.
// 独立Block void (^correctBlockObject)(id) = ^(id self){ // 将self作为参数传递 NSLog(@"self = %@", self); // 访问self变量 NSLog(@"self = %@", [self strName]); };
使用注意点:
Block作为属性用copy修饰:Block作为类属性,要用copy修饰,把Block从栈拷贝到堆中,防止被释放掉;
不能修改外部自动变量问题:
Block里面能修改全局或静态的变量,但是不能修改在Block外并且定义在所在方法内的自动变量,这时需要__block符修饰此变量;
例:
__block NSInteger val = 0; void (^block)(void) = ^{ val = 1; }; block(); NSLog(@"val = %ld", val); //(这是在ARC下这样使用,MRC下__block含义是不增加此对象引用计数,相当于ARC下的__weak)
(自动(automatic)变量,即局部变量:不作专门说明的局部变量,均是自动变量。自动变量也可用关键字auto作出说明,auto int c=3;/c为自动变量/。自动变量只有一种存储方式,就是存储在栈中。由于自动变量存储在栈中,所以自动变量的作用域只在函数内,其生命周期也只持续到函数调用的结束。)
循环引用问题:
Block在方法中被定义时,该方法执行完是需要被释放的,Block会强引用自己持有的对象,使其引用计数+1,如果所持有的对象也持有此Block时,需要__weak 在外修饰此对象,标识不+1,常见的是self或一些强类型的对象:
例如:(ARC下)
__weak HomeViewController * VC = self; __weak typeof(self) weakSelf = self; __weak __typeof(&*self)weakSelf = self; __weak typeof(_tableView) weakTableView = _tableView;
(MRC下,无__weak)
__block HomeViewController * VC = self;
ARC与MRC下Block内存分配机制不一样,ARC中iOS5+用
__weak,之前是用
__unsafe_unretained修饰符;
__unsafe_unretained缺点是指针所引用的对象被回收后,自己不会置为nil,因此可能导致程序崩溃;而weak指针不同,对象被回收后,指针会被赋值为nil。一般来说,weak修饰符更加安全。
另一种写法:
有时候weakSelf会在Block未执行完就会释放掉成为nil,为了防止这种情况出现,在Block内需要__strong对weakSelf强引用一下(更高级写法见RAC的@weakify(self),@strongify(self))。
//宏定义 - Block循环引用 #define weakify(var) __weak typeof(var) weakSelf = var #define strongify(var) __strong typeof(var) strongSelf = var weakify(self); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ strongSelf(weakSelf); [strongSelf doSomething];//防止weakSelf释放掉 [strongSelf doOtherThing]; });
这样不会造成循环引用是因为strongSelf是在Block里面声明的一个指针,当Block执行完毕后,strongSelf会释放,这个时候将不再强引用weakSelf,所以self会正确的释放。
问题一:Block的存储位置
在Objective-C语言中,一共有3种类型的block:1._NSConcreteGlobalBlock 全局的静态block,不会访问任何外部变量。
2._NSConcreteStackBlock 保存在栈中的block,当函数返回时会被销毁。
3._NSConcreteMallocBlock 保存在堆中的block(从栈中copy过去的),当引用计数为0时会被销毁。
Block内捕获变量会改变自身存储位置,包括读取变量和__block这种写变量,两种方式(其实结果是一样的)。
【在MRC下】:存在栈、全局、堆这三种形式。
【在ARC下】:大部分情况下系统会把Block自动copy到堆上。
Block作为变量:
方法中声明一个 block 的时候是在栈上;
引用了外部局部变量或成员变量,并且有赋值操作(有名字),会被 copy 到堆上;
赋值给附有__strong修饰符的id类型的类或者Blcok类型成员变量时;
赋值给一个 weak 变量不会被 copy;
Block作为属性:
用 copy 修饰会被 copy;
Block作为函数参数:
作为参数传入函数不会被 copy,依然在栈上,方法执行完即释放;
作为函数的返回值会被 copy 到堆;
例如:
-(void)method { //在ARC环境下,Block也是存在__NSStackBlock的时候的,平时见到最多的是_NSConcreteMallocBlock,是因为我们会对Block有赋值操作,所以ARC下,block 类型通过=进行传递时,会导致调用objc_retainBlock->_Block_copy->_Block_copy_internal方法链。并导致 __NSStackBlock__ 类型的 block 转换为 __NSMallocBlock__ 类型。 int a = 3; void(^block)() = ^{ NSLog(@"调用block%d",a);//这里的变量a,和self.string是一样效果 }; NSLog(@"%@" d4c9 ,block); //打印结果:<__NSMallocBlock__: 0x7fc498746000> //此时后面的匿名函数赋值给block指针(创建带名字的block),且引用了外部局部变量,block会copy到堆 NSLog(@"%@",^{NSLog(@"调用block%d",a);}); //打印结果:<__NSStackBlock__: 0x7fff54f0c700> //匿名函数无赋值操作,只存于栈上,会不定释放 static int b = 2; void(^block)() = ^{ NSLog(@"调用block%d",b);//若不引用任何变量,也是__NSGloBalBlock__ }; NSLog(@"%@",block); } //打印结果:<__NSGloBalBlock__: 0x7fc498746000> //此时引用了全局变量,block放在全局区
问题二:为什么__block
修饰自动变量后,就能在Block中更改?
首先,为什么Block不能修改外部自动变量?自动变量存于栈中,在当前方法执行完,会释放掉。一般来说,在 block 中用的变量值是被复制过来的,自动变量是值类型复制,新开辟栈空间,所以对于新复制变量的修改并不会影响这个变量的真实值(也称只读拷贝)。大多情况下,block是作为参数传递以供后续回调执行的。所以在你想要在block中修改此自动变量时,变量可能已被释放,所以不允许block进行修改是合理的。
对于 static 变量,全局变量,在 block中是有读写权限的,因为此变量存于全局数据区(非栈区),不会随时释放掉,也不会新开辟内存创建变量, block 拷贝的是指向这些变量的指针,可以修改原变量。
那么怎么让自动变量不被释放,还能被修改呢?
__block修饰符把所修饰的变量包装成一个
结构体对象,即可完美解决。Block既可以强引用此结构体对象,使之不会自动释放,也可以拷贝到指向该结构体对象的指针,通过指针修改结构体的变量,也就是__block所修饰的自动变量。
将下面main.m文件进行clang -rewrite-objc main.m命令查看编译时的c++代码:
//写在main.m文件的main方法里 __block NSInteger val = 0; void (^block)(void) = ^{ val = 1; }; block(); NSLog(@"val = %ld", val);
如下:
//把val变量封装成了结构体 struct __Block_byref_val_0 { void *__isa; __Block_byref_val_0 *__forwarding;//注意这个__forwarding指针 int __flags; int __size; NSInteger val; }; //block变量 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; } }; //结构体作为参数是通过指针传递,这里__cself指针传入变量所在结构体,并在__main_block_func_0中实现变量值的改变 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;//通过__forwarding指针找到val变量 } 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*/);} //block的描述 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};
在__main_block_func_0里,发现是通过
(val->__forwarding->val) = 1;找到val变量,这么做的原因是什么?
__Block_byref_val_0 *val = __cself->val; (val->__forwarding->val) = 1;
我们知道,ARC下Block很多情况会被自动拷贝到堆,而在栈上的__block变量被复制到堆上之后,会将结构体
__Block_byref_val_0的成员变量
__Block_byref_val_0 *__forwarding;的值替换为堆上的__block变量的地址。因此使用 __forwarding 指针就是为了无论在栈还是在堆,都能正确访问到该__block变量。
问题三:循环引用问题
循环引用的根本原因是Block和另一个对象互相持有,导致都无法释放,经常碰到的是self(当前控制器),导致控制器无法释放,内存泄漏严重。解决循环引用问题主要有两个办法:
事前避免,我们在会产生循环引用的地方使用 weak 弱引用,以避免产生循环引用。
事后补救,我们明确知道会存在循环引用,但是我们在合理的位置主动断开环中的一个引用,使得对象得以回收。
//巧神在YTKNetWorking是这么设计的: //Block应该结束完的时候,手动把该释放的Block置空 - (void)clearCompletionBlock { // nil out to break the retain cycle. self.successCompletionBlock = nil; self.failureCompletionBlock = nil; }
有时候纳闷为什么AFN或像UIView的Block动画不需要weakSelf也可以自己释放掉。其实明白了无法释放的原理,也就明白了。虽然Block强引用了self,但是self不强引用这个Block呀。
举例说明(需要和不需要使用__weak打破循环引用):
typedef void(^Block)(); @interface SecViewController () @property (nonatomic , strong) NSString *str; @property (nonatomic , copy) Block blk; @end @implementation SecViewController - (void)viewDidLoad { self.str = @"string"; //以下2种Block都不会被self强引用 //doSomthing1方法执行完,pop后此控制器会被释放掉 [self doSomthing1:^{ NSLog(@"str111:%@",self.str); }]; //doSomthing2方法执行完,pop后此控制器也会被释放掉 [self doSomthing2:^(int a, int b) { NSLog(@"str111:%@",self.str); }]; //self持有此Block,要用__weak __weak typeof(self) weakSelf = self; self.blk = ^(){ NSLog(@"str111:%@",weakSelf.str); }; //在doSomthing3方法中block参数赋值给self.blk,这样block参数就间接被self强引用,需用weakSelf,用self会导致循环引用,当前控制器无法释放; //经常碰到的都是这种间接持有导致的循环引用问题 [self doSomthing3:^{ NSLog(@"str111:%@",weakSelf.str); }]; } - (void)doSomthing1:(void(^)())block{ if(block){ block(); } } - (void)doSomthing2:(Block)block{ if(block){ block(); } } - (void)doSomthing3:(Block)block{ if(block){ self.blk = block; block(); } } -(void)dealloc{ NSLog(@"SecVC释放了"); }
相关文章推荐
- iOS中block的使用注意
- iOS 中使用Block时需要注意的retain circle
- ios 关于使用异步网络请求时block回调的内存注意
- iOS 开发中使用block的注意点
- iOS Block使用注意
- 小胖说事32-----iOS关于block使用的5点注意事项
- iOS关于block使用的注意和探讨
- 89.ios开发之block的使用,及注意事项
- iOS-Block使用注意点
- IOS使用Block —— 3 Block的两个注意点
- ios block 内存管理时使用注意
- ios开发之block的使用,及注意事项
- iOS中block的简单使用及注意点
- IOS-翻转时,使用2个view的交换(需注意)
- NSNotificationCenter使用block方式的一点注意事项
- 如何在 iOS 5 中使用 Block (2)
- 如何在 iOS 5 中使用 Block (1)
- 浅析iOS中Push通知的使用【图文+视频】
- iOS UIAlertView使用注意事项
- IOS-特别注意“self.label” 与“label_”的使用,统一并区别。