OC中的 block与__block、weak、__weak、__strong

首先需要知道:

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

block也有自己的isa指针,依据block的类别不同,分别指向
__NSGlobalBlock __ ( _NSConcreteGlobalBlock )
__NSStackBlock __ ( _NSConcreteStackBlock )
__NSMallocBlock __ ( _NSConcreteMallocBlock )

注:作为block的参数,传给block内部使用,对block的实际类型,无影响
global block:
1、位于全局区的(比如在类的外部进行声明的)
2、block内部仅直接使用了全局变量或者静态变量;

stack block变量:
内部使用了外部属性或者变量,创建时,使用的是weak变量指向,并且未显式做copy操作。
(1)block创建时,使用了外部变量,并且直接赋值给一个weak变量,未显式做copy操作。
(2)一个stack block,赋值给一个局部block变量(不论该变量是weak还是strong修饰)

malloc block:
通过栈区block进行拷贝而来;
(1)block创建时,使用了外部变量,此时为一个stack block
(3)进行显式copy操作,会拷贝出一个malloc block
(4)进行隐式copy操作(赋值给一个对象的strong、copy属性时,或者创建时直接赋值给一个strong局部变量时),会拷贝出一个malloc block

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

block特性:
1、对外部变量,会连__weak、__strong等修饰符一起拷贝进去
2、外部变量是弱引用,则block 内部拷贝的也是一个弱引用变量
3、外部变量是强引用,则block 内部拷贝的也是一个强引用变量

对于基础数据类型,是值传递,修改变量的值,修改的是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, <NSObject: 0x282ea0500>, 引用计数:1
声明时weakObj:0x16daa3960, <NSObject: 0x282ea0500>,<NSObject: 0x282ea0500>, <NSObject: 0x282ea0500>, 引用计数: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, <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对象最后打印的引用计数是多少,为什么?

    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代码块不会提前消亡。

有不对之处,欢迎大家一起讨论

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343