問(wèn)題
1.什么是block,block的本質(zhì)是什么?
2.block的屬性修飾詞為什么是copy?使用block有哪些使用注意?
3.block為什么會(huì)發(fā)生循環(huán)引用?
4.block的變量捕獲究竟是怎樣進(jìn)行的?
5.block在修改NSMutableArray,需不需要添加__block?
6.__block和__weak的作用是什么?有什么使用注意點(diǎn)?
7.block中訪問(wèn)的對(duì)象和變量什么時(shí)候會(huì)銷毀?
讓我們帶著這一系列問(wèn)題,開(kāi)始探尋block的本質(zhì)
1.什么是block,block的本質(zhì)是什么
- block本質(zhì)上也是一個(gè)對(duì)象,而這個(gè)對(duì)象是一個(gè)結(jié)構(gòu)體,其內(nèi)部含有一個(gè)isa指針指向自己的類。
- block是封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對(duì)象。
- block是封裝函數(shù)及其上下文的OC對(duì)象。
首先寫(xiě)一個(gè)簡(jiǎn)單的block
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
void(^block)(int ,int) = ^(int a, int b){
NSLog(@"this is block,a = %d,b = %d",a,b);
NSLog(@"this is block,age = %d",age);
};
block(3,5);
}
return 0;
}
使用命令行將代碼轉(zhuǎn)化為c++查看其內(nèi)部結(jié)構(gòu)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;//描述block的信息
int age;
// 構(gòu)造函數(shù)(類似于OC的init方法),返回結(jié)構(gòu)體對(duì)象
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp; //block內(nèi)代碼地址
Desc = desc; // 儲(chǔ)存block對(duì)象占用的大小
}
};
// 封裝了block執(zhí)行邏輯的函數(shù)
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_2rvx1kjx5p75z7v2crt0nw8h0000gn_T_main_6253a2_mi_0,a,b);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_2rvx1kjx5p75z7v2crt0nw8h0000gn_T_main_6253a2_mi_1,age);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
// 定義block變量以及實(shí)現(xiàn)
void(*block)(int , int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
// 執(zhí)行block內(nèi)部的代碼
((void (*)(__bloc k_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 3, 5);
return 0;
}
}

從定義和實(shí)現(xiàn)block變量開(kāi)始
// 定義block變量
void(*block)(int , int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
拋開(kāi)無(wú)關(guān)緊要的代碼,block除了傳入兩個(gè)int的參數(shù)還調(diào)用了__main_block_impl_0這個(gè)函數(shù),并將函數(shù)的地址賦值給了block,于是找到方法的定義
__main_block_imp_0結(jié)構(gòu)體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp; //block內(nèi)代碼地址
Desc = desc; // 儲(chǔ)存block描述
}
};
觀察結(jié)構(gòu)體內(nèi),有2個(gè)結(jié)構(gòu)體變量和1個(gè)基本變量,同時(shí)還有一個(gè)構(gòu)造函數(shù),而我們上面block實(shí)現(xiàn)傳入的函數(shù)不正是這個(gè)函數(shù)嗎?
而這個(gè)函數(shù)最終返回的不就是一個(gè)名為__main_block_impl_0的結(jié)構(gòu)體呀
那么也就是說(shuō)最終將一個(gè)__main_block_imp_0結(jié)構(gòu)體的地址賦值給了block變量
所以我們需要 看一看這個(gè)__main_block_imp_0結(jié)構(gòu)體里到底有什么貓膩?仔細(xì)一看,哇、美滋滋,只有4個(gè)參數(shù) void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0。
再看看block實(shí)現(xiàn)的時(shí)候調(diào)用__main_block_imp_0函數(shù)傳入了什么(void *)__main_block_func_0, &__main_block_desc_0_DATA, age。居然只有三個(gè)參數(shù),很好nice,說(shuō)明flags默認(rèn)傳的0基本與我們沒(méi)有和什么關(guān)系,繼續(xù)看第一個(gè)參數(shù)__main_block_func_0這就是我們的fp呀看看__main_block_func_0中有些什么。
- flags,標(biāo)志變量,在實(shí)現(xiàn)block的內(nèi)部操作時(shí)會(huì)用到
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_2rvx1kjx5p75z7v2crt0nw8h0000gn_T_main_6253a2_mi_0,a,b);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_2rvx1kjx5p75z7v2crt0nw8h0000gn_T_main_6253a2_mi_1,age);
}
在__main_block_func_0函數(shù)中首先取出block中age的值,緊接著可以看到兩個(gè)熟悉的NSLog,可以發(fā)現(xiàn)這兩段代碼恰恰是我們?cè)赽lock塊中寫(xiě)下的代碼。
那么__main_block_func_0函數(shù)中其實(shí)存儲(chǔ)著我們block中寫(xiě)下的代碼。而__main_block_impl_0函數(shù)中傳入的是(void *)__main_block_func_0,也就說(shuō)將我們寫(xiě)在block塊中的代碼封裝成__main_block_func_0函數(shù),并將__main_block_func_0函數(shù)的地址傳入了__main_block_impl_0的構(gòu)造函數(shù)中保存在結(jié)構(gòu)體內(nèi)。
也就是說(shuō)我們的fp就是__main_block_func_0函數(shù)的地址。
而這個(gè)時(shí)候我們?cè)倩氐?code>__main_block_imp_0結(jié)構(gòu)體的構(gòu)造函數(shù)。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp; //block內(nèi)代碼地址
Desc = desc; // 儲(chǔ)存block描述
}
};
這個(gè)魂淡居然把我們的block實(shí)現(xiàn)地址傳給了impl的FunctionPtr,找到impl的定義,居然又是一個(gè)結(jié)構(gòu)體__block_impl,好的,忍它一次,點(diǎn)進(jìn)去。
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
簡(jiǎn)單,又是2個(gè)清爽的成員變量和2個(gè)清爽的指針變量,到了這里我只知道flags是系統(tǒng)傳的我不管,然后我block的實(shí)現(xiàn)地址傳給了FuncPtr,還有兩個(gè)參數(shù)是什么東東呢?
此時(shí)此刻我們看著isa這個(gè)指針不就是指向所屬類的指針,也就是block的類型嗎
但是__main_block_impl_0構(gòu)造函數(shù)傳的是&_NSConcreteStackBlock這個(gè)東東,這個(gè)東東又是什么???哇好蒙蔽,這都不知道是哪里傳過(guò)來(lái)的,不要怕點(diǎn)進(jìn)去看
__OBJC_RW_DLLIMPORT void *_NSConcreteStackBlock[32];
德瑪西亞,這個(gè)block其實(shí)就是當(dāng)前類所屬的指針的地址,只是蘋(píng)果對(duì)它做了堆棧的處理。
而Reserved這個(gè)只是保留變量而已
總結(jié)一下:
- isa,指向所屬類的指針,也就是block的類型
- flags,標(biāo)志變量,在實(shí)現(xiàn)block的內(nèi)部操作時(shí)會(huì)用到
- Reserved,保留變量
- FuncPtr,block執(zhí)行時(shí)調(diào)用的函數(shù)指針
可以看出,它包含了isa指針(包含isa指針的皆為對(duì)象),也就是說(shuō)block也是一個(gè)對(duì)象(runtime里面,對(duì)象和類都是用結(jié)構(gòu)體表示)。
此時(shí)此刻 __main_block_func_0函數(shù)的地址我們已經(jīng)看吃透了,看__main_block_imp_0的第二個(gè)參數(shù)&__main_block_desc_0_DATA點(diǎn)進(jìn)去看是一個(gè)__main_block_desc_0結(jié)構(gòu)體
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
它也傳入了2個(gè)參數(shù),reserved系統(tǒng)傳了0,而Block_size傳入的不正是當(dāng)前block的size大小嗎,又是蘋(píng)果最喜歡的內(nèi)存處理,這也是ios的魅力所在呀。
到了這里block的定義和實(shí)現(xiàn)已經(jīng)走完了,可是還沒(méi)有調(diào)用呀?
block的調(diào)用
// 執(zhí)行block內(nèi)部的代碼
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 3, 5);
通過(guò)上述代碼可以發(fā)現(xiàn)調(diào)用block是通過(guò)block找到__block_impl的FunPtr直接調(diào)用
通過(guò)上面分析我們知道block指向的是__main_block_impl_0類型結(jié)構(gòu)體,但是我們發(fā)現(xiàn)__main_block_impl_0結(jié)構(gòu)體中并不直接就可以找到FunPtr,而FunPtr是存儲(chǔ)在__block_impl中的,為什么block可以直接調(diào)用__block_impl中的FunPtr呢?
重新查看上述源代碼可以發(fā)現(xiàn),(__block_impl *)block將block強(qiáng)制轉(zhuǎn)化為__block_impl類型的,因?yàn)?code>__block_impl是__main_block_impl_0結(jié)構(gòu)體的第一個(gè)成員,相當(dāng)于將__block_impl結(jié)構(gòu)體的成員直接拿出來(lái)放在__main_block_impl_0中,那么也就說(shuō)明__block_impl的內(nèi)存地址就是__main_block_impl_0結(jié)構(gòu)體的內(nèi)存地址開(kāi)頭。所以可以轉(zhuǎn)化成功。并找到FunPtr成員。
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_2rvx1kjx5p75z7v2crt0nw8h0000gn_T_main_6253a2_mi_0,a,b);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_2rvx1kjx5p75z7v2crt0nw8h0000gn_T_main_6253a2_mi_1,age);
}
上面我們知道,FunPtr中存儲(chǔ)著通過(guò)代碼塊封裝的函數(shù)地址,那么調(diào)用此函數(shù),也就是會(huì)執(zhí)行代碼塊中的代碼。并且回頭查看__main_block_func_0函數(shù),可以發(fā)現(xiàn)第一個(gè)參數(shù)就是__main_block_impl_0類型的指針。也就是說(shuō)將block傳入__main_block_func_0函數(shù)中,便于重中取出block捕獲的值。
總結(jié):
-
block結(jié)構(gòu)體內(nèi)部之間的關(guān)系
block結(jié)構(gòu)體內(nèi)部之間的關(guān)系 -
block底層的數(shù)據(jù)結(jié)構(gòu)
block底層的數(shù)據(jù)結(jié)構(gòu)
此時(shí)此刻你以為結(jié)束了嗎?
nonono
look
block的類型
block分為三種類型
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )
納尼,這不是我們剛剛__main_block_impl_0中的impl的isa指向的&_NSConcreteStackBlock的類型嗎?
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp; //block內(nèi)代碼地址
Desc = desc; // 儲(chǔ)存block描述
}
};
不管那么多寫(xiě)段代碼打印一下
我們通過(guò)代碼用class方法或者isa指針查看具體類型。
int main(int argc, const char * argv[]) {
@autoreleasepool {
auto int a = 10;
static int b = 11;
void(^block)(void) = ^{
NSLog(@"hello, a = %d, b = %d", a,b);
};
NSLog(@"%@", [block class]);
NSLog(@"%@", [[block class] superclass]);
NSLog(@"%@", [[[block class] superclass] superclass]);
NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);
}
return 0;
}

從上述打印內(nèi)容可以看出block最終都是繼承自NSBlock類型,而NSBlock繼承于NSObjcet。那么block其中的isa指針其實(shí)是來(lái)自NSObject中的。這也更加印證了block的本質(zhì)其實(shí)就是OC對(duì)象
通過(guò)代碼查看一下block在什么情況下其類型會(huì)各不相同
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 1. 內(nèi)部沒(méi)有調(diào)用外部變量的block
void (^block1)(void) = ^{
NSLog(@"Hello");
};
// 2. 內(nèi)部調(diào)用外部變量的block
int a = 10;
void (^block2)(void) = ^{
NSLog(@"Hello - %d",a);
};
// 3. 直接調(diào)用的block的class
NSLog(@"%@ %@ %@", [block1 class], [block2 class], [^{
NSLog(@"%d",a);
} class]);
}
return 0;
}

上述代碼轉(zhuǎn)化為c++代碼查看源碼時(shí)卻發(fā)現(xiàn)block的類型與打印出來(lái)的類型不一樣,c++源碼中三個(gè)block的isa指針全部都指向_NSConcreteStackBlock類型地址。
我們可以猜測(cè)runtime運(yùn)行時(shí)過(guò)程中也許對(duì)類型進(jìn)行了轉(zhuǎn)變。最終類型當(dāng)然以runtime運(yùn)行時(shí)類型也就是我們打印出的類型為準(zhǔn)。
這里就涉及到了block在內(nèi)存中的存儲(chǔ)位置

這五個(gè)區(qū)存儲(chǔ)的內(nèi)容也各有劃分:
- 棧區(qū)(stack):這一塊區(qū)域系統(tǒng)會(huì)自己進(jìn)行管理,我們不用干預(yù),主要存一些局部變量,以及函數(shù)跳轉(zhuǎn)時(shí)的現(xiàn)場(chǎng)保護(hù)。因此大量的局部變量、深遞歸、函數(shù)循環(huán)調(diào)用都可能耗盡內(nèi)存而造成運(yùn)行崩潰。
- 堆區(qū)(heap):與棧區(qū)相對(duì),這一塊一般由我們開(kāi)發(fā)人員管理,比如一些alloc、free的操作,存儲(chǔ)一些自己創(chuàng)建的對(duì)象。
- 全局區(qū)(靜態(tài)區(qū) static):全局變量和靜態(tài)變量都存儲(chǔ)在這里,已經(jīng)初始化的和沒(méi)有初始化的變量會(huì)分開(kāi)存儲(chǔ)在相鄰的區(qū)域,程序結(jié)束后系統(tǒng)來(lái)釋放。
- 常量區(qū):存儲(chǔ)常量字符串和const常量。
- 代碼區(qū):顧名思義,就是存我們寫(xiě)的代碼。
-
_NSConcreteGlobalBlock(全局) -
_NSConcreteStackBlock(棧)
-_NSConcreteMallocBlock(堆)
block是如何定義其類型
block是如何定義其類型,依據(jù)什么來(lái)為block定義不同的類型并分配在不同的空間呢?
接著我們使用代碼驗(yàn)證上述問(wèn)題,首先關(guān)閉ARC回到MRC環(huán)境下,因?yàn)锳RC會(huì)幫助我們做很多事情,可能會(huì)影響我們的觀察。
點(diǎn)擊【Project】 - 點(diǎn)擊【Target 】-點(diǎn)擊 【build Setting】- 輸入【“AutoMatic”】 -點(diǎn)擊【Object-C Automatic Reference Count】改為【NO】
// MRC環(huán)境?。。?int main(int argc, const char * argv[]) {
@autoreleasepool {
// Global:沒(méi)有訪問(wèn)auto變量:__NSGlobalBlock__
void (^block1)(void) = ^{
NSLog(@"block1---------");
};
// Stack:訪問(wèn)了auto變量: __NSStackBlock__
int a = 10;
void (^block2)(void) = ^{
NSLog(@"block2---------%d", a);
};
NSLog(@"%@ %@", [block1 class], [block2 class]);
// __NSStackBlock__調(diào)用copy : __NSMallocBlock__
NSLog(@"%@", [[block2 copy] class]);
}
return 0;
}

通過(guò)打印的內(nèi)容我們可以知道:
- 沒(méi)有訪問(wèn)auto變量的block是
__NSGlobalBlock__類型的,存放在數(shù)據(jù)段中。 - 訪問(wèn)了auto變量的block是
__NSStackBlock__類型的,存放在棧中。 -
__NSStackBlock__類型的block調(diào)用copy成為__NSMallocBlock__類型并被復(fù)制存放在堆中。
再看看ARC中的打印

我了個(gè)去,發(fā)現(xiàn)訪問(wèn)了auto變量的block是
__NSMallocBlock__類型了,可以猜想蘋(píng)果在ARC環(huán)境下是不是幫我們做了一次copy操作呢?
__NSStackBlock__訪問(wèn)了aotu變量,并且是存放在棧中的,上面提到過(guò),棧中的代碼在作用域結(jié)束之后內(nèi)存就會(huì)被銷毀,那么我們很有可能block內(nèi)存銷毀之后才去調(diào)用他,那樣就會(huì)獲取錯(cuò)誤。所以ARC下直接幫我們自動(dòng)進(jìn)行了一次copy操作。
[__NSStackBlock __ copy]操作就變成了__NSMallocBlock __
此時(shí)此刻,我們已經(jīng)知道ARC進(jìn)行了copy操作那它究竟針對(duì)不同類型的block做了什么操作呢?請(qǐng)看下圖:

所以在平時(shí)開(kāi)發(fā)過(guò)程中MRC環(huán)境下經(jīng)常需要使用copy來(lái)保存block,將棧上的block拷貝到堆中,即使棧上的block被銷毀,堆上的block也不會(huì)被銷毀,需要我們自己調(diào)用release操作來(lái)銷毀。而在ARC環(huán)境下回系統(tǒng)會(huì)自動(dòng)copy,是block不會(huì)被銷毀。
當(dāng)然,你以為結(jié)束了嗎?仔細(xì)看:
ARC幫我們做了什么
在ARC環(huán)境下,編譯器會(huì)根據(jù)情況自動(dòng)將棧上的block進(jìn)行一次copy操作,將block復(fù)制到堆上。
什么情況下ARC會(huì)自動(dòng)將block進(jìn)行一次copy操作?
以下代碼都在RAC環(huán)境下執(zhí)行。
1. 將block賦值給__strong指針時(shí)
// ARC環(huán)境!??!
int main(int argc, const char * argv[]) {
@autoreleasepool {
// block內(nèi)沒(méi)有訪問(wèn)auto變量
Block block = ^{
NSLog(@"block---------");
};
NSLog(@"%@",[block class]);
int a = 10;
// block內(nèi)訪問(wèn)了auto變量,但沒(méi)有賦值給__strong指針
NSLog(@"%@",[^{
NSLog(@"block1---------%d", a);
} class]);
// block賦值給__strong指針
Block block2 = ^{
NSLog(@"block2---------%d", a);
};
NSLog(@"%@",[block2 class]);
}
return 0;
}

我們發(fā)現(xiàn)打印的第二個(gè)block內(nèi)訪問(wèn)了auto變量,但沒(méi)有賦值給__strong指針直接打印了
__NSStackBlock__
所以當(dāng)block被賦值給__strong指針時(shí),RAC才會(huì)自動(dòng)進(jìn)行一次copy操作。
此時(shí)此刻,我們定義block屬性的時(shí)候是不是就可以用copy和strong呢?
block本身是像對(duì)象一樣可以retain,和release。但是,block在創(chuàng)建的時(shí)候,它的內(nèi)存是分配在棧上的,而不是在堆上。他本身的作于域是屬于創(chuàng)建時(shí)候的作用域,一旦在創(chuàng)建時(shí)候的作用域外面調(diào)用block將導(dǎo)致程序崩潰。因?yàn)闂^(qū)的特點(diǎn)就是創(chuàng)建的對(duì)象隨時(shí)可能被銷毀,一旦被銷毀后續(xù)再次調(diào)用空對(duì)象就可能會(huì)造成程序崩潰,在對(duì)block進(jìn)行copy后,block存放在堆區(qū).
使用retain也可以,但是block的retain行為默認(rèn)是用copy的行為實(shí)現(xiàn)的,
因?yàn)閎lock變量默認(rèn)是聲明為棧變量的,為了能夠在block的聲明域外使用,所以要把block拷貝(copy)到堆,所以說(shuō)為了block屬性聲明和實(shí)際的操作一致,最好聲明為copy。
2. 將block作為函數(shù)返回值時(shí)
// ARC環(huán)境?。?!
typedef void (^Block)(void);
Block myblock()
{
int a = 10;
// block中訪問(wèn)了auto變量,此時(shí)block類型應(yīng)為_(kāi)_NSStackBlock__
Block block = ^{
NSLog(@"---------%d", a);
};
return block;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block = myblock();
block();
// 打印block類型為 __NSMallocBlock__
NSLog(@"%@",[block class]);
}
return 0;
}

如果在block中訪問(wèn)了auto變量時(shí),block的類型為
__NSStackBlock__,上面打印內(nèi)容發(fā)現(xiàn)blcok為__NSMallocBlock__類型的,并且可以正常打印出a的值,說(shuō)明block內(nèi)存并沒(méi)有被銷毀。
block進(jìn)行copy操作會(huì)轉(zhuǎn)化為__NSMallocBlock__類型,來(lái)講block復(fù)制到堆中,那么說(shuō)明RAC在 block作為函數(shù)返回值時(shí)會(huì)自動(dòng)幫助我們對(duì)block進(jìn)行copy操作,以保存block,并在適當(dāng)?shù)牡胤竭M(jìn)行release操作。
3.block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時(shí)
NSArray * arr = @[@1,@2,@3,@4];
[arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
//一系列裝逼操作
}];
4.block作為GCD API的方法參數(shù)時(shí)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//一系列裝逼操作
});
注意
- 1,對(duì)于棧區(qū)block,在ARC情況下自動(dòng)拷貝到堆區(qū),MRC則放在棧區(qū),所在函數(shù)執(zhí)行完畢就會(huì)釋放。那么要想在外面調(diào)用就要用copy指向它,這樣
就copy到了堆區(qū)。 - 2,strong屬性不會(huì)拷貝,會(huì)造成野指針錯(cuò)區(qū)從而crash。另外這里需要注意的是,ARC是編譯器的一種特性,編譯器在編譯的時(shí)候會(huì)在合適的地方插入retain、release、autorelease,而不是iOS運(yùn)行時(shí)的特性。
- 3,堆區(qū)是動(dòng)態(tài)的,不像代碼區(qū)是不變化的,堆區(qū)會(huì)不斷地創(chuàng)建銷毀,當(dāng)沒(méi)有強(qiáng)指針指向的時(shí)候就會(huì)被銷毀,如果再去訪問(wèn)這段代碼,程序就會(huì)崩潰。所以在堆區(qū)要用strong或者copy修飾。
- 4,block是一段代碼塊,即不可變,所以使用copy也不會(huì)深copy。
所以要用strong替代copy修飾block要滿足兩個(gè)條件:
用strong修飾且引用了外部變量
使用block的時(shí)候要注意循環(huán)引用導(dǎo)致內(nèi)存泄露
一個(gè)block要使用self,會(huì)處理成在外部聲明一個(gè)weak變量指向self,在block里又聲明一個(gè)strong變量指向weakSelf?????
原因:block會(huì)把寫(xiě)在block里的變量copy一份,如果直接在block里使用self,(self對(duì)變量默認(rèn)是強(qiáng)引用)self對(duì)block持有,block對(duì)self持有,導(dǎo)致循環(huán)引用,所以這里需要聲明一個(gè)弱引用weakSelf,讓block引用weakSelf,打破循環(huán)引用。
而這樣會(huì)導(dǎo)致另外一個(gè)問(wèn)題,因?yàn)閣eakSelf是對(duì)self的弱引用,如果這個(gè)時(shí)候控制器pop或者其他的方式引用計(jì)數(shù)為0,就會(huì)釋放,如果這個(gè)block是異步調(diào)用而且調(diào)用的時(shí)候self已經(jīng)釋放了,這個(gè)時(shí)候weakSelf已就變成了nil。
當(dāng)控制器(也可以是其他的控件)pop回來(lái)之后(或者一些其他的原因?qū)е箩尫牛?,網(wǎng)絡(luò)請(qǐng)求完成,如果這個(gè)時(shí)候需要控制器做出反映,需要strongSelf再對(duì)weakSelf強(qiáng)引用一下。
但是,你可能會(huì)疑問(wèn),strongSelf對(duì)weakSelf強(qiáng)引用,weakSelf對(duì)self弱引用,最終不也是對(duì)self進(jìn)行了強(qiáng)引用,會(huì)導(dǎo)致循環(huán)引用嗎。不會(huì)的,因?yàn)閟trongSelf是在block里面聲明的一個(gè)指針,當(dāng)block執(zhí)行完畢后,strongSelf會(huì)釋放,這個(gè)時(shí)候?qū)⒉辉購(gòu)?qiáng)引用weakSelf,所以self會(huì)正確的釋放。

