iOS-block(二)-底層分析

前言

上一篇我們講完了block的基礎(chǔ)知識,這一篇我們就來看看block的底層原理。話不多說,我們創(chuàng)建一個testBlock.c的文件,輸入以下代碼:

#include "stdio.h"

int main() {
    int a = 5;
    void(^block)(void) = ^{
        printf("BLOCK_TEST==%d", a);
    };
    
    block();
    return 0;
}

然后對代碼編譯成.cpp文件,此時main函數(shù)就變成下面的樣子:

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int a;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int a = __cself->a; // bound by copy
    printf("BLOCK_TEST==%d", a);
}

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(){
    int a = 5;
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

使用clang指令的時候,需要注意不要使用字面量。

block分析

在對代碼的分析之前我們先拋出幾個問題:

  1. block的本質(zhì)到底是什么
  2. block為什么需要調(diào)用block()
  3. block是如何截獲外界變量的
  4. __block的實現(xiàn)

block本質(zhì)

Objective-C中,block是一個對象,從編譯的結(jié)果來看,block在本質(zhì)上還是一個結(jié)構(gòu)體struct。而我們通常所說的block是一個匿名函數(shù)也能提現(xiàn)出來,比如例子中,系統(tǒng)默認給block分配了一個函數(shù)名稱__main_block_impl_0,其參數(shù)分別是(void *)__main_block_func_0、__main_block_desc_0_DATAa;

調(diào)用block()

block的聲明是:

 ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a))

實現(xiàn)則是:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int a = __cself->a; // bound by copy 值拷貝
    printf("BLOCK_TEST==%d", a);
}

使用函數(shù)就需要調(diào)用,block的最終執(zhí)行調(diào)用就是block->FuncPtr(block)。

捕獲外界變量

通過編譯結(jié)果,可以看到__main_block_impl_0這個結(jié)構(gòu)體有一個int a的元素,而結(jié)構(gòu)體內(nèi)部的__main_block_impl_0函數(shù)有: a(_a)這么一個賦值過程。我們可以把源代碼中int a相關(guān)的代碼刪除,重新編譯,會發(fā)現(xiàn)得到的結(jié)果中并沒有相關(guān)的參數(shù)和賦值過程。這說明block捕獲外界變量的時候是自己創(chuàng)建一個同名變量將其進行復(fù)制操作,賦值之后,block內(nèi)部的變量已經(jīng)和外部變量沒有了關(guān)系。所以我們在block(一)-初探》中有一個例子:

typedef int(^myBlock)(int a, int b);

int d = 10;
myBlock mb = ^int(int a, int b) {
    return a + b + d;
};
d = 5;
NSLog(@"==myBlock==%d==", mb(1, 2)); // 13

而且我們可以看到,在block調(diào)用函數(shù)的時候,還會執(zhí)行一次創(chuàng)建臨時變量賦值的操作。int a = __cself->a,這又不是同一個變量。所以當(dāng)我們在block內(nèi)部直接對外部的變量進行操作(賦值)的時候,其實操作的內(nèi)部的同名臨時變量,而不是外部的變量。所以block無法直接給外部截獲的變量賦值,因為它在自己內(nèi)部生成了一個同名臨時變量,所有的操作都是內(nèi)部的臨時變量。那么我們需要在block內(nèi)部處理截獲的外部變量該怎么辦呢?答案是使用__block。

block源碼

通過源碼我們可以看出block在底層的結(jié)構(gòu)如下:

struct Block_layout {
    void *isa;              // isa指向
    volatile int32_t flags; // contains ref count 標志狀態(tài)
    int32_t reserved;
    BlockInvokeFunction invoke; // 函數(shù)執(zhí)行
    struct Block_descriptor_1 *descriptor; // block的附加描述信息 如size等
    // imported variables
};

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

flagsblock的狀態(tài)標志位。含義如下:

enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime 標記正在釋放
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime 存儲引用計數(shù)的值
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime 是否增加或減少引用計數(shù)的值
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler 是否擁有拷貝輔助函數(shù) 確定block是否存在Block_descriptor_2這個參數(shù)
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code 是否有C++析構(gòu)函數(shù)
    BLOCK_IS_GC =             (1 << 27), // runtime 是否有垃圾回收
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler 是否是全局block
    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_descriptor_3中的layout參數(shù)
};

另外還有兩個可選的參數(shù):

#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
};

這兩個參數(shù)是在某種條件下才會存在,Block_descriptor_2需要flagsBLOCK_HAS_COPY_DISPOSE才可以,而Block_descriptor_3需要flagsBLOCK_HAS_SIGNATURE。

了解了block的內(nèi)部結(jié)構(gòu),我們再來看看block從全局block、棧block到堆block是怎么變化的。
首先,我們實現(xiàn)一個全局block:

void(^myBlock)(void) = ^ {
    NSLog(@"==myBlock==");
};

block處設(shè)置一個斷點,進入?yún)R編,objc_retainBlock、我們在此時讀一下寄存器,繼續(xù)跳轉(zhuǎn)會進入,_Block_copy,在_Block_copyreturn處讀一下寄存器。結(jié)果如下圖所示:

image

這說明調(diào)用_Block_copy使得棧block變成了堆block。下面我們來看看其源碼:

// 傳入的對象
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // 如果需要對引用計數(shù)進行處理,那就直接處理,處理完就返回
        // block的引用計數(shù)是不由runtime下層處理,需要自己處理
        // 這個地方處理的是堆區(qū)block
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        // 如果是全局block 直接返回
        return aBlock;
    }
    else {
        // Its a stack block.  Make a copy
        // 棧區(qū)block 使用copy
        // 先在堆區(qū)初始化一塊內(nèi)存空間
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        // 將棧區(qū)的數(shù)據(jù)copy到堆區(qū)的空間
        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
        // 設(shè)置標志位
        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);
        // 設(shè)置為_NSConcreteMallocBlock
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

// 處理引用計數(shù)
static int32_t latching_incr_int(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return BLOCK_REFCOUNT_MASK;
        }
        if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
            return old_value+2;
        }
    }
}

從代碼,我們可以看出blockcopy動作主要如下:

  • 全局block不做任何事情直接返回
  • 堆區(qū)block增加引用計數(shù)然后返回
  • 棧區(qū)block
    • 在堆區(qū)申請一塊內(nèi)存空間
    • 將棧區(qū)的數(shù)據(jù)拷貝到堆區(qū)申請的空間
    • 給相關(guān)標志位賦值,對Block_descriptor_2copy動作,將isa設(shè)置為_NSConcreteMallocBlock
      block的引用計數(shù)是不由runtime下層處理,需要自己處理。

__block

下面我們再根據(jù)編譯的代碼來看看__block到底做了什么。先把下面代碼進行編譯:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        
        __block NSString *name = [NSString stringWithFormat:@"%@", @"AAA"];
        void(^myBlock)(void) = ^{
            name = @"BBB";
            NSLog(@"==name==%@==", name);
        };
        myBlock();
        
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

我們先來看看mian函數(shù)編譯之后的代碼:

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_name_0 name = {
            (void*)0,
            (__Block_byref_name_0 *)&name,
            33554432,
            sizeof(__Block_byref_name_0),
            __Block_byref_id_object_copy_131,
            __Block_byref_id_object_dispose_131,
            ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_nw_tqjtztpn1yq6w0_wmgdvn_vc0000gn_T_main_41740c_mi_0, (NSString *)&__NSConstantStringImpl__var_folders_nw_tqjtztpn1yq6w0_wmgdvn_vc0000gn_T_main_41740c_mi_1)
        };
        
        void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_name_0 *)&name, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

__block修飾的name對象被轉(zhuǎn)化成了一個__Block_byref_name_0的結(jié)構(gòu)體,其源碼如下:

struct Block_byref {
    void *isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep;
    BlockByrefDestroyFunction byref_destroy;
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};

可以看出,__Block_byref_name_0中的(void*)0就是Block_byrefvoid *isa(__Block_byref_name_0 *)&name即為struct Block_byref *forwarding__Block_byref_id_object_copy_131即為BlockByrefKeepFunction byref_keep函數(shù)。

上面我們講述了block從棧到堆的拷貝過程。下面再來看看__Block_byref_name_0的拷貝動作。在block里我們傳入了一個&__main_block_desc_0_DATA的結(jié)構(gòu)體地址,該結(jié)構(gòu)體在初始化的時候傳入了__main_block_copy_0方法進行拷貝操作,實現(xiàn)如下:

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};

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

enum {
    BLOCK_FIELD_IS_OBJECT   =  3,  // 截獲的是對象 __attribute__((NSObject)), block, ...
    BLOCK_FIELD_IS_BLOCK    =  7,  // 截獲的是block變量
    BLOCK_FIELD_IS_BYREF    =  8,  // 截獲的是__block修飾的對象
    BLOCK_FIELD_IS_WEAK     = 16,  // 截獲的是__weak修飾的對象
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};

// 根據(jù)傳入的對象的類型
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:
            _Block_retain_object(object);
            *dest = object;
            break;
        
        case BLOCK_FIELD_IS_BLOCK:
            *dest = _Block_copy(object);
            break;
        
        case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
        case BLOCK_FIELD_IS_BYREF:
            *dest = _Block_byref_copy(object);
            break;
        
        case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
        case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
            *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:
            *dest = object;
            break;
        
        default:
            break;
    }
}

分析上述源碼,可知,針對blockblock_byrefs截獲的對象的類型進行不同的內(nèi)存管理處理:

    1. 截獲的變量是對象,只需要賦值,引用計數(shù)不做任何處理,因為對象的引用計數(shù)是runtime底層自己處理的。
static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;
static void _Block_retain_object_default(const void *ptr __unused) { }
    1. 如果截獲的變量是block對象,調(diào)用_Block_copy方法。
void *_Block_copy(const void *arg) {
    // 詳見上面的分析
}
  1. 如果截獲的變量是__block對象,需要重新申請一塊堆內(nèi)存,然后將截獲的對象也就是上述例子中的__Block_byref_name_0結(jié)構(gòu)體賦值給新的結(jié)構(gòu)體,并將它們的forwarding指針都指向新生成的結(jié)構(gòu)體。其實也就是對__block修飾的對象做了一次拷貝動作,然后讓他們都指向同一塊內(nèi)存區(qū)域達到修改其中一個兩個都改變的目的。
static struct Block_byref *_Block_byref_copy(const void *arg) {
    // 創(chuàng)建一個臨時變量
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack 
        // 1.申請堆內(nèi)存空間
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        
        // 2. 給新申請的空間賦值
        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的對象和源對象都指向堆內(nèi)存的拷貝地址
        copy->forwarding = copy; // 堆拷貝指向自己
        src->forwarding = copy;  // ??截愔赶蚨褍?nèi)存
        copy->size = src->size;

        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
            // 處理desc2 內(nèi)存偏移取值 
            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) {
                // 處理desc3
                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 {
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    } 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)體__Block_byref_name_0中,還有一個名為__Block_byref_id_object_copy_131的方法,該方法的實現(xiàn)如下:

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

該方法依然是調(diào)用了_Block_object_assign方法,不過是傳遞的參數(shù)不同,是目標對象偏移了40字節(jié),由于此時目標對象就是__Block_byref_name_0,偏移40字節(jié)正好是NSString *name。也就是說此時又是對name這個對象的內(nèi)存地址做了一次拷貝。

struct __Block_byref_name_0 {
    void *__isa;                                        // 8
    __Block_byref_name_0 *__forwarding;                 // 8
    int __flags;                                        // 4
    int __size;                                         // 4
    void (*__Block_byref_id_object_copy)(void*, void*); // 8
    void (*__Block_byref_id_object_dispose)(void*);     // 8
    NSString *name;
};

所以,我們可以得出結(jié)論:__block修飾的外部變量,在block內(nèi)部能夠修改的主要原因在于3次拷貝。

    1. block的拷貝,從棧內(nèi)存到堆內(nèi)存。
    1. 對新生成的結(jié)構(gòu)體的拷貝。__block修飾的變量會生成一個名為__Block_byref_XXX_0結(jié)構(gòu)體,將原來的進行了封裝,然后把整個結(jié)構(gòu)體地址指針傳入block內(nèi)部。
    1. 對原來的對象的內(nèi)存的拷貝。

block的釋放

上面我們講過了block的持有過程,既然有持有,那就肯定有釋放。下面我們來看看block的釋放過程。

&__main_block_desc_0_DATA的定義中,會傳入__main_block_dispose_0這樣一個函數(shù)與void (*dispose)(struct __main_block_impl_0*)方法相對應(yīng)。其函數(shù)實現(xiàn)如下:

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

void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
        case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
        case BLOCK_FIELD_IS_BYREF:
            _Block_byref_release(object);
            break;
        case BLOCK_FIELD_IS_BLOCK:
            _Block_release(object);
            break;
        case BLOCK_FIELD_IS_OBJECT:
            _Block_release_object(object);
            break;
        case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
        case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
        case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
            break;
        default:
            break;
    }
}
    1. 需要釋放的如果是__block修飾的對象,判斷如果應(yīng)該釋放,則調(diào)用方法銷毀創(chuàng)建的__block結(jié)構(gòu)。
static void _Block_byref_release(const void *arg) {
    struct Block_byref *byref = (struct Block_byref *)arg;

    byref = byref->forwarding;
    
    if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
        int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
        os_assert(refcount);
        if (latching_decr_int_should_deallocate(&byref->flags)) {
            if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                (*byref2->byref_destroy)(byref);
            }
            free(byref);
        }
    }
}
    1. 需要釋放的如果是block,判斷是否該釋放,如果應(yīng)該釋放則調(diào)用釋放block的方法。
void _Block_release(const void *arg) {
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;
    if (aBlock->flags & BLOCK_IS_GLOBAL) return;
    if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;

    if (latching_decr_int_should_deallocate(&aBlock->flags)) {
        _Block_call_dispose_helper(aBlock);
        _Block_destructInstance(aBlock);
        free(aBlock);
    }
}
    1. 需要釋放的如果是對象,則什么都不用做,ARCruntime底層自己處理。
static void (*_Block_release_object)(const void *ptr) = _Block_release_object_default;

static void _Block_release_object_default(const void *ptr __unused) { }

block的簽名

我們在講述block的本質(zhì)的時候說了,block是匿名函數(shù),那么作為一個函數(shù),block是否也有自己的簽名。答案是肯定,上面我們的打印結(jié)果也體現(xiàn)出來了。

我們在講述block的源碼中提到block有兩個可選的參數(shù)Block_descriptor_2Block_descriptor_3。而block的簽名信息就放在Block_descriptor_3中,一個名為signature的元素。

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

從上面的打印結(jié)果可以看出block的簽名樣子如下:

signature: "v8@?0"

其中v表示返回值是void,@?表示未知的對象,即為block。這和方法簽名是有所不同的,方法簽名一般是v@:這樣的形式(此處只說返回值為void的場景),:表示SEL。

總結(jié)

  1. block的本質(zhì)是一個匿名函數(shù),也是一個對象,其底層實現(xiàn)是結(jié)構(gòu)體。
  2. block既有聲明,也有調(diào)用。
  3. block也有簽名,和方法簽名略有不同。
  4. block截獲外部變量是在定義的時候生成一個同名的中間變量,該變量的初值就是外部變量在被截獲的時候的值,之后就與外部變量沒有關(guān)系。
  5. __block修飾的外部變量在block內(nèi)部能夠修改的原因在于3次拷貝:
    • block的拷貝,從棧內(nèi)存到堆內(nèi)存
    • 將修飾的對象轉(zhuǎn)化為一個結(jié)構(gòu)體,將其拷貝到堆內(nèi)存。
    • 將修飾的對象的內(nèi)存地址也進行了拷貝用以修改。

參考文獻:

蘋果官方文檔
Objective-C高級編程 iOSOS X多線程和內(nèi)存管理》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Blocks Blocks Blocks 是帶有局部變量的匿名函數(shù) 截取自動變量值 int main(){ ...
    南京小伙閱讀 1,069評論 1 3
  • Block的底層基本結(jié)構(gòu) 通過clang命令查看編譯器是如何實現(xiàn)Block的,在終端輸入clang -rewrit...
    小涼介閱讀 20,453評論 22 96
  • Block概要 Block:帶有自動變量的匿名函數(shù)。 匿名函數(shù):沒有函數(shù)名的函數(shù),一對{}包裹的內(nèi)容是匿名函數(shù)的作...
    zweic閱讀 548評論 0 2
  • 一、block對對象變量的捕獲 block一般使用過程中都是對對象變量的捕獲,那么對象變量的捕獲同基本數(shù)據(jù)類型變量...
    iOS_進擊的小學(xué)生閱讀 268評論 0 1
  • js 里函數(shù)調(diào)用有4種模式:方法調(diào)用、正常函數(shù)調(diào)用、構(gòu)造器函數(shù)調(diào)用、apply/call 調(diào)用。無論哪種函數(shù)調(diào)用除...
    夢想怪閱讀 335評論 0 3

友情鏈接更多精彩內(nèi)容