iOS之Block总结以及内存管理
block定義
struct Block_descriptor {unsigned long int reserved;unsigned long int size;void (*copy)(void *dst, void *src);void (*dispose)(void *); }; struct Block_layout {void *isa;int flags;int reserved; void (*invoke)(void *, ...);struct Block_descriptor *descriptor;/* Imported variables. */ };從上面代碼看出,Block_layout就是對(duì)block結(jié)構(gòu)體的定義:
isa指針:指向表明該block類型的類。
flags:按bit位表示一些block的附加信息,比如判斷block類型、判斷block引用計(jì)數(shù)、判斷block是否需要執(zhí)行輔助函數(shù)等。
reserved:保留變量,我的理解是表示block內(nèi)部的變量數(shù)。
invoke:函數(shù)指針,指向具體的block實(shí)現(xiàn)的函數(shù)調(diào)用地址。
descriptor:block的附加描述信息,比如保留變量數(shù)、block的大小、進(jìn)行copy或dispose的輔助函數(shù)指針。
variables:因?yàn)閎lock有閉包性,所以可以訪問block外部的局部變量。這些variables就是復(fù)制到結(jié)構(gòu)體中的外部局部變量或變量的地址。
舉例,定義一個(gè)最簡單block 打印hello world:
int main(int argc, const char * argv[]) {void (^block)()=^{printf("hello world");};block();return 0; }使用clang指令
clang -rewrite-objc main.m
得到一個(gè)cpp文件,編譯后,你就會(huì)看到什么是block了
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__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;} }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf("hello world"); }static struct __main_block_desc_0 {size_t reserved;size_t Block_size; } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; int main(int argc, const char * argv[]) {void (*block)()=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);return 0; } static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 }; View Code你定義完block之后,其實(shí)是創(chuàng)建了一個(gè)函數(shù),在創(chuàng)建結(jié)構(gòu)體的時(shí)候把函數(shù)的指針一起傳給了block,所以之后可以拿出來調(diào)用。
再看看值捕獲的問題
int main(int argc, const char * argv[]) {int a=10;//__block int a=10; //__block前綴void (^block)()=^{printf("打印a=%d",a);};block();return 0; }定義block的時(shí)候,變量a的值就傳遞到了block結(jié)構(gòu)體中,僅僅是值傳遞,所以在block中修改a是不會(huì)影響到外面的a變量的。
而加了__block前綴,編譯后:
struct __Block_byref_a_0 {void *__isa; __Block_byref_a_0 *__forwarding;int __flags;int __size;int a; };struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__Block_byref_a_0 *a; // by ref__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;} }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_a_0 *a = __cself->a; // bound by ref printf("打印a=%d",(a->__forwarding->a));} static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}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}; int main(int argc, const char * argv[]) {__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};void (*block)()=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);return 0; } static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 }; View Code并不是直接傳遞a的值了,而是把a(bǔ)的地址(&a)傳過去了,所以在block內(nèi)部便可以修改到外面的變量了。
isa:isa指針,在Objective-C中,任何對(duì)象都有isa指針。block 有三種類型:
_NSConcreteGlobalBlock 全局靜態(tài),不會(huì)訪問任何外部變量,不會(huì)涉及到任何拷貝,比如一個(gè)空的block。例如:
_NSConcreteStackBlock 保存在棧中,出函數(shù)作用域就銷毀,例如:
#include int main() {char a = 'A';^{ printf("%c\n",a); } ();return 0; }_NSConcreteMallocBlock 保存在堆中,retainCount == 0銷毀
該類型的block都是由_NSConcreteStackBlock類型的block從棧中復(fù)制到堆中形成的。例如下面代碼中,在exampleB_addBlockToArray方法中的block還是_NSConcreteStackBlock類型的,在exampleB方法中就被復(fù)制到了堆中,成為_NSConcreteMallocBlock類型的block:
void exampleB_addBlockToArray(NSMutableArray *array) {char b = 'B';[array addObject:^{printf("%c\n", b);}]; } void exampleB() {NSMutableArray *array = [NSMutableArray array];exampleB_addBlockToArray(array);void (^block)() = [array objectAtIndex:0];block(); }總結(jié)一下:
_NSConcreteGlobalBlock類型的block要么是空block,要么是不訪問任何外部變量的block。它既不在棧中,也不在堆中,我理解為它可能在內(nèi)存的全局區(qū)。
_NSConcreteStackBlock類型的block有閉包行為,也就是有訪問外部變量,并且該block只且只有有一次執(zhí)行,因?yàn)闂V械目臻g是可重復(fù)使用的,所以當(dāng)棧中的block執(zhí)行一次之后就被清除出棧了,所以無法多次使用。
_NSConcreteMallocBlock類型的block有閉包行為,并且該block需要被多次執(zhí)行。當(dāng)需要多次執(zhí)行時(shí),就會(huì)把該block從棧中復(fù)制到堆中,供以多次執(zhí)行。
而ARC和MRC中,還略有不同
題目:下面代碼在按鈕點(diǎn)擊后,在ARC下會(huì)發(fā)生什么,MRC下呢?為什么?
@property(nonatomic, assign) void(^block)();- (void)viewDidLoad {[superviewDidLoad];int value = 10;void(^blockC)() = ^{NSLog(@"just a block === %d", value);};NSLog(@"%@", blockC);_block = blockC;}- (IBAction)action:(id)sender {NSLog(@"%@", _block); }在ARC 打印:
mytest[25284:7473527] test:<__NSMallocBlock__: 0x60000005f3e0> mytest[25284:7473527] NSShadow {0, -1} color = {(null)}雖然不會(huì)crash,第二個(gè)是野指針
MRC 會(huì)打印:test:<__NSStackBlock__: 0x7fff54941a38> 然后crash
例如:
NSArray *testArr = @[@"1", @"2"];NSLog(@"block is %@", ^{NSLog(@"test Arr :%@", testArr);});//結(jié)果:block is <__NSStackBlock__: 0x7fff54f3c808>void (^TestBlock)(void) = ^{NSLog(@"testArr :%@", testArr);};NSLog(@"block2 is %@", TestBlock);//block2 is <__NSMallocBlock__: 0x600000045e80>//其實(shí)上面這句在非arc中打印是?NSStackBlock,?但是在arc中就是NSMallocBlock
//即在arc中默認(rèn)會(huì)將block從棧復(fù)制到堆上,而在非arc中,則需要手動(dòng)copy.
?
循環(huán)引用
Block的循環(huán)引用是比較容易被忽視,原本也是相對(duì)比較難檢查出來的問題。當(dāng)然現(xiàn)在蘋果在XCode編譯的層級(jí)就已經(jīng)做了循環(huán)引用的檢查,所以這個(gè)問題的檢查就突然變的沒有難度了。
簡單說一下循環(huán)引用出現(xiàn)的原理:Block的擁有者在Block作用域內(nèi)部又引用了自己,因此導(dǎo)致了Block的擁有者永遠(yuǎn)無法釋放內(nèi)存,就出現(xiàn)了循環(huán)引用的內(nèi)存泄漏。下面舉個(gè)例子說明一下:
@interface ObjTest () {NSInteger testValue; } @property (copy, nonatomic) void (^block)(); @end@implement ObjTest - (void)function {self.block = ^() {self.testValue = 100;}; } @end在這個(gè)例子中,ObjTest擁有了一個(gè)名字叫block的Block對(duì)象;然后在這個(gè)Block中,又對(duì)ObjTest的一個(gè)成員變量testValue進(jìn)行了賦值。于是就產(chǎn)生了循環(huán)引用:ObjTest->block->ObjTest。
要避免循環(huán)引用的關(guān)鍵就在于破壞這個(gè)閉合的環(huán)。在目前只考慮ARC環(huán)境的情況下,筆者所知的只有一種方法可以破壞這個(gè)環(huán):在Block內(nèi)部對(duì)擁有者使用弱引用。
@interface ObjTest () {NSInteger testValue; } @property (copy, nonatomic) void (^block)(); @end@implement ObjTest - (void)function {__weak ObjTest* weakSelf = self;self.block = ^() {weakSelf.testValue = 100;}; } @end在單例模式下 Block避免循環(huán)引用,如下:
@interface Singleton : NSObject @property (nonatomic, copy) void(^block)(); + (instancetype)share; @end@implementation Singleton + (instancetype)share {static Singleton *singleton;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{singleton = [[Singleton alloc] init];});return singleton; } @end//============分割線================= //控制器中代碼的實(shí)現(xiàn)@implementation NextViewController- (void)viewDidLoad {[super viewDidLoad];__weak typeof(self) weakSelf=self;void (^blockTest)()=^(){ // NSLog(@"print %@", self);//會(huì)內(nèi)存泄漏NSLog(@"print %@", weakSelf);};Singleton *singleton = [Singleton share];singleton.block = blockTest; } - (IBAction)btnClick:(UIButton *)sender {[Singleton share].block(); }- (void)dealloc {NSLog(@"%s", __FUNCTION__); } @end?為什么iOS中系統(tǒng)的block方法可以使用self
因?yàn)?#xff1a;首先循環(huán)引用發(fā)生的條件就是持有這個(gè)block的對(duì)象,被block里邊加入的對(duì)象持有。當(dāng)然是強(qiáng)引用。
所以UIView的動(dòng)畫block不會(huì)造成循環(huán)引用的原因就是,這是個(gè)類方法,當(dāng)前控制器不可能強(qiáng)引用一個(gè)類,所以循環(huán)無法形成。
ARC情況下:
1、如果用copy修飾Block,該Block就會(huì)存儲(chǔ)在堆空間。則會(huì)對(duì)Block的內(nèi)部對(duì)象進(jìn)行強(qiáng)引用,導(dǎo)致循環(huán)引用。內(nèi)存無法釋放。
解決方法:新建一個(gè)指針(__weak typeof(Target) weakTarget = Target )指向Block代碼塊里的對(duì)象,然后用weakTarget進(jìn)行操作。就可以解決循環(huán)引用問題。
2、如果用weak修飾Block,該Block就會(huì)存放在棧空間。不會(huì)出現(xiàn)循環(huán)引用問題。MRC情況下用copy修飾后,如果要在Block內(nèi)部使用對(duì)象,則需要進(jìn)行(__block typeof(Target) blockTarget = Target )處理。在Block里面用blockTarget進(jìn)行操作。
返回值類型(^block變量名)(形參列表) = ^(形參列表) {};調(diào)用Block保存的代碼block變量名(實(shí)參);默認(rèn)情況下,,Block內(nèi)部不能修改外面的局部變量Block內(nèi)部可以修改使用__block修飾的局部變量
?
參考 收藏:https://www.zhihu.com/question/30779258/answer/49492783
Objective-C中的Block原理
云端之巔 Objective-C中block的底層原理
iOS學(xué)習(xí)之block總結(jié)及block內(nèi)存管理(必看)
Block總結(jié)以及內(nèi)存管理
轉(zhuǎn)載于:https://www.cnblogs.com/dhui69/p/6541324.html
總結(jié)
以上是生活随笔為你收集整理的iOS之Block总结以及内存管理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C语言 · 贪心算法
- 下一篇: hihocoder1479 三等分