本文將介紹block的類型档玻,循環(huán)引用的解決方法以及block底層分析
Block簡介
Block定義:帶有自動(dòng)變量的匿名函數(shù),它是C語言的拓展功能茫藏,之所以是擴(kuò)展误趴,是因?yàn)镃語言不允許存在這樣的匿名函數(shù)
- 匿名函數(shù)
- 匿名函數(shù)式指不帶函數(shù)名稱的函數(shù)
- 帶有自定變量
- Block擁有捕獲外部變量的功能,在Block中訪問一個(gè)外部的局部變量务傲,Block會(huì)持有它的臨時(shí)狀態(tài)凉当,自動(dòng)捕獲變量值,外部局部變量的變化不會(huì)影響它的狀態(tài)(這個(gè)下面會(huì)講到)树灶。
Block類型
作為一個(gè)開發(fā)者纤怒,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要糯而,這是一個(gè)我的iOS開發(fā)交流群:130 595 548天通,不管你是小白還是大牛都?xì)g迎入駐 ,讓我們一起進(jìn)步熄驼,共同發(fā)展O窈(群內(nèi)會(huì)免費(fèi)提供一些群主收藏的免費(fèi)學(xué)習(xí)書籍資料以及整理好的幾百道面試題和答案文檔!)
block主要有三種類型
- 1.
__NSGlobalBlock__
:全局block瓜贾,存儲(chǔ)在全局區(qū)
此時(shí)block無參也無返回值诺祸,屬于全局block
- 2.
__NSMallocBlock__
:堆區(qū)block,因?yàn)閎lock既是函數(shù)祭芦,也是對(duì)象
此時(shí)block會(huì)訪問外界變量筷笨,即底層拷貝a,所以是堆區(qū)block
- 3.
__NSStackBlock__
:棧區(qū)block
其中局部變量a在沒有處理之前(即沒有拷貝之前)是 棧區(qū)block龟劲, 處理后(即拷貝之后)是堆區(qū)block 胃夏,所以棧區(qū)block越來越少了
這個(gè)情況下,可以通過__weak不進(jìn)行強(qiáng)持有昌跌,block就還是棧區(qū)block
總結(jié)
- 1.block是直接存儲(chǔ)在
全局區(qū)
- 2.block如果
訪問外界變量
仰禀,并進(jìn)行block相應(yīng)copy
- 如果此時(shí)的
block是強(qiáng)引用
,則block存儲(chǔ)在堆區(qū)
蚕愤,即堆區(qū)block - 如果此時(shí)的
block通過——weak變成了弱引用
答恶,則block存儲(chǔ)在棧區(qū)
饺蚊,即棧區(qū)block
- 如果此時(shí)的
Block循環(huán)引用
【正常釋放】:當(dāng)A持有B
,當(dāng)A調(diào)用dealloc方法
悬嗓,給B發(fā)送release
信號(hào)污呼,B收到release信號(hào)
刃滓,如果此時(shí)B的引用計(jì)數(shù)為0
時(shí)诬辈,則會(huì)調(diào)用B的dealloc方法
艺普,此時(shí)A哼审,B都能正常釋放
【循環(huán)引用】:當(dāng)A持有B
岂嗓,B同時(shí)也持有A時(shí)
诡宗,此時(shí)A銷毀需要B先銷毀
玄糟,而B銷毀同樣需要A先銷毀
磷籍,就導(dǎo)致相互等待銷毀
堰氓,此時(shí)A挤渐,B的引用計(jì)數(shù)都不為0
,所以A双絮,B此時(shí)都無法釋放
浴麻。
解決循環(huán)引用
舉個(gè)循環(huán)引用的例子:如下圖上面代碼發(fā)生了
循環(huán)引用
,因?yàn)樵?code>block內(nèi)部使用了self的name變量囤攀,導(dǎo)致block持有self
软免,而self本來就持有block
,就導(dǎo)致了self和block相互持有
焚挠。
下面來解決循環(huán)引用
- 1.
weak-strong-dance
(最常用的方法) - 2.
__block修飾對(duì)象膏萧,同時(shí)置nil
- 3.
傳遞對(duì)象self作為block的參數(shù),提供給block內(nèi)部使用
- 4.
使用NSProxy
weak-strong-dance(弱強(qiáng)共舞)
- 1.
如果block內(nèi)部并未嵌套block蝌衔,直接使用__weak修飾self即可
@interface ViewController ()
@property (nonatomic, copy) void (^block)(void);
@property (nonatomic, copy) NSString *name;
@end
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"man";
__weak typeof(self) weakSelf = self;
self.block = ^(void){
NSLog(@"%@",weakSelf.name);
};
self.block();
}
由于此時(shí)的weakSelf和self指向同一片內(nèi)存空間
榛泛,而且使用__weak不會(huì)導(dǎo)致self的引用計(jì)數(shù)發(fā)生變化
,可以通過打印weakSelf和self的指針地址
噩斟,以及self的引用計(jì)數(shù)來驗(yàn)證 [圖片上傳失敗...(image-74b93d-1619163053376)]
- 2.如果
block內(nèi)部嵌套block
曹锨,則需要同時(shí)使用__weak和__strong
如果只用weak修飾
,則可能出現(xiàn)block內(nèi)部持有的對(duì)象被提前釋放
剃允,為了防止block內(nèi)部變量被提前釋放
沛简,使用strong對(duì)引用計(jì)數(shù)+1
,防止提前釋放
斥废。
其中strongSelf是一個(gè)臨時(shí)變量
椒楣,在block的作用域內(nèi)
,當(dāng)block執(zhí)行完就會(huì)釋放strongSelf
营袜,這種方式屬于打破self對(duì)block的強(qiáng)引用
撒顿,依賴于中間者模式
,屬于自動(dòng)置為nil
荚板,也就是自動(dòng)釋放
凤壁。
__block修飾變量
這種方式同樣依賴于中介者模式
吩屹,屬于手動(dòng)釋放
,是通過__block修飾對(duì)象
拧抖,主要是因?yàn)?code>__block修飾的對(duì)象是可以改變的煤搜。
這里的
block必須調(diào)用
,如果不調(diào)用block
唧席,vc就不會(huì)置空
擦盾,那么依舊是循環(huán)引用
,self和block都不會(huì)釋放
淌哟。
對(duì)象self作用參數(shù)
主要是將對(duì)象self作用參數(shù)
迹卢,提供給block內(nèi)部使用
,不會(huì)有引用計(jì)數(shù)問題
使用NSProxy虛擬類
-
OC
是只能單繼承
的語言徒仓,但它是基于運(yùn)行時(shí)的機(jī)制
腐碱,所以可以通過NSProxy
來實(shí)現(xiàn)偽多繼承
,填補(bǔ)多繼承的空白 -
NSProxy
和NSObject
是同級(jí)的類掉弛,是個(gè)虛擬類
症见,只是實(shí)現(xiàn)了NSObject的協(xié)議 -
NSProxy
其實(shí)是一個(gè)消息重定向封裝的一個(gè)抽象類
,類似一個(gè)代理人殃饿,中間件谋作,可以通過繼承它,并重新寫下面的兩個(gè)方法來實(shí)現(xiàn)消息轉(zhuǎn)發(fā)到另一個(gè)實(shí)例
使用場(chǎng)景
- 1.實(shí)現(xiàn)
多繼承
功能 - 2.解決
NSTimer&CADisplayLink
創(chuàng)建時(shí)對(duì)self強(qiáng)引用
問題乎芳,這個(gè)在YYKit中YYWeakProxy有所使用的
循環(huán)引用解決原理
主要是通過自定義的NSProxy類
的對(duì)象來代替self
遵蚜,并使用方法實(shí)現(xiàn)消息轉(zhuǎn)發(fā)
,下面是NSProxy子類
的實(shí)現(xiàn)以及使用的場(chǎng)景
@interface LjProxy ()
@property(nonatomic, weak, readonly) NSObject *objc;
@end
@implementation LjProxy
- (id)transformObjc:(NSObject *)objc{
_objc = objc;
return self;
}
+ (instancetype)proxyWithObjc:(id)objc{
return [[self alloc] transformObjc:objc];
}
// 有了方法簽名之后就會(huì)調(diào)用方法實(shí)現(xiàn)
- (void)forwardInvocation:(NSInvocation *)invocation{
SEL sel = [invocation selector];
if ([self.objc respondsToSelector:sel]) {
[invocation invokeWithTarget:self.objc];
}
}
// 查詢?cè)摲椒ǖ姆椒ê灻?- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
NSMethodSignature *signature;
if (self.objc) {
signature = [self.objc methodSignatureForSelector:sel];
}else{
signature = [super methodSignatureForSelector:sel];
}
return signature;
}
- (BOOL)respondsToSelector:(SEL)aSelector{
return [self.objc respondsToSelector:aSelector];
}
@end
自定義Man和Teacher類
@implementation Man
- (void)likeFood {
NSLog(@"%@-->牛肉", self);
}
@end
@implementation Teacher
- (void)likeWork {
NSLog(@"%@->教書育人", self);
}
@end
通過LjProxy實(shí)現(xiàn)多繼承功能 總結(jié)
循環(huán)引用解決的根本方式:
- 1.
打破self對(duì)block的強(qiáng)引用
谬晕,這需要對(duì)block進(jìn)行聲明
的時(shí)候使用weak修飾
碘裕,但是這會(huì)導(dǎo)致block提前釋放
携取,所以這種方式不可行
- 2.
打破block對(duì)self的強(qiáng)引用
,主要就是self的作用域
和block作用域
的數(shù)據(jù)交
換問題帮孔,我們可以通過代理
雷滋,通知
,傳值
等幾種方式文兢,用于解決循環(huán)
晤斩,我們對(duì)上面講的列一下- weak-strong-dance(弱強(qiáng)共舞)
- __block修飾變量
- 對(duì)象self作用參數(shù)使用
- 使用NSProxy子類代替self
上面介紹了block的定義
,用法
以及如何解決循環(huán)引用
姆坚,下面我們來探尋下block的C++實(shí)現(xiàn)
Block C++實(shí)現(xiàn)
研究底層可以先從C++澳泵,斷點(diǎn)調(diào)試開始
本質(zhì)
創(chuàng)建block.c文件通過xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc block.c -o block.cpp
,將block.c編譯成block.cpp兼呵,其中block在底層被編譯成了以下的形式
我們用一張圖來說明他們之間的聯(lián)系通過上圖我們可以知道
__main_block_impl_0
是一個(gè)結(jié)構(gòu)體
,同時(shí)可以說明block是一個(gè)__main_block_impl_0類型的對(duì)象
维苔,這也是為什么block能夠%@打印的原因
下面我們來解釋幾個(gè)問題
block為什么需要調(diào)用
在底層block的類型__main_block_impl_0
結(jié)構(gòu)體碰辅,通過其同名構(gòu)造函數(shù)創(chuàng)建
,第一個(gè)傳入的block的內(nèi)部實(shí)現(xiàn)代碼塊
介时,即__main_block_func_0
没宾,用fp表示
,然后賦值給impl的FuncPtr屬性
沸柔,然后在main中進(jìn)行了調(diào)用
循衰,這也是block為什么需要調(diào)用的原因
。如果不調(diào)用
褐澎,block內(nèi)部實(shí)現(xiàn)的代碼塊將無法執(zhí)行
羹蚣,可以總結(jié)為以下兩點(diǎn)
- 1.
函數(shù)聲明
:即block內(nèi)部實(shí)現(xiàn)聲明成了一個(gè)函數(shù)__main_block_func_0
- 2.
執(zhí)行具體的函數(shù)實(shí)現(xiàn)
:通過調(diào)用block的FuncPtr指針
,調(diào)用block執(zhí)行
block是如何獲取外界變量的
我們將上面的代碼當(dāng)中調(diào)用block
__main_block_func_0
中的a是值拷貝
乱凿,如果此時(shí)在block內(nèi)部實(shí)現(xiàn)中作 a++操作
顽素,是有問題的
,會(huì)造成編譯器的代碼歧義
徒蟆,即此時(shí)的a是只讀的
胁出。
【總結(jié)】:block捕獲外界變量時(shí),在內(nèi)部會(huì)自動(dòng)生成同一個(gè)屬性來保存
__block原理
將上面代碼的局部變量a使用__block修飾通過上面的截圖我們可以得出以下結(jié)論:
- 1.main中的
a是以__Block_byref_a_0結(jié)構(gòu)體
的形式出現(xiàn)的段审,是封裝的對(duì)象
- 2.在
結(jié)構(gòu)體__Block_byref_a_0
中全蝶,a的值存在int a中
- 3.在
__main_block_impl_0
中,將對(duì)象a的地址&a給構(gòu)造函數(shù)
- 4.在
__main_block_func_0
內(nèi)部對(duì)a的處理時(shí)指針拷貝
寺枉,此時(shí)創(chuàng)建的對(duì)象a
與傳入對(duì)象的a
指向的是同一片內(nèi)存空間
總結(jié)
- 1.
外界變量
通過__block
生成__Block_byref_a_0結(jié)構(gòu)體
- 2.
結(jié)構(gòu)體
用來保存
原始變量的指針和值
- 3.將變量生成的
結(jié)構(gòu)體對(duì)象的指針地址傳遞給block
抑淫,然后在block內(nèi)部就可以對(duì)外界變量進(jìn)行操作了
上面__block和非__block修飾局部變量產(chǎn)生兩種不同的拷貝
- 非__block修飾:
值拷貝
-淺拷貝
,只是拷貝數(shù)值
姥闪,且拷貝的值不可更改
始苇,指向不同
的內(nèi)存空間
,非__block修飾的變量a就是值拷貝 - __block修飾:
指針拷貝
-深拷貝
筐喳,生成的對(duì)象
指向同一片內(nèi)存空間
催式,通過__block修飾的變量a就是指針拷貝
Block底層原理
確定block源碼位置
在main函數(shù)中寫如下代碼我們發(fā)現(xiàn)走到了objc_retainBlock
避归,我們加符號(hào)斷點(diǎn)objc_retainBlock
打印符號(hào)斷點(diǎn)后荣月,我們發(fā)現(xiàn)執(zhí)行了_Block_copy
,我們?cè)偌臃?hào)斷點(diǎn)_Block_copy
此時(shí)我們需要看_Block_copy實(shí)現(xiàn)梳毙,它在libsystem_blocks.dylib
源碼中哺窄,我們?nèi)ヌO果官方下載下源碼libclosure-74,在源碼中搜索_Block_copy
通過查看_Block_copy
的源碼實(shí)現(xiàn),發(fā)現(xiàn)block在底層
的真正類型
是Block_layout
Block真正類型Block_layout
我們查看下Block_layout底層實(shí)現(xiàn)說明:
1.
isa
:指向的是block類型的類
-
2.flags:
標(biāo)識(shí)符
萌业,按bit位
表示一些block
的附加信息
蔑担,類似于isa中的位域
,其中flags
的種類
有以下幾種咽白,主要重點(diǎn)關(guān)注BLOCK_HAS_COPY_DISPOSE
和BLOCK_HAS_SIGNATURE
啤握。BLOCK_HAS_COPY_DISPOSE
決定是否有Block_descriptor_2
。BLOCK_HAS_SIGNATURE
決定是否有Block_descriptor_3
- 第一位:
BLOCK_DEALLOCATING
晶框,釋放標(biāo)記排抬,一般常用BLOCK_NEEDS_FREE
做位與
操作,一同傳入Flags授段,告知該block可釋放
- 第十六位:
BLOCK_REFCOUNT_MASK
蹲蒲,存儲(chǔ)引用計(jì)數(shù)的值
;是一個(gè)可選用參數(shù) - 第二十四位:
BLOCK_NEEDS_FREE
,第16是否有有效的標(biāo)志
侵贵,程序根據(jù)它來決定是否增加或是減少引用計(jì)數(shù)位
的 - 第二十五位:
BLOCK_HAS_COPY_DISPOSE
届搁,是否擁有拷貝輔助函數(shù)
(a copy helper function) - 第二十六位:
BLOCK_HAS_CTOR
,是否擁有block析構(gòu)函數(shù)
- 第二十七位:
BLOCK_IS_GC
窍育,標(biāo)志是否有垃圾回收
//OS X - 第二十八位:
BLOCK_IS_GLOBAL
卡睦,標(biāo)志是否是全局block
- 第三十位位:
BLOCK_HAS_SIGNATURE
,與BLOCK_USE_STRET
相對(duì)漱抓,判斷當(dāng)前block是否擁有一個(gè)簽名
表锻。用于runtime時(shí)動(dòng)態(tài)調(diào)用
- 第一位:
3.
reserved
:保留信息,可以理解預(yù)留位置
乞娄,猜測(cè)是用于存儲(chǔ)block內(nèi)部變量信息4.
invoke
:是一個(gè)函數(shù)指針
瞬逊,指向block的執(zhí)行代碼
-
5.
descriptor
:block的附加信息
,比如保留變量數(shù)
仪或、block的大小
确镊、進(jìn)行copy或dispose的輔助函數(shù)指針
。有三類-
Block_descriptor_1
是必選
-
Block_descriptor_2
和Block_descriptor_3
是可選
的
-
從上圖可以知道:
Block_descriptor_2
和Block_descriptor_3
都是通過Block_descriptor_1
的地址范删,經(jīng)過內(nèi)存平移
得到的
Block內(nèi)存變化
根據(jù)符號(hào)斷點(diǎn)
我們打斷點(diǎn)運(yùn)行蕾域,走到objc_retainBlock,我們打印寄存器x0我們?cè)黾油獠孔兞縜束铭,再次運(yùn)行,在相同的位置再次打印x0我們發(fā)現(xiàn)此時(shí)的
block
是全局block
瓶逃,即__NSGlobalBlock__
類型
此時(shí)讀取
block
發(fā)現(xiàn)是棧block
即__NSStackBlock__
執(zhí)行到符號(hào)斷點(diǎn)
objc_retainBlock
時(shí)厢绝,我們發(fā)現(xiàn)還是棧區(qū)block
我們?cè)谠黾臃?hào)斷點(diǎn)_Block_copy
,繼續(xù)往下走带猴,來到_Block_copy斷點(diǎn)昔汉,此時(shí)打印
此時(shí)的
x0地址不變
,說明此時(shí)的block還是棧區(qū)block
,我們?cè)?code>_Block_copy尾部ret處打斷點(diǎn)
靶病,執(zhí)行到斷點(diǎn)處会通,再次打印
發(fā)現(xiàn)經(jīng)過
_Block_copy之后
,x0
的地址
發(fā)生了變化
娄周,我們打印x0地址后發(fā)現(xiàn)block
由棧區(qū)block變?yōu)槎褏^(qū)block
涕侈,即__NSMallocBlock__
同樣上面的結(jié)論我們也可以通過讀寄存器地址來得出
根據(jù)寄存器地址
我們重新運(yùn)行項(xiàng)目,繼續(xù)前面的斷點(diǎn)煤辨,運(yùn)行前面的斷點(diǎn)裳涛,打印x0,x8众辨,x9我們將代碼運(yùn)行到41行鹃彻,在次打印上面的地址此時(shí)我們看到
x0
和x8
指向的是同一塊內(nèi)存空間
端三,用于存儲(chǔ)__NSStackBlock__
,此時(shí)的x9存儲(chǔ)
的是_block_invoke
此時(shí)的
x8
是_block_invoke
郊闯,blr就是跳轉(zhuǎn)進(jìn)入
的意思,也就是要進(jìn)入_block_invoke
里
當(dāng)我們進(jìn)入
_block_invoke
中蛛株,可以得出是通過內(nèi)存平移
得到block內(nèi)部實(shí)現(xiàn)
的
前面提到的Block_layout
的結(jié)構(gòu)體
源碼中知道其有個(gè)屬性invoke
团赁,即block的執(zhí)行者
,是從isa首地址平移16字節(jié)
得到invoke
谨履,然后進(jìn)行調(diào)用執(zhí)行
的然痊。
Block簽名
最開始我們拿到了block的地址,前面底層我們知道block底層
是Block_layout的結(jié)構(gòu)體
通過上圖我們知道descriptor是附加信息
屉符,我們打印下它的內(nèi)容
找到
block地址
剧浸,通過內(nèi)存平移找到descriptor
,然后x/8gx查看descriptor內(nèi)存情況矗钟,我們前面說了descriptor
會(huì)有_Block_descriptor_2
或者_Block_descriptor_3
唆香,只有_Block_descriptor_3存在簽名
。
判斷是否存在_Block_descriptor_2
吨艇,即flags的BLOCK_HAS_COPY_DISPOSE(拷貝輔助函數(shù))是否有值
- 1.先通過
p/x 1<<25
躬它,即1左移25
位得到BLOCK_HAS_COPY_DISPOSE
- 2.再拿
flags與上BLOCK_HAS_COPY_DISPOSE
(flags是block首地址平移8字節(jié)
,即:0x00000000c1000002
看到打印結(jié)果為0
东涡,表示沒有Block_descriptor_2
冯吓。
判斷是否存在Block_descriptor_3
,即flags的BLOCK_HAS_SIGNATURE(是否有簽名)是否有值
- 1.先通過
p/x 1<<30
疮跑,即1左移30
位得到BLOCK_HAS_SIGNATURE
- 2.再拿
flags與上BLOCK_HAS_SIGNATURE
(flags是block首地址平移8字節(jié)
组贺,還是:0x00000000c1000002
看到打印的結(jié)果不為0,說明有值
祖娘,說明是Block_descriptor_3
失尖,存在簽名
,看descriptor,其中第三個(gè)0x0000000104d63e87
表示簽名
掀潮。我們將簽名打印出來了
下面我們通過[NSMethodSignature signatureWithObjCTypes:"v8@?0"]看下簽名具體內(nèi)容
下面我們具體來看下簽名:
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
type encoding (@) '@?' // 類型是否是@
flags {isObject, isBlock} // @是isObject 菇夸,?是isBlock仪吧,代表 isBlockObject
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8} // 所在偏移位置是8字節(jié)
block的簽名信息
類似于方法的簽名信息
庄新,主要體現(xiàn)
在block返回值
,參數(shù)以及類型
等信息上薯鼠。
Block的三次copy分析
Block_copy源碼分析
- 進(jìn)入
_Block_copy
源碼择诈,將block
從棧區(qū)拷貝至堆區(qū)
- 如果需要釋放,如果需要?jiǎng)t直接釋放
- 如果是
globalBlock
人断,則不需要copy
吭从,直接返回
- 反之,只有兩種情況:
棧區(qū)block
or堆區(qū)block
恶迈,由于堆區(qū)block
需要申請(qǐng)空間
涩金,前面并沒有申請(qǐng)空間
的相關(guān)代碼,所以只能是棧區(qū)block
- 通過
malloc申請(qǐng)內(nèi)存空間
用于接收block
- 通過
memmove
將block拷貝至新申請(qǐng)的內(nèi)存中
- 設(shè)置
block對(duì)象
的類型為堆區(qū)block
暇仲,即result->isa = _NSConcreteMallocBlock
- 通過
_Block_object_assign分析
要分析block的三層copy步做,首先需要知道外部變量的種類有哪些,在__block的cpp文件中
奈附,對(duì)block修飾__main_block_desc_0_DATA
全度,而__main_block_desc_0_DATA
用的__main_block_copy_0
,最后對(duì)a的修飾_Block_object_assign
斥滤。對(duì)block修飾
其中用的最多
的是BLOCK_FIELD_IS_OBJECT
和BLOCK_FIELD_IS_BYREF
- 1.如果是
普通對(duì)象
顶掉,則交給ARC處理
,并拷貝對(duì)象指針
挑胸,即引用計(jì)數(shù)+1
痒筒,所以外界變量不能釋放
- 2.如果是
block類型
的,則通過_Block_copy
操作茬贵,將block從棧區(qū)拷貝到堆區(qū)
- 3.如果是
__block修飾的變量
簿透,調(diào)用_Block_byref_copy
函數(shù),進(jìn)行內(nèi)存拷貝
以及常規(guī)處理
- 1.將傳入對(duì)象解藻,
強(qiáng)轉(zhuǎn)
為Block_byref
結(jié)構(gòu)體類型對(duì)象老充,保存一份 - 2.
沒有
將外界變量拷貝到堆
,需要申請(qǐng)內(nèi)存
舆逃,進(jìn)行拷貝
- 3.如果已經(jīng)
拷貝過
了蚂维,則進(jìn)行處理并返回
- 4.其中
copy
和src
的forwarding指針都指向同一片內(nèi)存
戳粒,這也是為什么__block
修飾的對(duì)象具有修改能力
的原因
代碼驗(yàn)證
寫如下代碼:- 1.編譯后lj_name比普通變量多了__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131
- 2.__Block_byref_lj_name_0結(jié)構(gòu)體中多了__Block_byref_id_object_copy和__Block_byref_id_object_dispose
通過上面的分析路狮,我們可以知道這些方法的執(zhí)行順序_Block_copy
->_Block_byref_copy
->_Block_object_assign
,正好對(duì)應(yīng)上述的三層copy 綜上所述虫啥,那么block是如何拿到lj_name的呢?
- 1.通過
__block_copy方法
奄妨,將block拷貝至堆區(qū)
- 2.通過
_Block_object_assign
方法正惩孔眩拷貝
,因?yàn)?code>__block修飾的外界變量在底層是Block_byref結(jié)構(gòu)體
- 3.發(fā)現(xiàn)
外部變量
還存在一個(gè)對(duì)象
砸抛,從bref
中取出相應(yīng)的對(duì)象lj_name评雌,拷貝至block控件
,才能使用(相同空間才能使用直焙,不同則不能使用
)景东。最后通過內(nèi)存平移
得到lj_name
,此時(shí)的lj_name和外界lj_name是同一片內(nèi)存空間
(從_Block_object_assign方法中的*dest = object;看出)
三層copy總結(jié)
通過上面我們看出奔誓,block的三層拷貝指的是以下三層:
- 【第一層】通過
_Block_copy
實(shí)現(xiàn)對(duì)象的自身拷貝
,從棧區(qū)拷貝至堆區(qū)
- 【第二層】通過
_Block_byref_copy
方法斤吐,將對(duì)象拷貝
為Block_byref結(jié)構(gòu)體類型
- 【第三層】調(diào)用
_Block_object_assign
方法,對(duì)__block修飾
的當(dāng)前變量的拷貝
【注意】只有__block修飾的對(duì)象才三層copy
拓展
_Block_object_dispose 分析
在__Block_byref_id_object_dispose_131
實(shí)現(xiàn)中調(diào)用的就是_Block_object_dispose
厨喂,下面我們看下_Block_object_dispose的底層實(shí)現(xiàn):
下面看看_Block_byref_release實(shí)現(xiàn)通過源碼我們可以知道
_Block_object_dispose
是進(jìn)行release操作
和措,通過不同分區(qū)的block
,進(jìn)行不同的釋放操作
蜕煌。而_Block_object_assign
是進(jìn)行retain
操作的派阱,
寫到最后
寫的內(nèi)容比較多,由于本人能力有限斜纪,有些地方可能解釋的有問題贫母,請(qǐng)各位能夠指出,同時(shí)對(duì)Block有關(guān)的疑問盒刚,歡迎大家留言腺劣。希望大家能夠相互交流、探索伪冰,一起進(jìn)步誓酒!