首先需要知道:
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代码块不会提前消亡。