上一篇文章iOS底層原理總結 - 探尋block的本質(一)中已經介紹過block的底層本質實現以及瞭解了變量的捕獲,本文繼續探尋block的本質。ios
block通常使用過程當中都是對對象變量的捕獲,那麼對象變量的捕獲同基本數據類型變量相同嗎?c++
查看一下代碼思考:當在block中訪問的爲對象類型時,對象何時會銷燬?面試
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
block = ^{
NSLog(@"------block內部%d",person.age);
};
} // 執行完畢,person沒有被釋放
NSLog(@"--------");
} // person 釋放
return 0;
}
複製代碼
大括號執行完畢以後,person
依然不會被釋放。上一篇文章提到過,person
爲aotu
變量,傳入的block
的變量一樣爲person
,即block
有一個強引用引用person
,因此block
不被銷燬的話,peroson
也不會銷燬。 查看源代碼確實如此安全
將上述代碼轉移到MRC環境下,在MRC環境下即便block還在,person
卻被釋放掉了。由於MRC環境下block在棧空間,棧空間對外面的person
不會進行強引用。bash
//MRC環境下代碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
block = ^{
NSLog(@"------block內部%d",person.age);
};
[person release];
} // person被釋放
NSLog(@"--------");
}
return 0;
}
複製代碼
block調用copy操做以後,person不會被釋放。app
block = [^{
NSLog(@"------block內部%d",person.age);
} copy];
複製代碼
上文中也提到過,只須要對棧空間的block
進行一次copy
操做,將棧空間的block
拷貝到堆中,person
就不會被釋放,說明堆空間的block
可能會對person
進行一次retain
操做,以保證person
不會被銷燬。堆空間的block
本身銷燬以後也會對持有的對象進行release
操做。iphone
也就是說棧空間上的block不會對對象強引用,堆空間的block有能力持有外部調用的對象,即對對象進行強引用或去除強引用的操做。函數
__weak添加以後,person
在做用域執行完畢以後就被銷燬了。post
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
Block block;
{
Person *person = [[Person alloc] init];
person.age = 10;
__weak Person *waekPerson = person;
block = ^{
NSLog(@"------block內部%d",waekPerson.age);
};
}
NSLog(@"--------");
}
return 0;
}
複製代碼
將代碼轉化爲c++來看一下上述代碼之間的差異。 __weak修飾變量,須要告知編譯器使用ARC環境及版本號不然會報錯,添加說明-fobjc-arc -fobjc-runtime=ios-8.0.0
學習
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
__weak修飾的變量,在生成的__main_block_impl_0
中也是使用__weak
修飾。
當block中捕獲對象類型的變量時,咱們發現block結構體__main_block_impl_0
的描述結構體__main_block_desc_0
中多了兩個參數copy
和dispose
函數,查看源碼:
copy
和dispose
函數中傳入的都是__main_block_impl_0
結構體自己。
copy
本質就是__main_block_copy_0
函數,__main_block_copy_0
函數內部調用_Block_object_assign
函數,_Block_object_assign
中傳入的是person對象的地址,person對象,以及8。
dispose
本質就是__main_block_dispose_0
函數,__main_block_dispose_0
函數內部調用_Block_object_dispose
函數,_Block_object_dispose
函數傳入的參數是person對象,以及8。
當block進行copy操做的時候就會自動調用__main_block_desc_0
內部的__main_block_copy_0
函數,__main_block_copy_0
函數內部會調用_Block_object_assign
函數。
_Block_object_assign
函數會自動根據__main_block_impl_0
結構體內部的person
是什麼類型的指針,對person
對象產生強引用或者弱引用。能夠理解爲_Block_object_assign
函數內部會對person
進行引用計數器的操做,若是__main_block_impl_0
結構體內person
指針是__strong
類型,則爲強引用,引用計數+1,若是__main_block_impl_0
結構體內person
指針是__weak
類型,則爲弱引用,引用計數不變。
當block從堆中移除時就會自動調用__main_block_desc_0
中的__main_block_dispose_0
函數,__main_block_dispose_0
函數內部會調用_Block_object_dispose
函數。
_Block_object_dispose
會對person
對象作釋放操做,相似於release
,也就是斷開對person
對象的引用,而person
到底是否被釋放仍是取決於person
對象本身的引用計數。
一旦block中捕獲的變量爲對象類型,block
結構體中的__main_block_desc_0
會出兩個參數copy
和dispose
。由於訪問的是個對象,block但願擁有這個對象,就須要對對象進行引用,也就是進行內存管理的操做。好比說對對象進行retarn操做,所以一旦block捕獲的變量是對象類型就會會自動生成copy
和dispose
來對內部引用的對象進行內存管理。
當block內部訪問了對象類型的auto變量時,若是block是在棧上,block內部不會對person產生強引用。不論block結構體內部的變量是__strong
修飾仍是__weak
修飾,都不會對變量產生強引用。
若是block被拷貝到堆上。copy
函數會調用_Block_object_assign
函數,根據auto變量的修飾符(__strong,__weak,unsafe_unretained)作出相應的操做,造成強引用或者弱引用
若是block從堆中移除,dispose
函數會調用_Block_object_dispose
函數,自動釋放引用的auto變量。
1. 下列代碼person在什麼時候銷燬 ?
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
Person *person = [[Person alloc] init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",person);
});
NSLog(@"touchBegin----------End");
}
複製代碼
打印內容
答:上文提到過ARC環境中,block做爲GCD API的方法參數時會自動進行copy
操做,所以block
在堆空間,而且使用強引用訪問person
對象,所以block
內部copy
函數會對person
進行強引用。當block
執行完畢須要被銷燬時,調用dispose
函數釋放對person
對象的引用,person
沒有強指針指向時纔會被銷燬。
2. 下列代碼person在什麼時候銷燬 ?
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
Person *person = [[Person alloc] init];
__weak Person *waekP = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",waekP);
});
NSLog(@"touchBegin----------End");
}
複製代碼
打印內容
答:block中對waekP
爲__weak
弱引用,所以block
內部copy
函數會對person
一樣進行弱引用,當大括號執行完畢時,person
對象沒有強指針引用就會被釋放。所以block
塊執行的時候打印null
。
3. 經過示例代碼進行總結。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
Person *person = [[Person alloc] init];
__weak Person *waekP = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"weakP ----- %@",waekP);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"person ----- %@",person);
});
});
NSLog(@"touchBegin----------End");
}
複製代碼
打印內容
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
Person *person = [[Person alloc] init];
__weak Person *waekP = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"person ----- %@",person);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"weakP ----- %@",waekP);
});
});
NSLog(@"touchBegin----------End");
}
複製代碼
打印內容
本部分分析基於下面代碼。
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
Block block = ^ {
// age = 20; // 沒法修改
NSLog(@"%d",age);
};
block();
}
return 0;
}
複製代碼
默認狀況下block不能修改外部的局部變量。經過以前對源碼的分析能夠知道。
age
是在main
函數內部聲明的,說明age
的內存存在於main
函數的棧空間內部,可是block
內部的代碼在__main_block_func_0
函數內部。__main_block_func_0
函數內部沒法訪問age
變量的內存空間,兩個函數的棧空間不同,__main_block_func_0
內部拿到的age
是block
結構體內部的age
,所以沒法在__main_block_func_0
函數內部去修改main
函數內部的變量。
前文提到過static修飾的age
變量傳遞到block內部的是指針,在__main_block_func_0
函數內部就能夠拿到age
變量的內存地址,所以就能夠在block內部修改age的值。
__block用於解決block內部不能修改auto變量值的問題,__block不能修飾靜態變量(static) 和全局變量
__block int age = 10;
複製代碼
編譯器會將__block修飾的變量包裝成一個對象,查看其底層c++源碼。
上述源碼中能夠發現
首先被__block
修飾的age
變量聲明變爲名爲age
的__Block_byref_age_0
結構體,也就是說加上__block
修飾的話捕獲到的block
內的變量爲__Block_byref_age_0
類型的結構體。
經過下圖查看__Block_byref_age_0
結構體內存儲哪些元素。
__isa指針
:__Block_byref_age_0
中也有isa指針也就是說__Block_byref_age_0
本質也一個對象。
__forwarding
:__forwarding
是__Block_byref_age_0
結構體類型的,而且__forwarding
存儲的值爲(__Block_byref_age_0 *)&age
,即結構體本身的內存地址。
__flags
:0
__size
:sizeof(__Block_byref_age_0)
即__Block_byref_age_0
所佔用的內存空間。
age
:真正存儲變量的地方,這裏存儲局部變量10。
接着將__Block_byref_age_0
結構體age
存入__main_block_impl_0
結構體中,並賦值給__Block_byref_age_0 *age;
以後調用block
,首先取出__main_block_impl_0
中的age
,經過age結構體拿到__forwarding
指針,上面提到過__forwarding中
保存的就是__Block_byref_age_0
結構體自己,這裏也就是age(__Block_byref_age_0)
,在經過__forwarding
拿到結構體中的age(10)
變量並修改其值。
後續NSLog中使用age
時也經過一樣的方式獲取age
的值。
__forwarding
是指向本身的指針。這樣的作法是爲了方便內存管理,以後內存管理章節會詳細解釋。
到此爲止,__block
爲何能修改變量的值已經很清晰了。__block
將變量包裝成對象,而後在把age
封裝在結構體裏面,block內部存儲的變量爲結構體指針,也就能夠經過指針找到內存地址進而修改變量的值。
那麼若是變量自己就是對象類型呢?經過如下代碼生成c++源碼查看
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Person *person = [[Person alloc] init];
NSLog(@"%@",person);
Block block = ^{
person = [[Person alloc] init];
NSLog(@"%@",person);
};
block();
}
return 0;
}
複製代碼
經過源碼查看,將對象包裝在一個新的結構體中。結構體內部會有一個person
對象,不同的地方是結構體內部添加了內存管理的兩個函數__Block_byref_id_object_copy
和__Block_byref_id_object_dispose
__Block_byref_id_object_copy
和__Block_byref_id_object_dispose
函數的調用時機及做用在__block內存管理部分詳細分析。
1. 如下代碼是否能夠正確執行
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableArray *array = [NSMutableArray array];
Block block = ^{
[array addObject: @"5"];
[array addObject: @"5"];
NSLog(@"%@",array);
};
block();
}
return 0;
}
複製代碼
答:能夠正確執行,由於在block塊中僅僅是使用了array的內存地址,往內存地址中添加內容,並無修改arry的內存地址,所以array不須要使用__block修飾也能夠正確編譯。
所以當僅僅是使用局部變量的內存地址,而不是修改的時候,儘可能不要添加__block,經過上述分析咱們知道一旦添加了__block修飾符,系統會自動建立相應的結構體,佔用沒必要要的內存空間。
2. 上面提到過__block
修飾的age
變量在編譯時會被封裝爲結構體,那麼當在外部使用age
變量的時候,使用的是__Block_byref_age_0
結構體呢?仍是__Block_byref_age_0
結構體內的age變量呢?
爲了驗證上述問題 一樣使用自定義結構體的方式來查看其內部結構
typedef void (^Block)(void);
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(void);
void (*dispose)(void);
};
struct __Block_byref_age_0 {
void *__isa;
struct __Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
struct __Block_byref_age_0 *age; // by ref
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
Block block = ^{
age = 20;
NSLog(@"age is %d",age);
};
block();
struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block;
NSLog(@"%p",&age);
}
return 0;
}
複製代碼
打印斷點查看結構體內部結構
經過查看blockImpl結構體其中的內容,找到age
結構體,其中重點觀察兩個元素:
__forwarding
其中存儲的地址確實是age結構體變量本身的地址age
中存儲這修改後的變量20。上面也提到過,在block
中使用或修改age
的時候都是經過結構體__Block_byref_age_0
找到__forwarding
在找到變量age
的。
另外apple爲了隱藏__Block_byref_age_0
結構體的實現,打印age變量的地址發現實際上是__Block_byref_age_0
結構體內age
變量的地址。
經過上圖的計算能夠發現打印age
的地址同__Block_byref_age_0
結構體內age
值的地址相同。也就是說外面使用的age,表明的就是結構體內的age值。因此直接拿來用的age
就是以前聲明的int age
。
上文提到當block中捕獲對象類型的變量時,block中的__main_block_desc_0
結構體內部會自動添加copy
和dispose
函數對捕獲的變量進行內存管理。
那麼一樣的當block內部捕獲__block
修飾的對象類型的變量時,__Block_byref_person_0
結構體內部也會自動添加__Block_byref_id_object_copy
和__Block_byref_id_object_dispose
對被__block
包裝成結構體的對象進行內存管理。
當block
內存在棧上時,並不會對__block
變量產生內存管理。當blcok
被copy
到堆上時 會調用block
內部的copy
函數,copy
函數內部會調用_Block_object_assign
函數,_Block_object_assign
函數會對__block
變量造成強引用(至關於retain)
首先經過一張圖看一下block複製到堆上時內存變化
當block
被copy
到堆上時,block
內部引用的__block
變量也會被複制到堆上,而且持有變量,若是block
複製到堆上的同時,__block
變量已經存在堆上了,則不會複製。
當block從堆中移除的話,就會調用dispose函數,也就是__main_block_dispose_0
函數,__main_block_dispose_0
函數內部會調用_Block_object_dispose
函數,會自動釋放引用的__block變量。
block內部決定何時將變量複製到堆中,何時對變量作引用計數的操做。
__block
修飾的變量在block結構體中一直都是強引用,而其餘類型的是由傳入的對象指針類型決定。
一段代碼更深刻的觀察一下。
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
int number = 20;
__block int age = 10;
NSObject *object = [[NSObject alloc] init];
__weak NSObject *weakObj = object;
Person *p = [[Person alloc] init];
__block Person *person = p;
__block __weak Person *weakPerson = p;
Block block = ^ {
NSLog(@"%d",number); // 局部變量
NSLog(@"%d",age); // __block修飾的局部變量
NSLog(@"%p",object); // 對象類型的局部變量
NSLog(@"%p",weakObj); // __weak修飾的對象類型的局部變量
NSLog(@"%p",person); // __block修飾的對象類型的局部變量
NSLog(@"%p",weakPerson); // __block,__weak修飾的對象類型的局部變量
};
block();
}
return 0;
}
複製代碼
將上述代碼轉化爲c++代碼查看不一樣變量之間的區別
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int number;
NSObject *__strong object;
NSObject *__weak weakObj;
__Block_byref_age_0 *age; // by ref
__Block_byref_person_1 *person; // by ref
__Block_byref_weakPerson_2 *weakPerson; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number, NSObject *__strong _object, NSObject *__weak _weakObj, __Block_byref_age_0 *_age, __Block_byref_person_1 *_person, __Block_byref_weakPerson_2 *_weakPerson, int flags=0) : number(_number), object(_object), weakObj(_weakObj), age(_age->__forwarding), person(_person->__forwarding), weakPerson(_weakPerson->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
上述__main_block_impl_0
結構體中看出,沒有使用__block
修飾的變量(object 和 weadObj)則根據他們自己被block捕獲的指針類型對他們進行強引用或弱引用,而一旦使用__block
修飾的變量,__main_block_impl_0
結構體內一概使用強指針引用生成的結構體。
接着咱們來看__block
修飾的變量生成的結構體有什麼不一樣
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __Block_byref_person_1 {
void *__isa;
__Block_byref_person_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *__strong person;
};
struct __Block_byref_weakPerson_2 {
void *__isa;
__Block_byref_weakPerson_2 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *__weak weakPerson;
};
複製代碼
如上面分析的那樣,__block
修飾對象類型的變量生成的結構體內部多了__Block_byref_id_object_copy
和__Block_byref_id_object_dispose
兩個函數,用來對對象類型的變量進行內存管理的操做。而結構體對對象的引用類型,則取決於block捕獲的對象類型的變量。weakPerson
是弱指針,因此__Block_byref_weakPerson_2
對weakPerson
就是弱引用,person
是強指針,因此__Block_byref_person_1
對person就是強引用。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->object, (void*)src->object, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_assign((void*)&dst->weakObj, (void*)src->weakObj, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 8/*BLOCK_FIELD_IS_BYREF*/);
}
複製代碼
__main_block_copy_0
函數中會根據變量是強弱指針及有沒有被__block
修飾作出不一樣的處理,強指針在block內部產生強引用,弱指針在block內部產生弱引用。被__block
修飾的變量最後的參數傳入的是8,沒有被__block
修飾的變量最後的參數傳入的是3。
當block從堆中移除時經過dispose函數來釋放他們。
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->object, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_dispose((void*)src->weakObj, 3/*BLOCK_FIELD_IS_OBJECT*/);
_Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->weakPerson, 8/*BLOCK_FIELD_IS_BYREF*/);
}
複製代碼
上面提到過__forwarding
指針指向的是結構體本身。當使用變量的時候,經過結構體找到__forwarding
指針,在經過__forwarding
指針找到相應的變量。這樣設計的目的是爲了方便內存管理。經過上面對__block
變量的內存管理分析咱們知道,block
被複制到堆上時,會將block
中引用的變量也複製到堆中。
咱們重回到源碼中。當在block中修改__block
修飾的變量時。
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_jm_dztwxsdn7bvbz__xj2vlp8980000gn_T_main_b05610_mi_0,(age->__forwarding->age));
}
複製代碼
經過源碼能夠知道,當修改__block
修飾的變量時,是根據變量生成的結構體這裏是__Block_byref_age_0
找到其中__forwarding
指針,__forwarding
指針指向的是結構體本身所以能夠找到age變量進行修改。
當block在棧中時,__Block_byref_age_0
結構體內的__forwarding
指針指向結構體本身。
而當block被複制到堆中時,棧中的__Block_byref_age_0
結構體也會被複制到堆中一份,而此時棧中的__Block_byref_age_0
結構體中的__forwarding
指針指向的就是堆中的__Block_byref_age_0
結構體,堆中__Block_byref_age_0
結構體內的__forwarding
指針依然指向本身。
此時當對age進行修改時
// 棧中的age
__Block_byref_age_0 *age = __cself->age; // bound by ref
// age->__forwarding獲取堆中的age結構體
// age->__forwarding->age 修改堆中age結構體的age變量
(age->__forwarding->age) = 20;
複製代碼
經過__forwarding
指針巧妙的將修改的變量賦值在堆中的__Block_byref_age_0
中。
咱們經過一張圖展現__forwarding
指針的做用
所以block內部拿到的變量實際就是在堆上的。當block進行copy被複制到堆上時,_Block_object_assign
函數內作的這一系列操做。
使用如下代碼,生成c++代碼查看內部實現
typedef void (^Block)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Person *person = [[Person alloc] init];
Block block = ^ {
NSLog(@"%p", person);
};
block();
}
return 0;
}
複製代碼
來到源碼查看__Block_byref_person_0
結構體及其聲明
__Block_byref_person_0結構體
typedef void (*Block)(void);
struct __Block_byref_person_0 {
void *__isa; // 8 內存空間
__Block_byref_person_0 *__forwarding; // 8
int __flags; // 4
int __size; // 4
void (*__Block_byref_id_object_copy)(void*, void*); // 8
void (*__Block_byref_id_object_dispose)(void*); // 8
Person *__strong person; // 8
};
// 8 + 8 + 4 + 4 + 8 + 8 + 8 = 48
複製代碼
// __Block_byref_person_0結構體聲明
__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 (*__Block_byref_id_object_copy)(void*, void*);
和void (*__Block_byref_id_object_dispose)(void*);
。這兩個函數爲__block
修飾的對象提供了內存管理的操做。
能夠看出爲void (*__Block_byref_id_object_copy)(void*, void*);
和void (*__Block_byref_id_object_dispose)(void*);
賦值的分別爲__Block_byref_id_object_copy_131
和__Block_byref_id_object_dispose_131
。找到這兩個函數
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);
}
複製代碼
上述源碼中能夠發現__Block_byref_id_object_copy_131
函數中一樣調用了_Block_object_assign
函數,而_Block_object_assign
函數內部拿到dst
指針即block
對象本身的地址值加上40個字節。而且_Block_object_assign
最後傳入的參數是131,同block直接對對象進行內存管理傳入的參數3,8都不一樣。能夠猜測_Block_object_assign
內部根據傳入的參數不一樣進行不一樣的操做的。
經過對上面__Block_byref_person_0
結構體佔用空間計算髮現__Block_byref_person_0
結構體佔用的空間爲48個字節。而加40剛好指向的就爲person
指針。
也就是說copy函數會將person地址傳入_Block_object_assign
函數,_Block_object_assign
中對Person對象進行強引用或者弱引用。
若是使用__weak修飾變量查看一下其中的源碼
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
__block __weak Person *weakPerson = person;
Block block = ^ {
NSLog(@"%p", weakPerson);
};
block();
}
return 0;
}
複製代碼
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_weakPerson_0 *weakPerson; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakPerson_0 *_weakPerson, int flags=0) : weakPerson(_weakPerson->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
__main_block_impl_0
中沒有任何變化,__main_block_impl_0
對weakPerson
依然是強引用,可是__Block_byref_weakPerson_0
中對weakPerson
變爲了__weak
指針。
struct __Block_byref_weakPerson_0 {
void *__isa;
__Block_byref_weakPerson_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *__weak weakPerson;
};
複製代碼
也就是說不管如何block
內部中對__block
修飾變量生成的結構體都是強引用,結構體內部對外部變量的引用取決於傳入block內部的變量是強引用仍是弱引用。
mrc環境下,儘管調用了copy操做,__block
結構體不會對person
產生強引用,依然是弱引用。
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Person *person = [[Person alloc] init];
Block block = [^ {
NSLog(@"%p", person);
} copy];
[person release];
block();
[block release];
}
return 0;
}
複製代碼
上述代碼person會先釋放
block的copy[50480:8737001] -[Person dealloc]
block的copy[50480:8737001] 0x100669a50
複製代碼
當block從堆中移除的時候。會調用dispose
函數,block塊中去除對__Block_byref_person_0 *person;
的引用,__Block_byref_person_0
結構體中也會調用dispose
操做去除對Person *person;
的引用。以保證結構體和結構體內部的對象能夠正常釋放。
循環引用致使內存泄漏。
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"%d",person.age);
};
}
NSLog(@"大括號結束啦");
return 0;
}
複製代碼
運行代碼打印內容
block的copy[55423:9158212] 大括號結束啦
複製代碼
能夠發現大括號結束以後,person
依然沒有被釋放,產生了循環引用。
經過一張圖看一下他們之間的內存結構
上圖中能夠發現,Person對象和block對象相互之間產生了強引用,致使雙方都不會被釋放,進而形成內存泄漏。
首先爲了能隨時執行block,咱們確定但願person
對block對強引用,而block內部對person
的引用爲弱引用最好。
使用__weak
和 __unsafe_unretained
修飾符能夠解決循環引用的問題
咱們上面也提到過__weak
會使block
內部將指針變爲弱指針。block
對person
對象爲弱指針的話,也就不會出現相互引用而致使不會被釋放了。
__weak
和 __unsafe_unretained
的區別。
__weak
不會產生強引用,指向的對象銷燬時,會自動將指針置爲nil。所以通常經過__weak
來解決問題。
__unsafe_unretained
不會產生前引用,不安全,指向的對象銷燬時,指針存儲的地址值不變。
使用__block
也能夠解決循環引用的問題。
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Person *person = [[Person alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"%d",person.age);
person = nil;
};
person.block();
}
NSLog(@"大括號結束啦");
return 0;
}
複製代碼
上述代碼之間的相互引用可使用下圖表示
上面咱們提到過,在block內部使用變量使用的實際上是__block
修飾的變量生成的結構體__Block_byref_person_0
內部的person
對象,那麼當person
對象置爲nil也就斷開告終構體對person的強引用,那麼三角的循環引用就自動斷開。該釋放的時候就會釋放了。可是有弊端,必須執行block,而且在block內部將person
對象置爲nil。也就是說在block執行以前代碼是由於循環引用致使內存泄漏的。
使用__unsafe_unretained
解決。在MRC環境下不支持使用__weak
,使用原理同ARC環境下相同,這裏不在贅述。
使用__block
也能解決循環引用的問題。由於上文__block
內存管理中提到過,MRC環境下,儘管調用了copy操做,__block
結構體不會對person產生強引用,依然是弱引用。所以一樣能夠解決循環引用的問題。
__strong
和 __weak
__weak typeof(self) weakSelf = self;
person.block = ^{
__strong typeof(weakSelf) myself = weakSelf;
NSLog(@"age is %d", myself->_age);
};
複製代碼
在block
內部從新使用__strong
修飾self
變量是爲了在block
內部有一個強指針指向weakSelf
避免在block
調用的時候weakSelf
已經被銷燬。
上文中提到的面試題,仔細研讀兩篇文章中均可以找到答案。這裏不在贅述。
文中若是有不對的地方歡迎指出。我是xx_cc,一隻長大好久但尚未二夠的傢伙。須要視頻一塊兒探討學習的coder能夠加我Q:2336684744