iOS的OC的block底層原理(面試來復習下底層)

鏈接:https://juejin.im/post/6890071440998498311

前言

在iOS開發(fā)中精续,相信大家在開發(fā)中很頻繁使用block饿敲,使用block來作為參數(shù)输虱,屬性帘瞭,回調(diào)等等抄淑。雖然知道怎么使用block,但是block在底層的原理是怎樣的,應該還是有的人不是很清楚的氓癌,這篇文章就是主要介紹block的底層原理的。

1. Block的基礎

Block是一個OC的對象欧聘,它封裝了一段代碼,這段代碼可以在任何時候執(zhí)行端盆。Block可以作為函數(shù)參數(shù)或者函數(shù)的返回值怀骤,而其本身又可以帶輸入?yún)?shù)或返回值』烂睿可以嵌套定義蒋伦,可以定義在方法內(nèi)部和外部。

1.1 Block種類

在實際使用的Block種根據(jù)內(nèi)存情況焚鹊,可以將其分為3種痕届。

  • _NSConcreteGlobalBlock :全局Block韧献。
  • _NSConcreteMallocBlock:堆Block。
  • _NSConcreteStackBlock:棧Block(copy之前的)研叫。

通過一個小的demo可以分別打印出來

 void (^GlobalBlock)(void) = ^{
        NSLog(@"執(zhí)行GlobalBlock");
    };
    int a = 10;
    void (^MallocBlock)(void) = ^{
        NSLog(@"執(zhí)行MallocBlock- %d",a);
    };
    GlobalBlock();
    MallocBlock();
    NSLog(@"我是全局block--%@",GlobalBlock);
    NSLog(@"我是堆block--%@",MallocBlock);
    NSLog(@"我是棧%@",^{
        NSLog(@"執(zhí)行StackBlock--%d",a);
    });

//打印結(jié)果===================
執(zhí)行GlobalBlock
執(zhí)行MallocBlock- 10
我是全局block--<__NSGlobalBlock__: 0x10c443090>
我是堆block--<__NSMallocBlock__: 0x600003b937b0>
我是棧<__NSStackBlock__: 0x7ffee37bb458>

但是Block的種類有6種锤窑,另外3種是系統(tǒng)級別的很少用到。Block的種類如下:

void * _NSConcreteStackBlock[32] = { 0 };
void * _NSConcreteMallocBlock[32] = { 0 };
void * _NSConcreteAutoBlock[32] = { 0 };
void * _NSConcreteFinalizingBlock[32] = { 0 };
void * _NSConcreteGlobalBlock[32] = { 0 };
void * _NSConcreteWeakBlockVariable[32] = { 0 };

1.2 Block循環(huán)引用

在使用Block的時候嚷炉,最容易遇到的就是循環(huán)引用這種錯誤了渊啰。

@property (nonatomic, copy) NSString *name;
@property(nonatomic,copy) void(^testBlock)(void);

    self.name = @"jason";
    self.testBlock = ^{
        NSLog(@"%@",self.name);
    };
    self.testBlock();

其實在Xcode上寫這段代碼的時候也會直接報

Capturing 'self' strongly in this block is likely to lead to a retain cycle

為什么會循環(huán)引用呢?因為self持有了block申屹,block持有了self(self.name),就形成了self->block->self這樣的閉環(huán)绘证,然后導致循環(huán)引用。一般情況下哗讥,為了避免引起循環(huán)引用會加一個__weak來修飾嚷那。

    self.name = @"jason";
    __weak typeof(self) weakSelf = self;
    self.testBlock = ^{
        NSLog(@"%@",weakSelf.name);
    };
    self.testBlock();

此時的weakSelf是在一張弱引用表里面的,此時的持有狀態(tài)是weakSelf持有了self,self持有了block,block持有了weakSelf杆煞,這時候看上去是不是還是一個閉環(huán)魏宽?但是weakSelf持有self的時候引用次數(shù)并沒有處理的就是數(shù)量沒有增加的,所以此時正常還是會執(zhí)行到析構(gòu)函數(shù)(delloc)决乎。只是指向了self而已湖员。如果在testBlock加一個延遲的然后再打印,此時再打印出來的是weakSelf.name是一個nil

    self.name = @"jason";
    __weak typeof(self) weakSelf = self;
    self.testBlock = ^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            NSLog(@"%@",weakSelf.name);
        });
    };
    self.testBlock();

因為在延遲的過程中瑞驱,selfdelloc之后會被回收,此時的weakSelf就會被設置為nil了窄坦。為了防止這種情況發(fā)生唤反,可以加一個__strong的修飾

       __strong typeof(weakSelf) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
            NSLog(@"%@",strongSelf.name);
        });

此時相當于一個臨時的strong來持有了weakSelf,在strong還沒被銷毀的情況下weakSelf也不會銷毀,只有打印完之后才會銷毀鸭津。

2.Block的本質(zhì)

為了方便接下來的介紹彤侍,創(chuàng)建一個block.c文件,通過clang來查看block的底層源碼分析逆趋,其中block.c的源碼如下

#include "stdio.h"
int main(){
    void(^block)(void) = ^{
        printf("TestJason");
    };
    block();
    return 0;
}

在該文件的目錄下盏阶,用終端輸入命令行,就會在該目錄下生成一個block.cpp的文件

clang -rewrite-objc block.c -o block.cpp 

通過block.cpp文件可以看到部分的源碼

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        printf("TestJason");
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(){
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

由源碼可以知道__main_block_impl_0是一個結(jié)構(gòu)體,也就是一個對象闻书。其中在__main_block_impl_0這個構(gòu)造函數(shù)中傳進去的__main_block_func_0是一個函數(shù)并且是保存在impl.FuncPtr里面的名斟。在main里面的block->FuncPtr函數(shù)調(diào)用其實就是調(diào)用__main_block_func_0這個函數(shù),所以block在聲明之后是需要調(diào)用才可以實現(xiàn)的魄眉。

2.1 Block捕獲外部變量

還是用源碼的代碼砰盐,但是加多了一個變量int a,用clang重新來生成cpp文件

int main(){
    int a = 10;
    void(^block)(void) = ^{
        printf("TestJason---%d",a);
    };
    block();
    return 0;
}

此時生成的文件里面的源碼與上面的有點不一樣了坑律,可以看一下紅色圈出來的部分岩梳,此時的__main_block_impl_0的構(gòu)造函數(shù)變了,并且自動生成了一個int a這個屬性來捕獲外界的變量。

image

從源碼可以看到冀值,在__main_block_func_0函數(shù)中傳進來的__cself其實就是main函數(shù)里面生命的block,所以__main_block_func_0函數(shù)的int a = __cself->a的值是10也物,這時候其實就是一個值拷貝,此時的int a__cself->a不是同一個a列疗,只是指向同一個值而已滑蚯。所以如果在block中對a值進行修改(加或者減),其實就是對里面定義的int a進行修改作彤,并不會對main函數(shù)里面的a有修改的膘魄。

2.2 Block修改外部變量

為了修改外部變量就在mian函數(shù)中添加__block,源碼如下

int main(){
    __block int a = 10;
    void(^block)(void) = ^{
        a++;
        printf("TestJason---%d",a);
    };

    block();
    return 0;
}

還是通過clang來生成block.cpp的文件來看底層源碼

image

image

從源碼可以看到此時的a已經(jīng)是一個__Block_byref_a_0的結(jié)構(gòu)體,并且在main函數(shù)中初始化賦值竭讳,并且傳入__main_block_func_0函數(shù)的值中是__Block_byref_a_0這個結(jié)構(gòu)體的指針创葡,里面的定義的a__Block_byref_a_0 *a = __cself->a;,此時這是指針的拷貝,在對a進行修改的時候是可以修改得到main函數(shù)的a的绢慢。所以加了__block修飾灿渴,生成了相應的結(jié)構(gòu)體保存原始的指針和值,并且傳遞了一個指針地址給執(zhí)行的函數(shù)胰舆。

2.3 Block的內(nèi)存變化

通過上面的介紹可以知道骚露,Block有棧block,堆block缚窿,但是block如何從棧block轉(zhuǎn)變?yōu)槎裝lock的呢棘幸?這個是接下來要介紹的。在ViewController中實現(xiàn)如下代碼并開啟匯編的模式倦零。

- (void)viewDidLoad {
    [super viewDidLoad];
    int a = 10;

    void (^block1)(void) = ^{
        NSLog(@"JS_Block===%d",a);
    };
    block1();
}

進入到如下的界面误续,并且為objc_retainBlock打一個符號斷點,進入到里面去扫茅。

image

通過查看寄存器可以看到蹋嵌,此時的block還是一個棧block
image

_Block_copy打一個符號斷點,進入到里面去葫隙,在返回的時候打一個斷點栽烂,此時可以通過查看寄存器知道,block變?yōu)榱硕裝lock恋脚。

image

由圖可以知道棧block是通過_Block_copy這個函數(shù)變?yōu)槎裝lock的腺办。

3.Block簽名

在編譯出來的clang文件中

// Runtime copy/destroy helper functions (from Block_private.h)
#ifdef __OBJC_EXPORT_BLOCKS
extern "C" __declspec(dllexport) void _Block_object_assign(void *, const void *, const int);
extern "C" __declspec(dllexport) void _Block_object_dispose(const void *, const int);
extern "C" __declspec(dllexport) void *_NSConcreteGlobalBlock[32];
extern "C" __declspec(dllexport) void *_NSConcreteStackBlock[32];
#else
__OBJC_RW_DLLIMPORT void _Block_object_assign(void *, const void *, const int);
__OBJC_RW_DLLIMPORT void _Block_object_dispose(const void *, const int);
__OBJC_RW_DLLIMPORT void *_NSConcreteGlobalBlock[32];
__OBJC_RW_DLLIMPORT void *_NSConcreteStackBlock[32];

從注釋中可以知道,block的底層源碼是從Block_private.h這個文件來的糟描,而這個文件是在libclosure源碼中的菇晃。

首先來看block結(jié)構(gòu)體對象Block_layout源碼,這個相當于clang出來的__main_block_impl_0結(jié)構(gòu)體

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    BlockCopyFunction copy;
    BlockDisposeFunction dispose;
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor; //
    // imported variables
};

其中Block_layoutflags是記錄狀態(tài)的蚓挤,BlockInvokeFunction invoke是調(diào)用的函數(shù)磺送,在Block_layout中驻子,Block_descriptor_2Block_descriptor_3以結(jié)構(gòu)體的形式存在是可選的。為什么說這兩個是可選的呢估灿?由下面的源碼可以說明

static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}

static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
        desc += sizeof(struct Block_descriptor_2);
    }
    return (struct Block_descriptor_3 *)desc;
}

_Block_descriptor_2的值先用flags & BLOCK_HAS_COPY_DISPOSE,不存在就返回NULL崇呵,如果有就進行內(nèi)存偏移,先通過aBlock->descriptor的得到BLOCK_DESCRIPTOR_1,然后內(nèi)存偏移得到Block_descriptor_2馅袁。而Block_descriptor_3的值先用flags & BLOCK_HAS_SIGNATURE不存在就返回NULL,如果有先判斷_Block_descriptor_2是否存在域慷,并且也是通過內(nèi)存偏移來得到的,其中簽名是在Block_descriptor_3中的汗销。其中flags的狀態(tài)如下

// 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_NEEDS_FREE走位與操作一同傳入flags,告知該block可釋放。
  • 第16位:存儲引用計數(shù)的值弛针,是一個可選參數(shù)叠骑。
  • 第24位:第16位是否有效的標記,程序根據(jù)它來是否增加或者減少引用計數(shù)位的值削茁。
  • 第25位:是否擁有拷貝輔助函數(shù)宙枷。
  • 第26位:是否擁有block的析構(gòu)函數(shù)。
  • 第27位:標記是否有垃圾回收茧跋。
  • 第28位:標記是否是全局block慰丛。
  • 第29位:與BLOCK_USE_START相對,判斷當前block是否擁有一個簽名瘾杭,用于runtime時動態(tài)調(diào)用诅病。
  • 第30位:是否有簽名
  • 第31位:是否有擴展決定Block_descriptor_3

通過查看寄存器
image

block的簽名是在signature值,其中簽名是@?粥烁。

4.Block的3次copy

從第二部分的內(nèi)容贤笆,可以知道,block從棧block變?yōu)槎裝lock是通過了_Block_copy函數(shù)操作完成的页徐,以下就是該函數(shù)的源碼:

// Copy, or bump refcount, of a block.  If really copying, call the copy helper if present.
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;

    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // Its a stack block.  Make a copy.
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

從源碼中可以看到ifelse if的條件判斷分別是引用計數(shù)全局block的判斷,如果是都直接返回block出去银萍。else的條件判斷就是棧blockcopy操作变勇。通過aBlock開啟一個新的result空間,并且將aBlock的內(nèi)容等平移到result中去贴唇。這時候就實現(xiàn)了由棧變成堆搀绣。但是具體是怎么做到從棧block到堆block的還是不是很清楚,這里面是不是隱藏是一些細節(jié)呢戳气?

4.1clang查看源碼

mian.m文件中寫下如下代碼:

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
        __block NSString *js_name = [NSString stringWithFormat:@"jason"];
        void (^block1)(void) = ^{ // block_copy
            js_name = @"js_jason";
            NSLog(@"JS_Block - %@",js_name);
        };
        block1();
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

通過終端clang命令:xcrun -sdk iphonesimulator clang -rewrite-objc main.m 會生成一個mian.cpp的文件链患,從這個文件中查看源碼,從中可以看到js_name會生成一個__Block_byref_js_name_0這個類型,在源文件中可以看到這個類型的結(jié)構(gòu):

struct __Block_byref_js_name_0 {
  void *__isa;
__Block_byref_js_name_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *js_name;
};

main.cpp文件可以看到有兩個函數(shù)

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->js_name, (void*)src->js_name, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->js_name, 8/*BLOCK_FIELD_IS_BYREF*/);
}

這就驗證了Block_descriptor_2中分別有copy和dispose函數(shù)瓶您,其中__main_block_copy_0還調(diào)用了_Block_object_assign函數(shù)麻捻。通過源碼可以找到這個函數(shù)的調(diào)用

// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
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_OBJECT:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/

        _Block_retain_object(object);
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/

        *dest = _Block_copy(object);
        break;

      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;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held in the __block container
         // Note this is MRC unretained __block only. 
         // ARC retained __block is handled by the copy helper directly.
         __block id object;
         __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /*******
         // copy the actual field held in the __block container
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __weak __block id object;
         __weak __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      default:
        break;
    }
}

這個段代碼的就是當block或者Block_byrefs持有對象纲仍,就會通過這段代碼來進行分配復制涨薪,從上面的代碼可以知道在執(zhí)行copy函數(shù)調(diào)用_Block_object_assign會將js_name的對象傳進來养筒。并且switch里面的枚舉如下

// 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變量
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable __block修飾的結(jié)構(gòu)體
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers __weak修飾的變量
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines. 處理Block_byref內(nèi)部對象內(nèi)存的時候會加的一個額外標記,配合上面的枚舉使用
};

因為當前是__block修飾的就會執(zhí)行到_Block_byref_copy函數(shù)里面去,傳進去的是一個結(jié)構(gòu)體.

static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;

        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy

        copy->size = src->size;

        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }

            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }

    return src->forwarding;
}

這段代碼大概的意思是將傳進去的結(jié)構(gòu)體重新創(chuàng)建一個产禾,并且賦值相同的內(nèi)存大小通過

        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
復制代碼

指向相同的地址明棍,這就使得__block有了修改的能力乡革。最后通過滿足的條件會執(zhí)行到byref_keep函數(shù),而這個函數(shù)恰恰是之前clang出來的__Block_byref_js_name_0結(jié)構(gòu)體里面的__Block_byref_js_name_0函數(shù)

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

此時會再次執(zhí)行_Block_object_assign函數(shù)摊腋,為什么會加上40呢沸版?可以從上面得到的__Block_byref_js_name_0結(jié)構(gòu)體的內(nèi)存偏移可以知道,40是直接找到NSString js_name值兴蒸。

struct __Block_byref_js_name_0 {
  void *__isa;
__Block_byref_js_name_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *js_name;
};

此時就是單純的對象的內(nèi)存copy了视粮,至此block的三層copy就完成了。這里就是先通過_Block_copy進行一層copy类咧,然后通過__main_block_copy_0函數(shù)執(zhí)行_Block_object_assign函數(shù)馒铃,此時進去的是__block的狀態(tài)進去的,執(zhí)行到_Block_byref_copy函數(shù)進行第二層的copy痕惋。之后會執(zhí)行(*src2->byref_keep)(copy, src);就是生成的__Block_byref_id_object_copy_131函數(shù)区宇,再次執(zhí)行 _Block_object_assign函數(shù),此時進去就是通過內(nèi)存偏移找到js_name的對象進去進行copy的值戳。

5.最后

這篇文章介紹了block的基礎议谷,通過底層源碼來分析了block的內(nèi)存變化,由全局block變?yōu)闂lock堕虹,再到堆block的過程卧晓。找到了block的簽名是@?,從底層分析了block通過三層copy可以用__block對變量值的修改赴捞。有一個學習的氛圍跟一個交流圈子特別重要逼裆,這是一個我的iOS交流群:891488181 不管你是大牛還是小白都歡迎入駐 ,分享BAT,阿里面試題赦政、面試經(jīng)驗胜宇,討論技術(shù), 大家一起交流學習成長恢着!

文章到這里就結(jié)束了桐愉,你也可以私信我及時獲取面試相關資料。如果你有什么意見和建議歡迎給我留言掰派。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末从诲,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子靡羡,更是在濱河造成了極大的恐慌系洛,老刑警劉巖俊性,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異碎罚,居然都是意外死亡磅废,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門荆烈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拯勉,“玉大人,你說我怎么就攤上這事憔购」停” “怎么了?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵玫鸟,是天一觀的道長导绷。 經(jīng)常有香客問我,道長屎飘,這世上最難降的妖魔是什么妥曲? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮钦购,結(jié)果婚禮上檐盟,老公的妹妹穿的比我還像新娘。我一直安慰自己押桃,他們只是感情好葵萎,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著唱凯,像睡著了一般羡忘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上磕昼,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天卷雕,我揣著相機與錄音,去河邊找鬼票从。 笑死漫雕,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的纫骑。 我是一名探鬼主播蝎亚,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼九孩,長吁一口氣:“原來是場噩夢啊……” “哼先馆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起躺彬,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤煤墙,失蹤者是張志新(化名)和其女友劉穎梅惯,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仿野,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡铣减,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了脚作。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片葫哗。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖球涛,靈堂內(nèi)的尸體忽然破棺而出劣针,到底是詐尸還是另有隱情,我是刑警寧澤亿扁,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布捺典,位于F島的核電站,受9級特大地震影響从祝,放射性物質(zhì)發(fā)生泄漏襟己。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一牍陌、第九天 我趴在偏房一處隱蔽的房頂上張望擎浴。 院中可真熱鬧,春花似錦呐赡、人聲如沸退客。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽萌狂。三九已至,卻和暖如春怀泊,著一層夾襖步出監(jiān)牢的瞬間茫藏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工霹琼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留务傲,地道東北人。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓枣申,卻偏偏與公主長得像售葡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子忠藤,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

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