前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS block与__block、weak、__weak、__strong

iOS block与__block、weak、__weak、__strong

原创
作者头像
conanma
修改2021-10-28 17:00:19
1.7K0
修改2021-10-28 17:00:19
举报
文章被收录于专栏:正则正则

首先需要知道:

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引用,引用+次数,一旦使用使用完毕,马上-次数,而不是超出其作用域再-次数

代码语言:javascript
复制
    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, <NSObject: 0x282ea0500>, 引用计数:1 声明时weakObj:0x16daa3960, <NSObject: 0x282ea0500>,<NSObject: 0x282ea0500>, <NSObject: 0x282ea0500>, 引用计数:5 声明后weakObj引用计数:2

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

代码语言:javascript
复制
    NSLog(@"声明时weakObj:%p, %@,%@, %@, 引用计数:%ld",&weakObj, weakObj,weakObj,weakObj, CFGetRetainCount((__bridge CFTypeRef)(weakObj)));

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

代码语言:javascript
复制
   NSLog(@"声明后weakObj引用计数:%ld", CFGetRetainCount((__bridge CFTypeRef)(weakObj)));

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

__weak 与 weak

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

代码语言:javascript
复制
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, <LGTestBlockObj: 0x281e1c140>,<LGTestBlockObj: 0x281e1c140>, <LGTestBlockObj: 0x281e1c140>, 引用计数:2 prpertyObj:, 引用计数:2 testObj:, 引用计数:2 weakTestObj:0x16b387958, <LGTestBlockObj: 0x281e1c140>,<LGTestBlockObj: 0x281e1c140>, <LGTestBlockObj: 0x281e1c140>, 引用计数: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对象最后打印的引用计数是多少,为什么?

代码语言:javascript
复制
    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代码块不会提前消亡。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 首先需要知道:
  • 以变量a为例
    • 1、基础数据类型,都是指值的地址
      • 1.1无__block修饰,
      • 1.2有__block修饰
    • 2、指针数据类型
      • 2.1无__block修饰
      • 2.1有__block修饰
  • block内修改变量的实质(有__block修饰):
  • __weak,__strong
  • __weak 与 weak
  • Block常见疑问收录
    • 1、block循环引用
      • 2、__weak为什么能打破循环引用?
        • 3、为什么有时候又需要使用__strong来修饰__weak声明的变量?
          • 5、为什么栈区block要copy到堆上
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档