要了解什么是block
, 我們先寫一個(gè)block
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^myBlock)(void) = ^{
NSLog(@"this is a block");
};
myBlock();
}
return 0;
}
現(xiàn)在我寫了一個(gè)簡單的block
利用
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
命令行生成編譯完的C++
代碼, 發(fā)現(xiàn)block
被編譯后的樣子:
這是block
的聲明:
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
這是block
的調(diào)用:
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
去掉一些無用的類型轉(zhuǎn)換:
void(*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA
);
我們發(fā)現(xiàn)block實(shí)際上就是一個(gè)__main_block_impl_0
函數(shù)的返回值的地址, 將地址賦值給一個(gè)名叫block
的函數(shù)指針
那么__main_block_impl_0
是什么呢?
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;
}
};
我們發(fā)現(xiàn)__main_block_impl_0
是一個(gè)結(jié)構(gòu)體,
要注意的是, 還有一個(gè)__main_block_impl_0
的同名函數(shù), 這是C++
里定義的一個(gè)結(jié)構(gòu)體的構(gòu)造函數(shù), 也就是說, 這個(gè)構(gòu)造函數(shù)返回的是一個(gè)結(jié)構(gòu)體struct __main_block_impl_0
, 我們在上面看到的這個(gè)就是利用這個(gè)構(gòu)造函數(shù)產(chǎn)生的一個(gè)struct __main_block_impl_0
結(jié)構(gòu)體
void(*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA
);
-
第一個(gè)參數(shù)
__main_block_func_0
,
image.png
通過這個(gè)NSLog
就可以看出, 這是block
這里面的代碼實(shí)現(xiàn). -
第二個(gè)參數(shù)
&__main_block_desc_0_DATA
這相當(dāng)于是一個(gè)block
的描述, 也是一個(gè)block
,
第一個(gè)成員變量是保留字段, 現(xiàn)在傳的是0
第二個(gè)成員變量是Block_size
, 就是block
的大小. 傳的就是struct __main_block_impl_0
的大小(size of
)
image.png
參數(shù)傳進(jìn)去了之后就是給這個(gè)結(jié)構(gòu)體賦值, 讓我們再來看看這個(gè)結(jié)構(gòu)體
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;
}
};
第一個(gè)成員變量impl
, 類型是struct __block_impl
是這樣的:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
相當(dāng)于block
的內(nèi)存布局是這樣的:
struct __main_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0* Desc;
};
我們發(fā)現(xiàn)block
也是有isa
指針的, 所以從本質(zhì)上說, block
也是OC
對象
__main_block_func_0
就是函數(shù)指針, 當(dāng)做參數(shù)傳進(jìn)去構(gòu)造函數(shù), 然后在構(gòu)造函數(shù)里傳給結(jié)構(gòu)體里的變量FuncPtr
那我們來看一下block
的調(diào)用:
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
去掉一些類型轉(zhuǎn)換
myBlock->FuncPtr(myBlock)
實(shí)際上就是找到myBlock
中保存的FuncPtr
函數(shù)指針, 然后直接調(diào)用就好了
我們再來看一下復(fù)雜一點(diǎn)的block
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
void (^myBlock)(void) = ^{
NSLog(@"this is a block--%d", a);
};
a = 20;
myBlock();
}
return 0;
}
然后再編譯成C++
文件, 看看發(fā)生了什么:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
可以很清楚的看到, 這個(gè)block
包含了一個(gè)新的成員變量int a
, 這個(gè)block
的內(nèi)存布局就是:
struct __main_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0* Desc;
int a;
};
這個(gè)結(jié)構(gòu)體的最后一個(gè)成員變量就是int a
, 看這個(gè)結(jié)構(gòu)體的構(gòu)造函數(shù):
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
這個(gè)構(gòu)造函數(shù)多了一個(gè)參數(shù), 就是int _a
而創(chuàng)建這個(gè)block
的時(shí)候, 傳進(jìn)去的參數(shù)就是我定義的int a = 10
, 傳進(jìn)去了之后, 就把_a
的值, 賦給了結(jié)構(gòu)體內(nèi)部的成員變量a
,
: a(_a)
這就是C++
的語法, 將_a
賦值給a
, 所以外面的a
和里面的a
, 不是同一個(gè)東西, 外面的a, 是我定義的變量a
, 里面的a
是block
在創(chuàng)建的時(shí)候, 由編譯器生成的成員變量a
相當(dāng)于, 結(jié)構(gòu)體在創(chuàng)建的時(shí)候, 捕獲了這個(gè)外部的變量a
這就造成, 即使在
block
調(diào)用之前, 修改變量的值為20
, 也不會改變block
調(diào)用時(shí)獲取的變量值, 因?yàn)?code>block調(diào)用的值, 是block
的成員變量的那個(gè)值.
請記住一個(gè)關(guān)鍵的詞- Capture捕獲
那么什么情況下會捕獲呢?
記住以下原則:
- 局部變量會捕獲
- 全局變量不會捕獲
請問, 什么是局部變量, 什么是全局變量呢?
簡單來說, 聲明在函數(shù)內(nèi)部的變量是局部變量, 聲明在函數(shù)外部的變量稱之為全局變量
那block
真的不會捕獲全局變量嗎?
好, 記住了兩條大的原則:
- 局部變量會捕獲
- 全局變量不會捕獲
還有, 局部變量又分為auto
變量和static
變量
像這種聲明之后存在于棧上的變量稱之為auto
變量,auto
這個(gè)關(guān)鍵字是可以省略的
image.png
那么, 被static
修飾了的變量和auto
變量有什么不同呢?
放在常量區(qū)的變量有一個(gè)特點(diǎn)是生命周期延長了, 他的生命周期跟程序的運(yùn)行周期是一致的, 只要程序沒有終止, 那么常量區(qū)的數(shù)據(jù)是一直存在的. 這和棧區(qū)的數(shù)據(jù)不同, 棧區(qū)存放的數(shù)據(jù)的特點(diǎn)是, 只要作用域結(jié)束, 那棧區(qū)的內(nèi)存就會被回收. 那我們來看看是不是這樣的:
如圖2的所示, 當(dāng)用static
關(guān)鍵字修飾變量時(shí), 當(dāng)作用域結(jié)束時(shí), 變量是不會銷毀的, 當(dāng)時(shí)離開作用域, 是訪問不到變量的, 意思是, 雖然變量存在數(shù)據(jù)段, 但離開作用域, 無法訪問變量. 這相當(dāng)于變量的作用域不變, 變量的生命周期延長了.
由于這個(gè)情況, 局部變量中, auto
變量和static
變量被block
時(shí), 處理情況是不同的:
值傳遞(
auto
變量)
因?yàn)?code>auto變量在作用域結(jié)束之后, 變量就會回收, 它的生命周期是很短的, 所以block
在捕獲時(shí), 會把auto
變量的值賦值給block
內(nèi)部的同名成員變量, 這個(gè)成員變量是一個(gè)新的內(nèi)存空間存儲這個(gè)值指針傳遞(
static
變量)
但static
變量就不一樣了, 它存儲在數(shù)據(jù)段(常量區(qū)), 它的生命周期是跟程序的生命周期一致的, 也就是說, 在block
需要訪問這個(gè)變量的時(shí)候, 我訪問的仍然是這個(gè)變量本身, 那么這時(shí)候, 我捕獲的變量, 就是這個(gè)變量的指針(存儲這個(gè)變量的地址),block
把變量的指針賦值給block
的同名成員變量
可以很清楚地看到, 是將變量a
的地址值傳到了block
的構(gòu)造函數(shù)中
最終, 賦值給了block
內(nèi)部的指針變量a
, 所以block
的成員變量的類型是int *
.
因此, 在調(diào)用myBlock
之前, 修改了static
變量a
的值, 在調(diào)用myBlock
時(shí), a
的值已經(jīng)改了
上面是基本數(shù)據(jù)類型的情況, 那么如果是OC
對象, block
又將如何捕獲呢?
通過編譯, 我們發(fā)現(xiàn), 在struct __main_block_desc_0
結(jié)構(gòu)體中, 多出來兩個(gè)成員變量
copy
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
棧 -> 堆
dispose
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
那么這個(gè)有什么用呢? 還得從ARC
的自動copy
說起
在ARC
模式下,
在
-
block
作為函數(shù)返回值時(shí) - 將
block
賦值給__strong
指針時(shí)(ARC
環(huán)境下, 一般默認(rèn)就是__strong
指針, 除非用__weak
或者__unsafe_unretained
修飾的指針), - 或者
GCD
, - 或者函數(shù)名中有
usingBlock
的,
都會對block
進(jìn)行一次copy
操作. 那么這個(gè)copy
操作有什么用呢?
我們先來看看block
的種類:
從上圖可以看出, 我們把編譯環(huán)境改成MRC
(這是因?yàn)?code>ARC環(huán)境編譯器幫我們做了很多事情, 不方便我們探究本質(zhì)), 然后打印這三個(gè)block
的類型, 我們發(fā)現(xiàn)block
是分為三種的:
-
__NSGlobalBlock__
這個(gè)block
是存在數(shù)據(jù)段的, 暫時(shí)不探究 -
__NSStackBlock__
這個(gè)是存在棧區(qū), 也叫棧block
-
__NSMallocBlock__
這個(gè)是在堆區(qū), 所以叫堆block
說回來copy
, 當(dāng)我們對一個(gè)block
執(zhí)行copy
操作時(shí)
-
__NSGlobalBlock__
還是__NSGlobalBlock__
-
__NSStackBlock__
會升級為__NSMallocBlock__
(這一點(diǎn)要尤其注意) -
__NSMallocBlock__
還是__NSMallocBlock__
當(dāng)棧block
升級為堆block
時(shí), 這時(shí)候堆中的數(shù)據(jù)就依靠程序員來管理了, 而不是像棧block
一樣, 椣胄恚空間自動回收之后, 保存在棧中的block
就沒有了. 在MRC
環(huán)境下, 如果是棧block
的話, 如下圖所示
棧block
內(nèi)部的person
指針只是指向person
存儲的那片內(nèi)存空間, 并不會對person
對象引用計(jì)數(shù)+1
, 那么當(dāng)person
對象被回收后[person release]
, 再去訪問棧block
中的person
指針指向的那片內(nèi)存空間, 就很危險(xiǎn)了, 就會造成野指針訪問.
但如果是堆block
呢? 這時(shí)候就會對person
對象進(jìn)行引用計(jì)數(shù)+1
, 那這時(shí)候再去訪問堆block
中的person
指針指向的那片內(nèi)存空間, 是沒有問題的
但如果使用__unsafe_unretained
關(guān)鍵字修飾person
對象時(shí), 會發(fā)生什么呢?
可以看到的是, 壞內(nèi)存訪問. 這是因?yàn)? 因?yàn)槲覀冇?code>__unsafe_unretained關(guān)鍵字修飾了person
對象, 所以, 即使block
被拷貝到堆區(qū), block
內(nèi)部也不會對person
對象引用計(jì)數(shù)+1
, 那么當(dāng)我向person
對象發(fā)送release
消息后, person
對象引用計(jì)數(shù)-1
, 這時(shí)候是會被銷毀的, 此時(shí)再去訪問block
內(nèi)部的person
指針指向的那片內(nèi)存空間, 就會造成野指針訪問
在ARC
模式下,
在
-
block
作為函數(shù)返回值時(shí) - 將
block
賦值給__strong
指針時(shí)(ARC
環(huán)境下, 一般默認(rèn)就是__strong
指針, 除非用__weak
或者__unsafe_unretained
修飾的指針), - 或者
GCD
, - 或者函數(shù)名中有
usingBlock
的,
ARC
在以上四種情況下, 會自動對block
進(jìn)行copy
操作, 也就是說, 這個(gè)棧block
會升級成堆block
, 升級成堆block
后, 堆block
中的person
指針會對person
對象強(qiáng)引用, 那么這樣一來, 即使block
外面的person
指針被回收了,person
對象依然不會銷毀, 它會隨著block
的生命周期結(jié)束而銷毀.
上面提到的例子中, 如果是局部的auto
變量, 我們其實(shí)是無法修改變量的值. 因?yàn)?code>auto變量的地址沒有變, 假設(shè)我們要修改定義的局部變量的值, 我們需要做一件事, 就是加上__block
關(guān)鍵字, 那么__block
的作用是什么呢?
還是先看看編譯情況:
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 18};;
簡化下來就是:
__Block_byref_a_0 a = {
0,
&a,
0,
sizeof(__Block_byref_a_0),
18
};
那__Block_byref_a_0
這個(gè)又是什么東西呢?
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
可以看到的是, __block
修飾的變量, 在編譯時(shí)期, 自動包裝成了一個(gè)結(jié)構(gòu)體(這個(gè)結(jié)構(gòu)體的名字跟修飾的變量同名), 這個(gè)結(jié)構(gòu)體的內(nèi)存布局如上圖所示.
-
0
賦值給了_isa
指針 -
&a
(結(jié)構(gòu)體a
的地址)賦值給了__forwarding
指針 -
0
賦值給__flags
-
sizeof(__Block_byref_a_0)
賦值給了__size
- 最后
18
這個(gè)變量賦值給了結(jié)構(gòu)體的最后一個(gè)變量a
所以原本的int a
, 包裝成了一個(gè)結(jié)構(gòu)體, 然后這個(gè)結(jié)構(gòu)體的地址作為參數(shù)傳給了block
的構(gòu)造函數(shù). block
內(nèi)部有一個(gè)成員變量__Block_byref_a_0 *
相當(dāng)于捕獲了這個(gè)變量
但是很明顯, 在MRC
模式下, 包裝過后的這個(gè)結(jié)構(gòu)體也存在于棧區(qū)
block
此時(shí)也是一個(gè)棧block
, 那棧的內(nèi)存空間是隨著作用域的結(jié)束而回收的!事實(shí)上, 我理解的是, 例如下段代碼:
在31行代碼的時(shí)候, age
作為一個(gè)存在于棧的變量, 它已經(jīng)被系統(tǒng)回收了, 所以31行block
去訪問age
這片內(nèi)存空間的時(shí)候, 其實(shí)是很危險(xiǎn)的. 雖然這里成功打印了age
的值, 但這么做是不合理的.
這幅圖應(yīng)該這么畫. 也就是說, 在
MRC
情況下, 實(shí)際上并沒有強(qiáng)弱引用的概念. 指針只是指向這這片存儲空間, 并沒有強(qiáng)指針, 弱指針的概念. 當(dāng)作用域結(jié)束, 椛ǎ空間被系統(tǒng)回收, 指針再指向被回收的棧空間, 是一件很危險(xiǎn)的事, 可能取到的值不正確.
即使是指針指向的是堆空間的對象, 也沒有強(qiáng)弱指針的概念, 這就是為什么在MRC
環(huán)境下, 需要手動給引用計(jì)數(shù)+1.
那么在MRC
環(huán)境下, 需要手動將block
拷貝到堆區(qū). 或者, 在屬性修飾時(shí), 使用copy
修飾
沒有用copy
修飾
打印出來就是棧
block
使用
copy
修飾打印出來就是堆
block
當(dāng)使用了copy
關(guān)鍵字時(shí), block
已經(jīng)升級成了堆block
, 同時(shí), __block
修飾的變量也在堆區(qū), 何以見得? 請看下圖:
很明顯,
age
這個(gè)變量已經(jīng)到了堆區(qū). 那么問題來了, block內(nèi)部會對這個(gè)__block
修飾的變量有一個(gè)retain
操作嗎? 我覺得應(yīng)該有一個(gè)類似retain
的操作, 理由如下:__block
修飾的變量在堆區(qū), 堆區(qū)的變量回收是程序員來決定的, 而__block
修飾的變量的生命周期是和block
一致的, 其實(shí)就相當(dāng)于block
持有了__block
修飾的變量
我猜測內(nèi)部的原理是這樣的:
堆區(qū)的block
會調(diào)用__main_block_copy_0
方法
__main_block_copy_0
方法內(nèi)部又調(diào)用了_Block_object_assign
你也可以理解為將__block
修飾的變量(此時(shí)被包裝成了一個(gè)對象), 然后堆block
會持有這個(gè)對象(也就是引用計(jì)數(shù)+1).
在這一點(diǎn)上ARC
和MRC
是一致的, 那么不同點(diǎn)是什么呢?
MRC
環(huán)境下, __block
修飾的變量(包裝后的對象), 這個(gè)對象內(nèi)部的指針并不會持有外面的對象, 舉個(gè)例子:
這里出現(xiàn)了壞內(nèi)存訪問的錯(cuò)誤, 盡管
block
持有了 __block
修飾的變量(包裝后的對象), 這個(gè)對象內(nèi)部的指針并不會持有外面的對象, 也就是圖中的person
對象并沒有被__block
修飾的變量(包裝后的對象)持有, 在MRC
環(huán)境下:
在MRC
環(huán)境下, 給person
對象發(fā)送release
消息, 引用計(jì)數(shù)-1, 對象直接銷毀, 壞內(nèi)存訪問, 說明person1
并沒有持有person
對象, 畫圖表示就是
但是, 在ARC
環(huán)境下, __block
修飾的變量的person
指針是會通過__Block_byref_id_object_copy
方法, 對person
對象強(qiáng)引用的(引用計(jì)數(shù)+1)
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
簡化下來就是:
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign(dst + 40, src + 40, 131);
}
這句代碼實(shí)際上就是調(diào)用_Block_object_assign
, 內(nèi)部對person
對象的引用計(jì)數(shù)+1, 這樣就形成了強(qiáng)引用.畫圖表示就是:
通過這幅圖, 可以看出來的是, block
內(nèi)部的內(nèi)存管理就是:
在MRC
環(huán)境下, 執(zhí)行copy
操作. 在ARC
環(huán)境下, 系統(tǒng)默認(rèn)執(zhí)行copy
操作, 此時(shí), block
內(nèi)部的person
指針會指向__block
修飾的結(jié)構(gòu)體, 通過
_Block_object_assign
方法, 對__block
修飾的結(jié)構(gòu)體進(jìn)行一次copy
操作, 也就是引用計(jì)數(shù)+1
, 在block
執(zhí)行完畢, 需要被銷毀時(shí), 執(zhí)行_Block_object_dispose
方法, 對__block
修飾的結(jié)構(gòu)體進(jìn)行一次release
操作, 也就是引用計(jì)數(shù)-1
.此時(shí), __block
修飾的結(jié)構(gòu)體和block
一起被銷毀.
不同的是, 在MRC
環(huán)境下, __block
修飾的結(jié)構(gòu)體內(nèi)部的_Block_object_assign
不會再對person
對象引用計(jì)數(shù)+1, _Block_object_dispose
方法也不會對person
對象引用計(jì)數(shù)-1,
, 在ARC
環(huán)境下, __block
修飾的結(jié)構(gòu)體內(nèi)部的_Block_object_assign
會對person
對象引用計(jì)數(shù)+1, _Block_object_dispose
方法也會對person
對象引用計(jì)數(shù)-1.
以上就是block內(nèi)部的內(nèi)存管理.
還有一點(diǎn)需要提到的是, __block
修飾的變量轉(zhuǎn)化成的結(jié)構(gòu)體中, __forwarding
指針是干嘛用的? 在我們之前的代碼中, 看到是將結(jié)構(gòu)體自己的地址傳給了__forwarding
指針. 那么這個(gè)指針的值就是自己這個(gè)結(jié)構(gòu)體的地址, 也就是說__forwarding
指針指向了自己, 那么為什么不直接從結(jié)構(gòu)體中取值, 而是要通過一個(gè)__forwarding
指針呢?
當(dāng)__block修飾的這個(gè)變量包裝成的結(jié)構(gòu)體還存在于堆區(qū)的時(shí)候, 現(xiàn)在這個(gè)地址是指向棧區(qū)的地址的, 但這本身并沒有什么意義.
但當(dāng)這個(gè)包裝結(jié)構(gòu)體被拷貝到了堆區(qū), 此時(shí)再去訪問這個(gè)變量的時(shí)候, 就會指向堆區(qū)的那個(gè)包裝結(jié)構(gòu)體. 也就是說, a->__forwarding->a的這個(gè)過程就是訪問堆區(qū)數(shù)據(jù)的過程.
那么假設(shè), 我想和MRC
一樣, 對person
對象不進(jìn)行強(qiáng)引用呢?
這時(shí)候就需要用到__weak
和__unsafe_unretain
關(guān)鍵字了. 事實(shí)上, 默認(rèn)狀態(tài)下, 都是相當(dāng)于使用了__strong
關(guān)鍵字, 相當(dāng)于__block
修飾的變量結(jié)構(gòu)體里的那根person
指針默認(rèn)就是強(qiáng)指針. 當(dāng)使用__weak
修飾時(shí), 被__block
修飾的變量就變成了:
編譯器還爆出了警告, 讓我不要這么做:
將持有的對象賦值給一個(gè)弱指針, 對象將在賦值完成后立即釋放
使用__unsafe_unretained
也是差不多的效果:
__weak
和__unsafe_unretained
這兩個(gè)關(guān)鍵字是用來解決循環(huán)引用的時(shí)候用到的. 那么什么是循環(huán)引用呢? 如下圖所以:
在ARC
環(huán)境下, 被強(qiáng)指針指向的對象引用計(jì)數(shù)+1, 此時(shí)person
對象創(chuàng)建時(shí)引用計(jì)數(shù)+1, 被強(qiáng)指針指向時(shí), 引用計(jì)數(shù)+1, 此時(shí)引用計(jì)數(shù)是2, block
對象創(chuàng)建時(shí)引用計(jì)數(shù)+1, 被person對象內(nèi)部的強(qiáng)指針指向時(shí), 引用計(jì)數(shù)+1, 此時(shí)引用計(jì)數(shù)是2. 那么當(dāng)他們引用計(jì)數(shù)都是2時(shí). 它們兩個(gè)就都無法銷毀. 此時(shí), 必須打破這個(gè)循環(huán)
一般打破循環(huán)的方式, 就是讓其中一根指針變成弱指針, 一般就是將block
內(nèi)部指向?qū)ο蟮闹羔樧兂扇踔羔?br>
一旦這跟指針變成弱指針后,
person
對象銷毀后, person
對象內(nèi)部的那根指針被回收, 回收后, block
對象釋放.
其實(shí)被__block
修飾的變量也是同理, 它是這樣形成:
而我們用__weak
修飾變量后, 形成的閉環(huán)其實(shí)是將__block
修飾的結(jié)構(gòu)體里的person
變成弱指針:
那么__weak
和__unsafe_unretained
有什么區(qū)別呢?
__weak
修飾的變量, 一旦內(nèi)存空間被回收, __weak
修飾的指針變量就會置為nil, 后面再訪問就會直接return
, 因此它是安全的
__unsafe_unretained
修飾的變量, 一旦內(nèi)存空間被回收,__unsafe_unretained
修飾的指針變量不會置為nil, 后面再訪問, 是非常危險(xiǎn)的. 有可能會造成野指針訪問.
最后再探討一個(gè)問題:
iOS block內(nèi)部為什么要加__strong
?
這是為了防止當(dāng)程序執(zhí)行block
時(shí), block
內(nèi)部的指針指向的那塊地址突然為空. 舉個(gè)例子:
假設(shè)上圖中, 在程序執(zhí)行到第22行時(shí), self突然為空, 如果不寫
__strong typeof(self)strongSelf = weakSelf;
這行代碼時(shí), 那后面訪問weakSelf
指向的地址空間時(shí), 就可能為空. 但是當(dāng)我寫了__strong typeof(self)strongSelf = weakSelf;
時(shí), 此時(shí), 我用一個(gè)棧區(qū)的局部變量強(qiáng)引用了self
對象. 那self
此時(shí)的引用計(jì)數(shù)+1, 它不會被置為空. 我后面的代碼就可以繼續(xù)訪問. 等到作用域結(jié)束, 局部變量椨诟觯空間回收, self對象的引用計(jì)數(shù)-1. 這樣是不會形成循環(huán)引用的, 如下圖所示iOS開發(fā)中在block中為什么要__weak和__strong配合使用
上文中舉出了一個(gè)__weak
和__strong
配合使用的例子, 僅供參考