Block定義
閉包是一個函數(或指向函數的指針),再加上該函數執(zhí)行的外部的上下文變量(有時候也稱為自由變量)
block實際上就是OC對于閉包的實現。
block本質上也是一個OC對象枣氧,它內部也有個isa指針
block是封裝了函數調用以及函數調用環(huán)境的OC對象
Block結構分析
void blockTest()
{
void (^block)(void) = ^{
NSLog(@"Hello World!");
};
block();
}
int main(int argc, char * argv[]) {
@autoreleasepool {
blockTest();
}
}
編譯后得到??:
struct __block_impl {
void *isa;//isa指針,指向一個類對象懊缺,有三種類型:_NSConcreteStackBlock、_NSConcreteGlobalBlock某弦、_NSConcreteMallocBlock桐汤,可以看出這里使用的是_NSConcreteStackBlock
int Flags;//Block的負載信息(引用計數和類型信息),按位存儲
int Reserved;//保留變量
void *FuncPtr;//一個指針靶壮,指向Block執(zhí)行時的函數怔毛,也就是Block需要執(zhí)行的代碼塊,在這里指向的是__blockTest_block_func_0函數
};
//通常包含兩個成員變量:__block_impl腾降、__blockTest_block_desc_0拣度,和一個構造函數
struct __blockTest_block_impl_0 {
struct __block_impl impl;//
struct __blockTest_block_desc_0* Desc;
__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//__blockTest_block_func_0就是Block執(zhí)行調用的函數,參數是一個__blockTest_block_impl_0類型的指針
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_42229c_mi_0);
}
static struct __blockTest_block_desc_0 {
size_t reserved;//Block版本升級所需預留區(qū)空間,這里為0
size_t Block_size;//Block的大小抗果, sizeof(struct __blockTest_block_impl_0)
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};//是__blockTest_block_desc_0的一個實例
void blockTest()
{
/**
1.block的定義:通過__blockTest_block_impl_0結構體生成一個實例筋帖,并用一個指針指向了當前實例,
__blockTest_block_impl_0q在初始化時需要兩個參數:
__blockTest_block_func_0:Block塊的函數指針
__blockTest_block_desc_0_DATA:作為靜態(tài)全局變量初始化__blockTest_block_desc_0_DATA結構體的實例指針
*/
void (*block)(void) = (&__blockTest_block_impl_0(
__blockTest_block_func_0,
&__blockTest_block_desc_0_DATA)
);
/**
2.調用block:通過block)->FuncPtr找到__blockTest_block_func_0函數指針
然后將block作為參數傳遞給這個函數
*/
(block)->FuncPtr)(block);
}
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
blockTest();
}
}
- block的定義部分:
block是一個結構體冤馏,該結構體需要兩個參數
__blockTest_block_func_0
:Block塊的函數指針日麸;
__blockTest_block_desc_0_DATA
:作為靜態(tài)全局變量初始化__blockTest_block_desc_0_DATA結構體的實例指針;
通過__blockTest_block_impl_0結構體生成一個實例逮光,并用一個指針指向了當前實例代箭。 - block調用部分:
通過block)->FuncPtr找到__blockTest_block_func_0函數指針,并將step1 block指針傳遞給該函數涕刚,
__blockTest_block_func_0就是block執(zhí)行時調用的函數嗡综,接收的參數是__blockTest_block_impl_0類型的指針,step1生成的就是__blockTest_block_impl_0結構體的實例
Flags(Block_private.h)
enum {
BLOCK_DEALLOCATING = (0x0001), // 釋放標記杜漠。一般常用 BLOCK_NEEDS_FREE 做 位與 操作极景,一同傳入 Flags ,告知該 block 可釋放驾茴。
BLOCK_REFCOUNT_MASK = (0xfffe), // 一般參與判斷引用計數盼樟,是一個可選用參數。
BLOCK_NEEDS_FREE = (1 << 24), // 通過設置該枚舉位沟涨,來告知該 block 可釋放恤批。意在說明 block 是 heap block 异吻,即我們常說的 _NSConcreteMallocBlock 裹赴。
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // 是否擁有拷貝輔助函數(a copy helper function)。
BLOCK_HAS_CTOR = (1 << 26), // 是否擁有 block 析構函數(dispose function)诀浪。
BLOCK_IS_GC = (1 << 27), // 是否啟用 GC 機制(Garbage Collection)棋返。
BLOCK_IS_GLOBAL = (1 << 28), // compiler
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30) // 與 BLOCK_USE_STRET 相對,判斷是否當前 block 擁有一個簽名雷猪。用于 runtime 時動態(tài)調用睛竣。
};
Block結構如圖(網上借的):
Block捕獲變量
捕獲auto變量(局部變量)
先看下面這段代碼:
void blockTest()
{
int num = 10;
void (^block)(void) = ^{
NSLog(@"%d",num);
};
num = 20;
block();
}
int main(int argc, char * argv[]) {
@autoreleasepool {
blockTest();
}
}
num應該輸出什么?
答案:應該輸出10
編譯后的代碼??:
struct __blockTest_block_impl_0 {
struct __block_impl impl;
struct __blockTest_block_desc_0* Desc;
int num;
__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
void blockTest()
{
int num = 10;
void (*block)(void) = (&__blockTest_block_impl_0(
__blockTest_block_func_0,
&__blockTest_block_desc_0_DATA,
num)
);
num = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
__blockTest_block_impl_0
結構體多了一個成員變量num
求摇,
構造函數__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0)
可以看到第三個參數num只是變量的值射沟,這就解釋了為什么num打印的是10,因為block捕獲auto變量時与境,捕獲的是其值验夯。
捕獲static變量
void blockTest()
{
static int num = 10;
void (^block)(void) = ^{
NSLog(@"%d",num);
num = 30;
};
num = 20;
block();
NSLog(@"%d",num);
}
兩次num分別輸出什么?
答案:block塊內的num輸出20摔刁,第二個num輸出30
編譯后??:
struct __blockTest_block_impl_0 {
struct __block_impl impl;
struct __blockTest_block_desc_0* Desc;
int *num;
__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int *_num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
int *num = __cself->num; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_a383ea_mi_0,(*num));
(*num) = 30;
}
void blockTest()
{
static int num = 10;
void (*block)(void) = (&__blockTest_block_impl_0(
__blockTest_block_func_0,
&__blockTest_block_desc_0_DATA,
&num));
num = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_a383ea_mi_1,num);
}
構造函數__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int *_num, int flags=0)
第三個參數*num傳入的是num的指針挥转,所以可以在內部和外部修改變量的值。
為什么auto變量就是傳遞的值,而static變量傳遞的是指針呢绑谣?
auto變量保存在棧中党窜,并且會隨著當前作用域(blockTest)消失而銷毀,有可能銷毀時機會比block更早借宵,所以block內訪問銷毀的變量時會產生問題幌衣,而static變量保存在全局存儲區(qū)(靜態(tài)存儲區(qū)),不會出現這樣的問題壤玫。
全局變量
int num = 10;
void blockTest()
{
void (^block)(void) = ^{
NSLog(@"%d",num);
num = 30;
};
num = 20;
block();
NSLog(@"%d",num);
}
編譯后??:
int num = 10;
struct __blockTest_block_impl_0 {
struct __block_impl impl;
struct __blockTest_block_desc_0* Desc;
__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_141607_mi_0,num);
num = 30;
}
static struct __blockTest_block_desc_0 {
size_t reserved;
size_t Block_size;
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};
void blockTest()
{
void (*block)(void) = (&__blockTest_block_impl_0(
__blockTest_block_func_0,
&__blockTest_block_desc_0_DATA)
);
num = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_141607_mi_1,num);
}
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
blockTest();
}
}
可以看到這里構造函數并沒有傳入變量的值或者指針泼掠,因為全局變量是直接可以訪問的。
總結一下:
變量類型 | 是否捕獲到block內部 | 訪問方式 |
---|---|---|
auto變量 | 是 | 值訪問 |
static變量 | 是 | 指針訪問 |
全局變量 | 否 | 直接訪問 |
__block修飾的變量
1.在 block 內為什么不能修改 block 外部變量
答案:block 本質上是一個對象垦细,block 的花括號區(qū)域是對象內部的一個函數择镇,變量進入 花括號,實際就是已經進入了另一個函數區(qū)域---改變了作用域括改。在幾個作用域之間進行切換時腻豌,如果不加上這樣的限制,變量的可維護性將大大降低嘱能。又比如我想在block內聲明了一個與外部同名的變量吝梅,此時是允許呢還是不允許呢?只有加上了這樣的限制惹骂,這樣的情景才能實現苏携。詳解
2.除了使用static變量、全局變量外如何在block內改變變量的值对粪?為什么右冻?
答案:
使用__block;
static變量: block 內部對外部static修飾的變量進行指針捕獲著拭;
全局變量:block 內外可直接訪問全局變量纱扭;
__block變量:要想在block內部修改auto變量,需要兩個條件:
(1)從棧區(qū)拷貝到堆區(qū)(棧的內存是由系統管理儡遮,堆由我們管理乳蛾,其實在ARC下所有進入block內的auto變量都會被拷貝到堆區(qū)見這里)
(2)把auto變量包裝成結構體(對象),_block 作用是將 auto 變量封裝為結構體(對象)鄙币,在結構體內部新建一個同名 auto 變量肃叶,block 內截獲該結構體的指針,在 block 中使用自動變量時十嘿,使用指針指向的結構體中的自動變量因惭。于是就可以達到修改外部變量的作用。
總結一下就是如果想在block內修改變量:將 auto 從棧 copy 到堆详幽;將 auto 變量封裝為結構體(對象)
3.這三種修改變量值的方式哪個最好的筛欢?
這個問題請查看https://github.com/ChenYilong/iOSInterviewQuestions第38題浸锨,結論是__block是最優(yōu)解。
從源碼層面論證
void blockTest()
{
__block int num = 10;
void (^block)(void) = ^{
NSLog(@"%d",num);
num = 30;
};
num = 20;
block();
NSLog(@"%d",num);
}
編譯后??:
struct __Block_byref_num_0 {
void *__isa;
__Block_byref_num_0 *__forwarding;
int __flags;
int __size;
int num;
};
struct __blockTest_block_impl_0 {
struct __block_impl impl;
struct __blockTest_block_desc_0* Desc;
__Block_byref_num_0 *num; // by ref
__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
__Block_byref_num_0 *num = __cself->num; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_a71778_mi_0,(num->__forwarding->num));
(num->__forwarding->num) = 30;
}
static void __blockTest_block_copy_0(struct __blockTest_block_impl_0*dst, struct __blockTest_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __blockTest_block_dispose_0(struct __blockTest_block_impl_0*src) {_Block_object_dispose((void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __blockTest_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __blockTest_block_impl_0*, struct __blockTest_block_impl_0*);
void (*dispose)(struct __blockTest_block_impl_0*);
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0), __blockTest_block_copy_0, __blockTest_block_dispose_0};
void blockTest()
{
__attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 10};
void (*block)(void) = ((void (*)())&__blockTest_block_impl_0(
__blockTest_block_func_0,
&__blockTest_block_desc_0_DATA,
(__Block_byref_num_0 *)&num,
570425344));
(num.__forwarding->num) = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_a71778_mi_1,(num.__forwarding->num));
}
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
blockTest();
}
}
__block修飾的變量的拷貝
static void __blockTest_block_copy_0(struct __blockTest_block_impl_0*dst, struct __blockTest_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}
注意:__block變量的結構體__Block_byref_num_0內有一個__Block_byref_num_0類型的指針 __forwarding版姑,而且獲取__Block_byref_num_0結構體時候都會使用__forwarding獲取柱搜,至于原因會在后面講
該方法有一個dst(接收拷貝完成的對象)指針和一個src對象(被拷貝的對象),并調用了方法_Block_object_assign
方法剥险, _Block_object_assign
需要三個參數聪蘸,分別是:dst->num
、src->num
表制、和一個flags 8
健爬,前兩個參數就是__Block_byref_num_0
的對象,這里先看一下這個flags枚舉(Block_private.h):
// Runtime support functions used by compiler when generating copy/dispose helpers
enum {
// see function implementation for a more complete description of these fields and combinations
BLOCK_FIELD_IS_OBJECT = 3, //OC對象類型
BLOCK_FIELD_IS_BLOCK = 7, //一個block變量
BLOCK_FIELD_IS_BYREF = 8, // 在棧上被__block修飾的變量
BLOCK_FIELD_IS_WEAK = 16, // 被__weak修飾的變量么介,只在Block_byref管理內部對象內存時使用
BLOCK_BYREF_CALLER = 128, // 處理Block_byref內部對象內存的時候會加的一個額外標記(告訴內部實現不要進行retain或者copy)
};
這里使用的是BLOCK_FIELD_IS_BYREF
_Block_object_assign(__block對象的copy)
接著通過runtime源碼查看該方法的實現(只截取了關鍵部分):
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
*dest = _Block_byref_copy(object);
break;
調用_Block_byref_copy
方法把object(src)
傳入到該函數娜遵,并返回到一個新的對象賦值給dest
,dest
就是新得到從棧上拷貝到堆上的新值壤短。
_Block_object_dispose(__block對象的釋放)
// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// to help dispose of the contents
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// get rid of the __block data structure held in a Block
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:
_Block_release(object);
break;
case BLOCK_FIELD_IS_OBJECT:
_Block_release_object(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
當需要釋放堆上的auto變量對象時设拟,調用_Block_byref_release
釋放該對象
__block修飾的變量的包裝
被__block修飾的auto變量會被包裝成一個 __Block_byref_num_0
的結構體,同樣擁有isa久脯,因此也是一個對象纳胧;
Block的內存管理
__block修飾的變量什么時候會被從棧拷貝到堆帘撰?
看這個問題之前我們先看一下我們先了解下Block的內存管理跑慕,
Block一共有三種類型:
NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )
他們都繼承自NSBlock,NSBlock繼承自NSObject
三種類型對應的內存分配以及調用copy后的效果如下:
Block類型 | 副本源的配置存儲域 | copy效果 |
---|---|---|
NSGlobalBlock | 數據區(qū) | 什么也不做 |
NSStackBlock | 棧 | 從棧復制到堆 |
NSMallocBlock | 堆 | 引用計數增加 |
有以下情況會把BLock從棿菡遥拷貝到堆:
1.調用Block的copy實例方法時
2.Block作為函數返回值返回時
3.在帶有usingBlock的Cocoa方法或者GCD的API中傳遞Block時候
4.將block賦給帶有__strong修飾符的id類型或者Block類型時
下面通過驗證一下第4種情況看下是否準確(前三種可自行測試):
無__strong修飾符的id類型或者Block類型核行,代碼??:
int num = 10;
NSLog(@"%@",[^{
NSLog(@"%d",num);
} class]);
打印結果:
__NSStackBlock__
有__strong修飾符的id類型或者Block類型,代碼??:
void (^block)(void) = ^{
NSLog(@"%d",num);
};
NSLog(@"%@",[block class]);
打印結果:
__NSMallocBlock__
Block的拷貝時機介紹完了慰于,那么__block修飾的變量何時會從棧區(qū)拷貝到堆區(qū)呢钮科?
答案:當Block從椈缴溃拷貝到堆區(qū)的時候婆赠,__block變量也會跟著Block被拷貝到堆區(qū)。
驗證一下??佳励,
先看一下不拷貝到堆區(qū)的情況:
__block int num = 10;
NSLog(@"block前:%p",&num);
^{
num = 20;
NSLog(@"block內:%p",&num);
}();
NSLog(@"block后:%p",&num);
打印結果:
block前:0x7ffeea3f2c98
block內:0x7ffeea3f2c98
block后:0x7ffeea3f2c98
拷貝到堆區(qū)的情況:
NSLog(@"block前:%p",&num);
void (^block)(void) = ^{
num = 20;
NSLog(@"block內:%p",&num休里;
};
block();
NSLog(@"block后:%p",&num);
打印:
block前:0x7ffee4af3c98
block內:0x600003650738
block后:0x600003650738
從內存地址不難看出不拷貝堆區(qū)時赃承,__block變量也不會進行拷貝妙黍,當Block從棧區(qū)拷貝到堆區(qū),__block變量也會進行拷貝
下面這段代碼為什么MRC和ARC下打印不一樣
int num = 1;
void (^block)(void) = ^{
NSLog(@"%d",num);
};
NSLog(@"%@",[block class]);
MRC打忧破省:
__NSStackBlock__
ARC打邮眉蕖:
__NSMallocBlock__
原因:
由于在ARC環(huán)境下可免,使用strong修飾的變量指向block,會持有這個block做粤。因此臨時變量block會從棧復制到堆上
__forwarding指針存在的意義是什么浇借?
__forwarding指針是為了在__block變量從棧復制到堆上后,在Block外對__block變量的修改也可以同步到堆上實際存儲__block變量的結構體上怕品。
__forwarding確保不管是堆棧訪問__block變量結構體時都能訪問到同一個對象
Block捕獲對象
NSStackBlock
在棧上的Block不會對auto對象進行強引用妇垢;
NSMallocBlock
堆上的Block會對auto對象進行強引用,直到Block釋放時肉康,才解除對auto對象的強引用
typedef void(^Block)(void);
int main(int argc, char * argv[]) {
Block block;
{
Person *person = [[Person alloc] init];
person.name = @"toby";
block = ^{
NSLog(@"%@",person.name);
};
person.name = @"david";
NSLog(@"即將退出person作用域");
}
NSLog(@"已經退出person作用域");
block ();
}
打印結果:
即將退出person作用域
已經退出person作用域
david
-[Person dealloc]
Block的循環(huán)引用
什么情況下會造成循環(huán)引用闯估?
當一個對象person持有了了block對象,而block內又持有了person互相持有吼和,這就造成了循環(huán)引用
如何打破循環(huán)引用涨薪?
1.使用__block修飾對象person
2.使用__unsafe_unretained修飾對象person
3.使用__weak修飾對象person
__block
__block Person *person = [[Person alloc] init];
person.blockTest = ^{
person.name = @"toby";
person = nil;
};
person.blockTest();
需要在block內指定person=nil,并且需要調用調用block函數炫乓。
__unsafe_unretained
Person *person = [[Person alloc] init];
__unsafe_unretained typeof(person) weakPerson = person;
weakPerson.blockTest = ^{
weakPerson.name = @"toby";
};
使用__unsafe_unretained雖然能解除循環(huán)引用尤辱,但是不安全,當指向對象銷毀時厢岂,指針存儲地址不變光督,如果再次訪問可能會造成懸垂指針??:
__weak
Person *person = [[Person alloc] init];
__weak typeof(person) weakPerson = person;
weakPerson.blockTest = ^{
weakPerson.name = @"toby";
};
查看編譯后的源碼??:
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;
}
};
可以看到當前block內引用的是一個weak類型的person,對person的引用變成了弱引用塔粒,這就打破了雙向持有的局面结借,至于該weak person的釋放時機是由runtime維護的一個hash table決定的,當person對象dealloc時卒茬,會以person 地址當作鍵值在hash table中查找weak對象置為nil船老。runtime如何實現weak變量的自動置nil
但是實際應用會發(fā)現如果此時Block回調晚一些(異步線程執(zhí)行耗時任務),此時的person已經出作用域,在block內訪問weakPerson的時候就nil了圃酵。
Person *person = [[Person alloc] init];
__weak typeof(person) weakPerson = person;
person.blockTest = ^{
dispatch_async(dispatch_queue_create(0,DISPATCH_QUEUE_CONCURRENT), ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"person = %@",weakPerson);
});
};
person.blockTest();
此時的辦法就是在block內使用__strong再修飾一下weakPerson柳畔,讓person延遲釋放,至于釋放時機當然是block執(zhí)行完成
Person *person = [[Person alloc] init];
__weak typeof(person) weakPerson = person;
person.blockTest = ^{
__strong typeof(person) strongPerson = weakPerson;
dispatch_async(dispatch_queue_create(0,DISPATCH_QUEUE_CONCURRENT), ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"strong person = %@",strongPerson);
});
};
person.blockTest();
}
總結
1.Block是一個對象
2.Block捕獲變量:
(1)auto變量:捕獲的是值
(2)static變量:捕獲指針
(3)global變量:無需捕獲郭赐,直接訪問
3.Block捕獲__block修飾的auto變量時薪韩,會把該變量包裝成一個對象,并會根據Block是否會被拷貝到堆區(qū)對auto變量進行拷貝捌锭,修改auto變量時需要滿足兩個條件:
(1)將 auto 從棧 copy 到堆俘陷;
(2)將 auto 變量封裝為結構體(對象)
4.Block有三種類型,他們都繼承自NSBlock->NSObject
NSGlobalBlock ( _NSConcreteGlobalBlock ) 數據區(qū)域
NSStackBlock ( _NSConcreteStackBlock ) 棧區(qū)
NSMallocBlock ( _NSConcreteMallocBlock ) 堆區(qū)
5.Block會被從椆矍拷貝到堆的情況:
(1)調用Block的copy實例方法時
(2)Block作為函數返回值返回時
(3)在帶有usingBlock的Cocoa方法或者GCD的API中傳遞Block時候
(4)將block賦給帶有__strong修飾符的id類型或者Block類型時
6. __forwarding指針是為了在__block變量從棧復制到堆上后拉盾,在Block外對__block變量的修改也可以同步到堆上實際存儲__block變量的結構體上。
7.在棧上的Block不會對auto對象進行強引用豁状;堆上的Block會對auto對象進行強引用捉偏,直到Block釋放時倒得,才解除對auto對象的強引用
8.解除Block的循環(huán)引用,最安全的方法是使用__weak修飾auto變量夭禽,并在block內部對auto變量進行__strong修飾