注册
iOS

iOS block与__block、weak、__weak、__strong

首先需要知道:

block,本质是OC对象,对象的内容,是代码块。
封装了函数调用以及函数调用环境。

block也有自己的isa指针,依据block的类别不同,分别指向
__NSGlobalBlock __ ( _NSConcreteGlobalBlock )
__NSStackBlock __ ( _NSConcreteStackBlock )
__NSMallocBlock __ ( _NSConcreteMallocBlock )
需要注意是,ARC下只存在__NSGlobalBlock和__NSMallocBlock。
通常作为参数时,才可能是栈区block,但是由于ARC的copy作用,会将栈区block拷贝到堆上。
通常不管作为属性、参数、局部变量的block,都是__NSGlobalBlock,即使block内部出现了常量、静态变量、全局变量,也是__NSGlobalBlock,
除非block内部出现其他变量,auto变量或者对象属性变量等,就是__NSMallocBlock

为什么block要被拷贝到堆区,变成__NSMallocBlock,可以看如下链接解释:Ios开发-block为什么要用copy修饰

对于基础数据类型,是值传递,修改变量的值,修改的是a所指向的内存空间的值,不会改变a指向的地址。

对于指针(对象)数据类型,修改变量的值,是修改指针变量所指向的对象内存空间的地址,不会改变指针变量本身的地址
简单来说,基础数据类型,只需要考虑值的地址,而指针类型,则需要考虑有指针变量的地址和指针变量指向的对象的地址

以变量a为例

1、基础数据类型,都是指值的地址

1.1无__block修饰,

a=12,地址为A
block内部,a地址变B,不能修改a的值
block外部,a的地址依旧是A,可以修改a的值,与block内部的a互不影响
内外a的地址不一致

1.2有__block修饰

a=12,地址为A
block内部,地址变为B,可以修改a的值,修改后a的地址依旧是B
block外部,地址保持为B,可以修改a的值,修改后a的地址依旧是B

2、指针数据类型

2.1无__block修饰

a=[NSObject new],a指针变量的地址为A,指向的对象地址为B
block内部,a指针变量的地址为C,指向的对象地址为B,不能修改a指向的对象地址
block外部,a指针变量的地址为A,指向的对象地址为B,可以修改a指向的对象地址,
block外部修改后,
外部a指针变量的地址依旧是A,指向的对象地址变为D
内部a指针变量的地址依旧是C,指向的对象地址依旧是B

2.1有__block修饰

a=[NSObject new],a指针变量的地址为A,指向的对象地址为B
block内部,a指针变量的地址为C,指向的对象地址为B,能修改a指向的对象地址
block外部,a指针变量的地址为C,指向的对象地址为B,能修改a指向的对象地址
block内外,或者另一个block中,无论哪里修改,a指针变量地址都保持为C,指向的对象地址保持为修改后的一致

block内修改变量的实质(有__block修饰):

block内部能够修改的值,必须都是存放在堆区的。
1、基础数据类型,__block修饰后,调用block时,会在堆区开辟新的值的存储空间,
指针数据类型,__block修饰后,调用block时,会在堆区开辟新的指针变量地址的存储空间

2、并且无论是基础数据类型还是指针类型,block内和使用block之后,变量的地址所有地址(包括基础数据类型的值的地址,指针类型的指针变量地址,指针指向的对象的地址),都是保持一致的
当然,只有block进行了真实的调用,才会在调用后发生这些地址的变化

另外需要注意的是,如果对一个已存在的对象(变量a),进行__block声明另一个变量b去指向它,
a的指针变量地址为A,b的指针变量会是B,而不是A,
原因很简单,不管有没__block修饰,不同变量名指向即使指向同一个对象,他们的指针变量地址都是不同的。

__weak,__strong

两者本身也都会增加引用计数。
区别在于,__strong声明,会在作用域区间范围增加引用计数1,超过其作用域然后引用计数-1
而__weak声明的变量,只会在其使用的时候(这里使用的时候,指的是一句代码里最终并行使用的次数),临时生成一个__strong引用,引用+次数,一旦使用使用完毕,马上-次数,而不是超出其作用域再-次数

    NSObject *obj = [NSObject new];
NSLog(@"声明时obj:%p, %@, 引用计数:%ld",&obj, obj, CFGetRetainCount((__bridge CFTypeRef)(obj)));
__weak NSObject *weakObj = obj;
NSLog(@"声明时weakObj:%p, %@,%@, %@, 引用计数:%ld",&weakObj, weakObj,weakObj,weakObj, CFGetRetainCount((__bridge CFTypeRef)(weakObj)));
NSLog(@"声明后weakObj引用计数:%ld", CFGetRetainCount((__bridge CFTypeRef)(weakObj)));

声明时obj:0x16daa3968, , 引用计数:1
声明时weakObj:0x16daa3960, ,, , 引用计数:5
声明后weakObj引用计数:2

这个5,是因为obj本来计数是1,

    NSLog(@"声明时weakObj:%p, %@,%@, %@, 引用计数:%ld",&weakObj, weakObj,weakObj,weakObj, CFGetRetainCount((__bridge CFTypeRef)(weakObj)));

这句代码打印5,是因为除去&weakObj(&这个不是使用weakObj指向的对象,而只是取weakObj的指针变量地址,所以不会引起计数+1),另外还使用了4次weakObj,导致引用计数+4

   NSLog(@"声明后weakObj引用计数:%ld", CFGetRetainCount((__bridge CFTypeRef)(weakObj)));

这句打印2,说明上一句使用完毕后,weakObj引用增加的次数会马上清楚,重新变回1,而这句使用了一次weakObj,加上obj的一次引用,就是2了

__weak 与 weak

通常,__weak是单独为某个对象,添加一条弱引用变量的。
weak则是property属性里修饰符。

LGTestBlockObj *testObj = [LGTestBlockObj new];
self.prpertyObj = testObj;
__weak LGTestBlockObj *weakTestObj = testObj;
NSLog(@"testObj:, 引用计数:%ld", CFGetRetainCount((__bridge CFTypeRef)(testObj)));
NSLog(@"prpertyObj:%p, %@,%@, %@, 引用计数:%ld",&(_prpertyObj), self.prpertyObj,self.prpertyObj,self.prpertyObj, CFGetRetainCount((__bridge CFTypeRef)(self.prpertyObj)));
NSLog(@"prpertyObj:%p, %@,%@, %@, 引用计数:%ld",&(_prpertyObj), _prpertyObj,_prpertyObj,_prpertyObj, CFGetRetainCount((__bridge CFTypeRef)(_prpertyObj)));
NSLog(@"prpertyObj:, 引用计数:%ld", CFGetRetainCount((__bridge CFTypeRef)(_prpertyObj)));
NSLog(@"testObj:, 引用计数:%ld", CFGetRetainCount((__bridge CFTypeRef)(testObj)));
NSLog(@"weakTestObj:%p, %@,%@, %@, 引用计数:%ld",&weakTestObj, weakTestObj,weakTestObj,weakTestObj, CFGetRetainCount((__bridge CFTypeRef)(weakTestObj)));

prpertyObj:0x1088017b0, ,, , 引用计数:2
prpertyObj:, 引用计数:2
testObj:, 引用计数:2
weakTestObj:0x16b387958, ,, , 引用计数:6

待补充...

Block常见疑问收录

1、block循环引用

通常,block作为属性,并且block内部直接引用了self,就会出现循环引用,这时就需要__weak来打破循环。

2、__weak为什么能打破循环引用?

一个变量一旦被__weak声明后,这个变量本身就是一个弱引用,只有在使用的那行代码里,才会临时增加引用结束,一旦那句代码执行完毕,引用计数马上-1,所以看起来的效果是,不会增加引用计数,block中也就不会真正持有这个变量了

3、为什么有时候又需要使用__strong来修饰__weak声明的变量?

在block中使用__weak声明的变量,由于block没有对该变量的强引用,block执行的过程中,一旦对象被销毁,该变量就是nil了,会导致block无法继续正常向后执行。
使用__strong,会使得block作用区间,保存一份对该对象的强引用,引用计数+1,一旦block执行完毕,__strong变量就会销毁,引用计数-1
比如block中,代码执行分7步,在执行第二步时,weak变量销毁了,而第五步要用到weak变量。
而在block第一步,可先判断weak变量是否存在,如果存在,加一个__strong引用,这样block执行过程中,就始终存在对weak变量的强引用了,直到block执行完毕

4、看以下代码,obj对象最后打印的引用计数是多少,为什么?

    NSObject *obj = [NSObject new];
void (^testBlock)(void) = ^{
NSLog(@"%@",obj);
};
NSLog(@"引用计数:%ld",CFGetRetainCount((__bridge CFTypeRef)(obj)));

最后的打印的是3
作为一个局部变量的block,由于引用了外部变量(非静态、常量、全局),定义的时候其实是栈区block,但由于ARC机制,使其拷贝到堆上,变成堆block,所以整个函数执行的过程中,实际上该block,存在两份,一个栈区,一个堆区,这就是使得obj引用计数+2了,加上创建obj的引用,就是3了

5、为什么栈区block要copy到堆上

block:我们称代码块,他类似一个方法。而每一个方法都是在被调用的时候从硬盘到内存,然后去执行,执行完就消失,所以,方法的内存不需要我们管理,也就是说,方法是在内存的栈区。所以,block不像OC中的类对象(在堆区),他也是在栈区的。如果我们使用block作为一个对象的属性,我们会使用关键字copy修饰他,因为他在栈区,我们没办法控制他的消亡,当我们用copy修饰的时候,系统会把该 block的实现拷贝一份到堆区,这样我们对应的属性,就拥有的该block的所有权。就可以保证block代码块不会提前消亡。

转载自:https://cloud.tencent.com/developer/article/1894410

0 个评论

要回复文章请先登录注册