Block原理探索

Block定義

閉包是一個函數(或指向函數的指針),再加上該函數執(zhí)行的外部的上下文變量(有時候也稱為自由變量)
block實際上就是OC對于閉包的實現。
block本質上也是一個OC對象枣氧,它內部也有個isa指針
block是封裝了函數調用以及函數調用環(huán)境的OC對象

Block結構分析

void blockTest()
{
    void (^block)(void) = ^{
        NSLog(@"Hello World!");
    };
    block();
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        blockTest();
    }
}

編譯后得到??:

struct __block_impl {
  void *isa;//isa指針,指向一個類對象懊缺,有三種類型:_NSConcreteStackBlock、_NSConcreteGlobalBlock某弦、_NSConcreteMallocBlock桐汤,可以看出這里使用的是_NSConcreteStackBlock
  int Flags;//Block的負載信息(引用計數和類型信息),按位存儲
  int Reserved;//保留變量
  void *FuncPtr;//一個指針靶壮,指向Block執(zhí)行時的函數怔毛,也就是Block需要執(zhí)行的代碼塊,在這里指向的是__blockTest_block_func_0函數
};

//通常包含兩個成員變量:__block_impl腾降、__blockTest_block_desc_0拣度,和一個構造函數
struct __blockTest_block_impl_0 {
  struct __block_impl impl;//
  struct __blockTest_block_desc_0* Desc;
  __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//__blockTest_block_func_0就是Block執(zhí)行調用的函數,參數是一個__blockTest_block_impl_0類型的指針
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_42229c_mi_0);
}

static struct __blockTest_block_desc_0 {
  size_t reserved;//Block版本升級所需預留區(qū)空間,這里為0
  size_t Block_size;//Block的大小抗果, sizeof(struct __blockTest_block_impl_0)
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};//是__blockTest_block_desc_0的一個實例

void blockTest()
{
    /**
     1.block的定義:通過__blockTest_block_impl_0結構體生成一個實例筋帖,并用一個指針指向了當前實例,
     __blockTest_block_impl_0q在初始化時需要兩個參數:
     __blockTest_block_func_0:Block塊的函數指針
     __blockTest_block_desc_0_DATA:作為靜態(tài)全局變量初始化__blockTest_block_desc_0_DATA結構體的實例指針
     */
    void (*block)(void) = (&__blockTest_block_impl_0(
                                                     __blockTest_block_func_0,
                                                     &__blockTest_block_desc_0_DATA)
                           );
    /**
     2.調用block:通過block)->FuncPtr找到__blockTest_block_func_0函數指針
     然后將block作為參數傳遞給這個函數
     */
    (block)->FuncPtr)(block);
}

int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        blockTest();
    }
}
  1. block的定義部分:
    block是一個結構體冤馏,該結構體需要兩個參數
    __blockTest_block_func_0:Block塊的函數指針日麸;
    __blockTest_block_desc_0_DATA:作為靜態(tài)全局變量初始化__blockTest_block_desc_0_DATA結構體的實例指針;
    通過__blockTest_block_impl_0結構體生成一個實例逮光,并用一個指針指向了當前實例代箭。
  2. block調用部分:
    通過block)->FuncPtr找到__blockTest_block_func_0函數指針,并將step1 block指針傳遞給該函數涕刚,

__blockTest_block_func_0就是block執(zhí)行時調用的函數嗡综,接收的參數是__blockTest_block_impl_0類型的指針,step1生成的就是__blockTest_block_impl_0結構體的實例

Flags(Block_private.h)
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // 釋放標記杜漠。一般常用 BLOCK_NEEDS_FREE 做 位與 操作极景,一同傳入 Flags ,告知該 block 可釋放驾茴。
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // 一般參與判斷引用計數盼樟,是一個可選用參數。
    BLOCK_NEEDS_FREE =        (1 << 24), // 通過設置該枚舉位沟涨,來告知該 block 可釋放恤批。意在說明 block 是 heap block 异吻,即我們常說的 _NSConcreteMallocBlock 裹赴。
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // 是否擁有拷貝輔助函數(a copy helper function)。
    BLOCK_HAS_CTOR =          (1 << 26), // 是否擁有 block 析構函數(dispose function)诀浪。
    BLOCK_IS_GC =             (1 << 27), // 是否啟用 GC 機制(Garbage Collection)棋返。
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30)  // 與 BLOCK_USE_STRET 相對,判斷是否當前 block 擁有一個簽名雷猪。用于 runtime 時動態(tài)調用睛竣。
};

Block結構如圖(網上借的):


Block

Block捕獲變量

捕獲auto變量(局部變量)

先看下面這段代碼:

void blockTest()
{
    int num = 10;
    void (^block)(void) = ^{
        NSLog(@"%d",num);
    };
    num = 20;
    block();
}

int main(int argc, char * argv[]) {
    @autoreleasepool {
        blockTest();
    }
}

num應該輸出什么?
答案:應該輸出10
編譯后的代碼??:

struct __blockTest_block_impl_0 {
  struct __block_impl impl;
  struct __blockTest_block_desc_0* Desc;
  int num;
  __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

void blockTest()
{
    int num = 10;
    void (*block)(void) = (&__blockTest_block_impl_0(
                                                     __blockTest_block_func_0,
                                                     &__blockTest_block_desc_0_DATA,
                                                     num)
                           );
    num = 20;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

__blockTest_block_impl_0結構體多了一個成員變量num求摇,
構造函數__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0)可以看到第三個參數num只是變量的值射沟,這就解釋了為什么num打印的是10,因為block捕獲auto變量時与境,捕獲的是其值验夯。

捕獲static變量

void blockTest()
{
    static int num = 10;
    void (^block)(void) = ^{
        NSLog(@"%d",num);
        num = 30;
    };
    num = 20;
    block();
    NSLog(@"%d",num);
}

兩次num分別輸出什么?
答案:block塊內的num輸出20摔刁,第二個num輸出30
編譯后??:

struct __blockTest_block_impl_0 {
  struct __block_impl impl;
  struct __blockTest_block_desc_0* Desc;
  int *num;
  __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int *_num, int flags=0) : num(_num) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
  int *num = __cself->num; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_a383ea_mi_0,(*num));
        (*num) = 30;
}

void blockTest()
{
    static int num = 10;
    void (*block)(void) = (&__blockTest_block_impl_0(
                                                     __blockTest_block_func_0,
                                                     &__blockTest_block_desc_0_DATA,
                                                     &num));
    num = 20;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_a383ea_mi_1,num);
}

構造函數__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int *_num, int flags=0)第三個參數*num傳入的是num的指針挥转,所以可以在內部和外部修改變量的值。

為什么auto變量就是傳遞的值,而static變量傳遞的是指針呢绑谣?

auto變量保存在棧中党窜,并且會隨著當前作用域(blockTest)消失而銷毀,有可能銷毀時機會比block更早借宵,所以block內訪問銷毀的變量時會產生問題幌衣,而static變量保存在全局存儲區(qū)(靜態(tài)存儲區(qū)),不會出現這樣的問題壤玫。

全局變量

int num = 10;

void blockTest()
{
    void (^block)(void) = ^{
        NSLog(@"%d",num);
        num = 30;
    };
    num = 20;
    block();
    NSLog(@"%d",num);
}

編譯后??:

int num = 10;


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

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_141607_mi_0,num);
        num = 30;
    }

static struct __blockTest_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)};
void blockTest()
{
    void (*block)(void) = (&__blockTest_block_impl_0(
                                                     __blockTest_block_func_0,
                                                     &__blockTest_block_desc_0_DATA)
                           );
    num = 20;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_141607_mi_1,num);
}



int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        blockTest();
    }
}

可以看到這里構造函數并沒有傳入變量的值或者指針泼掠,因為全局變量是直接可以訪問的。
總結一下:

變量類型 是否捕獲到block內部 訪問方式
auto變量 值訪問
static變量 指針訪問
全局變量 直接訪問

__block修飾的變量

1.在 block 內為什么不能修改 block 外部變量
答案:block 本質上是一個對象垦细,block 的花括號區(qū)域是對象內部的一個函數择镇,變量進入 花括號,實際就是已經進入了另一個函數區(qū)域---改變了作用域括改。在幾個作用域之間進行切換時腻豌,如果不加上這樣的限制,變量的可維護性將大大降低嘱能。又比如我想在block內聲明了一個與外部同名的變量吝梅,此時是允許呢還是不允許呢?只有加上了這樣的限制惹骂,這樣的情景才能實現苏携。詳解
2.除了使用static變量、全局變量外如何在block內改變變量的值对粪?為什么右冻?
答案:
使用__block;
static變量: block 內部對外部static修飾的變量進行指針捕獲著拭;
全局變量:block 內外可直接訪問全局變量纱扭;
__block變量:要想在block內部修改auto變量,需要兩個條件:
(1)從棧區(qū)拷貝到堆區(qū)(棧的內存是由系統管理儡遮,堆由我們管理乳蛾,其實在ARC下所有進入block內的auto變量都會被拷貝到堆區(qū)見這里)
(2)把auto變量包裝成結構體(對象),_block 作用是將 auto 變量封裝為結構體(對象)鄙币,在結構體內部新建一個同名 auto 變量肃叶,block 內截獲該結構體的指針,在 block 中使用自動變量時十嘿,使用指針指向的結構體中的自動變量因惭。于是就可以達到修改外部變量的作用。

總結一下就是如果想在block內修改變量:將 auto 從棧 copy 到堆详幽;將 auto 變量封裝為結構體(對象)

3.這三種修改變量值的方式哪個最好的筛欢?
這個問題請查看https://github.com/ChenYilong/iOSInterviewQuestions第38題浸锨,結論是__block是最優(yōu)解。

從源碼層面論證
void blockTest()
{
    __block int num = 10;
    void (^block)(void) = ^{
        NSLog(@"%d",num);
        num = 30;
    };
    num = 20;
    block();
    NSLog(@"%d",num);
}

編譯后??:

struct __Block_byref_num_0 {
  void *__isa;
__Block_byref_num_0 *__forwarding;
 int __flags;
 int __size;
 int num;
};

struct __blockTest_block_impl_0 {
  struct __block_impl impl;
  struct __blockTest_block_desc_0* Desc;
  __Block_byref_num_0 *num; // by ref
  __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
  __Block_byref_num_0 *num = __cself->num; // bound by ref

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_a71778_mi_0,(num->__forwarding->num));
        (num->__forwarding->num) = 30;
    }
static void __blockTest_block_copy_0(struct __blockTest_block_impl_0*dst, struct __blockTest_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __blockTest_block_dispose_0(struct __blockTest_block_impl_0*src) {_Block_object_dispose((void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __blockTest_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __blockTest_block_impl_0*, struct __blockTest_block_impl_0*);
  void (*dispose)(struct __blockTest_block_impl_0*);
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0), __blockTest_block_copy_0, __blockTest_block_dispose_0};
void blockTest()
{
    __attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 10};
    void (*block)(void) = ((void (*)())&__blockTest_block_impl_0(
                                                                 __blockTest_block_func_0,
                                                                 &__blockTest_block_desc_0_DATA,
                                                                 (__Block_byref_num_0 *)&num,
                                                                 570425344));
    (num.__forwarding->num) = 20;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_yz_sz8wz4q52_xf5dw0q_8nbgch0000gn_T_main_a71778_mi_1,(num.__forwarding->num));
}



int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        blockTest();
    }
}
__block修飾的變量的拷貝
static void __blockTest_block_copy_0(struct __blockTest_block_impl_0*dst, struct __blockTest_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}

注意:__block變量的結構體__Block_byref_num_0內有一個__Block_byref_num_0類型的指針 __forwarding版姑,而且獲取__Block_byref_num_0結構體時候都會使用__forwarding獲取柱搜,至于原因會在后面講

該方法有一個dst(接收拷貝完成的對象)指針和一個src對象(被拷貝的對象),并調用了方法_Block_object_assign方法剥险, _Block_object_assign需要三個參數聪蘸,分別是:dst->numsrc->num表制、和一個flags 8健爬,前兩個參數就是__Block_byref_num_0的對象,這里先看一下這個flags枚舉(Block_private.h):

// Runtime support functions used by compiler when generating copy/dispose helpers
enum {
    // see function implementation for a more complete description of these fields and combinations
    BLOCK_FIELD_IS_OBJECT   =  3,  //OC對象類型
    BLOCK_FIELD_IS_BLOCK    =  7,  //一個block變量
    BLOCK_FIELD_IS_BYREF    =  8,  // 在棧上被__block修飾的變量
    BLOCK_FIELD_IS_WEAK     = 16,  // 被__weak修飾的變量么介,只在Block_byref管理內部對象內存時使用
    BLOCK_BYREF_CALLER      = 128, // 處理Block_byref內部對象內存的時候會加的一個額外標記(告訴內部實現不要進行retain或者copy)
};

這里使用的是BLOCK_FIELD_IS_BYREF

_Block_object_assign(__block對象的copy)

接著通過runtime源碼查看該方法的實現(只截取了關鍵部分):

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

調用_Block_byref_copy方法把object(src)傳入到該函數娜遵,并返回到一個新的對象賦值給destdest就是新得到從棧上拷貝到堆上的新值壤短。

_Block_object_dispose(__block對象的釋放)
// When Blocks or Block_byrefs hold objects their destroy helper routines call this entry point
// to help dispose of the contents
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:
        // get rid of the __block data structure held in a Block
        _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;
    }
}

當需要釋放堆上的auto變量對象時设拟,調用_Block_byref_release釋放該對象

__block修飾的變量的包裝

被__block修飾的auto變量會被包裝成一個 __Block_byref_num_0的結構體,同樣擁有isa久脯,因此也是一個對象纳胧;

Block的內存管理

__block修飾的變量什么時候會被從棧拷貝到堆帘撰?

看這個問題之前我們先看一下我們先了解下Block的內存管理跑慕,
Block一共有三種類型:

NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )

他們都繼承自NSBlock,NSBlock繼承自NSObject
三種類型對應的內存分配以及調用copy后的效果如下:

Block類型 副本源的配置存儲域 copy效果
NSGlobalBlock 數據區(qū) 什么也不做
NSStackBlock 從棧復制到堆
NSMallocBlock 引用計數增加

有以下情況會把BLock從棿菡遥拷貝到堆:
1.調用Block的copy實例方法時
2.Block作為函數返回值返回時
3.在帶有usingBlock的Cocoa方法或者GCD的API中傳遞Block時候
4.將block賦給帶有__strong修飾符的id類型或者Block類型時
下面通過驗證一下第4種情況看下是否準確(前三種可自行測試):
無__strong修飾符的id類型或者Block類型核行,代碼??:

int num = 10;
NSLog(@"%@",[^{
        NSLog(@"%d",num);
 } class]);

打印結果:

__NSStackBlock__

有__strong修飾符的id類型或者Block類型,代碼??:

void (^block)(void) = ^{
        NSLog(@"%d",num);
    };
    NSLog(@"%@",[block class]);

打印結果:

__NSMallocBlock__

Block的拷貝時機介紹完了慰于,那么__block修飾的變量何時會從棧區(qū)拷貝到堆區(qū)呢钮科?
答案:當Block從椈缴溃拷貝到堆區(qū)的時候婆赠,__block變量也會跟著Block被拷貝到堆區(qū)。
驗證一下??佳励,
先看一下不拷貝到堆區(qū)的情況:

    __block int num = 10;
    NSLog(@"block前:%p",&num);
    ^{
        num = 20;
        NSLog(@"block內:%p",&num);
    }();
    NSLog(@"block后:%p",&num);

打印結果:

block前:0x7ffeea3f2c98
block內:0x7ffeea3f2c98
block后:0x7ffeea3f2c98

拷貝到堆區(qū)的情況:

    NSLog(@"block前:%p",&num);
    void (^block)(void) = ^{
        num = 20;
        NSLog(@"block內:%p",&num休里;
    };
    block();
    NSLog(@"block后:%p",&num);

打印:

block前:0x7ffee4af3c98
block內:0x600003650738
block后:0x600003650738

從內存地址不難看出不拷貝堆區(qū)時赃承,__block變量也不會進行拷貝妙黍,當Block從棧區(qū)拷貝到堆區(qū),__block變量也會進行拷貝

下面這段代碼為什么MRC和ARC下打印不一樣
    int num = 1;
    void (^block)(void) = ^{
        NSLog(@"%d",num);
    };
    NSLog(@"%@",[block class]);

MRC打忧破省:

__NSStackBlock__

ARC打邮眉蕖:

__NSMallocBlock__

原因:
由于在ARC環(huán)境下可免,使用strong修飾的變量指向block,會持有這個block做粤。因此臨時變量block會從棧復制到堆上

__forwarding指針存在的意義是什么浇借?

__forwarding指針是為了在__block變量從棧復制到堆上后,在Block外對__block變量的修改也可以同步到堆上實際存儲__block變量的結構體上怕品。

__forwarding.png

__forwarding確保不管是堆棧訪問__block變量結構體時都能訪問到同一個對象

Block捕獲對象

NSStackBlock

在棧上的Block不會對auto對象進行強引用妇垢;

NSMallocBlock

堆上的Block會對auto對象進行強引用,直到Block釋放時肉康,才解除對auto對象的強引用

typedef void(^Block)(void);

int main(int argc, char * argv[]) {
    Block block;
    {
        Person *person = [[Person alloc] init];
        person.name = @"toby";

        block = ^{
            NSLog(@"%@",person.name);
        };
        person.name = @"david";
        NSLog(@"即將退出person作用域");
    }
    NSLog(@"已經退出person作用域");
    block ();
}

打印結果:

即將退出person作用域
已經退出person作用域
david
-[Person dealloc]

Block的循環(huán)引用

什么情況下會造成循環(huán)引用闯估?

當一個對象person持有了了block對象,而block內又持有了person互相持有吼和,這就造成了循環(huán)引用

如何打破循環(huán)引用涨薪?

1.使用__block修飾對象person
2.使用__unsafe_unretained修飾對象person
3.使用__weak修飾對象person

__block

    __block Person *person = [[Person alloc] init];
    person.blockTest = ^{
        person.name = @"toby";
        person = nil;
    };
    person.blockTest();

需要在block內指定person=nil,并且需要調用調用block函數炫乓。
__unsafe_unretained

    Person *person = [[Person alloc] init];
    __unsafe_unretained typeof(person) weakPerson = person;
    weakPerson.blockTest = ^{
        weakPerson.name = @"toby";
    };

使用__unsafe_unretained雖然能解除循環(huán)引用尤辱,但是不安全,當指向對象銷毀時厢岂,指針存儲地址不變光督,如果再次訪問可能會造成懸垂指針??:

訪問懸垂指針.png

__weak

    Person *person = [[Person alloc] init];
    __weak typeof(person) weakPerson = person;
    weakPerson.blockTest = ^{
        weakPerson.name = @"toby";
    };

查看編譯后的源碼??:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__weak weakPerson;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

可以看到當前block內引用的是一個weak類型的person,對person的引用變成了弱引用塔粒,這就打破了雙向持有的局面结借,至于該weak person的釋放時機是由runtime維護的一個hash table決定的,當person對象dealloc時卒茬,會以person 地址當作鍵值在hash table中查找weak對象置為nil船老。runtime如何實現weak變量的自動置nil
但是實際應用會發(fā)現如果此時Block回調晚一些(異步線程執(zhí)行耗時任務),此時的person已經出作用域,在block內訪問weakPerson的時候就nil了圃酵。

    Person *person = [[Person alloc] init];
    __weak typeof(person) weakPerson = person;
    person.blockTest = ^{
        dispatch_async(dispatch_queue_create(0,DISPATCH_QUEUE_CONCURRENT), ^{
            [NSThread sleepForTimeInterval:3];
            NSLog(@"person = %@",weakPerson);
        });
    };
    person.blockTest();

此時的辦法就是在block內使用__strong再修飾一下weakPerson柳畔,讓person延遲釋放,至于釋放時機當然是block執(zhí)行完成

    Person *person = [[Person alloc] init];
    __weak typeof(person) weakPerson = person;
    person.blockTest = ^{
        __strong typeof(person) strongPerson = weakPerson;
        dispatch_async(dispatch_queue_create(0,DISPATCH_QUEUE_CONCURRENT), ^{
            [NSThread sleepForTimeInterval:3];
            NSLog(@"strong person = %@",strongPerson);
        });
    };
    person.blockTest();
}

總結

1.Block是一個對象
2.Block捕獲變量:
(1)auto變量:捕獲的是值
(2)static變量:捕獲指針
(3)global變量:無需捕獲郭赐,直接訪問
3.Block捕獲__block修飾的auto變量時薪韩,會把該變量包裝成一個對象,并會根據Block是否會被拷貝到堆區(qū)對auto變量進行拷貝捌锭,修改auto變量時需要滿足兩個條件:
(1)將 auto 從棧 copy 到堆俘陷;
(2)將 auto 變量封裝為結構體(對象)
4.Block有三種類型,他們都繼承自NSBlock->NSObject
NSGlobalBlock ( _NSConcreteGlobalBlock ) 數據區(qū)域
NSStackBlock ( _NSConcreteStackBlock ) 棧區(qū)
NSMallocBlock ( _NSConcreteMallocBlock ) 堆區(qū)
5.Block會被從椆矍拷貝到堆的情況:
(1)調用Block的copy實例方法時
(2)Block作為函數返回值返回時
(3)在帶有usingBlock的Cocoa方法或者GCD的API中傳遞Block時候
(4)將block賦給帶有__strong修飾符的id類型或者Block類型時
6. __forwarding指針是為了在__block變量從棧復制到堆上后拉盾,在Block外對__block變量的修改也可以同步到堆上實際存儲__block變量的結構體上。
7.在棧上的Block不會對auto對象進行強引用豁状;堆上的Block會對auto對象進行強引用捉偏,直到Block釋放時倒得,才解除對auto對象的強引用
8.解除Block的循環(huán)引用,最安全的方法是使用__weak修飾auto變量夭禽,并在block內部對auto變量進行__strong修飾

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末屎暇,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子驻粟,更是在濱河造成了極大的恐慌根悼,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜀撑,死亡現場離奇詭異挤巡,居然都是意外死亡,警方通過查閱死者的電腦和手機酷麦,發(fā)現死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門矿卑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人沃饶,你說我怎么就攤上這事母廷。” “怎么了糊肤?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵琴昆,是天一觀的道長。 經常有香客問我馆揉,道長业舍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任升酣,我火速辦了婚禮舷暮,結果婚禮上,老公的妹妹穿的比我還像新娘噩茄。我一直安慰自己下面,他們只是感情好,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布绩聘。 她就那樣靜靜地躺著沥割,像睡著了一般。 火紅的嫁衣襯著肌膚如雪君纫。 梳的紋絲不亂的頭發(fā)上驯遇,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機與錄音蓄髓,去河邊找鬼。 笑死舒帮,一個胖子當著我的面吹牛会喝,可吹牛的內容都是我干的陡叠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼肢执,長吁一口氣:“原來是場噩夢啊……” “哼枉阵!你這毒婦竟也來了?” 一聲冷哼從身側響起预茄,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤兴溜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后耻陕,有當地人在樹林里發(fā)現了一具尸體拙徽,經...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年诗宣,在試婚紗的時候發(fā)現自己被綠了膘怕。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡召庞,死狀恐怖岛心,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情篮灼,我是刑警寧澤忘古,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站诅诱,受9級特大地震影響存皂,放射性物質發(fā)生泄漏。R本人自食惡果不足惜逢艘,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一旦袋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧它改,春花似錦疤孕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鲜戒,卻和暖如春专控,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背遏餐。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工伦腐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人失都。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓柏蘑,卻偏偏與公主長得像幸冻,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子咳焚,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355