1.對象類型的auto變量
在第一篇文章中我們講了在block中使用基本類型的自動變量的情況,現(xiàn)在我們研究一下在block中使用對象類型的自動變量又是什么情況。
首先看一段代碼:
typedef void(^PDBlock)(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
PDBlock block;
Person *person = [[Person alloc] init];
person.age = 10;
block = ^{
NSLog(@"%ld", (long)person.age);
};
block();
return 0;
}
}
這段代碼執(zhí)行的結(jié)果是10招狸,完全沒有問題蜡塌。我們?yōu)榱搜芯縫erson對象的釋放時機古今,在person.m文件中實現(xiàn)dealloc方法:
- (void)dealloc{
NSLog(@"--------dealloc");
}
然后我們把代碼改一下,在大括號下面一行打斷點:
運行代碼轮蜕,發(fā)現(xiàn)運行到斷點處的時候,已經(jīng)打印了
--------dealloc
良姆,這說明這個時候person對象已經(jīng)被釋放了肠虽。這個很好理解幔戏,因為person對象是在大括號內(nèi)聲明的局部變量玛追,它的生命周期僅限于這個大括號。下面我們在大括號里加入一個block:
這個時候運行代碼闲延,發(fā)現(xiàn)代碼運行到斷點處痊剖,也就是出了大括號,還沒有打印
--------dealloc
垒玲,這說明person對象出了大括號還沒有被釋放陆馁。為了探索其中的原因,我們把代碼轉(zhuǎn)化為c++源碼看看合愈,為了簡潔叮贩,把代碼簡化如下:
typedef void(^PDBlock)(void);
int main(int argc, char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;
PDBlock block = ^{
NSLog(@"%ld", (long)person.age);
};
}
return 0;
}
直接看_mian_block_imol_0這個結(jié)構(gòu):
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
這個結(jié)構(gòu)中多了一個成員變量Person *person
,因為person是自動變量佛析,所以這里捕獲了自動變量person作為_main_block_impl_0結(jié)構(gòu)體的成員變量益老。而且還要注意的是,由于是自動變量寸莫,所以在block外面捺萌,自動變量是什么類型,在結(jié)構(gòu)體里面作為成員變量就是什么類型膘茎。person在結(jié)構(gòu)體外面作為自動變量是指針類型桃纯,所以作為結(jié)構(gòu)體里面的成員變量也是指針類型。
結(jié)構(gòu)體里面有一個成員變量是person指針披坏,這個person指針指向在外面創(chuàng)建的那個person對象态坦。所以出了大括號以后,外面的person指針被銷毀了棒拂,但是block內(nèi)有一個person指針還指向原來的person對象驮配,所以原來的person對象沒有銷毀。
下面我們在MRC下運行一下代碼着茸,對代碼進行一下小修改:
int main(int argc, char * argv[]) {
@autoreleasepool {
PDBlock block;
{
Person *person = [[Person alloc] init];
person.age = 10;
block = ^{
NSLog(@"%ld", (long)person.age);
};
[person release];
}
}
return 0;
}
還是在這個大括號外面打斷點壮锻,我們驚奇的發(fā)現(xiàn)代碼運行到斷點處時已經(jīng)打印了--------dealloc
,這個時候唯一的不同就是在MRC環(huán)境下block是在椾汤空間猜绣,那這是不是就說明在棧空間的block對person對象沒有像堆空間的block那樣的強引用效果呢敬特?我們把這個block進行copy操作查看一下結(jié)果掰邢。
int main(int argc, char * argv[]) {
@autoreleasepool {
PDBlock block;
{
Person *person = [[Person alloc] init];
person.age = 10;
block = [^{
NSLog(@"%ld", (long)person.age);
} copy];
[person release];
}
}
return 0;
}
這個時候我們發(fā)現(xiàn)在大括號外的斷點處牺陶,并沒有打印--------dealloc
,這說明這個時候person對象還沒有被釋放辣之,也就是說此時堆區(qū)的block對person對象是有強引用作用的掰伸,棧空間的block對person對象沒有強引用作用怀估。
我們再切換回ARC狮鸭,執(zhí)行下面代碼:
int main(int argc, char * argv[]) {
@autoreleasepool {
PDBlock block;
{
Person *person = [[Person alloc] init];
person.age = 10;
__weak Person *weakPerson = person;
block = ^{
NSLog(@"%ld", (long)weakPerson.age);
};
}
}
return 0;
}
還是在大括號外面打斷點,我們看到在斷點處就已經(jīng)打印了--------dealloc
多搀,這說明在這里person對象就已經(jīng)被銷毀了歧蕉。我們輸入clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.00 main.m
將其轉(zhuǎn)化為c++的代碼查看一下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__weak weakPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
查看_main_block_impl_0,我們發(fā)現(xiàn)康铭,這里面多了一個成員變量Person *__weak weakPerson
惯退,可以看到weakPerson這個成員變量帶有weak屬性嗎,這和外面的自動變量weakPerson保持一致从藤。由于這里成員變量是weak屬性催跪,所以對person對象是弱引用,也就不能改變person對象的生命周期夷野。
總結(jié) 當block內(nèi)部訪問了對象類型的auto變量時
如果block是在棧上懊蒸,將不會對auto變量產(chǎn)生強引用
如果block被拷貝到堆上
- 會調(diào)用block內(nèi)部的copy函數(shù)
- copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
- _Block_object_assign函數(shù)會根據(jù)auto變量的修飾符(__strong,_weak)做出相應的操作,形成強引用扫责,弱引用榛鼎。
如果block從堆上移除
- 會調(diào)用block內(nèi)部的dispose函數(shù)
- dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
_Block_object_dispose函數(shù)會自動釋放引用的auto變量,類似于release鳖孤。
2.__block修飾符
有時候我們想要在block內(nèi)部修改自動變量的值者娱,就像下面這樣:
PDBlock block;
int age = 10;
block = ^{
age = 20;
NSLog(@"%d", age);
};
但是很快我們就會發(fā)現(xiàn)這樣寫報了編譯錯誤,不能在block內(nèi)修改自動變量的值苏揣。其實從c++的源碼上我們也能明白黄鳍,age是在main函數(shù)中聲明的自動變量,而block內(nèi)的代碼塊是_main_block_func_0這個函數(shù)平匈,這兩個函數(shù)互不相干框沟,所以在_main_block_func_0函數(shù)內(nèi)是不能修改在main函數(shù)中定義的自動變量的。換個角度思考這個問題增炭,由于自動變量被block捕獲是值傳遞忍燥,所以block只是捕獲了age的值10,并把它賦值給自己的成員變量隙姿,使得結(jié)構(gòu)體中有一個成員變量的值是10梅垄,要用的時候就把這個值讀出來,能夠修改結(jié)構(gòu)體的成員變量的值输玷,使之變成20队丝,但是這并不會改變外面自動變量的值靡馁。
那么如果我們把局部變量改成用static修飾會怎么樣呢?
PDBlock block;
static int age = 10;
block = ^{
age = 20;
NSLog(@"%d", age);
};
block();
編譯沒有問題机久,打印結(jié)果:
20
得到了我們所期望的結(jié)果臭墨。
那么為什么使用static修飾的局部變量就可以在block內(nèi)修改值呢?
因為用static修飾的局部變量膘盖,被block捕獲時是指針傳遞胧弛,也就是把存放這個值的地址給傳進去了,在結(jié)構(gòu)體內(nèi)會有一個指針類型的成員變量存放傳經(jīng)來的這個地址衔憨,如果要修改外面的局部變量的值叶圃,那么就可以通過地址值去改變其值袄膏。
但是使用static修飾符會改變變量的生命周期践图,使之一直存在內(nèi)存中,有時候這并不是我們想要的沉馆,那么有沒有其它辦法使自動變量的值能夠在block內(nèi)被修改呢码党?這個時候就要用到__block修飾符了。
PDBlock block;
__block int age = 10;
block = ^{
age = 20;
NSLog(@"%d", age);
};
block();
執(zhí)行代碼斥黑,輸出結(jié)果是20揖盘。那么__block修飾符的本質(zhì)原理是什么呢?我們還是從c++的源碼來尋找結(jié)果:
main函數(shù):
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
PDBlock block;
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
化簡一下:
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
PDBlock block;
__Block_byref_age_0 age = {0,
&age,
0,
sizeof(__Block_byref_age_0),
10};
block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &age, 570425344));
block)->FuncPtr(block);
}
return 0;
}
我們可以看到锌奴,__block int age = 10
這句賦值語句被轉(zhuǎn)換成了:
__Block_byref_age_0 age = {0,
&age,
0,
sizeof(__Block_byref_age_0),
10};
把age轉(zhuǎn)換成了一個結(jié)構(gòu)體兽狭,我們看一下_Block_byref_age_0這個結(jié)構(gòu)體的結(jié)構(gòu):
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
通過賦值我們可以知道,&age被賦值給了自己的成員變量_forwarding這個指針鹿蜀,_size是結(jié)構(gòu)體的大小箕慧,成員變量age存放的是自動變量的值。所以_forwarding這個指針是指向結(jié)構(gòu)體自身的一個指針茴恰。總結(jié)起來就是age這個結(jié)構(gòu)體的成員變量包括isa指針颠焦,指向自身的_forwarding指針,還有一個int類型的成員變量存放值往枣。
我們再看一下_main_blcok_impl_0的結(jié)構(gòu):
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
這里面有一個結(jié)構(gòu)體指針age伐庭,在其結(jié)構(gòu)體函數(shù)中是用&age來初始化的,也就是用前面創(chuàng)建的age結(jié)構(gòu)體的地址來初始化_main_blcok_impl_0的成員變量(age指針)分冈。
我們再看一下_main_blcok_func_0的結(jié)構(gòu):
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_74_wk04zv690mz36wn0g18r5nxm0000gn_T_main_db631a_mi_0, (age->__forwarding->age));
}
通過_cself->age訪問自己的成員變量來獲取結(jié)構(gòu)體指針圾另,然后age->_forwarding->age通過結(jié)構(gòu)體指針訪問成員變量來改變成員變量的值,最后在取值的時候也是通過age->_forwarding->age來取值雕沉。
3.__block內(nèi)存管理
我們已經(jīng)知道了在棧上的block不會對對象類型的局部變量產(chǎn)生引用集乔,但是堆上的block會對對象類型的局部變量產(chǎn)生引用。那么這個過程是怎么樣的呢蘑秽?我們看一段代碼:
PDBlock block;
NSObject *objc = [[NSObject alloc] init];
__weak NSObject *weakObjc = objc;
^{
NSLog(@"%@ %@", objc, weakObjc);
};
我們把它轉(zhuǎn)化為c++的源碼看看:
找到_main_block_desc_0這個結(jié)構(gòu)體:
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
可以看到饺著,這個結(jié)構(gòu)體對比之前有一些變化箫攀,以前成員變量只有reserved和Block_size這兩個,但是這里又增加了兩個函數(shù)作為成員變量幼衰,一個是copy靴跛,一個是dispose函數(shù)。并且我們從結(jié)構(gòu)體的構(gòu)造函數(shù)可以知道這個copy函數(shù)是由_main_block_copy_0這個函數(shù)初始化的渡嚣,dispose是由_main_block_dispose_0這個函數(shù)初始化的梢睛。當block從棧區(qū)復制到堆區(qū)的時候會調(diào)用這里的copy函數(shù),當block銷毀的時候會調(diào)用dispose函數(shù)识椰。我們往上查看一下這兩個函數(shù):
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->objc, (void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_assign((void*)&dst->weakObjc, (void*)src->weakObjc, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_dispose((void*)src->weakObjc, 3/*BLOCK_FIELD_IS_OBJECT*/);}
在_main_block_copy_0這個函數(shù)中主要是調(diào)用了_Block_object_assign這個函數(shù)绝葡,這個函數(shù)的作用就是對捕獲的對象類型的自動變量產(chǎn)生引用。如果外面的對象類型的自動變量是用weak修飾腹鹉,那么_Block_object_assign函數(shù)就會使block對這個對象產(chǎn)生弱引用藏畅,如果是strong類型的,那么就會產(chǎn)生強引用功咒。
在_main_block_dispose_0這個函數(shù)中愉阎,主要是調(diào)用了_Block_object_dispose這個函數(shù),釋放對對象的強引用或弱引用力奋。
上面是分析的對對象類型的auto變量的內(nèi)存管理問題榜旦,我們知道,__block修飾的自動變量在本質(zhì)上就轉(zhuǎn)化為了結(jié)構(gòu)體實例景殷,也就是一個OC對象溅呢,既然這樣,那么也必然存在內(nèi)存管理的問題猿挚。
在block還未復制到堆上時咐旧,由__block修飾的自動變量產(chǎn)生的結(jié)構(gòu)體實例也存在棧上,當block從棧上復制到堆上后亭饵,block結(jié)構(gòu)體中指向自動變量產(chǎn)生的結(jié)構(gòu)體實例的那個指針也隨之被復制到了堆區(qū)捍壤,這樣就產(chǎn)生了堆區(qū)的指針指向棧區(qū)的對象這個問題例证,這顯然是不行的困乒,所以就要將棧區(qū)的結(jié)構(gòu)體實例賦值到堆區(qū)斜友,同時,block對堆區(qū)的結(jié)構(gòu)體實例進行強引用八秃。
總結(jié)
當block在棧上時碱妆,并不會對__block變量產(chǎn)生強引用。
當block被copy到堆上時
- 會調(diào)用block內(nèi)部的copy函數(shù)
- copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
- _Block_object_assign函數(shù)會對__block變量形成強引用
當block從堆中移除時 - 會調(diào)用block內(nèi)部的dispose函數(shù)
- dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
- _Block_object_dispose函數(shù)會自動釋放引用的__block變量
4._forwarding指針的作用
在2中我們了解到昔驱,讀取和改變age的值都是通過age->_forwarding->age這樣的疹尾,也就是先通過_main_block_impl_0這個結(jié)構(gòu)體獲取其成員變量age指針-指向結(jié)構(gòu)體的指針,然后再通過age指針獲取結(jié)構(gòu)體的成員變量_forwarding指針,這個指針是指向結(jié)構(gòu)體自身的纳本,然后再通過這個指向自身的結(jié)構(gòu)體指針訪問自己的成員變量age窍蓝,這個age成員變量里面真正存放著值。那么問題來了繁成,這個_forwarding指針到底有什么用呢吓笙?
我們思考一個問題,當block還在棧區(qū)時巾腕,這時候包裝age的結(jié)構(gòu)體實例也是分配在棧區(qū)的面睛,所以block這個結(jié)構(gòu)體內(nèi)部的age指針是指向棧區(qū)的結(jié)構(gòu)體實例的,這時候通過age結(jié)構(gòu)體指針直接訪問結(jié)構(gòu)體內(nèi)部的age成員變量值和通過age->_forwarding->age來訪問尊搬,結(jié)果都是一樣的叁鉴。
但是,當block從棧區(qū)復制到堆區(qū)時佛寿,封裝age的結(jié)構(gòu)體也會復制一份到堆區(qū)幌墓,這個時候其實block結(jié)構(gòu)體內(nèi)部age指針還是指向棧區(qū)的age結(jié)構(gòu)體,那這樣的話無論如何都無法取得堆區(qū)的age結(jié)構(gòu)體狗准。Apple顯然想到了這個問題克锣,在age結(jié)構(gòu)體從棧區(qū)復制到堆區(qū)的過程中茵肃,內(nèi)部將棧區(qū)的age結(jié)構(gòu)體的_forwarding指針修改了腔长,改為指向堆區(qū)的age結(jié)構(gòu)體。這樣一來验残,我們再通過block結(jié)構(gòu)體訪問age指針時捞附,訪問到了棧區(qū)的age結(jié)構(gòu)體,然后通過棧區(qū)的age結(jié)構(gòu)體的_forwarding指針去訪問堆區(qū)的age結(jié)構(gòu)體您没,也就是age->_forwarding->age這樣一個過程鸟召。
5.__block修飾對象
前面分析了__block修飾基本類型,現(xiàn)在來分析一下__block修飾對象類型氨鹏。我們看一段代碼:
PDBlock block;
__block Person *person = [[Person alloc] init];
block = ^{
NSLog(@"%@", person);
};
這段代碼是用__block修飾對象類型的簡單的例子欧募,我們看一下轉(zhuǎn)化的源碼:
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
PDBlock block;
__attribute__((__blocks__(byref))) __Block_byref_person_0 person = {
(void*)0,
(__Block_byref_person_0 *)&person,
33554432,
sizeof(__Block_byref_person_0),
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"))};
block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344));
}
return 0;
}
我們可以看,這里對象類型的自動變量person也是被封裝為了一個結(jié)構(gòu)體仆抵,查看一下_Block_byref_person_0這個結(jié)構(gòu)體的結(jié)構(gòu):
struct __Block_byref_person_0 {
void *__isa;
__Block_byref_person_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);//從棧上復制到堆上要進行的操作
void (*__Block_byref_id_object_dispose)(void*);//從棧上復制到堆上要進行的操作
Person *person;
};
這里和之前的結(jié)構(gòu)不同的是最后一個成員變量是一個指針跟继,這個指針是指向外面用alloc創(chuàng)建的person對象。同樣镣丑,在block結(jié)構(gòu)體中也有一個結(jié)構(gòu)體指針舔糖,這個指針是指向_Block_byref_person_0這個結(jié)構(gòu)體的,總結(jié)一下就是:如此一來莺匠,當block從棧區(qū)復制到堆區(qū)的時候金吗,會調(diào)用_main_block_copy_0來實現(xiàn)第一個箭頭的引用,那么第二個箭頭的引用是怎么實現(xiàn)的呢?我們發(fā)現(xiàn)摇庙,在_Block_byref_person_0中多了兩個函數(shù)旱物,通過其初始化可以知道這兩個函數(shù)分別是__Block_byref_id_object_copy_131
和__Block_byref_id_object_dispose_131
這兩個函數(shù),這兩個函數(shù)就是在block從棧區(qū)復制到堆區(qū)時卫袒,實現(xiàn)person指針對person對象的引用异袄,也就是圖中第二個箭頭。我們搜索一下這兩個函數(shù):
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_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
這兩個函數(shù)其實和_main_block_copy_0和_main_block_dispose_0一樣玛臂,最終都是調(diào)用_Block_object_assign和_Block_object_dispose這兩個函數(shù)烤蜕。那么這里為什么都加上了40呢?我們分析一下_Block_byref_person_0的結(jié)構(gòu):
struct __Block_byref_person_0 {
void *__isa; //指針迹冤,8字節(jié)
__Block_byref_person_0 *__forwarding; //指針讽营,8字節(jié)
int __flags; //int型,4字節(jié)
int __size; //int型泡徙,4字節(jié)
void (*__Block_byref_id_object_copy)(void*, void*);//指針型橱鹏,8字節(jié)
void (*__Block_byref_id_object_dispose)(void*);//指針型,8字節(jié)
Person *person;
};
這樣一來堪藐,_Block_byref_person_0的地址和person指針的地址就相差40字節(jié)莉兰,所以+40的目的就是找到person指針。
如果我們對__block修飾的對象類型用__weak類型來修飾會怎樣呢礁竞?
PDBlock block;
Person *person0 = [[Person alloc] init];
__block __weak Person *person = person0;
block = ^{
NSLog(@"%@", person);
};
我們從c++的源碼查找一下不同之處:
struct __Block_byref_person_0 {
void *__isa;
__Block_byref_person_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *__weak person;
};
發(fā)現(xiàn)不同之處只體現(xiàn)在_Block_byref_person_0這個結(jié)構(gòu)體上糖荒,這個結(jié)構(gòu)體的person指針變成了使用__weak修飾了,也就是person指針對person對象產(chǎn)生弱引用模捂,即上圖中第二條線是虛線捶朵,弱引用。
要驗證person指針對person對象是強引用還是若引用非常簡單
int main(int argc, char * argv[]) {
@autoreleasepool {
PDBlock block;
{
Person *person0 = [[Person alloc] init];
__block __weak Person *person = person0;
block = ^{
NSLog(@"%@", person);
};
}
//在這里打斷點
}
return 0;
}
我們在代碼中標記的位置打斷點狂男,發(fā)現(xiàn)執(zhí)行在斷點時已經(jīng)打印了--------dealloc
综看,說明person對象出了大括號就被釋放了,所以_Block_byref_person_0結(jié)構(gòu)體中的person指針對person對象是弱引用岖食。
如果代碼是這樣:
int main(int argc, char * argv[]) {
@autoreleasepool {
PDBlock block;
{
Person *person = [[Person alloc] init];
block = ^{
NSLog(@"%@", person);
};
}
//在這里打斷點
}
return 0;
}
那么代碼執(zhí)行到斷點處是還沒有打印--------dealloc
红碑,說明此時person對象還沒有被釋放,也即person指針對person對象有強引用的作用泡垃。
6.Block中循環(huán)引用的問題
首先看一個循環(huán)引用的例子:
//Person.h
typedef void(^PDBlock)(void);
@interface Person : NSObject
@property (nonatomic, copy)PDBlock mblock;
@property (nonatomic, assign)NSInteger age;
@end
//main.m
int main(int argc, char * argv[]) {
@autoreleasepool {
{
Person *person = [[Person alloc] init];
person.mblock = ^{
NSLog(@"%@", person);
};
}
//在這里打斷點
}
return 0;
}
這段代碼寫完析珊,編譯器會提示會出現(xiàn)循環(huán)引用,那么我們一起來分析一下它們之間的相互引用問題兔毙。
首先分析這個block:
^{
NSLog(@"%@", person);
};
在這個block中唾琼,由于使用了對象類型的auto變量,所以block結(jié)構(gòu)體中有一個成員變量person指針澎剥,這個person指針指向的是在外面利用alloc創(chuàng)建的person對象锡溯,并且有強引用赶舆,用圖表示就是這樣:接下來我們再來分析一下person對象,這個person對象本質(zhì)也是一個結(jié)構(gòu)體祭饭,這個結(jié)構(gòu)體中就有成員變量mblock芜茵,mblock又對上面的block是強引用,同時外面創(chuàng)建的person指針也會強引用alloc出來的person對象倡蝙,所以總的引用關系圖就是這樣的:
當代碼運行到代碼中的斷點處時九串,并沒有打印--------dealloc
,這說明這個時候person對象還沒有被釋放寺鸥,我們希望的是person對象出了大括號也就是作用域就被釋放猪钮,不然就是內(nèi)存泄漏了,那么這里為什么會內(nèi)存泄漏呢胆建?上圖中烤低,當出了大括號時,圖中的紅色箭頭就沒有了笆载,紅色箭頭沒有之后就變成了block強引用person對象扑馁,person對象強引用block,形成循環(huán)引用凉驻,就這樣person對象的內(nèi)存就一直不能被釋放腻要。
循環(huán)引用的解決辦法
要解決循環(huán)引用這個問題,很明確就要從上面的綠箭頭和黑箭頭上想辦法涝登,如果把其中之一變成虛線雄家,也即是弱引用,那循環(huán)引用就解決了缀拭。
那這個時候一個合適的方法就是把黑色的箭頭變成虛線咳短。
__weak,__unsafe_unretained
使用__weak或者__unsafe_unretained創(chuàng)建weakPerson對象,這樣block結(jié)構(gòu)體內(nèi)的成員變量person指針就是一個弱指針蛛淋,就能使黑色箭頭變成虛線:
int main(int argc, char * argv[]) {
@autoreleasepool {
{
Person *person = [[Person alloc] init];
__weak typeof(person) weakperson = person;
person.mblock = ^{
NSLog(@"%@", weakperson);
};
}
//在這里打斷點
}
return 0;
}
這樣代碼執(zhí)行到斷點處已經(jīng)打印了--------dealloc
,說明循環(huán)引用已經(jīng)解決了。
用__block解決(必須要調(diào)用block)
image.png
使用__block,篡腌,它們之間的相互引用是這樣的:
那么有人就好奇了褐荷,這不是一個更大的循環(huán)引用嗎?這確實是一個循環(huán)引用嘹悼,但是如果block執(zhí)行了叛甫,也即將auto類型的自動變量置為nil,那么這條藍線就消失了杨伙,就打破了循環(huán)引用其监。