99热99这里只有精品6国产,亚洲中文字幕在线天天更新,在线观看亚洲精品国产福利片 ,久久久久综合网

歡迎加入QQ討論群258996829
麥子學院 頭像
蘋果6袋
6
麥子學院

iOS中Block內(nèi)存管理如何實現(xiàn)?

發(fā)布時間:2016-10-26 22:31  回復:0  查看:2501   最后回復:2016-10-26 22:31  

ios開發(fā)中,相信說道block大家都不陌生,內(nèi)存管理問題也是開發(fā)者最頭疼的問題,網(wǎng)上很多講block的博客,但大都是理論性多點,今天結(jié)合一些實例來講解下。

  存儲域

  首先和大家聊聊block的存儲域,根據(jù)block在內(nèi)存中的位置,block被分為三種類型:

  NSGlobalBlock

  NSStackBlock

  NSMallocBlock

  從字面意思上大家也可以看出來


NSGlobalBlock是位于全局區(qū)的block,它是設置在程序的數(shù)據(jù)區(qū)域(.data區(qū))中。

  NSStackBlock是位于棧區(qū),超出變量作用域,棧上的Block以及 __block變量都被銷毀。

  NSMallocBlock是位于堆區(qū),在變量作用域結(jié)束時不受影響。

  注意:在 ARC 開啟的情況下,將只會有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 類型的 block。

  說了這么多理論的東西,有些人可能很懵,覺得講這些有什么用呢,我平時使用block并沒有什么問題啊,好了,接下來我們先來個感受下:

  #import "ViewController.h"

  void(^block)(void);@implementation ViewController

  - (void)viewDidLoad {

  [super viewDidLoad];

NSInteger i = 10;

  block = ^{

  NSLog(@"%ld", i);

  };

  }

  - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

  block();

  }

  @end

  聲明這樣一個block,點擊屏幕的時候去調(diào)用這個block,然后就會發(fā)生以下錯誤:

iOS中Block內(nèi)存管理如何實現(xiàn)?


 野指針錯誤,顯而易見,這個是生成在棧上的block,因為超出了作用域而被釋放,所以再調(diào)用的時候報錯了,通過打印這個block我們也可以看到是生成在棧上的:

iOS中Block內(nèi)存管理如何實現(xiàn)?

 解決辦法

  解決辦法呢有兩種:

  Objective-C為塊常量的內(nèi)存管理提供了復制(Block_copy())和釋放(Block_release())命令。 使用Block_copy()命令可以將塊常量復制到堆中,這就像實現(xiàn)了一個將塊常量引用作為輸入?yún)?shù)并返回相同類型塊常量的函數(shù)。

  - (void)viewDidLoad {

  [super viewDidLoad];

  NSInteger i = 10;

  block = Block_copy(^{

  NSLog(@"%ld", i);

  });

  }

  為了避免內(nèi)存泄漏,Block_copy()必須與相應的Block_release()命令達到平衡:

  Block_release(block);

  Foundation框架提供了處理塊的copyrelease方法,這兩個方法擁有與Block_copy()Block_release()函數(shù)相同的功能:

  - (void)viewDidLoad {

  [super viewDidLoad]; NSInteger i = 10;

  block = [^{ NSLog(@"%ld", i);

  } copy];

  }

  [block release];

  到這里有人可能會有疑問了,為什么相同的代碼我建了一個工程,沒有調(diào)用copy,也沒有報錯啊,并且可以正確打印。 那是因為我們上面的操作都是在MRC下進行的,ARC下編譯器已經(jīng)默認執(zhí)行了copy操作,所以上面的這個例子就解釋了Block超出變量作用域可存在的原因。

  接下來可能有人又要問了,block什么時候在全局區(qū),什么時候在棧上,什么時候又在堆上呢?上面的例子是對生成在棧上的Block作了copy操作,如果對另外兩種作copy操作,又是什么樣的情況呢?

iOS中Block內(nèi)存管理如何實現(xiàn)?

通過這張表我們可以清晰看到三種Block copy之后到底做了什么,接下來我們就來分別看看這三種類型的Block。

  NSGlobalBlock

  在記述全局變量的地方使用block語法時,生成的block_NSConcreteGlobalBlock類對象

  void(^block)(void) = ^ { NSLog(@"Global Block");};int main() {

  }

  在代碼不截獲自動變量時,生成的block也是在全局區(qū):

  int(^block)(int count) = ^(int count) {

  return count;

  };

  block(2);

  但是通過clang改寫的底層代碼指向的是棧區(qū):

  impl.isa = &_NSConcreteStackBlock

  這里引用巧神的一段話:由于 clang 改寫的具體實現(xiàn)方式和 LLVM 不太一樣,并且這里沒有開啟 ARC。所以這里我們看到 isa 指向的還是_NSConcreteStackBlock。但在 LLVM 的實現(xiàn)中,開啟 ARC 時,block 應該是 _NSConcreteGlobalBlock 類型

  總結(jié)下,生成在全局區(qū)block有兩種情況:

定義全局變量的地方有block語法時

block語法的表達式中沒有使用應截獲的自動變量時

  NSStackBlock

  配置在全局區(qū)的block,從變量作用域外也可以通過指針安全地使用。但是設置在棧上的block,如果其作用域結(jié)束,該block就被銷毀。同樣的,由于__block變量也配置在棧上,如果其作用域結(jié)束,則該__block變量也會被銷毀。

  上面舉得例子其實就是生成在棧上的block

  NSInteger i = 10;

  block = ^{

  NSLog(@"%ld", i);

  };

  除了配置在程序數(shù)據(jù)區(qū)域的block(全局Block),其余生成的block_NSConcreteStackBlock類對象,且設置在棧上,那么配置在堆上的__NSConcreteMallocBlock類何時使用呢?

  NSMallocBlock

  Blocks提供了將Block__block變量從棧上復制到堆上的方法來解決這個問題,這樣即使變量作用域結(jié)束,堆上的Block依然存在。

  impl.isa = &_NSConcreteMallocBlock;

  這也是為什么Block超出變量作用域還可以存在的原因。

  那么什么時候棧上的Block會復制到堆上呢?

  調(diào)用Blockcopy實例方法時

  Block作為函數(shù)返回值返回時

 

  將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時

  將方法名中含有usingBlockCocoa框架方法或GCDAPI中傳遞Block

  上面只對Block進行了說明,其實在使用__block變量的Block從棧上復制到堆上時,__block變量也被從棧復制到堆上并被Block所持有。

  接下來我們再來看一個????

  void(^block)(void);99int main(int argc, const char * argv[]) {

  @autoreleasepool {

  __block NSInteger i = 10;

  block = [^{

  ++i;

  } copy];

  ++i;

  block();

  NSLog(@"%ld", i);

  }

  return 0;

  }

  我們對這個生成在棧上的block執(zhí)行了copy操作,Block__block變量均從棧復制到堆上。

  然后在Block作用域之后我們又使用了與Block無關的變量:

  ++i;

  一個是存在于棧上的變量,一個是復制到堆上的變量,我們是如何做到正確的訪問這個變量值的呢?

  通過clang轉(zhuǎn)換下源碼來看下:

  void(*block)(void);

  struct __Block_byref_i_0 {

  void *__isa;

  __Block_byref_i_0 *__forwarding;

  int __flags;

  int __size;

  NSInteger i;

  };

  struct __main_block_impl_0 {

  struct __block_impl impl;

  struct __main_block_desc_0* Desc;

  __Block_byref_i_0 *i; // by ref

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; // bound by ref

  ++(i->__forwarding->i);

  }static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

  static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 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[]) {

  /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

  __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 10};

  block = (void (*)())((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344)), sel_registerName("copy"));

  ++(i.__forwarding->i);

  ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_47_s4m8c9pj5mg0k9mymsm7rbmw0000gn_T_main_e69554_mi_0, (i.__forwarding->i));

  }

  return 0;

  }

  static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

  我們發(fā)現(xiàn)相比于沒有__block關鍵字修飾的變量,源碼中增加了一個名為 __Block_byref_i_0 的結(jié)構(gòu)體,用來保存我們要 capture 并且修改的變量 i。

  在__Block_byref_i_0結(jié)構(gòu)體中我們可以看到成員變量__forwarding,它持有指向該實例自身的指針。那么為什么會有這個成員變量__forwarding呢?這也是正是問題的關鍵。

  我們可以看到源碼中這樣一句:

  ++(i->__forwarding->i);

  棧上的__block變量復制到堆上時,會將成員變量__forwarding的值替換為復制到堆上的__block變量用結(jié)構(gòu)體實例的地址。所以不管__block變量配置在棧上還是堆上,都能夠正確的訪問該變量,這也是成員變量__forwarding存在的理由。

  循環(huán)引用

  循環(huán)引用比較簡單,造成循環(huán)引用的原因無非就是對象和block相互強引用,造成誰都不能釋放,從而造成了內(nèi)存泄漏?;镜囊恍├游揖筒辉僦貜土耍W(wǎng)上很多,也比較簡單,我就一個問題來討論下,也是開發(fā)中有人問過我的一個問題:

  block里面使用self會造成循環(huán)引用嗎?

  很顯然答案不都是,有些情況下是可以直接使用self的,比如調(diào)用系統(tǒng)的方法:

  [UIView animateWithDuration:0.5 animations:^{

  NSLog(@"%@", self);

  }];

  因為這個block存在于靜態(tài)方法中,雖然blockself強引用著,但是self卻不持有這個靜態(tài)方法,所以完全可以在block內(nèi)部使用self

  還有一種情況:

  當block不是self的屬性時,self并不持有這個block,所以也不存在循環(huán)引用

  void(^block)(void) = ^() {

  NSLog(@"%@", self);

  };

  block();

  只要我們抓住循環(huán)引用的本質(zhì),就不難理解這些東西。

  希望可以通過上面的一些例子,可以讓大家加深對block的理解,知其然并且知其所以然。

 

文章來源:CocoaChina

您還未登錄,請先登錄

熱門帖子

最新帖子

?