OC底層面試知識(shí)點(diǎn)之 —— Block底層原理!

本文將介紹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

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ǔ)多繼承的空白
  • NSProxyNSObject是同級(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)多繼承功能

通過LjProxy解決定時(shí)器中self的強(qiáng)引用問題

運(yù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在底層被編譯成了以下的形式

相當(dāng)于block等于__main_block_impl_0兔辅,是一個(gè)函數(shù)腊敲。下面查看__main_block_impl_0

通過上圖我們可以知道__main_block_impl_0是一個(gè)結(jié)構(gòu)體,同時(shí)可以說明block是一個(gè)__main_block_impl_0類型的對(duì)象维苔,這也是為什么block能夠%@打印的原因

我們用一張圖來說明他們之間的聯(lián)系

下面我們來解釋幾個(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

再將它編譯成.cpp文件

__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修飾

再將它編譯成.cpp文件

通過上面的截圖我們可以得出以下結(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ù)中寫如下代碼

通過在block處打斷點(diǎn),運(yùn)行block

我們發(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_DISPOSEBLOCK_HAS_SIGNATURE啤握。 BLOCK_HAS_COPY_DISPOSE 決定是否有Block_descriptor_2BLOCK_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_2Block_descriptor_3可選

我們?cè)倏聪滤麄兊讓訉?shí)現(xiàn)

從上圖可以知道:Block_descriptor_2Block_descriptor_3都是通過Block_descriptor_1的地址范删,經(jīng)過內(nèi)存平移得到的

Block內(nèi)存變化

根據(jù)符號(hào)斷點(diǎn)

我們打斷點(diǎn)運(yùn)行蕾域,走到objc_retainBlock,我們打印寄存器x0

我們發(fā)現(xiàn)此時(shí)的block全局block瓶逃,即__NSGlobalBlock__類型

我們?cè)黾油獠孔兞縜束铭,再次運(yùn)行,在相同的位置再次打印x0

此時(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

此時(shí)我們看到x0x8指向的是同一塊內(nèi)存空間端三,用于存儲(chǔ)__NSStackBlock__,此時(shí)的x9存儲(chǔ)的是_block_invoke

我們將代碼運(yùn)行到41行鹃彻,在次打印上面的地址

此時(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ū)blockor堆區(qū)block恶迈,由于堆區(qū)block需要申請(qǐng)空間涩金,前面并沒有申請(qǐng)空間的相關(guān)代碼,所以只能是棧區(qū)block
      • 通過malloc申請(qǐng)內(nèi)存空間用于接收block
      • 通過memmoveblock拷貝至新申請(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_OBJECTBLOCK_FIELD_IS_BYREF

而_Block_object_assign是在底層編譯代碼中将鸵,外部變量拷貝是調(diào)用的方法就是它∮悠模看下_Block_object_assign的源碼
  • 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ī)處理

我們看下_Block_byref_copy源碼實(shí)現(xiàn)
  • 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.其中copysrcforwarding指針都指向同一片內(nèi)存戳粒,這也是為什么__block修飾的對(duì)象具有修改能力的原因

代碼驗(yàn)證

寫如下代碼:

進(jìn)行clang編譯結(jié)果如下
  • 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_object_dispose是進(jìn)行release操作和措,通過不同分區(qū)的block,進(jìn)行不同的釋放操作蜕煌。而_Block_object_assign是進(jìn)行retain操作的派阱,

下面看看_Block_byref_release實(shí)現(xiàn)

下面我們畫圖來更容易的了解Block的三層copy的流程

寫到最后

寫的內(nèi)容比較多,由于本人能力有限斜纪,有些地方可能解釋的有問題贫母,請(qǐng)各位能夠指出,同時(shí)對(duì)Block有關(guān)的疑問盒刚,歡迎大家留言腺劣。希望大家能夠相互交流、探索伪冰,一起進(jìn)步誓酒!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市贮聂,隨后出現(xiàn)的幾起案子靠柑,更是在濱河造成了極大的恐慌,老刑警劉巖吓懈,帶你破解...
    沈念sama閱讀 212,222評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件歼冰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡耻警,警方通過查閱死者的電腦和手機(jī)隔嫡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門甸怕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人腮恩,你說我怎么就攤上這事梢杭。” “怎么了秸滴?”我有些...
    開封第一講書人閱讀 157,720評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵武契,是天一觀的道長。 經(jīng)常有香客問我荡含,道長咒唆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,568評(píng)論 1 284
  • 正文 為了忘掉前任释液,我火速辦了婚禮全释,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘误债。我一直安慰自己浸船,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,696評(píng)論 6 386
  • 文/花漫 我一把揭開白布找前。 她就那樣靜靜地躺著糟袁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪躺盛。 梳的紋絲不亂的頭發(fā)上项戴,一...
    開封第一講書人閱讀 49,879評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音槽惫,去河邊找鬼周叮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛界斜,可吹牛的內(nèi)容都是我干的仿耽。 我是一名探鬼主播,決...
    沈念sama閱讀 39,028評(píng)論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼各薇,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼项贺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起峭判,我...
    開封第一講書人閱讀 37,773評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤开缎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后林螃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奕删,經(jīng)...
    沈念sama閱讀 44,220評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,550評(píng)論 2 327
  • 正文 我和宋清朗相戀三年疗认,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了完残。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伏钠。...
    茶點(diǎn)故事閱讀 38,697評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖谨设,靈堂內(nèi)的尸體忽然破棺而出熟掂,到底是詐尸還是另有隱情,我是刑警寧澤铝宵,帶...
    沈念sama閱讀 34,360評(píng)論 4 332
  • 正文 年R本政府宣布打掘,位于F島的核電站华畏,受9級(jí)特大地震影響鹏秋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜亡笑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,002評(píng)論 3 315
  • 文/蒙蒙 一侣夷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧仑乌,春花似錦百拓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至厕九,卻和暖如春蓖捶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背扁远。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評(píng)論 1 266
  • 我被黑心中介騙來泰國打工俊鱼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人畅买。 一個(gè)月前我還...
    沈念sama閱讀 46,433評(píng)論 2 360
  • 正文 我出身青樓并闲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谷羞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子帝火,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,587評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容