一、block的語(yǔ)法
返回類型(^block名稱)(參數(shù)類型) = ^返回類型(變量類型 變量名稱){實(shí)現(xiàn)}
直接定義block時(shí)晃危,可以省略定義時(shí)的返回類型,即
返回類型(^block名稱)(參數(shù)類型) = ^(變量類型 變量名稱){實(shí)現(xiàn)}
若參數(shù)類型為void,可省略寫成
返回類型(^block名稱)(void) = ^{實(shí)現(xiàn)}
匿名block:block定義時(shí)孟抗,等號(hào)右邊的即為匿名block
1.2 typedef簡(jiǎn)化block的聲明
typedef 返回類型(^block名稱)(參數(shù)類型);
1.3 block是個(gè)對(duì)象
NSLog(@"%@",block);
輸出這個(gè)block疫诽,通過這個(gè)%@
我們可以看出block其實(shí)是個(gè)對(duì)象,通過輸出結(jié)果__NSGlobalBlock__
可以看出鳍鸵,該block存儲(chǔ)在全局區(qū)
二苇瓣、block的循環(huán)應(yīng)用
解決block循環(huán)引用的三種方法
1、__weak來解決
self.name = @"hello";
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);
});
};
block();
ps : 其實(shí)很多時(shí)候偿乖,__strong
會(huì)被省略掉击罪,這樣在一定情形下,會(huì)發(fā)生數(shù)據(jù)丟失贪薪。
比如外邓,我們進(jìn)入一個(gè)頁(yè)面,代碼執(zhí)行block古掏,接著在不過三秒的情況下损话,退出該頁(yè)面。如果不用__strong
來修飾,二級(jí)頁(yè)面銷毀后丧枪,其成員變量name
也跟著銷毀釋放了光涂,這樣在三秒后執(zhí)行的這個(gè)線程里,name
這個(gè)成員變量拧烦,通過getter
方法訪問忘闻,返回結(jié)果會(huì)是null
2、__block
self.name = @"hello";
__block ViewController *vc = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
vc = nil; // self -> block -> vc(nil) ->(斷開循環(huán)引用) block
});
};
block();
3恋博、通過block參數(shù)解決
self.name = @"hello";
self.block = ^(ViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
});
};
self.block(self);
三齐佳、分類
棧block
_NSConcreteStackBlock
:保存在棧中的 block,當(dāng)函數(shù)返回時(shí)會(huì)被銷毀债沮。堆block
_NSConcreteMallocBlock
保存在堆中的 block炼吴,當(dāng)引用計(jì)數(shù)為 0 時(shí)會(huì)被銷毀。全局的block
_NSConcreteGlobalBlock
:全局的靜態(tài) block疫衩,在block中不訪問外部局部變量硅蹦,可以訪問外部全局變量和靜態(tài)變量。此時(shí)為NSGlobalBlock闷煤。
三童芹、代碼+底層分析
- 通過clang命令查看編譯器是如何實(shí)現(xiàn)Block的,在終端輸入clang -rewrite-objc main.m鲤拿,然后會(huì)在當(dāng)前目錄生成main.cpp的C++文件假褪,但是,這一步可能會(huì)報(bào)錯(cuò)近顷,如下:
In file included from /Users/apple/Desktop/Block_test/Block_test/ViewController.m:8:
/Users/apple/Desktop/Block_test/Block_test/ViewController.h:8:9: fatal error:
'UIKit/UIKit.h' file not found
#import <UIKit/UIKit.h>
^~~~~~~~~~~~~~~
1 error generated.
- 沒關(guān)系嗜价,可以試試這個(gè)方法感覺還是挺管用的,cd到當(dāng)前的文件目錄下幕庐,然后將之前執(zhí)行的命令替換成為:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk XXX.m文件
成功:
1久锥、Demo1
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
void(^block)(void) = ^{
NSLog(@"hello world!");
};
block();
// Do any additional setup after loading the view.
}
@end
- 底層代碼的實(shí)現(xiàn):
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
// 構(gòu)造函數(shù)
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_db_80twdv6s18s_zk2z35wwh1cw0000gn_T_ViewController_8cc697_mi_0);
}
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
void(*block)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
為了方便我們閱讀代碼,我們先熟悉幾個(gè)規(guī)律:
①异剥、C++中 結(jié)構(gòu)體的名稱瑟由、方法體的名稱,一般可以看成是一層一層拼接的
②冤寿、xxx_0的意思歹苦,這個(gè)0 代表的是 第一個(gè),也就是首個(gè)
③督怜、C++的強(qiáng)轉(zhuǎn)換比較多殴瘦,可以對(duì)代碼進(jìn)行一定量的刪除操作。代碼分析:
通過底層我們可以看出号杠,block的C++實(shí)現(xiàn)蚪腋,是一個(gè)結(jié)構(gòu)體丰歌,有兩個(gè)成員變量(impl
、* Desc
)和一個(gè)構(gòu)造函數(shù)(對(duì)結(jié)構(gòu)體進(jìn)行初始化的函數(shù))屉凯。
接著我們看看他的兩個(gè)成員變量:
__block_impl
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
__block_impl
也是一個(gè)結(jié)構(gòu)體
*isa
:isa指針立帖,指向一個(gè)類對(duì)象,有三種類型:_NSConcreteStackBlock悠砚、_NSConcreteGlobalBlock晓勇、_NSConcreteMallocBlock。
Flags
:block 的負(fù)載信息(引用計(jì)數(shù)和類型信息)灌旧,按位存儲(chǔ)绑咱。
Reserved
:保留變量。
*FuncPtr
:一個(gè)指針枢泰,指向Block執(zhí)行時(shí)調(diào)用的函數(shù)描融,也就是Block需要執(zhí)行的代碼塊。在本例中是__ViewController__viewDidLoad_block_func_0
函數(shù)宗苍。
__ViewController__viewDidLoad_block_desc_0
static struct __ViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};
__ViewController__viewDidLoad_block_desc_0
是一個(gè)結(jié)構(gòu)體,包含兩個(gè)成員變量:
reserved:Block版本升級(jí)所需的預(yù)留區(qū)空間薄榛,在這里為0讳窟。
Block_size:Block大小(sizeof(struct __blockTest_block_impl_0))。
__ViewController__viewDidLoad_block_desc_0_DATA
是一個(gè)__ViewController__viewDidLoad_block_desc_0
的一個(gè)實(shí)例敞恋。
__ViewController__viewDidLoad_block_func_0
__ViewController__viewDidLoad_block_func_0
就是Block的執(zhí)行時(shí)調(diào)用的函數(shù)丽啡,參數(shù)是一個(gè)__ViewController__viewDidLoad_block_impl_0
類型的指針。
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_db_80twdv6s18s_zk2z35wwh1cw0000gn_T_ViewController_8cc697_mi_0);
}
ViewDidLoad方法
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
void(*block)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
代碼簡(jiǎn)化硬猫,并進(jìn)行一一對(duì)應(yīng)
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
// [super viewDidLoad];
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
// void(^block)(void) = ^{NSLog(@"hello world");};
void(*block)(void) = &__ViewController__viewDidLoad_block_impl_0
(
__ViewController__viewDidLoad_block_func_0,
&__ViewController__viewDidLoad_block_desc_0_DATA
);
// block();
block->FuncPtr(block);
}
- 第二行代碼:定義了Block补箍。
我們看到block變成了一個(gè)指針,指向一個(gè)通過__ViewController__viewDidLoad_block_impl_0
構(gòu)造函數(shù)實(shí)例化的結(jié)構(gòu)體__ViewController__viewDidLoad_block_impl_0
實(shí)例啸蜜,__ViewController__viewDidLoad_block_impl_0
在初始化的時(shí)候需要兩個(gè)個(gè)參數(shù):
__ViewController__viewDidLoad_block_func_0
:Block塊的函數(shù)指針坑雅。
__ViewController__viewDidLoad_block_desc_0_DATA
:作為靜態(tài)全局變量初始化__ViewController__viewDidLoad_block_desc_0
的結(jié)構(gòu)體實(shí)例指針
- 第三行代碼:調(diào)用了Block
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)
通過block->FuncPtr
指針找到__blockTest_block_func_0
函數(shù)并且轉(zhuǎn)成(void (*)(__block_impl *))
類型。
((__block_impl *)block)
然后將block作為參數(shù)傳給這個(gè)函數(shù)調(diào)用衬横。
簡(jiǎn)單點(diǎn)說就是裹粤,block調(diào)用xxxx_func_0方法,并將自己傳了過去蜂林。
補(bǔ)充:Flags遥诉,__block_impl
的參數(shù)的用途
在這里Block_private.h可以看到Flags
的具體信息:
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_NEEDS_FREE = (1 << 24), // runtime
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
引用淺談 block(1) - clang 改寫后的 block 結(jié)構(gòu)的解釋:
也就是說,一般情況下噪叙,一個(gè)
block
的 flags 成員默認(rèn)設(shè)置為 0矮锈。如果當(dāng)block
需要Block_copy()
和Block_release
這類拷貝輔助函數(shù),則會(huì)設(shè)置成1 << 25
睁蕾,也就是BLOCK_HAS_COPY_DISPOSE
類型苞笨。可以搜索到大量講述Block_copy
方法的博文,其中涉及到了BLOCK_HAS_COPY_DISPOSE
猫缭。
總結(jié)一下枚舉類的用法葱弟,前 16 位即起到標(biāo)記作用,又可記錄引用計(jì)數(shù):
- BLOCK_DEALLOCATING:釋放標(biāo)記猜丹。一般常用 BLOCK_NEEDS_FREE 做 位與 操作芝加,一同傳入 Flags ,告知該 block 可釋放射窒。
- BLOCK_REFCOUNT_MASK:一般參與判斷引用計(jì)數(shù)藏杖,是一個(gè)可選用參數(shù)。
- BLOCK_NEEDS_FREE:通過設(shè)置該枚舉位脉顿,來告知該 block 可釋放蝌麸。意在說明 block 是 heap block ,即我們常說的 _NSConcreteMallocBlock 艾疟。
- BLOCK_HAS_COPY_DISPOSE:是否擁有拷貝輔助函數(shù)(a copy helper function)来吩。
- BLOCK_HAS_CTOR:是否擁有 block 析構(gòu)函數(shù)(dispose function)。
- BLOCK_IS_GC:是否啟用 GC 機(jī)制(Garbage Collection)蔽莱。
- BLOCK_HAS_SIGNATURE:與 BLOCK_USE_STRET 相對(duì)弟疆,判斷是否當(dāng)前 block 擁有一個(gè)簽名。用于 runtime 時(shí)動(dòng)態(tài)調(diào)用盗冷。
Demo1總結(jié)
以上是一個(gè)最基本的block的底層實(shí)現(xiàn)怠苔。到這里其實(shí)我們才走了第一步。你想當(dāng)我們使用__block 仪糖、__weak柑司、block調(diào)取外部變量又是什么情況呢?底層又是怎樣實(shí)現(xiàn)的锅劝?變量是如何捕獲的攒驰?
Demo2 探究block截獲變量
截獲auto變量值
我們看到直接在block修改變量會(huì)提示錯(cuò)誤,為什么呢故爵?
void blockTest()
{
int num = 10;
void (^block)(void) = ^{
NSLog(@"%d",num);
};
num = 20;
block();
}
int main(int argc, char * argv[]) {
@autoreleasepool {
blockTest();
}
}
打印結(jié)果是10讼育,clang改寫后的代碼如下:
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_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_3c2714_mi_0,num);
}
void blockTest()
{
int num = 10;
void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__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
多了一個(gè)成員變量int num;
,再看看構(gòu)造函數(shù)__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0)
稠集,可以看到第三個(gè)參數(shù)只是變量的值奶段,這也就解釋了為什么打印的是10,因?yàn)閎lock截獲的是值剥纷。并未截獲指針痹籍。
使用static修飾變量
void blockTest()
{
static int num = 10;
void (^block)(void) = ^{
NSLog(@"%d",num);
num = 30;
};
num = 20;
block();
NSLog(@"%d",num);
}
可以在block內(nèi)部修改變量了,同時(shí)打印結(jié)果是20晦鞋,30蹲缠。clang改寫后的代碼如下:
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_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_5a95f6_mi_0,(*num));
(*num) = 30;
}
void blockTest()
{
static int num = 10;
void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, &num));
num = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_5a95f6_mi_1,num);
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
__blockTest_block_impl_0
多了一個(gè)成員變量int *num;
棺克,和上面不同的是,這次block截獲的是指針
线定,所以可以在內(nèi)部通過指針修改變量的值娜谊,同時(shí)在外部修改變量的值,block也能"感知到"斤讥。那么為什么之前不傳遞指針呢纱皆?因?yàn)樽兞渴菞I希饔糜蚴呛瘮?shù)blockTest內(nèi)芭商,那么有可能變量比block先銷毀派草,這時(shí)候block再通過指針去訪問變量就會(huì)有問題。而static修飾的變量不會(huì)被銷毀铛楣,也就不用擔(dān)心近迁。
全局變量
int num = 10;
void blockTest()
{
void (^block)(void) = ^{
NSLog(@"%d",num);
num = 30;
};
num = 20;
block();
NSLog(@"%d",num);
}
打印結(jié)果是20,30簸州。clang改寫后的代碼如下:
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_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_1875c6_mi_0,num);
num = 30;
}
非常簡(jiǎn)單鉴竭,在初始化__blockTest_block_impl_0
并沒有把num作為參數(shù),__blockTest_block_func_0
中也是直接訪問全局變量岸浑。
總結(jié):
變量類型 | 是否捕獲到block內(nèi)部 | 訪問方式 |
---|---|---|
局部auto變量 | 是 | 值傳遞 |
局部static變量 | 是 | 指針傳遞 |
全局變量 | 否 | 直接訪問 |
使用__block修飾變量
void blockTest()
{
__block int num = 10;
void (^block)(void) = ^{
NSLog(@"%d",num);
num = 30;
};
num = 20;
block();
NSLog(@"%d",num);
}
效果和使用static修飾變量一樣搏存,clang改寫后的代碼如下:
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_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_018b76_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((void *)__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_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_018b76_mi_1,(num.__forwarding->num));
}
哇,難受啊兄dei助琐,怎么多出來這么多東西祭埂,沒關(guān)系面氓,慢慢分析兵钮。
__blockTest_block_impl_0
多出來一個(gè)成員變量__Block_byref_num_0 *num;
,我們看到經(jīng)過__block
修飾的變量類型變成了結(jié)構(gòu)體__Block_byref_num_0
舌界,__blockTest_block_impl_0
多出來一個(gè)成員變量__Block_byref_num_0 *num;
掘譬,block捕獲的是__Block_byref_num_0
類型指針,
__Block_byref_num_0
我們看到__Block_byref_num_0
是一個(gè)結(jié)構(gòu)體呻拌,并且有一個(gè)isa
葱轩,因此我們可以知道它其實(shí)就是一個(gè)對(duì)象。同時(shí)還有一個(gè)__Block_byref_a_0 *
類型的__forwarding
和num
藐握,num
我們能猜到就是用來保存變量的值靴拱,__forwarding
就有一點(diǎn)復(fù)雜了,后面慢慢講猾普。
__blockTest_block_copy_0和__blockTest_block_dispose_0
__blockTest_block_copy_0
中調(diào)用的是_Block_object_assign
袜炕,__blockTest_block_dispose_0
中調(diào)用的是_Block_object_dispose
。
函數(shù) | 調(diào)用時(shí)機(jī) |
---|---|
__blockTest_block_copy_0 |
__block變量結(jié)構(gòu)體實(shí)例從棾跫遥拷貝到堆時(shí) |
__blockTest_block_dispose_0 |
__block變量結(jié)構(gòu)體實(shí)例引用計(jì)數(shù)為0時(shí) |
關(guān)于_Block_object_assign
和_Block_object_dispose
更詳細(xì)代碼可以在runtime.c 中查看偎窘。
// Runtime support functions used by compiler when generating copy/dispose helpers
// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
// see function implementation for a more complete description of these fields and combinations
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
-
BLOCK_FIELD_IS_OBJECT
:OC對(duì)象類型 -
BLOCK_FIELD_IS_BLOCK
:是一個(gè)block -
BLOCK_FIELD_IS_BYREF
:在棧上被__block修飾的變量 -
BLOCK_FIELD_IS_WEAK
:被__weak修飾的變量乌助,只在Block_byref管理內(nèi)部對(duì)象內(nèi)存時(shí)使用 -
BLOCK_BYREF_CALLER
:處理Block_byref內(nèi)部對(duì)象內(nèi)存的時(shí)候會(huì)加的一個(gè)額外標(biāo)記(告訴內(nèi)部實(shí)現(xiàn)不要進(jìn)行retain或者copy)
__blockTest_block_desc_0
我們可以看到它多了兩個(gè)回調(diào)函數(shù)指針*copy
和*dispose
,這兩個(gè)指針會(huì)被賦值為__main_block_copy_0
和__main_block_dispose_0
最后我們看到訪問num是這樣的:
__Block_byref_num_0 *num = __cself->num; // bound by ref
(num->__forwarding->num) = 30;
下面就講一講為什么要這樣陌知。
Block的內(nèi)存管理
在前面我們講到__block_impl
指向的_NSConcreteStackBlock
類型的類對(duì)象他托,其實(shí)總共有三種類型:
類型 | 存儲(chǔ)區(qū)域 |
---|---|
_NSConcreteStackBlock |
棧 |
_NSConcreteGlobalBlock |
數(shù)據(jù)區(qū) |
_NSConcreteMallocBlock |
堆 |
前面也講到copy
和dispose
,在ARC環(huán)境下仆葡,有哪些情況編譯器會(huì)自動(dòng)將棧上的把Block從棧上復(fù)制到堆上呢赏参?
Block從棧中復(fù)制到堆 |
---|
調(diào)用Block的copy實(shí)例方法時(shí) |
Block作為函數(shù)返回值返回時(shí) |
在帶有usingBlock的Cocoa方法或者GCD的API中傳遞Block時(shí)候 |
將block賦給帶有__strong修飾符的id類型或者Block類型時(shí) |
當(dāng)Bock
從棧中復(fù)制到堆,__block
也跟著變化:
當(dāng)Block
在棧上時(shí)浙芙,__block
的存儲(chǔ)域是棧登刺,__block
變量被棧上的Block
持有。
當(dāng)Block
被復(fù)制到堆上時(shí)嗡呼,會(huì)通過調(diào)用Block
內(nèi)部的copy
函數(shù)纸俭,copy
函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign
函數(shù)。此時(shí)__block
變量的存儲(chǔ)域是堆南窗,__
block變量被堆上的Block
持有揍很。
當(dāng)堆上的Block
被釋放,會(huì)調(diào)用Block
內(nèi)部的dispose
万伤,dispose
函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose
窒悔,堆上的__block
被釋放。
- 當(dāng)多個(gè)棧上的
Block
使用棧上的__block
變量敌买,__block
變量被棧上的多個(gè)Block
持有简珠。 - 當(dāng)
Block0
被復(fù)制到堆上時(shí),__block
也會(huì)被復(fù)制到堆上虹钮,被堆上Block0
持有聋庵。Block1
仍然持有棧上的__block
,原棧上__block
變量的__forwarding
指- 向拷貝到堆上之后的__block
變量芙粱。 - 當(dāng)
Block1
也被復(fù)制到堆上時(shí)祭玉,堆上的__block
被堆上的Block0
和Block1
只有,并且__block
的引用計(jì)數(shù)+1春畔。 - 當(dāng)堆上的
Block
都被釋放脱货,__block
變量結(jié)構(gòu)體實(shí)例引用計(jì)數(shù)為0,調(diào)用_Block_object_dispose
律姨,堆上的__block被釋放振峻。
下圖是描述__forwarding
變化。這也就能解釋__forwarding
存在的意義:
__forwarding 保證在棧上或者堆上都能正確訪問對(duì)應(yīng)變量
int main(int argc, char * argv[]) {
int num = 10;
NSLog(@"%@",[^{
NSLog(@"%d",num);
} class]);
void (^block)(void) = ^{
NSLog(@"%d",num);
};
NSLog(@"%@",[block class]);
}
打印結(jié)果:
2019-05-04 18:40:48.470228+0800 BlockTest[35824:16939613] __NSStackBlock__
2019-05-04 18:40:48.470912+0800 BlockTest[35824:16939613] __NSMallocBlock__
- 我們可以看到第一個(gè)
Block
沒有賦值給__strong
指針择份,而第二個(gè)Block
賦值給__strong
指針扣孟,所以第一個(gè)在棧上,而第二個(gè)在堆上缓淹。
Block截獲對(duì)象(對(duì)象類型的auto變量)
1哈打、定義一個(gè)簡(jiǎn)單的block:
MJBlock block;
{
MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
block = ^{
NSLog(@"---------%d", person.age);
};
}
NSLog(@"------");
- 第二個(gè)nslog打印完成之后塔逃,person不會(huì)銷毀,因?yàn)閎lock有個(gè)指針指向了外面的person對(duì)象料仗,block在堆上湾盗,是malloc類型的。block不銷毀立轧,person也不會(huì)銷毀格粪。但是如果改成mrc環(huán)境,棧上的block不會(huì)強(qiáng)引用auto對(duì)象氛改。但是如果person用__weak修飾的話帐萎,person就會(huì)先銷毀。
上面的block改成下面這樣:
MJBlock block;
{
MJPerson *person = [[MJPerson alloc] init];
person.age = 10;
__weak MJPerson *weakPerson = person;
block = ^{
NSLog(@"---------%d", weakPerson.age);
};
}
NSLog(@"------");
- 這種情況下用之前的
clang xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
轉(zhuǎn)變成底層c++代碼會(huì)報(bào)錯(cuò)cannot create __weak reference in file using manual reference
因?yàn)閣eak是弱引用是在runtime下進(jìn)行的胜卤,所以用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
轉(zhuǎn)換之后:
typedef void (*MJBlock)(void);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
MJPerson *__weak person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MJPerson *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
MJPerson *__strong person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c41e64_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
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};
之前的MJPerson * person;也轉(zhuǎn)換成了MJPerson *__weak person;
總結(jié)如下:
- 當(dāng)block內(nèi)部訪問了對(duì)象類型的auto變量時(shí)
- 1疆导、如果block是在棧上,不論是強(qiáng)指針還是弱指針都不會(huì)去對(duì)auto對(duì)象進(jìn)行強(qiáng)引用葛躏。
- 2澈段、如果block被拷貝到堆上。在arc環(huán)境下舰攒,當(dāng)一個(gè)block被強(qiáng)引用引用著败富,就會(huì)進(jìn)行copy操作,如果block進(jìn)行copy操作的時(shí)候會(huì)調(diào)用內(nèi)部的_Block_object_assign函數(shù) 摩窃,調(diào)用這個(gè)函數(shù)會(huì)根據(jù)外部的auto對(duì)象的修飾關(guān)鍵字MJPerson *__weak person或者M(jìn)JPerson *__strong person對(duì)auto進(jìn)行強(qiáng)引用或者弱引用
-
3兽叮、如果block從堆上移除。會(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)釋放引用的auto變量,類似于release操作匪蟀。
image.png
面試題:
MJPerson *p = [[MJPerson alloc] init];
__weak MJPerson *weakP = p;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1-------%@", p);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2-------%@", weakP);
});
});
NSLog(@"-----------");
經(jīng)過nslog1跟nslog2位置的變換椎麦,得出一個(gè)結(jié)論宰僧,person的釋放時(shí)間是看person強(qiáng)引用什么時(shí)候釋放材彪,不用管弱引用釋放
block在ARC和MRC中的聲明引用有些區(qū)別.
- block可以存儲(chǔ)在棧中,也可以在堆中
- 默認(rèn)存儲(chǔ)在棧中,不需要管理內(nèi)存
- 存儲(chǔ)在堆中的block會(huì)對(duì)block進(jìn)行retain操作
- (MRC)block在堆中時(shí),不想對(duì)block進(jìn)行retain操作,前面加__block
- (ARC)前面加__weak或__unsafe_unretained
- __weak和__unsafe_unretained的區(qū)別:__weak則在釋放時(shí)會(huì)對(duì)對(duì)象賦值nil,后者不會(huì)
- Block_copy使棧中的block轉(zhuǎn)移到堆中,并對(duì)block會(huì)引用的對(duì)象進(jìn)行retain操作
- 避免block引用的對(duì)象進(jìn)行retain操作,在引用對(duì)象聲明時(shí)前面加__block
ARC下獲取引用計(jì)數(shù)(retain count)
注意:以下方法只可用于debug,而且在多線程等情況下返回值不是100%可信琴儿。
1.使用KVC
[obj valueForKey:@"retainCount"]
2.使用私有API
OBJC_EXTERN int _objc_rootRetainCount(id);
_objc_rootRetainCount(obj)
3.使用CFGetRetainCount
CFGetRetainCount((__bridge CFTypeRef)(obj))
出處
鏈接:http://www.reibang.com/p/221d0778dcaa
鏈接:http://www.reibang.com/p/60c0bc161201
鏈接:https://blog.csdn.net/weixin_37547351/article/details/105106559
鏈接:https://blog.csdn.net/zhangwenhai001/article/details/46702271
鏈接:https://blog.csdn.net/iuyo89007/article/details/51761720/