Block
Block定義及本質(zhì)
block本質(zhì)上也是一個(gè)OC對象,它內(nèi)部有個(gè)isa指針(有isa指針就可以認(rèn)為是OC對象)
block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對象
函數(shù)調(diào)用環(huán)境:函數(shù)調(diào)用所需要的參數(shù)及外部參數(shù)等
//block表達(dá)式:
^ 返回值類型 (參數(shù)列表) {表達(dá)式}
^ int (int count) {
return count + 1;
};
//聲明Block類型變量語法:
返回值類型 (^變量名)(參數(shù)列表) = Block表達(dá)式
int age = 20;
void (^Block)(void) = ^{
NSLog(@"age = %d",age);
}
Block的內(nèi)部結(jié)構(gòu)
//以上block的內(nèi)部結(jié)構(gòu)
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
};
//impl內(nèi)部結(jié)構(gòu)
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
}
//Desc內(nèi)部結(jié)構(gòu)
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
通過將oc代碼轉(zhuǎn)換為c++代碼可以看出
int main(int argc, const char * argv[]) {
// 定義block變量
// 調(diào)用__main_block_impl_0函數(shù),返回結(jié)構(gòu)體變量地址
void (*block)(void) = &__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA
);
// 執(zhí)行block內(nèi)部的代碼
// 此時(shí),若block有參數(shù)時(shí),會(huì)將參數(shù)一起傳入
block->FuncPtr(block);
//之所以block能直接調(diào)用FuncPtr是因?yàn)榇鎯?chǔ)FuncPtr的impl結(jié)構(gòu)體位于結(jié)構(gòu)體的第一位,所以其地址與結(jié)構(gòu)體的地址是一樣的
//從另一方面看,由于impl直接是結(jié)構(gòu)體對象,相當(dāng)于可以直接將__block_impl結(jié)構(gòu)體的東西賦值過去到__main_block_impl_0中,故從這方面看也是可以直接調(diào)用的
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 構(gòu)造函數(shù)(類似于OC的init方法),返回結(jié)構(gòu)體對象
__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;
}
};
// 封裝了block執(zhí)行邏輯的函數(shù)
// 若block中含有參數(shù),則參數(shù)會(huì)在這個(gè)函數(shù)中被當(dāng)做參數(shù)傳遞進(jìn)來
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//定義block時(shí),block內(nèi)部執(zhí)行的函數(shù)
}

Block的變量捕獲
當(dāng)在block內(nèi)部使用外部局部變量時(shí),block的結(jié)構(gòu)體__main_block_impl_0中也會(huì)定義一個(gè)同名的成員變量,并在構(gòu)造函數(shù)中,將外部局部變量的值賦值給內(nèi)部的變量,在block定義中,若有使用到局部變量,會(huì)將外部的局部變量的值存儲(chǔ)到結(jié)構(gòu)體內(nèi)部的成員變量中,所以外邊的變量怎么改變,都不會(huì)修改函數(shù)定義中的變量值
變量捕獲 : 專門新建一個(gè)成員變量來保存外部的變量,稱為捕獲
auto變量 : 自動(dòng)變量,離開作用域就銷毀

當(dāng)使用static修飾的局部變量,在變量捕獲時(shí),會(huì)將變量的地址值傳入,在結(jié)構(gòu)體內(nèi)部定義的,就是一個(gè)對應(yīng)的指針類型
auto變量值傳遞,static變量指針傳遞是因?yàn)閍uto變量什么時(shí)候被釋放是不確定的,而static修飾的局部變量會(huì)始終在內(nèi)存中
全局變量并沒有捕獲,是因?yàn)楹瘮?shù)在哪里都是可以直接使用全局變量的,故不需要捕獲,而局部變量需要捕獲,是因?yàn)榫植孔兞慷x是在一個(gè)函數(shù),使用又是在另一個(gè)函數(shù),是跨函數(shù)使用的,故需要捕獲
注:所有的方法都會(huì)附帶兩個(gè)參數(shù),一個(gè)是self(方法調(diào)用者),一個(gè)是SEL _cmd(方法名),而參數(shù)都是局部變量,故在方法中的block使用self,也是會(huì)捕獲的
今后凡是涉及到會(huì)不會(huì)捕獲,只需要判斷是否為局部變量即可
在方法中的block直接使用成員變量,即_name時(shí),也是捕獲方法調(diào)用者self,再通過self去取_name的值
Block的類型
block有3種類型,可以通過調(diào)用class方法或者isa指針查看具體類型,最終都是繼承自NSBlock類型
-
__NSGlobalBlock__(_NSConcreteGlobalBlock) -
__NSStackBlock__(_NSConcreteStackBlock) -
__NSMallocBlock__(_NSConcreteMallocBlock)
block的類型一切以運(yùn)行時(shí)的結(jié)果為準(zhǔn)
同時(shí)通過clang轉(zhuǎn)成的代碼(C++)并不是真正的底層代碼
clang是屬于LLVM中的一部分

堆:動(dòng)態(tài)分配內(nèi)存,需要程序員申請,也需要程序員自己管理內(nèi)存

- 只要沒有訪問auto變量就是global類型,哪怕有訪問static變量或者局部變量
- 在棧上的block由于處于棧區(qū),受作用域影響,在作用域之外,有可能被銷毀,導(dǎo)致數(shù)據(jù)錯(cuò)亂
- globalBlock調(diào)用copy方法,依然為global類型
Block的copy操作

在ARC環(huán)境下,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block復(fù)制到堆上,比如以下情況:
- block作為函數(shù)的返回值
- 將block賦值給
__strong指針時(shí) - block作為cocoaAPI中方法含有usingBlock的方法參數(shù)時(shí)
- block作為GCD方法的參數(shù)時(shí)
MRC環(huán)境下block屬性建議寫法:使用copy關(guān)鍵字
MRC環(huán)境下若使用retain,只會(huì)令block的引用計(jì)數(shù)+1,并不會(huì)將block復(fù)制到棧區(qū)
ARC環(huán)境下,copy和strong都是可以的,因?yàn)樵贏RC環(huán)境下,對block強(qiáng)引用也會(huì)對block進(jìn)行一次copy操作,但還是建議統(tǒng)一寫copy
Block的訪問外部對象類型auto變量
- ??臻g上的block,是不會(huì)持有外部對象的
- 堆空間上的block,則會(huì)持有外部對象,當(dāng)block使用到外部變量時(shí),會(huì)對外部變量進(jìn)行一次retain操作,在block自己銷毀時(shí),也會(huì)對內(nèi)部用到的外部變量做一次release操作
當(dāng)block內(nèi)部訪問了對象類型的auto變量時(shí):
如果block是在棧上:
無論block內(nèi)部對對象類型的auto變量是強(qiáng)引用還是弱引用,都不會(huì)持有該對象,即不會(huì)對auto變量產(chǎn)生強(qiáng)引用如果block被拷貝到堆上:
會(huì)調(diào)用block內(nèi)部的Desc中的copy函數(shù)
copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)
_Block_object_assign函數(shù)會(huì)根據(jù)auto變量的修飾符(__strong,__weak,__unsafe_unretained)做出相應(yīng)的操作,類似于retain(形成強(qiáng)引用、弱引用)當(dāng)block從堆上移除:
會(huì)調(diào)用block內(nèi)部的Desc中的dispose函數(shù)
dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)
_Block_object_dispose函數(shù)會(huì)自動(dòng)釋放引用的auto變量,類似于release
copy函數(shù)和dispose函數(shù)都是做內(nèi)存管理用的,只要見到這兩個(gè)函數(shù),就大概知道與對象有關(guān)

當(dāng)訪問的是個(gè)對象類型對象,會(huì)自動(dòng)生成_Block_object_assign函數(shù)和_Block_object_dispose函數(shù)對該對象進(jìn)行內(nèi)存管理
看強(qiáng)引用什么時(shí)候被釋放,則該對象就什么時(shí)候釋放,與弱引用無關(guān)
Block的修改外部auto變量
默認(rèn)情況下,在block中是無法修改外部的變量的
從本質(zhì)上看,定義變量是在一個(gè)函數(shù),而block調(diào)用的函數(shù)又是另一個(gè)函數(shù),二者作用域不同,故肯定是無法修改(代碼)
要想修改外部變量,做法:
- 將變量變?yōu)閟tatic或者為全局變量
-
__block關(guān)鍵字
__block可以用于解決block內(nèi)部無法修改auto變量值的問題
__block不能修飾全局變量,靜態(tài)變量(static)
編譯器會(huì)將__block包裝成一個(gè)對象,故其也有copy與dispose函數(shù)
//包裝的對象結(jié)構(gòu)體為:
struct __Block_byref_age_0 {
void *__isa;
___Block_byref_age_0 *__forwarding;//該指針指向自己
int __flags;
int __size;
int age;
}
在block中定義了一個(gè)指針,指向上面的這個(gè)結(jié)構(gòu)體,當(dāng)需要修改值時(shí),通過指針找到對應(yīng)的__forwarding,在找到對應(yīng)的age,從而能夠成功地修改和讀取值了
此時(shí)在外邊打印age的地址值,實(shí)際上就是__block_byref_age_0內(nèi)部的age的地址,即實(shí)際上外部的age就是結(jié)構(gòu)體內(nèi)部的age
注意點(diǎn)1:若block外部定義了一個(gè)可變數(shù)組array,在block內(nèi)部使用方法[array addObject:@“111”],是可以的,因?yàn)槠浔举|(zhì)是將array用來使用,而不是直接修改array的指針的東西,例如array = nil,(該語句就會(huì)報(bào)錯(cuò) )
注意點(diǎn)2:若對象類型外部在使用__block修飾,則可以修改對象類型的指針變量,即可以令array = nil,其內(nèi)部也會(huì)生成一個(gè)__Block_byref_XXX的結(jié)構(gòu)體,結(jié)構(gòu)體內(nèi)部還會(huì)存在一個(gè)copy函數(shù)和dispose函數(shù)(因?yàn)閎lock外部需要管理結(jié)構(gòu)體對象,故需要一個(gè)copy和dispose,而結(jié)構(gòu)體內(nèi)部需要管理真正的對象類型,故還是需要一個(gè)copy和dispose函數(shù))
__block的內(nèi)存管理
- 當(dāng)block在棧上時(shí),并不會(huì)對__block變量產(chǎn)生強(qiáng)引用
- 當(dāng)block被copy到堆上的時(shí)候,會(huì)調(diào)用block內(nèi)部的copy函數(shù),copy函數(shù)內(nèi)部會(huì)調(diào)用
_Block_object_assign函數(shù),_Block_object_assign函數(shù)會(huì)對__block變量形成強(qiáng)引用(retain)(一般都是強(qiáng)引用)

當(dāng)block從棧copy到堆上,會(huì)將內(nèi)部用到的__block變量也一起拷貝到堆上,并對其進(jìn)行強(qiáng)引用,當(dāng)時(shí)__block修飾的對象類型,在__block的變量結(jié)構(gòu)體被拷貝到堆上時(shí),會(huì)調(diào)用結(jié)構(gòu)體內(nèi)部的copy函數(shù)對結(jié)構(gòu)體里邊的對象變量進(jìn)行管理
若另外一個(gè)block進(jìn)行同樣的操作,也使用到一樣的__block變量,則該block拷貝到堆上的同時(shí),對該變量進(jìn)行強(qiáng)引用,由于之前__block已經(jīng)在堆上了,故直接強(qiáng)引用就好了,不需要再次復(fù)制
- 當(dāng)block從堆中移除時(shí),會(huì)調(diào)用block內(nèi)部的dispose函數(shù),dispose函數(shù)內(nèi)部會(huì)調(diào)用
_Block_object_dispose函數(shù),_Block_object_dispose函數(shù)會(huì)自動(dòng)釋放引用的__block變量(release)

block與對象類型的auto對象變量,__block變量內(nèi)存管理總結(jié)
- 當(dāng)block在棧上時(shí),對他們都不會(huì)產(chǎn)生強(qiáng)引用
- 當(dāng)block拷貝到堆上時(shí),都會(huì)通過copy函數(shù)來處理它們
- 當(dāng)block從堆中移除時(shí),都會(huì)調(diào)用dispose函數(shù)來釋放
使用__block修飾基本數(shù)據(jù)類型和直接傳入OC對象的不同是:
使用__block修飾的對象,在block內(nèi)部都是對其有著強(qiáng)引用的
使用OC對象傳入會(huì)根據(jù)外部是強(qiáng)指針(__strong)還是弱引用(__weak)來決定內(nèi)部對該對象是強(qiáng)引用還是弱引用
注:不存在
__block __weak int age這樣的寫法,因?yàn)?code>__weak只能用來修飾普通OC對象
__forwarding指針
- 當(dāng)
__block修飾的變量在棧上時(shí),其__forwarding指針指向自己 - 當(dāng)
__block修飾的變量被拷貝到堆上時(shí),棧上的對象中的__forwarding指針指向堆上的對象,堆上對象的__forwarding指針指向自己

這樣的好處是,無論訪問堆上還是棧上的__block對象,都能確保讀取的對象一定是堆中的__block對象
被__block修飾的對象類型
- block內(nèi)部指向被
__block包裝的對象結(jié)構(gòu)體的指針一定是強(qiáng)指針,而結(jié)構(gòu)體內(nèi)部的指向外部對象的指針會(huì)根據(jù)外部是強(qiáng)指針還是弱指針,對應(yīng)的是強(qiáng)引用還是弱引用 - 當(dāng)棧上的block調(diào)用了copy操作,會(huì)調(diào)用block中Desc函數(shù)內(nèi)部的copy函數(shù)將修飾的變量中的結(jié)構(gòu)體也復(fù)制一份到堆上
- 在MRC環(huán)境中,使用
__block修飾的對象變量中結(jié)構(gòu)體指向外部對象變量的指針始終都是弱引用(即不會(huì)進(jìn)行retain操作),只有__block對象才會(huì)這樣.比較特殊
循環(huán)引用
解決方法:
- ARC:
1.使用__weak或__unsafe_unretained指針
__weak:不會(huì)產(chǎn)生強(qiáng)引用,當(dāng)指向的對象被銷毀,會(huì)自動(dòng)將指針置為nil
__unsafe_unretained:不會(huì)產(chǎn)生強(qiáng)引用,不安全,指向的對象銷毀時(shí),指針存儲(chǔ)的地址值不變(有可能產(chǎn)生野指針錯(cuò)誤)
2.使用__block,在block內(nèi)部將__block修飾的指針置為空,且必須要執(zhí)行block
__block MXPerson *person = [[MXPerson alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"%d",person.age);
person = nil;
}
person.block();
- MRC
1.因?yàn)镸RC是不支持__weak的,所以在MRC環(huán)境中,可以使用__unsafe_unretained
2.可以使用__block