本文是作者Lefe所創(chuàng)署辉,轉(zhuǎn)載請注明出處族铆,如果你在閱讀的時候發(fā)現(xiàn)問題歡迎一起討論。本文會不斷更新哭尝。
說明:
使用 Block 的時候哥攘,我們通常會有以下幾點疑問,我們帶著這種疑問來閱讀本文材鹦,本文難免會有遺漏或者錯誤逝淹,望讀者朋友們提出來。Lefe 在使用 Block 的時候主要遇到了以下問題:
- 難道只要使用了 self 就需要使用 __weak 來避免循環(huán)引用嗎桶唐?
- 為避免循環(huán)應(yīng)用栅葡,為什么使用了 __weak 還需要使用 __strong ?
- 為什么使用 __block 修改變量后就可以在 Block 內(nèi)部修改它的值?
- Block 什么時候釋放呢尤泽?它是如何進(jìn)行內(nèi)存管理的欣簇?
- Block 為什么要用 copy?
- 為什么有些 Block 即使捕獲了 self 也不會產(chǎn)生循環(huán)引用坯约?
- 自動變量(局部變量)如何被 Block 捕獲的熊咽,對象又是如何被 Block 捕獲的?
帶著這些問題闹丐,我們一塊來揭開 Block 的真實面目横殴,本文篇幅較長,可以分段閱讀卿拴,建議讀者耐心閱讀衫仑,很枯燥的,如果能動手實現(xiàn)以下巍棱,會有趣很多。在閱讀之前我們先了解下 Clang
Clang
本文主要用到了 Clang
蛋欣,那什么是 Clang
呢航徙?它是 Xcode 默認(rèn)的編譯器。更多關(guān)于Clang
可以參考 本文 陷虎。這里我們主要用 Clang 把 Block 的實現(xiàn)轉(zhuǎn)換成 C++ 到踏,其實和 C 差不多杠袱,除了構(gòu)造函數(shù)外昂利。
打開 shell森缠,進(jìn)入 Lefe 的測試項目中,輸入:
clang -rewrite-objc HelloLefe.m
瞧栗,這是會在當(dāng)前目錄下生產(chǎn)一個對應(yīng)的 HelloLefe.cpp 文件伴榔,打開它就對了纹蝴。截個圖看看,別光看美女踪少。
內(nèi)存分配
在閱讀下文前塘安,我們需要對內(nèi)存分配有一定的了解
- 棧: 在執(zhí)行函數(shù)時,函數(shù)內(nèi)局部變量的存儲單元都可以在棧上創(chuàng)建援奢,函數(shù)執(zhí)行結(jié)束時這些存儲單元自動被釋放兼犯。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高集漾,但是分配的內(nèi)存容量有限切黔。
- 堆: 就是那些由 new分配的內(nèi)存塊,他們的釋放編譯器不去管具篇,由我們的應(yīng)用程序去控制纬霞,一般一個new就要對應(yīng)一個 release。如果程序員沒有釋放掉栽连,那么在程序結(jié)束后险领,操作系統(tǒng)會自動回收。這部分內(nèi)存需要程序員手動釋放秒紧。當(dāng)然使用 ARC 后我們不需要處理绢陌。
- 全局/靜態(tài)存儲區(qū):全局變量和靜態(tài)變量被分配到同一塊內(nèi)存中,在以前的C語言中熔恢,全局變量又分為初始化的和未初始化的脐湾,在C++里面沒有這個區(qū)分了,他們共同占用同一塊內(nèi)存區(qū)叙淌。這部分?jǐn)?shù)據(jù)不需要程序員手動釋放秤掌,他會隨著程序的消失二釋放。
- 常量存儲區(qū): 這是一塊比較特殊的存儲區(qū)鹰霍,他們里面存放的是常量闻鉴,不允許修改。
關(guān)于內(nèi)存分配的閱讀 本文
Block 是如何實現(xiàn)的
掌握了 Clange
的基本使用茂洒,那我們就看看 Block 究竟做了什么孟岛。從一個簡單的例子開始。
Lefe 在 HelloLefe.m
文件中,寫了一個 Block渠羞,使用 clang -rewrite-objc HelloLefe.m
轉(zhuǎn)換斤贰,轉(zhuǎn)換后可以看到 Block 的具體實現(xiàn)。
- (void)lefeTestComplete
{
void (^complete)(void) = ^(void){
NSLog(@"Block\n");
};
complete();
}
@end
轉(zhuǎn)換后的代碼如下:
- 發(fā)現(xiàn)每個結(jié)構(gòu)體的生成都會是一個又長又臭的名字次询,它會使用類名
HelloLefe
和方法名lefeTestComplete
等生成一個結(jié)構(gòu)體荧恍,也就是 block 的實現(xiàn),它是一個很重要的結(jié)構(gòu)體屯吊。它主要包含了2個結(jié)構(gòu)體和一個構(gòu)造方法送巡。
struct __HelloLefe__lefeTestComplete_block_impl_0 {
struct __block_impl impl;
struct __HelloLefe__lefeTestComplete_block_desc_0* Desc;
// 構(gòu)造方法
__HelloLefe__lefeTestComplete_block_impl_0(void *fp, struct __HelloLefe__lefeTestComplete_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
-
__block_impl
結(jié)構(gòu)體,是__HelloLefe__lefeTestComplete_block_impl_0
結(jié)構(gòu)體的第一個變量
struct __block_impl {
void *isa; // isa 指針雌芽,block 其實也是一個 OC 對象授艰,每個類都有一個指向其實例的一個指針
int Flags;
int Reserved;
void *FuncPtr; // 相當(dāng)于 block 中要執(zhí)行的函數(shù)的指針
};
- 結(jié)構(gòu)體
__HelloLefe__lefeTestComplete_block_impl_0
的第二個變量
static struct __HelloLefe__lefeTestComplete_block_desc_0 {
size_t reserved;
size_t Block_size;
} __HelloLefe__lefeTestComplete_block_desc_0_DATA = { 0, sizeof(struct __HelloLefe__lefeTestComplete_block_impl_0)};
- 同樣,以類名和方法名生成一個函數(shù)世落,這個函數(shù)也就是
^(void){ NSLog(@"Block\n"); };
轉(zhuǎn)換后的結(jié)果淮腾,__cself 和 OC 中的 self 差不多一個意思,它就是__HelloLefe__lefeTestComplete_block_impl_0
屉佳,是指向結(jié)構(gòu)體__HelloLefe__lefeTestComplete_block_impl_0
的指針
static void __HelloLefe__lefeTestComplete_block_func_0(struct __HelloLefe__lefeTestComplete_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__cv2l59cn5x90wh88hrkp35x80000gp_T_HelloLefe_b006ae_mi_0);
}
- 主函數(shù)谷朝,編譯器編譯后會自動給每個方法添加兩個參數(shù)
self
和_cmd
static void _I_HelloLefe_lefeTestComplete(HelloLefe * self, SEL _cmd) {
// 這段代碼是對 void (^complete)(void) = ^(void){ NSLog(@"Block\n");}; 的轉(zhuǎn)換
void (*complete)(void) = ((void (*)())&__HelloLefe__lefeTestComplete_block_impl_0((void *)__HelloLefe__lefeTestComplete_block_func_0, &__HelloLefe__lefeTestComplete_block_desc_0_DATA));
// 這段代碼相當(dāng)于對 complete(); 的轉(zhuǎn)換,
((void (*)(__block_impl *))((__block_impl *)complete)->FuncPtr)((__block_impl *)complete);
}
從上面的轉(zhuǎn)換過程可以看出武花,聲明一個 block 首先調(diào)用結(jié)構(gòu)體 __HelloLefe__lefeTestComplete_block_impl_0
的構(gòu)造函數(shù)圆凰,得到一個 IMP,相當(dāng)于 OC 中的 IPM体箕,它保存了這個 block 所需要的信息专钉,當(dāng)調(diào)用 block 的時候,直接調(diào)用 IPM-> FuncPtr累铅。
到這里相信讀者還是對 Block 的實現(xiàn)很陌生跃须,很正常,堅持閱讀一會娃兽,試試看菇民。頭腦中試著把 Block 就當(dāng)做是一個 NSObject 對象。
Block 捕獲變量
記得剛接觸 Block 的時候投储,只是隱約聽到 Block 可以自動捕獲 Block 中使用的變量第练。是的,Block 可以捕獲它所用到的自動變量或?qū)ο舐贶瘢撬皇遣东@了它所用到的變量娇掏,其他用不到的變量它并不會捕獲,這里就是引起循環(huán)引用的一個重點勋眯,下文會詳細(xì)將到婴梧。對應(yīng)全局變量 Block 并不或去捕獲壁涎。
以上的 block 的實現(xiàn)多少有點眉目了,那么 block 是如何捕獲變量的志秃,我把將要轉(zhuǎn)換的代碼改為:
- (void)lefeTestComplete
{
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^complete)(void) = ^(void){
printf(fmt, val);
};
complete();
}
轉(zhuǎn)換后的代碼如下,觀察的實現(xiàn)發(fā)現(xiàn)多了
const char *fmt; int val;
這就是 block 捕獲的變量嚼酝,但我們發(fā)現(xiàn) dmy
這個變量并沒有捕獲浮还,因為在 block 中壓根就沒使用。結(jié)構(gòu)體的構(gòu)造方法也需要傳入捕獲的變量來構(gòu)造結(jié)構(gòu)體闽巩。
struct __HelloLefe__lefeTestComplete_block_impl_0 {
struct __block_impl impl;
struct __HelloLefe__lefeTestComplete_block_desc_0* Desc;
const char *fmt;
int val;
__HelloLefe__lefeTestComplete_block_impl_0(void *fp, struct __HelloLefe__lefeTestComplete_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __HelloLefe__lefeTestComplete_block_func_0(struct __HelloLefe__lefeTestComplete_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int val = __cself->val; // bound by copy
printf(fmt, val);
}
static struct __HelloLefe__lefeTestComplete_block_desc_0 {
size_t reserved;
size_t Block_size;
} __HelloLefe__lefeTestComplete_block_desc_0_DATA = { 0, sizeof(struct __HelloLefe__lefeTestComplete_block_impl_0)};
static void _I_HelloLefe_lefeTestComplete(HelloLefe * self, SEL _cmd) {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (*complete)(void) = ((void (*)())&__HelloLefe__lefeTestComplete_block_impl_0((void *)__HelloLefe__lefeTestComplete_block_func_0, &__HelloLefe__lefeTestComplete_block_desc_0_DATA, fmt, val));
((void (*)(__block_impl *))((__block_impl *)complete)->FuncPtr)((__block_impl *)complete);
}
修改 Block 中的捕獲的變量
上面的例子中钧舌,并不能在 Block 中修改所捕獲的變量,那么如何修改 Block 中所捕獲的變量呢涎跨?可以使用 __block洼冻。如果修改 Block 中的變量,編譯器會直接報錯隅很。比如:
- (void)leftTestBlock
{
int age = 0;
void (^block)(void) = ^{
age = 10;
};
}
這段代碼編譯器直接會報錯撞牢,可能有些同學(xué)會說直接用 __block,但是為什么使用 __block 就可以呢叔营?再看一下下面的代碼:
// 全局變量
int global_val = 1;
// 全局靜態(tài)變量
static int static_global_val = 2;
- (void)lefeTestComplete
{
// 靜態(tài)變量
static int static_val = 3;
void (^complete)(void) = ^{
global_val *= 1;
static_global_val *= 2;
static_val *= 3;
};
complete();
}
這段代碼是沒有任何問題的屋彪,它可以正常的編譯通過,它沒有使用 __block绒尊。詳細(xì)大部分的同學(xué)讀到這里都會有一個疑惑畜挥,這是為什么呢?我們不妨來看一下他的具體實現(xiàn)婴谱。發(fā)現(xiàn)全局變量并沒有被捕獲到 __HelloLefe__lefeTestComplete_block_impl_0
中蟹但,僅僅捕獲了 static_val,想想也是谭羔,全局變量直接可以獲取到华糖,為什么還要捕獲他呢?但捕獲靜態(tài)變量和以前不一樣的是它捕獲的是一個指針 int *static_val;
哦口糕,對啊缅阳,直接使用它的指針就可以修改它了,但是為什么普通變量不可以使用其指針呢景描?因為一個 block 必須存在即使它所捕獲變量的作用域釋放掉十办,作用域釋放掉后其變量也隨之銷毀,這意味著 block 就不能訪問所捕獲的自動變量了超棺,如何修改向族?但是靜態(tài)變量和全局變量不會釋放啊棠绘!
int global_val = 1;
static int static_global_val = 2;
struct __HelloLefe__lefeTestComplete_block_impl_0 {
struct __block_impl impl;
struct __HelloLefe__lefeTestComplete_block_desc_0* Desc;
int *static_val;
__HelloLefe__lefeTestComplete_block_impl_0(void *fp, struct __HelloLefe__lefeTestComplete_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __HelloLefe__lefeTestComplete_block_func_0(struct __HelloLefe__lefeTestComplete_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
}
static struct __HelloLefe__lefeTestComplete_block_desc_0 {
size_t reserved;
size_t Block_size;
} __HelloLefe__lefeTestComplete_block_desc_0_DATA = { 0, sizeof(struct __HelloLefe__lefeTestComplete_block_impl_0)};
static void _I_HelloLefe_lefeTestComplete(HelloLefe * self, SEL _cmd) {
static int static_val = 3;
void (*complete)(void) = ((void (*)())&__HelloLefe__lefeTestComplete_block_impl_0((void *)__HelloLefe__lefeTestComplete_block_func_0, &__HelloLefe__lefeTestComplete_block_desc_0_DATA, &static_val));
((void (*)(__block_impl *))((__block_impl *)complete)->FuncPtr)((__block_impl *)complete);
}
通過上面的學(xué)習(xí)我們可以了解到件相,修改 Block 中捕獲的變量再扭,可以使用一下幾種方式:
- 通過 __block 修飾變量
- 使用靜態(tài)變量
- 使用全局變量、全局靜態(tài)變量
- 使用指針夜矗,靜態(tài)變量就是通過指針來修改它的值的
讀到這里泛范,相信你已經(jīng)明白如何捕獲自動變量了,也知道如何修改 Block 中所捕獲的變量了紊撕,難道你不想知道為啥使用 __block 修飾后就可以修改 Block 中所捕獲的變量嗎罢荡?哈哈,堅持一下对扶!
__block 究竟是如何實現(xiàn)的呢区赵?
__block 如同 static, auto 等修飾符,主要作用是覺得某一變量該保存到哪里浪南×牛看看它是如何實現(xiàn)的。把下面的代碼轉(zhuǎn)化:
- (void)lefeTestComplete
{
__block int val = 10;
void (^complete)(void) = ^{val = 1;};
complete();
}
轉(zhuǎn)換后發(fā)現(xiàn)多了很多內(nèi)容络凿,為什么使用 __block 需要增加這么多代碼呢骡送?Lefe 表示很好奇。當(dāng)使用 __block 變量時絮记,會將 __block 變量從椄餮瑁拷貝的堆上。當(dāng)多個 block 共用一個 __block 變量時到千,__block 變量有一個計數(shù)器來記錄有多少個 block 引用了它昌渤,block 釋放掉的時候,__block 變量的引用計數(shù)將減1憔四,直到為0時膀息,__block 變量才會釋放。
- __block 轉(zhuǎn)換后的結(jié)構(gòu)體
struct __Block_byref_val_0 {
void *__isa;
// __forwarding 主要用來獲取 __block 變量的值了赵,它的指向會根據(jù) block 所處的內(nèi)存位置不同潜支,所指向的也不同。
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val; // 值
};
- Block 的實現(xiàn)柿汛,發(fā)現(xiàn)多了 __Block_byref_val_0冗酿,它就是一個 block 變量
struct __HelloLefe__lefeTestComplete_block_impl_0 {
struct __block_impl impl;
struct __HelloLefe__lefeTestComplete_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__HelloLefe__lefeTestComplete_block_impl_0(void *fp, struct __HelloLefe__lefeTestComplete_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
-
^{val = 1;}
的實現(xiàn)被轉(zhuǎn)換后:
static void __HelloLefe__lefeTestComplete_block_func_0(struct __HelloLefe__lefeTestComplete_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
static void __HelloLefe__lefeTestComplete_block_copy_0(struct __HelloLefe__lefeTestComplete_block_impl_0*dst, struct __HelloLefe__lefeTestComplete_block_impl_0*src) {
_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __HelloLefe__lefeTestComplete_block_dispose_0(struct __HelloLefe__lefeTestComplete_block_impl_0*src) {
_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static struct __HelloLefe__lefeTestComplete_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __HelloLefe__lefeTestComplete_block_impl_0*, struct __HelloLefe__lefeTestComplete_block_impl_0*);
void (*dispose)(struct __HelloLefe__lefeTestComplete_block_impl_0*);
} __HelloLefe__lefeTestComplete_block_desc_0_DATA = { 0, sizeof(struct __HelloLefe__lefeTestComplete_block_impl_0), __HelloLefe__lefeTestComplete_block_copy_0, __HelloLefe__lefeTestComplete_block_dispose_0};
-
__block int val = 10;
轉(zhuǎn)化后的代碼如下,轉(zhuǎn)換后變成一個結(jié)構(gòu)體络断,并且初始化的時候值為 10
static void _I_HelloLefe_lefeTestComplete(HelloLefe * self, SEL _cmd) {
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {
(void*)0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
10
};
void (*complete)(void) = ((void (*)())&__HelloLefe__lefeTestComplete_block_impl_0((void *)__HelloLefe__lefeTestComplete_block_func_0, &__HelloLefe__lefeTestComplete_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
((void (*)(__block_impl *))((__block_impl *)complete)->FuncPtr)((__block_impl *)complete);
}
Block 的內(nèi)存段
下圖主要說明 block 主要保存在棧裁替,堆和數(shù)據(jù)區(qū)。
那什么樣的變量分派到棧中貌笨、堆中或數(shù)據(jù)區(qū)呢弱判?
- 1、當(dāng) block 字面量寫在全局作用域時锥惋,即為 global block昌腰;
- 2开伏、當(dāng) block 字面量不獲取任何外部變量時,即為 global block遭商;
除了以上2中情況外固灵,其他的都分派到棧區(qū),分派到棧區(qū)的 block 劫流,當(dāng)作用域結(jié)束后怎虫,它所捕獲的變量也就釋放掉了。為了解決這個問題困介,Blocks 提供了一個函數(shù),可以把棧上的 block 拷貝到堆上蘸际。這樣即使作用域結(jié)束也不會使 block 被釋放座哩。被 copy 后的 block ,它的 isa 指針就會變成 impl.isa = &_NSConcreteMallocBlock
粮彤。Block 也就成了堆上的 Block根穷。
使用 ARC 后,編譯器會自動把棧中的 block 復(fù)制到堆上导坟。
typedef int (^blk_t)(int);
blk_t func(int rate) {
return ^(int count){return rate * count;};
}
blk_t func(int rate) {
blk_t tmp = &__func_block_impl_0(
__func_block_func_0, &__func_block_desc_0_DATA, rate);
// 直接復(fù)制了一個 block屿良,也就是拷貝到了堆上,即使當(dāng)這個函數(shù)結(jié)束后惫周,這個 block 任然不會被銷毀
tmp = objc_retainBlock(tmp);
return objc_autoreleaseReturnValue(tmp); }
但是不是所有的時候尘惧,編譯器都會執(zhí)行 copy 操作的,以下情況編譯器不會執(zhí)行 copy 操作的
- Block 作為一個參數(shù)傳遞給一個函數(shù)或方法時递递。
舉個例子喷橙,下面這個例子會直接 crash,所以需要給數(shù)組中的 block 要執(zhí)行 copy 操作
+ (id)getBlockArray {
int val = 10;
return [[NSArray alloc] initWithObjects:
^{NSLog(@"blk0:%d", val);},
^{NSLog(@"blk1:%d", val);},
nil
];
}
+ (void)lefeTestComplete
{
id obj = [self getBlockArray];
typedef void (^blk_t)(void);
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk();
}
但是使用系統(tǒng)提供的方法不需要執(zhí)行 copy 操作登舞,比如 GCD贰逾,因為在函數(shù)內(nèi)部自己已經(jīng)實現(xiàn)了 copy。
捕獲對象:
前面都在講捕獲的是基本類型的變量菠秒,那么 Block 是如何捕獲對象的呢疙剑?下面的例子中的數(shù)組中,打印結(jié)果為:
Array: (
Lefe,
Wang,
Su,
Yan
)
說明數(shù)組沒有被釋放掉践叠。Block 內(nèi)部會強(qiáng)引用對象言缤,直到 Block 被釋放,被引用的對象也將被釋放禁灼。
@implementation HelloLefe
LefeBlock block;
+ (void)lefeTestComplete
{
NSMutableArray *array = [NSMutableArray array];
block = ^(NSString *name){
[array addObject:name];
NSLog(@"Array: %@", array);
};
}
+ (void)addObject
{
block(@"Lefe");
block(@"Wang");
block(@"Su");
block(@"Yan");
}
@end
循環(huán)引用一:
- (void)testMemoryLeakCase1
{
self.logId = @"Hello logId";
/**
這種情況最容易發(fā)現(xiàn)轧简,因為編譯器會自動提示出現(xiàn)循環(huán)引用
Why?
self(SecondViewController)持有了 finshBlock匾二,你可以把它當(dāng)作一個普通的屬性哮独,是強(qiáng)引用
而 finshBlock 又引用了 self拳芙,這樣就形成了一個閉環(huán)。
How皮璧?
既然是因為出現(xiàn)了閉環(huán)舟扎,我們只需要打破這層閉環(huán)就可以,讓 finshBlock 持有一個弱引用悴务,這樣 self(SecondViewController)持有了 finshBlock睹限,但是 finshBlock 沒有持有 self
*/
// __weak typeof(self) weakSelf = self; 一般的宏定義是這樣的
__weak SecondViewController *wSelf = self;
self.finshBlock = ^(BOOL isSuccess) {
[wSelf loginTest];
};
/**
在我們的應(yīng)用中一般是下面這種方式寫,為啥使用了 __weak 和 __strong ?
有人可能會問讯檐,先 weak 后 strong羡疗,那相當(dāng)于還是強(qiáng)引用了 self,你確定 strong的是 self别洪?
*/
/**
打舆逗蕖:
(lldb) p weakSelf
(SecondViewController *) $0 = 0x0000000101c16f10
(lldb) p self
(SecondViewController *) $1 = 0x0000000101c16f10
(lldb) p strongSelf
(SecondViewController *) $2 = 0x0000000101c16f10
(lldb)
發(fā)現(xiàn) weakSelf self 和 strongSelf 的內(nèi)存地址是一樣的,只是一次淺拷貝;
*/
__weak typeof(self) weakSelf = self;
self.finshBlock = ^(BOOL isSuccess) {
// 如果沒有這句話挖垛,當(dāng) self 被釋放后痒钝,weakSelf 就變?yōu)榱丝眨躁P(guān)于 weakSelf 的一些操作也就沒什么意義了痢毒,如果還想讓 weakSelf 所調(diào)用的一些方法有意義那么久需要強(qiáng)引用 weakSelf送矩;
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"weakSelf.logId: %@", strongSelf.logId);
NSString *name = strongSelf.logId;
if (name.length > 0) {
NSLog(@"Hello world");
}
[strongSelf loginTest];
});
};
self.finshBlock(YES);
/**
修改前的:
self.finshBlock = ^(BOOL isSuccess) {
[self loginTest];
};
*/
}
循環(huán)引用二:
- (void)testMemoryLeakCase2
{
/**
這里面出現(xiàn)了兩個對象的內(nèi)存泄漏: task 和 self
task的內(nèi)存泄漏:
task 有個屬性叫 blcok,但是在 block 中又捕獲了 task哪替,這樣就形成了一個閉環(huán)
self 的內(nèi)存泄漏:
因為這個 block 中捕獲了 self栋荸,block 沒有釋放那么 self 咋么能釋放呢?
所以只要打破這個閉環(huán)凭舶,self 就釋放了蒸其。
*/
AsyncTask *task = [AsyncTask new];
__weak AsyncTask *wTask = task;
task.block = ^(BOOL isFinish) {
NSString *name = wTask.lastLoginId;
self.logId = name;
};
[task sendLogin];
/**
AsyncTask *task = [AsyncTask new];
task.block = ^(BOOL isFinish) {
NSString *name = task;
self.logId = name;
};
[task sendLogin];
*/
}
循環(huán)引用三:
其實實例變量是通過 self->name 訪問的,所以也可能造成循環(huán)引用库快。
- (void)testMemoryLeakCase3
{
/**
這里可能不太容易看出來摸袁,訪問 name 實例變量相當(dāng)于 self->name
這樣 self 持有 finshBlock, finshBlock 持有 self义屏,形成閉環(huán)靠汁,造成循環(huán)引用
*/
__weak SecondViewController *wSelf = self;
self.finshBlock = ^(BOOL isFinish) {
/*
Dereferencing a __weak pointer is not allowed due to possible null value caused by race condition, assign it to strong variable first
*/
// 發(fā)現(xiàn)這樣寫不行,還報錯闽铐,它的意思是 __weak 指針可能為空蝶怔,必須要強(qiáng)引用
// wSelf->name = @"Hello lefe";
/**
那么為什么在 testMemoryLeakCase1 中 wSelf.logId = @"Hello logId"; 沒有編譯錯誤呢?我想
估計 wSelf.logId 等價于 [wSelf logId]兄墅,相當(dāng)于調(diào)用了一個方法踢星,
nil 調(diào)用方法是沒有錯誤的。你知道屬性和實例變量的區(qū)別嗎隙咸?
下面這行代碼也會報錯的:
__weak AsyncTask *task;
task->_sex;
*/
wSelf.logId = @"Hello logId";
__strong SecondViewController *strongSelf = wSelf;
strongSelf->_name = @"Hello lefe";
};
/**
也可以使用下面方法來解除循環(huán)引用
__block id temp = self;
self.finshBlock = ^(BOOL isFinish) {
temp = nil;
};
self.finshBlock(YES);
*/
/**
修改前的代碼:
self.finshBlock = ^(BOOL isFinish) {
name = @"Hello lefe";
};
*/
}
===== 我是有底線的 ======
喜歡我的文章沐悦,歡迎關(guān)注我的新浪微博 Lefe_x成洗,我會不定期的分享一些開發(fā)技巧