差不多有四个月没有写帖子了 最近一段时间 公司终端部 全部转去做前端开发 搞了一个月Meteor 搞了一个月Vue.js 有搞了一个月的HTML CSS 现在又在搞 JSP! 哎!公司东搞搞西搞搞的 都把我给整蒙圈了! 好久没写OC代码 今天抽闲复习复习!
在面试的时候经常会被问到 block 避免循环引用的问题时,发现好多人都说通过添加 __block 修饰词来避免!今天忙里抽闲给大家说道说道Block中__Weak 与 __Block 的正确使用方法;如有错误 还请指正!废话不多说直接上代码!
准备工作:
首先定义了一个类 MyObject 继承 NSObject,并添加了一个属性 text,重写了description方法,返回 text 的值。这个主要是因为编译器本身对 NSString 是有优化的,创建的 string 对象有可能是静态存储区永不释放的,为了避免使用 NSString 引起一些问题,还是创建一个 NSObject 对象比较合适。
在自己定义了一个 TLog 方法输出对象相关值,定义如下:
#define TLog(prefix,Obj) {NSLog(@"变量内存地址:%p, 变量值:%p, 指向对象值:%@, --> %@",&Obj,Obj,Obj,prefix);}
1.__weak 在Block 中的使用
MyObject *obj =[[MyObject alloc] init];
obj.text = @"my-object";
TLog(@"obj", obj);
__weak MyObject *weakobj = obj;
TLog(@"weakObj", weakobj);
void(^testBlock)() = ^() {
TLog(@"weakObj - block", weakobj);
};
testBlock();
obj = nil;
testBlock();
从输出结果可以看出:
block 内的 weakObj 和外部的 weakObj 并不是同一个变量
block 捕获了 weakObj 同时也是对 obj 进行了弱引用,当我在 block 外把 obj 释放了之后,block 内也读不到这个变量了
当 obj 赋值 nil 时,block 内部的 weakObj 也为 nil 了,也就是说 obj 实际上是被释放了,可见 __weak 是可以避免循环引用问题的。
第二段代码:
MyObject *obj = [[MyObject alloc]init];
obj.text = @"my-object";
TLog(@"obj", obj);
__weak MyObject *weakObj = obj;
TLog(@"weakObj-0", weakObj);
void(^testBlock)() = ^(){
__strong MyObject *strongObj = weakObj;
TLog(@"weakObj - block", weakObj);
TLog(@"strongObj - block", strongObj);
};
TLog(@"weakObj-1", weakObj);
testBlock();
TLog(@"weakObj-2", weakObj);
obj = nil;
testBlock();
TLog(@"weakObj-3", weakObj);
从上面例子我们看到即使在 block 内部用 strong 强引用了外面的 weakObj
但是一旦 obj 释放了之后,内部的 strongObj 同样会变成 nil,那么这种写法又有什么意义呢?
MyObject *obj = [[MyObject alloc]init];
obj.text = @"my-object";
TLog(@"obj", obj);
__weak MyObject *weakObj = obj;
TLog(@"weakObj-0", weakObj);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong MyObject *strongObj = weakObj;
TLog(@"weakObj - block", weakObj);
TLog(@"strongObj - block", strongObj);
sleep(3);
TLog(@"weakObj - block", weakObj);
TLog(@"strongObj - block", strongObj);
});
NSLog(@"------ sleep 1s");
sleep(1);
obj = nil;
TLog(@"weakObj-1", weakObj);
NSLog(@"------ sleep 5s");
sleep(5);
TLog(@"weakObj-2", weakObj);
代码中使用 sleep 来保证代码执行的先后顺序。从结果中我们可以看到,只要 block 部分执行了,即使我们中途释放了 obj,block 内部依然会继续强引用它。对比上面代码,也就是说 block 内部的 __strong 会在执行期间进行强引用操作,保证在 block 内部 strongObj 始终是可用的。这种写法非常巧妙,既避免了循环引用的问题,又可以在 block 内部持有该变量。
2.__block在Block 中的使用
MyObject *obj = [[MyObject alloc] init];
obj.text = @"my-object-1";
TLog(@"obj", obj);
__block MyObject *blockObj =obj;
obj = nil;
TLog(@"blockObj -1", blockObj);
void(^testBlock)() =^() {
TLog(@"blockObj - blokc", blockObj);
MyObject *obje2 = [[MyObject alloc] init];
obje2.text =@"my-object-2";
TLog(@"obj2", obje2);
blockObj = obje2;
TLog(@"blockObj - block",blockObj);
};
NSLog(@"%@",testBlock);
TLog(@"blockObj -2",blockObj);
testBlock();
TLog(@"blockObj -3",blockObj);
MyObject *obj = [[MyObject alloc]init];
obj.text = @"11111111111111";
TLog(@"obj",obj);
__block MyObject *blockObj = obj;
obj = nil;
void(^testBlock)() = ^(){
TLog(@"blockObj - block",blockObj);
};
obj = nil;
testBlock();
TLog(@"blockObj",blockObj);
当外部 obj 指向 nil 的时候,obj 理应被释放,但实际上 blockObj 依然强引用着 obj,obj 其实并没有被真正释放。因此使用 __block 并不能避免循环引用的问题。
但是我们可以通过手动释放 blockObj 的方式来释放 obj,这就需要我们在 block 内部将要退出的时候手动释放掉 blockObj ,如下这种形式
MyObject *obj = [[MyObject alloc]init];
obj.text = @"11111111111111";
TLog(@"obj",obj);
__block MyObject *blockObj = obj;
obj = nil;
void(^testBlock)() = ^(){
TLog(@"blockObj - block",blockObj);
blockObj = nil;
};
obj = nil;
testBlock();
TLog(@"blockObj",blockObj);
这种形式既能保证在 block 内部能够访问到 obj,又可以避免循环引用的问题,但是这种方法也不是完美的,其存在下面几个问题
必须记住在 block 底部释放掉 block 变量,这其实跟 MRC 的形式有些类似了,不太适合 ARC
当在 block 外部修改了 blockObj 时,block 内部的值也会改变,反之在 block 内部修改 blockObj 在外部再使用时值也会改变。这就需要在写代码时注意这个特性可能会带来的一些隐患
__block 其实提升了变量的作用域,在 block 内外访问的都是同一个 blockObj 可能会造成一些隐患
3. Block如何修改局部变量
在block 中 不可以直接修改局部变量 如果要修改局部变量的值 在局部变量前使用下划线下划线block修饰,在声明Block之后,调用Block之前对局部变量进行修改,在调用Block时局部变量值是修改之后的新值。
Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。
__block所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。 进而在block内部也可以修改外部变量的值。
__block int global = 100;
void(^myBlock)() =^{
NSLog(@"global = %d",global);
};
global = 200;
myBlock();
在这里global是局部变量,所以是放在栈当中的。block相当于另一个内部函数,要在另一个作用域中改变栈中指针的内存地址是不行的。此时,如果block想要在内部进行操作就要将global变量拷贝到堆中,程序才能去改变。
在block中再次打印global地址,可以发现地址不一样了。这就是__block的作用,它将栈中的变量a进行copy放到堆上。(此时输出的是堆地址)
0x7开头的是在栈上的地址。0x1开头的为堆上地址。
在调用block后,global变量就已经是新的内存地址,也不再是栈上。
这里如果不加__block,global = 200就是修改a指针的内存地址。这样是不行的。
4.堆 栈 简介
在计算机系统中,运行的应用程序的数据都是保存在内存中的,不同类型的数据,保存的内存区域不同。
- 栈区(stack) 由编译器自动分配并释放,存放函数的参数值,局部变量等。栈是系统数据结构,对应线程/进程是唯一的。
优点是快速高效,缺点时有限制,数据不灵活。[先进后出]
栈空间分静态分配 和动态分配两种。
静态分配是编译器完成的,比如自动变量(auto)的分配。
动态分配由alloca函数完成。
栈的动态分配无需释放(是自动的),也就没有释放函数。
为可移植的程序起见,栈的动态分配操作是不被鼓励的!
2.堆区(heap) 由程序员分配和释放,如果程序员不释放,程序结束时,可能会由操作系统回收 ,比如在ios 中 alloc 都是存放在堆中。
优点是灵活方便,数据适应面广泛,但是效率有一定降低。[顺序随意]
堆是函数库内部数据结构,不一定唯一。
不同堆分配的内存无法互相操作。
堆空间的分配总是动态的
虽然程序结束时所有的数据空间都会被释放回系统,但是精确的申请内存,释放内存匹配是良好程序的基本要素。
总结
__weak 本身是可以避免循环引用的问题的,但是其会导致外部对象释放了之后,block 内部也访问不到这个对象的问题,我们可以通过在 block 内部声明一个 __strong 的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题。
__block 本身无法避免循环引用的问题,但是我们可以通过在 block 内部手动把 blockObj 赋值为 nil 的方式来避免循环引用的问题。
另外一点就是 __block 修饰的变量在 block 内外都是唯一的,要注意这个特性可能带来的隐患。
网友评论