iOS Block 部分三

主要講解 Block內(nèi)修改外部變量, 內(nèi)存管理, 以及循環(huán)引用;

Block部分一
Block部分二
Block部分三
Block知識(shí)點(diǎn)總結(jié)

以下內(nèi)容的測(cè)試主要針對(duì)ARC環(huán)境; MRC下直接貼出測(cè)試結(jié)果, 不再貼出測(cè)試代碼, 具體請(qǐng)自行測(cè)試MRC環(huán)境;

1. __block的用法(基礎(chǔ)類型)

當(dāng)block內(nèi)部需要修改外部變量時(shí)()
全局變量, 全局靜態(tài)變量可以直接進(jìn)行修改, 因?yàn)?code>block內(nèi)部不對(duì)他們進(jìn)行捕獲;
靜態(tài)局部變量也可以直接在block內(nèi)部修改, 因?yàn)?block捕獲了它的地址;
auto變量則不能直接在block內(nèi)部修改; 需要用__block修飾才行;


先從底層結(jié)構(gòu)代碼看下為什么auto變量為什么不能直接在block內(nèi)部修改;
首先我們知道int c = 3整個(gè)變量的作用域就是viewDidLoad這個(gè)方法, 出了這個(gè)方法后就不能再訪問;
底層代碼如下:

///viewDidLoad的底層代碼如下
static void _I_ViewController3_viewDidLoad(ViewController3 * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController3"))}, sel_registerName("viewDidLoad"));
     int c = 3;
    static int d = 4;
    void(*Case3Block)(void) = ((void (*)())&__ViewController3__viewDidLoad_block_impl_0((void *)__ViewController3__viewDidLoad_block_func_0, &__ViewController3__viewDidLoad_block_desc_0_DATA, c, &d));

    ((void (*)(__block_impl *))((__block_impl *)Case3Block)->FuncPtr)((__block_impl *)Case3Block);
}

block內(nèi)部執(zhí)行的代碼是封裝在這個(gè)函數(shù)中, 從明面即可得知, 這個(gè)函數(shù)中并不能訪問另一個(gè)函數(shù)的auto變量, 這個(gè)函數(shù)內(nèi)部里面的變量c; 是block底層的結(jié)構(gòu)體重新創(chuàng)建的變量 int c = __cself->c;

static void __ViewController3__viewDidLoad_block_func_0(struct __ViewController3__viewDidLoad_block_impl_0 *__cself) {
  int c = __cself->c; // bound by copy
  int *d = __cself->d; // bound by copy
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController3_2637aa_mi_0, a,b,c,(*d));
    }

為什么用__block修飾后的auto變量就可以在block內(nèi)部修改了呢?

下面代碼, 通過(guò)__block修飾后就可以在block內(nèi)部修改;

@implementation ViewController3
- (void)viewDidLoad {
    [super viewDidLoad];
  __block    int XXXX = 3;
    void(^Case3Block)(void) =  ^{
        XXXX = 300;
    };
    NSLog(@"XXXX = %d", XXXX);
    Case3Block();
    NSLog(@"XXXX = %d", XXXX);
}
@end

2020-06-27 15:27:21.694208+0800 BlockMore2[31764:1243071] XXXX = 3
2020-06-27 15:27:21.694316+0800 BlockMore2[31764:1243071] XXXX = 300

底層的結(jié)構(gòu)為

///viewDidLoad的底層
static void _I_ViewController3_viewDidLoad(ViewController3 * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController3"))}, sel_registerName("viewDidLoad"));
  ///變量XXXX被封裝成了一個(gè)對(duì)象
  __attribute__((__blocks__(byref))) __Block_byref_XXXX_0 XXXX = {(void*)0,(__Block_byref_XXXX_0 *)&XXXX, 0, sizeof(__Block_byref_XXXX_0), 3};
   ///block的底層
 void(*Case3Block)(void) = ((void (*)())&__ViewController3__viewDidLoad_block_impl_0((void *)__ViewController3__viewDidLoad_block_func_0, &__ViewController3__viewDidLoad_block_desc_0_DATA, (__Block_byref_XXXX_0 *)&XXXX, 570425344));
     ///NSlog刪掉了
    ///block的調(diào)用
    ((void (*)(__block_impl *))((__block_impl *)Case3Block)->FuncPtr)((__block_impl *)Case3Block);
     ///NSlog刪掉了
}
===>
///block的底層結(jié)構(gòu)
struct __ViewController3__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController3__viewDidLoad_block_desc_0* Desc;
  ///將XXXX變量封裝成一個(gè)對(duì)象
  __Block_byref_XXXX_0 *XXXX; // by ref
  __ViewController3__viewDidLoad_block_impl_0(void *fp, struct __ViewController3__viewDidLoad_block_desc_0 *desc, __Block_byref_XXXX_0 *_XXXX, int flags=0) : XXXX(_XXXX->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
===>
///變量XXXX的封裝對(duì)象, 底層也是一個(gè)結(jié)構(gòu)體
struct __Block_byref_XXXX_0 {
///isa指針
  void *__isa;
///指向自身的一個(gè)指針
__Block_byref_XXXX_0 *__forwarding;
///flags: 他的作用就是傳入不同的值進(jìn)行不同的操作;
 int __flags;
///結(jié)構(gòu)體的size
 int __size;
///變量XXXX
 int XXXX;
};

===>
///封了block內(nèi)部執(zhí)行代碼塊的函數(shù)
static void __ViewController3__viewDidLoad_block_func_0(struct __ViewController3__viewDidLoad_block_impl_0 *__cself) {
  __Block_byref_XXXX_0 *XXXX = __cself->XXXX; // bound by ref
        ///通過(guò)__forwarding指針訪問XXXX變量
        (XXXX->__forwarding->XXXX) = 300;
    }
===>
注意:
///block的描述信息的函數(shù)發(fā)生了變化, 多了兩個(gè)函數(shù)
static struct __ViewController3__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  ///ARC下棧block會(huì)被拷貝到堆上, 這個(gè)過(guò)程會(huì)降封裝的變量也進(jìn)行拷貝;
  void (*copy)(struct __ViewController3__viewDidLoad_block_impl_0*, struct __ViewController3__viewDidLoad_block_impl_0*);
///當(dāng)block執(zhí)行完畢從堆上移出時(shí),會(huì)調(diào)用dispose函數(shù)對(duì)所持有的對(duì)象進(jìn)行類似release操作;
  void (*dispose)(struct __ViewController3__viewDidLoad_block_impl_0*);
} __ViewController3__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController3__viewDidLoad_block_impl_0), __ViewController3__viewDidLoad_block_copy_0, __ViewController3__viewDidLoad_block_dispose_0};

===>
///copy函數(shù)
static void __ViewController3__viewDidLoad_block_copy_0(struct __ViewController3__viewDidLoad_block_impl_0*dst, struct __ViewController3__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->XXXX, (void*)src->XXXX, 8/*BLOCK_FIELD_IS_BYREF*/);}
===>
///dispose函數(shù)
static void __ViewController3__viewDidLoad_block_dispose_0(struct __ViewController3__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->XXXX, 8/*BLOCK_FIELD_IS_BYREF*/);}

總結(jié):當(dāng)用__block修飾的auto變量(基礎(chǔ)類型)使用block的過(guò)程為:

  • 首先被__block修飾的變量在底層會(huì)被封裝成__Block_byref_XXXX_0的對(duì)象變量;
  • 當(dāng)block被拷貝大堆上時(shí)會(huì)調(diào)用內(nèi)部的copy函數(shù), copy函數(shù)會(huì)通過(guò)_Block_object_assign函數(shù)對(duì)封裝的對(duì)象進(jìn)行強(qiáng)引用;
  • 當(dāng)block執(zhí)行完/從堆上移出時(shí), 會(huì)調(diào)用block內(nèi)部的dispose函數(shù), 其內(nèi)部調(diào)用_Block_object_dispose函數(shù)對(duì)結(jié)構(gòu)體持有的對(duì)象進(jìn)行類似release的釋放操作;

2. __block的用法(對(duì)象類型)

首先弄清楚下面兩個(gè)操作的不同之處;為什么第一個(gè)會(huì)報(bào)錯(cuò)第二個(gè)不報(bào)錯(cuò)呢;



因?yàn)槟抢锏?code>arr操作是訪問它的方法或者屬性, 并不是修改它; 如果arr = nil也是會(huì)報(bào)錯(cuò)的;
首先看下方代碼

- (void)viewDidLoad {
    [super viewDidLoad];
    __block TestObject *obj = [[TestObject alloc] init];
    void(^Case4Block)(void) = ^ {
        obj = nil;
    };
    NSLog(@"obc = %@", obj);
    Case4Block();
    NSLog(@"obc = %@", obj);
}

2020-06-27 16:29:03.310124+0800 BlockMore2[32512:1277607] obc = <TestObject: 0x600002e5e920>
2020-06-27 16:29:03.310238+0800 BlockMore2[32512:1277607] obc = (null)

通過(guò)指令后獲得底層代碼

///__Block_byref_obj_0封裝對(duì)象結(jié)構(gòu)
struct __Block_byref_obj_0 {
  void *__isa;
__Block_byref_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
///對(duì)obj強(qiáng)引用
 TestObject *__strong obj;
};
///block結(jié)構(gòu)
struct __ViewController4__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController4__viewDidLoad_block_desc_0* Desc;
  ///obj對(duì)象被封裝成__Block_byref_obj_0對(duì)象, block對(duì)其強(qiáng)引用
  __Block_byref_obj_0 *obj; // by ref
  __ViewController4__viewDidLoad_block_impl_0(void *fp, struct __ViewController4__viewDidLoad_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

弱引用代碼示例:

    ///弱引用
    TestObject *obj = [[TestObject alloc] init];
    __block   __weak TestObject *weakObj = obj;
    void(^Case4Block)(void) = ^ {
        NSLog(@"weakObj = %@", weakObj);
    };
    NSLog(@"obc = %@", obj);
    Case4Block();

底層代碼實(shí)現(xiàn)為

///將obj封裝成__Block_byref_weakObj_0對(duì)象
struct __Block_byref_weakObj_0 {
  void *__isa;
__Block_byref_weakObj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
///對(duì)obj對(duì)象弱引用
 TestObject *__weak weakObj;
};
///block底層結(jié)構(gòu)
struct __ViewController4__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController4__viewDidLoad_block_desc_0* Desc;
///對(duì)obj封裝成的__Block_byref_weakObj_0強(qiáng)引用;
  __Block_byref_weakObj_0 *weakObj; // by ref
  __ViewController4__viewDidLoad_block_impl_0(void *fp, struct __ViewController4__viewDidLoad_block_desc_0 *desc, __Block_byref_weakObj_0 *_weakObj, int flags=0) : weakObj(_weakObj->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

總結(jié):當(dāng)用__block修飾的對(duì)象類型時(shí)使用block整個(gè)過(guò)程為:

  • 首先被__block修飾的變量在底層會(huì)被封裝成__Block_byref_XXXX_0的對(duì)象變量;
  • 當(dāng)block被拷貝大堆上時(shí)會(huì)調(diào)用內(nèi)部的copy函數(shù), copy函數(shù)調(diào)用_Block_object_assign函數(shù), 會(huì)對(duì)對(duì)象的修飾符__strong, __weak, unsafe_unretained做出相應(yīng)的操作(強(qiáng)引用或者弱引用);
  • 當(dāng)block執(zhí)行完/從堆上移出時(shí), 會(huì)調(diào)用block內(nèi)部的dispose函數(shù), 其內(nèi)部調(diào)用_Block_object_dispose函數(shù)對(duì)結(jié)構(gòu)體持有的對(duì)象進(jìn)行類似release的釋放操作;
總結(jié): __block在MRC和ARC下的不同
  • MRC下: 自動(dòng)變量對(duì)象都是被淺拷貝, 不會(huì)強(qiáng)引用(不會(huì)增加引用計(jì)數(shù)); 因?yàn)?code>MRC環(huán)境下, block放在棧區(qū), 所以不會(huì)對(duì)對(duì)象強(qiáng)引用, 如果對(duì)block進(jìn)行拷貝到堆區(qū)操作則是強(qiáng)引用;
  • ARC下: block訪問局部變量后會(huì)自動(dòng)進(jìn)行拷貝操作到堆區(qū), 所以會(huì)自動(dòng)變量對(duì)象封裝后的__Block_byref_XXX對(duì)象強(qiáng)引用(引用計(jì)數(shù)會(huì)增加);__Block_byref_XXX對(duì)變量根據(jù)實(shí)際的修飾符進(jìn)行強(qiáng)/弱引用;

3. 循環(huán)引用問題

首先看下方代碼, 我們都知道產(chǎn)生了循環(huán)引用, 但是為什么會(huì)循環(huán)引用, 通過(guò)底層代碼查看下原因

#import "ViewController4.h"
typedef void(^Block)(void);
@interface ViewController4 ()
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy)  Block  Block1;
@property (nonatomic, copy)  Block  Block2;
@end
@implementation ViewController4
- (void)viewDidLoad {
    [super viewDidLoad];
    ///產(chǎn)生循環(huán)引用
    self.Block1 =    ^{
        NSLog(@"name = %@", self.name);
    };
     self.Block1();
    ///weak 修飾不產(chǎn)生循環(huán)引用
    __weak typeof(self) weakSelf = self;
    self.Block2 =    ^{
        NSLog(@"name = %@", weakSelf.name);
    };
    self.Block2();
}
@end

底層代碼:

#Block0的底層結(jié)構(gòu)
struct __ViewController4__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController4__viewDidLoad_block_desc_0* Desc;
  ///對(duì) self 進(jìn)行強(qiáng)引用
  ViewController4 *const __strong self;
  __ViewController4__viewDidLoad_block_impl_0(void *fp, struct __ViewController4__viewDidLoad_block_desc_0 *desc, ViewController4 *const __strong _self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

#Block1的底層結(jié)構(gòu)
struct __ViewController4__viewDidLoad_block_impl_1 {
  struct __block_impl impl;
  struct __ViewController4__viewDidLoad_block_desc_1* Desc;
  ///對(duì) self 進(jìn)行弱引用
  ViewController4 *const __weak weakSelf;
  __ViewController4__viewDidLoad_block_impl_1(void *fp, struct __ViewController4__viewDidLoad_block_desc_1 *desc, ViewController4 *const __weak _weakSelf, int flags=0) : weakSelf(_weakSelf) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

引用block結(jié)構(gòu)中對(duì)self指針有強(qiáng)引用, 而self又對(duì)block有強(qiáng)引用, 所以造成循環(huán)引用;

ARC解決循環(huán)引用方案:

  • __weak修飾, 這樣block底層對(duì)self的引用變成了XXX *__weak weakSelf; 推薦使用;
  • __unsafe_unretained修飾, 具體效果跟__weak類似, 但是需要注意__unsafe_unretained不會(huì)將使用完的對(duì)象置為nil; 不推薦使用;

MRC解決循環(huán)引用方案:

  • __block修飾, 將對(duì)象封裝為__Block_byref_XX_0形式, bock對(duì)__Block_byref_XX_0是強(qiáng)引用, 但是__Block_byref_XX_0對(duì)其結(jié)構(gòu)體內(nèi)的變量不是強(qiáng)引用;
  • __unsafe_unretained修飾, 具體效果跟__weak類似, 但是需要注意__unsafe_unretained不會(huì)將使用完的對(duì)象置為nil;


參考文章和下載鏈接
文中測(cè)試代碼
iOS clang指令報(bào)錯(cuò)問題總結(jié)
Apple 一些源碼的下載地址
auto關(guān)鍵字是什么
C++中結(jié)構(gòu)體的構(gòu)造函數(shù)
全局變量、靜態(tài)全局變量、靜態(tài)局部變量和普通局部變量的區(qū)別

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

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