长春建设厅官方网站,上海外包公司排行,网站建设平台推广,无锡新吴区建设环保局网站文章目录 前言2. Block2.1 Block的使用规范2.2 __block修饰符2.3 Block的类型2.4 Block的循环引用及解决循环引用的场景引入解决循环引用Block循环引用场景 2.5 Block的实现及其本质2.5.1 初始化部分2.5.2 调用部分2.5.3 捕获变量 Block本质2.6 Block捕获变量 和 对象2.7 Block… 文章目录 前言2. Block2.1 Block的使用规范2.2 __block修饰符2.3 Block的类型2.4 Block的循环引用及解决循环引用的场景引入解决循环引用Block循环引用场景 2.5 Block的实现及其本质2.5.1 初始化部分2.5.2 调用部分2.5.3 捕获变量 Block本质2.6 Block捕获变量 和 对象2.7 Block的内存管理 3 Blcok的问题总结1. block在修改NSMutableArray需不需要添加__block2. __block如何达到修改内部的值__Block_byref_age_0结构体 __block 和Block 总结Block为什么用copy修饰 前言
博客以总结为主随时更新
2. Block
带有自动变量局部变量的匿名函数
2.1 Block的使用规范
Block完整格式就是变量声明 定义 、
int(^sumBlk)(int num1, int num2) ^int(int a, int b) {return a b;};Block变量类似于函数指针
用途自动变量局部变量 函数参数 静态变量 静态全局变量 全局变量
截获自动变量带有自动变量值在Block中表现为“截获自动变量值”。
值得注意的点 在现在的Blocks中截获自动变量的方法并没有实现对C语言数组的截获
2.2 __block修饰符
block可以截获变量但是在块里不能修改变量的值。
此时我们使用__block修饰符修饰变量对需要在block内进行赋值的变量使用修饰符修饰保证可以对变量进行赋值。
// blk不允许修改截获变量的值需要的时候加上__block修饰符即可id tstArray [blk, 不允许赋值, 给截获的变量];__block id array [[NSMutableArray alloc] init];void (^blk)(void) ^{id obj [[NSObject alloc] init];// [array addObject:obj];array tstArray;};2.3 Block的类型
block分为全局 block、堆 block、栈 block Block分类简单总结如下
A、没有引用外部变量 — block存放在全局区B、引用了外部变量----显式声明为weak类型的block则存放在栈区反之则是存在在堆区的也就是说block是strong类型的。NSGlobalBlock block内部没有引用外部变量Block类型都是NSGlobalBlock类型存储于全局数据区由系统管理其内存retain、copy、release操作都无效。如果访问了外部static或者 全局变量也是这种类型。NSStackBlock 访问了外部变量但没有强引用指向这个block如直接打印出来的blockNSMallocBlock ARC环境下只要访问了外部变量而且有强引用指向该block(或者作为函数返回值)就会自动将__NSStackBlock类型copy到堆上。
2.4 Block的循环引用及解决
循环引用的场景引入
循环引用就是对方面持有导致对象不能正常释放会发生内存泄漏
在BLock里互相的强引用可能造成循环引用。
typedef void(^TBlock)(void);interface ViewController ()
property (nonatomic, strong) TBlock block;
property (nonatomic, copy) NSString *name;
endimplementation ViewController- (void)viewDidLoad {[super viewDidLoad];// 循环引用self.name Hello;self.block ^(){NSLog(%, self.name);};self.block();
}
这里self持有了blockblock持有了self导致循环引用。 只要有一方没有进行强引用就可以解除循环引用
- (void)viewDidLoad {[super viewDidLoad];// 循环引用self.name Hello;void(^block)(void);block ^(){NSLog(%, self.name);};block();
}为什么这个案例就没有出现循环引用的状况呢因为当前self也就是ViewController并没有对block进行强持有block的生命周期只在viewDidLoad方法内viewDidLoad方法执行完block就会释放。
解决循环引用
__weak 因为__weak不会对对象的引用计数操作也就是不会进行强引用.上面的案例修改成weak来避免循环引用如下 // 循环引用self.name Hello;__weak typeof(self) weakSelf self;self.block ^(){NSLog(%, weakSelf.name);};self.block();此时self持有blockblock弱引用self弱引用会自动变为nil强持有中断所以不会引起循环引用。但该方法可能存在中途就释放掉的问题手动延迟可能需要调用self.name的时候name已经被释放了如果self被销毁那么block则无法获取name。
强弱共舞 self.name Hello;__weak typeof(self) weakSelf self;self.block ^(){__strong __typeof(weakSelf)strongWeak weakSelf;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(%, strongWeak.name);});};self.block();完全解决了以上self中途被释放的问题。 原理在完成block中的操作之后才调用了dealloc方法。添加strongWeak之后持有关系为self - block - strongWeak - weakSelf - self。
weakSelf被强引用了就不会自动释放因为strongWeak只是一个临时变量它的声明周期只在block内部block执行完毕后strongWeak就会释放而弱引用weakSelf也会自动释放。
手动中断 self.name Hello;__block ViewController * ctrl self;self.block ^(){__strong __typeof(weakSelf)strongWeak weakSelf;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(%, ctrl.name);ctrl nil;});};self.block();使用ctrl之后持有关系为 self - block - ctrl - selfctrl在block使用完成后被置空至此block对self持有就解除不构成循环引用
参数形式解决循环引用 block传参(指针拷贝) // 循环引用self.name Hello;self.block ^(ViewController * ctrl){dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(%, ctrl.name);});};self.block(self);将self作为参数参入block中进行指针拷贝并没有对self进行持有
Block循环引用场景
静态变量持有 // staticSelf_定义static ViewController *staticSelf_;- (void)blockWeak_static {__weak typeof(self) weakSelf self;staticSelf_ weakSelf;}weakSelf虽然是弱引用但是staticSelf_静态变量并对weakSelf进行了持有staticSelf_释放不掉所以weakSelf也释放不掉导致循环引用
2.5 Block的实现及其本质
BLock的实现是基于指针和函数指针Block属性是指向结构体的指针 //这是正常的block流程void(^myBlock)(void) ^{printf(myBlock);};myBlock();得到Block的结构的源码定义C void(*myBlock)(void) ((void (*)())__main_block_impl_0((void *)__main_block_func_0, __main_block_desc_0_DATA));((void (*)(__block_impl *))((__block_impl *)myBlock)-FuncPtr)((__block_impl *)myBlock);初始化定义部分block语法变成了__main_block_impl_0((void *)__main_block_func_0, __main_block_desc_0_DATA)); 调用block的过程变成了(__block_impl *)myBlock)-FuncPtr
2.5.1 初始化部分
初始化部分就是Block结构体
//Block结构体
struct __main_block_impl_0 {struct __block_impl impl;//impl:Block的实际函数指针就是指向包含Block主体部分的__main_block_func_0结构体struct __main_block_desc_0* Desc;//Desc指针指向包含Block附加信息的__main_block_desc_0()结构体__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags0) {//__main_block_impl_0:Block构造函数可以看到都是对上方两个成员变量的赋值操作impl.isa _NSConcreteStackBlock; // impl.Flags flags;impl.FuncPtr fp;Desc desc;}
};__main_block_impl_0结构体也就是Block结构体包含了三个部分
成员变量impl成员变量Desc指针__main_block_impl_0构造函数
struct __block_impl结构包含Block实际函数指针的结构体
struct __block_impl {void *isa;//用于保存Block结构体的实例指针int Flags;//标志位int Reserved;//今后版本升级所需的区域大小void *FuncPtr;//函数指针
};_block_impl包含了Block实际函数指针FuncPtrFuncPtr指针指向Block的主体部分也就是Block对应OC代码中的^{…}的部分还包含了标志位Flags在实现block的内部操作时可能会用到今后版本升级所需的区域大小Reserved__block_impl结构体的实例指针isa
struct __main_block_desc_0结构
Block附加信息结构体包含今后版本升级所需区域的大小Block的大小
static struct __main_block_desc_0 {size_t reserved;//今后版本升级所需区域大小size_t Block_size;//Block大小
} __main_block_desc_0_DATA { 0, sizeof(struct __main_block_impl_0)};
Block构造函数__main_block_impl_0
作为构造函数注意和Block结构体是一个名字。 负责初始化__main_block_impl_0结构体也就是Block结构体struct __block_impl的成员变量 //可以看到里面都是一些赋值操作__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags0) {impl.isa _NSConcreteStackBlock;impl.Flags flags;impl.FuncPtr fp;Desc desc;}
2.5.2 调用部分
函数原型 ((void (*)(__block_impl *))((__block_impl *)myBlock)-FuncPtr)((__block_impl *)myBlock);
逐步解析这段代码
((__block_impl *)myBlock)-FuncPtr这部分将 myBlock 转换为 __block_impl 指针类型并访问 FuncPtr 成员。它获取了块实现内部存储的函数指针。((void (*)(__block_impl *))((__block_impl *)myBlock)-FuncPtr)在这里函数指针被转换为一个函数类型该函数接受一个类型为 __block_impl* 的参数并返回 void。它将函数指针转换为可以调用的实际函数类型。((void (*)(__block_impl *))((__block_impl *)myBlock)-FuncPtr)((__block_impl *)myBlock)最后使用 myBlock 作为参数调用了所得到的函数指针。它使用块实现对象调用该函数。
总结一下该代码获取了存储在块实现内部的函数指针然后调用该函数将块实现对象自身作为参数传递进去。
2.5.3 捕获变量
Block捕获变量的时候 结构体里面多了 *NSObject obj; 以便Block去捕获。
Block本质
用一句话来说Block是个对象其内部第一个成员为isa指针Block为什么出生就在栈上
在他的初始化函数里面 impl.isa _NSConcreteStackBlock; _NSConcreteStackBlock相当于该block实例的父类.将Block作为OC对象调用时关于该类的信息放置于_NSConcretestackBlock中这也证明了 block出生就是在栈上
2.6 Block捕获变量 和 对象
变量
全局变量 不捕获局部变量 捕获值静态全局变量 不捕获静态局部变量 捕获指针const修饰的局部常量捕获值const修饰的静态局部常量捕获指针
对象 BLOCK 可以捕获对象其中需要知道两个方法。
在捕获对象的时候代码出现了_main_block_copy_0 和 _main_block_depose_0。
__main_block_copy_0作用就是调用_Block_object_assign相当于retain将对象赋值在对象类型的结构体变量__main_block_impl_0中。在栈上的Block复制到堆时会进行调用。__main_block_dispose_0调用_Block_object_dispose相当于release释放赋值在对象类型的结构体变量中的对象。在堆上的Block被废弃时会被调用。
2.7 Block的内存管理
Block内存分析
block 在使用的时候堆block注意作用域的问题弱引用指针赋值出了作用域就释放了可以通过强引用解决
3 Blcok的问题总结
1. block在修改NSMutableArray需不需要添加__block
int main(int argc, const char * argv[]) {autoreleasepool {NSMutableArray *array [NSMutableArray array];Block block ^{[array addObject: 20];[array addObject: 30];NSLog(%,array);};block();}return 0;可以正确执行因为在block块中仅仅是使用了array的内存地址往内存地址中添加内容并没有修改arry的内存地址因此array不需要使用__block修饰也可以正确编译。
‼️ 因此当仅仅是使用局部变量的内存地址而不是修改的时候尽量不要添加__block通过上述分析我们知道一旦添加了__block修饰符系统会自动创建相应的结构体占用不必要的内存空间。
2. __block如何达到修改内部的值
Block可以捕获值看如下代码 原理 首先被__block修饰的age变量声明变为名为age的__Block_byref_age_0结构体也就是说加上__block修饰的话捕获到的block内的变量为__Block_byref_age_0类型的结构体
__Block_byref_age_0结构体
__isa指针 __Block_byref_age_0中也有isa指针也就是说__Block_byref_age_0本质也一个对象。*__forwarding __forwarding是__Block_byref_age_0结构体类型的并且__forwarding存储的值为(__Block_byref_age_0 )age即结构体自己的内存地址。__flags 0__size sizeof(__Block_byref_age_0)即__Block_byref_age_0所占用的内存空间。age 真正存储变量的地方这里存储局部变量10。
接着将__Block_byref_age_0结构体age存入__main_block_impl_0结构体中并赋值给__Block_byref_age_0 *age; 之后调用block首先取出__main_block_impl_0中的age通过age结构体拿到__forwarding指针__forwarding中保存的就是__Block_byref_age_0结构体本身这里也就是age(__Block_byref_age_0)在通过__forwarding拿到结构体中的age(10)变量并修改其值。
__forwarding是指向自己的指针。
总结__block为什么能够修改变量的值是因为__block把变量包装成了一个带有指针的对象然后把age封装在结构体里面block内部存储的变量为结构体指针也可以通过指针找到内存地址修改变量的值。
__block 和Block 总结
Block本质是一个对象使用结构体实现。
//Block结构体
struct __main_block_impl_0 {struct __block_impl impl;//impl:Block的实际函数指针就是指向包含Block主体部分的__main_block_func_0结构体struct __main_block_desc_0* Desc;//Desc指针指向包含Block附加信息的__main_block_desc_0()结构体__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags0) {//__main_block_impl_0:Block构造函数可以看到都是对上方两个成员变量的赋值操作impl.isa _NSConcreteStackBlock; // impl.Flags flags;impl.FuncPtr fp;Desc desc;}
};__block作用可以获取对应变量的指针使其可以在block内部被修改。
__block的数据结构如下
struct __Block_byref_a_0 {void *__isa;//有isa是一个对象
__Block_byref_a_0 *__forwarding;//指向自身类型对象的指针int __flags;//不用关心int __size;//自己所占大小int a;//被封装的 基本数据类型变量
};Block为什么用copy修饰
因为Block的内存地址显示在栈区栈区的特点就是创建的对象随时销毁一旦销毁后续再次调用空对象就会造成程序崩溃。 对Block进行copy操作之后block存在堆区所以在使用Block属性的时候Copy修饰。
堆中的block也就是copy修饰的block。他的生命周期就是随着对象的销毁而结束的。只要对象不销毁我们就可以调用的到在堆中的block。
这就是为什么我们要用copy来修饰block。因为不用copy修饰的访问外部变量的block只在他所在的函数被调用的那一瞬间可以使用。之后就消失了。
注意ARC的Block一般都是在堆上因为系统默认堆Block进行copy操作。 不论ARC还是MRC 用copy Strong修饰Block都是堆Block。