主要講解 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ū)別