block存儲區域
首先我們得明白iOS系統存儲有哪幾大記憶體區域:
1、棧區
由編譯器自動配置設定并釋放,存放函數的參數值(實參),局部變量等。棧是向低位址擴充的資料結構,是不連續的記憶體區域,采用後進先出(LIFO )。優點是快速高效,缺點時有限制,資料不靈活。
2、堆區
由程式員配置設定和釋放,如果程式員不釋放,程式結束時,可能會由作業系統回收。堆是向高位址擴充的資料結構,是不連續的記憶體區域,以連結清單的方式進行存儲。
3、全局靜态區
全局區分為初始化全局區(bbs)和未初始化全局區(data)。全局變量和靜态變量的存儲是放在一塊的,初始化的全局變量和靜态變量在一塊區域, 未初始化的全局變量和未初始化的靜态變量在相鄰的另一塊區域,在程式結束後有系統釋放。
4、常量區
存儲常量資料,通常程式結束後由系統自動釋放。
5、代碼區
用來存放函數的二進制代碼,在運作時要防止被非法修改,隻允許讀取不允許操作。
下面看一段示例
block是一個匿名函數,具體自動捕獲外部變量的能力
從上面的示例我們可以發現:
一個普通的不包含任何外部變量的block(比如示例中的1、4、7)是存儲在全局靜态區的,不管這個block作為臨時變量還是屬性或者其他方式;
單純作為block存在,但是有用到外部變量的(比如示例中的1、2、3)存儲在棧區,因為這樣可以更快的通路外部變量;
如果block用=複制給了一個變量,同時block擷取了外部變量(比如示例中的5、6、8、9)存儲在堆區,因為相當于從棧區拷貝到堆區。ARC情況下,可以用copy、strong修飾block,隻是為了習慣,基本都是用的copy。
block循環引用産生情況及解決方法
1、最常見的情況,循環鍊self -> block -> self
解決方法:
(1)用__weak修飾self
(2)用一個中間臨時變量儲存self
(3)将self作為參數傳進block
2、單純用__weak修飾self有時也會産生一個新的問題
因為在 block裡面用到self的屬性時,self已經被釋放,self = nil,是以此時拿到的屬性值為空。
解決方法:__strong再次強引用一遍
下面再看看 block到底做了什麼,或者說block是怎麼實作的?
現在main.m有這麼一段代碼
在終端執行clang -rewrite-objc main.m得到main.cpp
void (^block)(void) = ^{
NSLog(@"這是一個block");
};
block();
反編譯之後,再簡化
block = __main_block_impl_0(__main_block_func_0, __main_block_desc_0_DATA);
block->FuncPtr(block);
block是__main_block_impl_0
block->FuncPtr = fp = __main_block_func_0 = ^{ NSLog(@"這是一個block"); };
最後看__block修飾是如何修改外部變量
main.m有這麼一段代碼
在終端執行clang -rewrite-objc main.m得到main.cpp
__block int a = 1;
void (^block)(void) = ^{
a++;
};
block();
反編譯再簡化
a = {0, &a, 0, sizeof(__Block_byref_a_0), 1};
block = __main_block_impl_0(__main_block_func_0, __main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, flag);
block->FuncPtr(block)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref // 現在用的是a的位址,是以block裡面修改a的值,外面會有變化
(a->__forwarding->a)++;
}