我們在前一章介紹了block的用法,而正確使用block必須要求正確理解block的記憶體管理問題。我們針對不同情況來討論block的存放位置:這一章,我們只陳述結果而不追尋原因,我們將在下一章深入其原因。
以下情況中的block位於堆中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void
foo()
;
//blk在棧裡
blkinheap = block_copy(blk);
//blkinheap在堆裡
}
- (
void
)foobar
;
void
(^oblkinheap)(
void
) = [oblk
copy
];
//oblkinheap在堆中
}
以下情況中的block位於全域性區:
1
2
3
4
5
6
7
8
9
10
static
int
(^maxintblock)(
int
,
int
) = ^(
int
a,
int
b);
- (
void
)foobar
void
foo()
需要注意的是,這裡複製過後的block依舊位於全域性區,實際上,複製操作是直接返回了原block物件。
全域性區的變數儲存位置與block無關:
1
2
3
4
5
6
7
8
static
int
gvar =
0
;
//__block static int gmvar = 1;
void
foo()
注意,static變數是不允許新增__block標記的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void
foo()
;
//此時,blk已經初始化,它會拷貝沒有__block標記的常規變數自己所持有的一塊記憶體區,這塊記憶體區現在位於棧上,而對於具有__block標記的變數,其位址會被拷貝置前述的記憶體區中
blk();
//1024, 1
void
(^blkinheap)(
void
) = block_copy(blk);
//複製block後,block所持有的記憶體區會被拷貝至堆上,此時,我們可以說,這個block現在位於堆上
blkinheap();
//1024,1
i++;
j++;
blk();
//1025,1
blkinheap();
//1025,1
}
讓我們一步步剖析:
首先,我們在棧上建立了變數ij,並賦予初始值,然後建立乙個block變數名為blk,但未賦值。
然後我們初始化這個blk,賦值為乙個只有一句printf的block,值得注意的是,乙個block一旦建立,其引用到的常規變數會進行如下操作:
沒有__block標記的變數,其值會被複製乙份到block私有記憶體區
有__block標記的變數,其位址會被記錄在block私有記憶體區
然後呼叫blk,列印1024, 1很好理解
接下來複製blk到堆,名曰blkinheap,呼叫之,列印1024, 1也很好理解
接下來我們為ij增值,使其變為1025和2,此時再呼叫blk或者blkinheap,會發現結果為1025, 1,這是因為變數j早已在建立原始的block時,被賦值進block的私有記憶體區,後續對i的操作並非操作的私有記憶體區的複製品,當呼叫blk或者blkinheap時,其列印使用的是私有記憶體區的複製品,故而列印結果依舊為1;而變數j的修改會實時生效,因為block記錄的是它的位址,通過位址來訪問其值,使得外部對j的修改在block中得以生效。對於變數i來講,可算是物是人非吧?
因此,無論j++這一句放到blk()這句之前或者之後,只要它位於block初始化之後,這段**執行的控制台列印結果都會是:1024, 1。而不是1024, 2(假設不呼叫i++)
對block呼叫複製,有以下幾種情況:
1.對全域性區的block呼叫copy,會返回原指標,並且這期間不處理任何東西(至少目前的內部實現是這樣);
2.對棧上的block呼叫copy,每次會返回新複製到堆上的block的指標,同時,所有__block變數都會被複製至堆乙份(多次拷貝,只會生成乙份)。
3.對已經位於heap上的block,再次呼叫copy,只會增加block的引用計數。
為什麼我們不討論retian的行為?原因是並沒有block_retain()這樣的函式,而且objc裡面的retain訊息傳送給block物件後,其內部實現是幾乎什麼都不做(會增加objective-c引用計數)。objc類例項方法中的block如果被複製至heap,那麼當前例項會被增加引用計數,當這個block被釋放時,此例項會被減少引用計數。
但如果這個block沒有使用當前例項的任何成員,那麼當前例項不會被增加引用計數。這也是很自然的道理,我既然沒有用到這個instance的任何東西,那麼我幹嘛要retian它?
我們要注意的一點是,我看到網上有很多人說block引起了例項與block之間的迴圈引用(retain-cycle),並且給出解決方案:不直接使用self而先將self賦值給乙個臨時變數,然後再使用這個臨時變數。但是,大家注意,我們一定要為這個臨時變數增加__block標記(多謝第三篇文章回帖網友的提醒)。
這一章我們以結果導向的方式來說明了各種情況下,block的記憶體問題,下一章,我將剖析執行時庫的原始碼,從根源闡述block的行為。也就是過程導向的方式了。
Block介紹(二)記憶體管理與其他特性
我們在前一章介紹了block的用法,而正確使用block必須要求正確理解block的記憶體管理問題。這一章,我們只陳述結果而不追尋原因,我們將在下一章深入其原因。我們針對不同情況來討論block的存放位置 以下情況中的block位於堆中 1 2 3 4 5 6 7 8 9 10 11 12 13 1...
Block介紹(二)記憶體管理與其他特性
一 block放在 我們針對不同情況來討論block的存放位置 1.棧和堆 以下情況中的block位於堆中 12 3456 78910 1112 1314 1516 17voidfoo blk在棧裡 blkinheap block copy blk blkinheap在堆裡 void foobar ...
iOS中Block介紹(二)記憶體管理與其他特性
我們針對不同情況來討論block的存放位置 1.棧和堆 以下情況中的block位於堆中 void foo blk在棧裡 blkinheap block copy blk blkinheap在堆裡 void foobar void oblkinheap void oblk copy oblkinhea...