接上篇来看看__blcok
我们一般用来解决在block
里无法修改局部变量的问题,而且它不能修饰全局变量,静态变量(static)
首先如下来看看修饰基本数据类型
__block int a = 10;
void (^mallocBlock)(void) = ^void {
NSLog(@"%d",a);
};
转变cpp
代码
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
void (*mallocBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5c_vc7szrdj0xj63p71bn1fz8n80000gn_T_main_ae0187_mi_0,(a->__forwarding->a));
}
- 可以看到当用
__block
修饰基本数据类型的时候,会将其包装成__Block_byref_a_0
对象,__Block_byref_a_0
里的*__forwarding
是&a
即__Block_byref_a_0
对象的地址,也就是指向的是它自己,里面的a
则为基本数据类型的值10
,然后这个对象被捕获进block
里,这里是地址捕获,看到传的是&a
,那么就自然可以拿到地址改变其内部a
的值了。 - 当我们访问a的时候,是怎么访问的呢,不论是在
block
外还是在block
内,都是通过a->__forwarding->a
,这里可能疑问为啥不直接a->a
呢,实际上在block
里访问,这两种方式没什么区别。这里是因为在block
复制到堆上的时候,由于是__block
修饰,会进行一个_Block_byref_copy
操作,这里面会将__Block_byref_a_0
对象也copy
到堆区,那么我们在block
内部修改了__Block_byref_a_0
里的a
=20,实际上访问的是堆区的__Block_byref_a_0
对象的a
,但是我们再在外面访问栈区的__Block_byref_a_0
对象的a
,按理说应该还是10,因为一个在栈区一个在堆区,所以为了保证两边访问到的都是改过后的20
,_Block_byref_copy
里还有一个将栈区block
的__forwarding
指向堆区__Block_byref_a_0
对象的操作,那么栈区就可以通过它自己的__forwarding
来访问到堆区__Block_byref_a_0
对象的a
了也就是20,这样就达到了我们想要的修改效果了。
这部分源码可以自己去看看·
static struct Block_byref *_Block_byref_copy(const void *arg) {
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);///堆区申请内存
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
copy->forwarding = copy; // 堆区的block的forwarding 指向自己
src->forwarding = copy; // 栈区的block的forwarding 指向堆区的block
copy->size = src->size;
printf("_Block_byref_copy 111\n");
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
printf("_Block_byref_copy 222\n");
///内存拷贝
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
printf("_Block_byref_copy 333\n");
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
再来看看__block
修饰对象
struct __Block_byref_obj_0 {
void *__isa; //8
__Block_byref_obj_0 *__forwarding; //8
int __flags; //4
int __size; // 4
void (*__Block_byref_id_object_copy)(void*, void*); // 8
void (*__Block_byref_id_object_dispose)(void*); // 8
NSObject *__strong obj;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_obj_0 *obj; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {(void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"))};
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
- 上一篇最后有讲
ARC
下,在栈block进行copy到堆上的时候,并没有走_Block_object_assign
,这里__block
修饰还是走这个函数的。而_Block_object_assign
内部判断走的_Block_byref_copy
.。 - 先继续看上面的
cpp
代码,可以看到包装成的新对象里多了2个函数指针,__Block_byref_id_object_copy
和__Block_byref_id_object_dispose
追踪到最后发现是_Block_object_assign
和_Block_object_dispose
,昂,咋又是它,不过参数发生了变化,经过源码追踪发现ARC
下_Block_object_assign
并不会走2次,于是有了下面的探索。 - 那么跟上一篇一样,转成.ll中间文件来看看
main函数里的部分代码
%14 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %6, i32 0, i32 4
store i8* bitcast (void (i8*, i8*)* @__Block_byref_object_copy_ to i8*), i8** %14, align 8
%15 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %6, i32 0, i32 5
store i8* bitcast (void (i8*)* @__Block_byref_object_dispose_ to i8*), i8** %15, align 8
两个主要函数
define internal void @__Block_byref_object_copy_(i8* %0, i8* %1) #3 {
%3 = alloca i8*, align 8
%4 = alloca i8*, align 8
store i8* %0, i8** %3, align 8
store i8* %1, i8** %4, align 8
%5 = load i8*, i8** %3, align 8
%6 = bitcast i8* %5 to %struct.__block_byref_obj*
%7 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %6, i32 0, i32 6
%8 = load i8*, i8** %4, align 8
%9 = bitcast i8* %8 to %struct.__block_byref_obj*
%10 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %9, i32 0, i32 6
%11 = load %0*, %0** %10, align 8
store %0* null, %0** %7, align 8
%12 = bitcast %0** %7 to i8**
%13 = bitcast %0* %11 to i8*
call void @llvm.objc.storeStrong(i8** %12, i8* %13) #2
%14 = bitcast %0** %10 to i8**
call void @llvm.objc.storeStrong(i8** %14, i8* null) #2
ret void
}
define internal void @__Block_byref_object_dispose_(i8* %0) #3 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
%3 = load i8*, i8** %2, align 8
%4 = bitcast i8* %3 to %struct.__block_byref_obj*
%5 = getelementptr inbounds %struct.__block_byref_obj, %struct.__block_byref_obj* %4, i32 0, i32 6
%6 = bitcast %0** %5 to i8**
call void @llvm.objc.storeStrong(i8** %6, i8* null) #2
ret void
}
那么大致可以看出这里存了这2个函数,__Block_byref_object_copy_
和__Block_byref_object_dispose_
,通过objc
的storeStrong
方法来强引用以及释放的操作,那么这个是什么时候调用的呢,可以在_Block_byref_copy
源码里找到蛛丝马迹。
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
printf("_Block_byref_copy 444\n");
(*src2->byref_keep)(copy, src);
最后调用了(*src2->byref_keep)(copy, src);
,这里的byref_keep
和byref_destroy
对应的应该就是__Block_byref_obj_0
里的那2函数指针了。我们在这里打上断点print
看看这个byref_keep
。
算是验证了我的猜测。
因为默认我们申明的局部变量就是
strong
修饰的,我们再来看看weak
修饰,并转换.ll文件
NSObject *obj = [NSObject new];
__block __weak NSObject *weakObj = obj;
void (^mallocBlock)(void) = ^void {
NSLog(@"%@",weakObj);
};
转换.ll 再看看__Block_byref_object_copy_和__Block_byref_object_dispose_
define internal void @__Block_byref_object_copy_(i8* %0, i8* %1) #3 {
%3 = alloca i8*, align 8
%4 = alloca i8*, align 8
store i8* %0, i8** %3, align 8
store i8* %1, i8** %4, align 8
%5 = load i8*, i8** %3, align 8
%6 = bitcast i8* %5 to %struct.__block_byref_weakObj*
%7 = getelementptr inbounds %struct.__block_byref_weakObj, %struct.__block_byref_weakObj* %6, i32 0, i32 6
%8 = load i8*, i8** %4, align 8
%9 = bitcast i8* %8 to %struct.__block_byref_weakObj*
%10 = getelementptr inbounds %struct.__block_byref_weakObj, %struct.__block_byref_weakObj* %9, i32 0, i32 6
%11 = bitcast %0** %7 to i8**
%12 = bitcast %0** %10 to i8**
call void @llvm.objc.moveWeak(i8** %11, i8** %12) #2
ret void
}
define internal void @__Block_byref_object_dispose_(i8* %0) #3 {
%2 = alloca i8*, align 8
store i8* %0, i8** %2, align 8
%3 = load i8*, i8** %2, align 8
%4 = bitcast i8* %3 to %struct.__block_byref_weakObj*
%5 = getelementptr inbounds %struct.__block_byref_weakObj, %struct.__block_byref_weakObj* %4, i32 0, i32 6
%6 = bitcast %0** %5 to i8**
call void @llvm.objc.destroyWeak(i8** %6) #2
ret void
}
这时候你会发现__Block_byref_object_copy_
和__Block_byref_object_dispose_
一个是调用的moveWeak
,一个是destroyWeak
,进行弱引用的操作。
所以在栈block
进行copy
到堆上时,会调用block
内部的copy
函数,copy
函数内部会调用_Block_object_assign
函数,再而根据传进去的flags
调用_Block_byref_copy
,最终调用__Block_byref_object_copy_
会根据修饰符对__block
对象进行强/弱引用操作。
实际看看
image.pngimage.png
可以看出来,确实__weak
没有增加obj
的引用计数,而且不管使用__weak
还是__strong
,block
对新生成的__Block_byref
对象(blockObj)都是强引用,这里要注意block前
和block后
的blockObj
不是一个东西,因为会被copy
到堆。
但是这里发现,现在ARC
下实际上obj
引用计数的增加,在加上__block
后就已经发生了,而好像不是像前面分析的那样,在block
复制到堆的时候进行操作,我猜测在block
复制到堆的时候应该只是改变了引用obj
的对象,看.ll
文件里的做法,像是把栈区__Block_byref
对象对obj
的引用取消了,再把堆区的引用加上,所以没有发生改变,因为反正经过block
复制到堆后,通过__forwarding
都是访问到堆上的__Block_byref
里的obj
。-------昂,希望有大佬帮我捋一下。
循环引用
前面捋的差不多了,那么就来看看实际一点的东西。
- 你强引用我,我强引用你, 或者多个对象绕成了一个环 即产生了循环引用。
- 这篇文章只看一下
block
的循环引用,如果看了上一篇应该已经很清楚了,总结过只有堆区的block
才持有对象 - 那么第一步确认一下是否堆区的
block
,看看是否访问了auto
对象。然后看看这个auto
对象是否和这个block
有无直接或间接的关系不就行了吗。
先写一个循环引用
@interface Person : NSObject
@property(nonatomic,copy)void (^mallocBlock)(void);
@end
@implementation Person
- (void)dealloc
{
NSLog(@"dealloc -- %@",self);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^mallocBlock)(void);
{
Person *person = [Person new];
mallocBlock = ^void {
NSLog(@"%@",person);
};
person.mallocBlock = mallocBlock;
}
}
return 0;
}
- 如上这样,
mallocBlock
访问了auto
变量person
,再进行了赋值,从而从栈block
复制到堆
,所以其对person
进行了进行了强引用,然后person
里又有个成员变量对block
进行了强引用,这样就循环引用了,最终结果person
无法释放,dealloc
没有走。
要解决循环引用,打断其中的一条线就行了 - 比如让
block
对person
是弱引用(__weak typeof(Person) *weakPerson = person;
) - 或者
person
对block
是弱引用(@property(nonatomic,weak)void (^mallocBlock)(void);
) 这种一般是不会这么做的,会有一些提前释放的问题。 - 或者将
person
当做参数传入block
,(void (^mallocBlock)(Person *per);
) - 或者用
__block
修饰对象,但是需要在block
里用完了手动置为nil
,缺点是这个block
必须被调用 - 或者使用
__unsafe_unretained
修饰符,但是如它的名字一样,是unsafe不安全的,如下:
__unsafe_unretained Person *unsafePer = nil;
Person *person = [Person new];
unsafePer = person;
void (^mallocBlock)(void) = ^{
NSLog(@"%@",unsafePer);
};
person.mallocBlock = mallocBlock;
person = nil;
NSLog(@"%p ",unsafePer);
[unsafePer sayHello];
image.png
可以看出
person
已经释放了,但是unsafePer
还是指向原来的地址,然后就因为访问了野指针崩溃了。所以我基本都是
__weak
来解决block
的循环引用我们平常可能大部分都是由于
堆block
内部访问self
或者self.xxx
或者[self xxx]
,然后self
跟block
直接或者间接也有引用,形成了一个环,导致了循环引用。这里
self
也是一个局部变量,通过方法参数传入方法内,因为iOS里的方法默认会有俩参数self
和_cmd
,所以在block
里访问self
也是访问了局部变量,如果block
有copy
行为,就会产生引用关系。image.png
举几个常见的栗子来看看
@interface TestManager : NSObject
+(instancetype)sharedManager;
@property(nonatomic,copy)void(^block)(void);
-(void)requestData:(void(^)(void))block;
+(void)requestData:(void(^)(void))block;
@end
@implementation TestManager
+(instancetype)sharedManager {
static dispatch_once_t onceToken;
static TestManager *instance;
dispatch_once(&onceToken, ^{
instance = [[TestManager alloc] init];
});
return instance;
}
-(void)requestData:(void(^)(void))block{
NSLog(@"%@",[block class]);
!block?:block();
}
+(void)requestData:(void(^)(void))block{
NSLog(@"%@",[block class]);
!block?:block();
}
@end
@interface NextViewController ()
@property(nonatomic,strong)TestManager *testManager;
@end
@implementation NextViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"海贼:王路飞";
void (^testBlock)(void) = ^{
self.name = @"1. 海贼:王路飞 是一定会成为海贼王的";
};
// [TestManager sharedManager].block = ^{
// self.name = @"2. 海贼:王路飞 是一定会成为海贼王的";
// };
// [[TestManager sharedManager]requestData:^{
// self.name = @"3. 海贼:王路飞 是一定会成为海贼王的";
// }];
// [TestManager requestData:^{
// self.name = @"4. 海贼:王路飞 是一定会成为海贼王的";
// }];
// self.testManager = [TestManager new];
// [self.testManager requestData:^{
// self.name = @"5. 海贼:王路飞 是一定会成为海贼王的";
// }];
// self.testManager = [TestManager new];
// self.testManager.block = ^{
// self.name = @"6. 海贼:王路飞 是一定会成为海贼王的";
// };
}
@end
ARC
下你能清晰的知道上面6种情况,哪些对self
强引用了(注意区分栈block
和堆block
,以下是针对堆block
),哪些会循环引用吗。
- 强引用了,不会循环引用,因为
testBlock
没有跟self
产生关系,没有形成环,会释放 - 强引用了,不会循环引用,但是单例一直不释放
block
,而block
强引用self
导致self
无法释放 - 未强引用,不会循环引用,
block
仍然是栈block
,会释放,因为单例并不持有block
- 未强引用,不会循环引用,
block
仍然是栈block
,会释放 - 未强引用,不会循环引用,
block
仍然是栈block
,会释放 - 强引用了,会循环引用,
self->testManager->block->self
,无法释放
搞清楚了我相信其他情况也可以清晰了。
最后带一下__strong
和__weak
配合使用场景
比如从一个ViewController present
过来,在第二个NextViewController
,定义一个如下block
__weak typeof(self) weakSelf = self;
self.testBlock = ^{
/// 现实中可能是异步网络请求
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"我是%@",weakSelf.name);
});
};
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.testBlock();
[self dismissViewControllerAnimated:YES completion:nil];
}
用__weak
修饰,点击返回上一个ViewController
,延时2秒打印,结果如下
这样就不是我们想要的结果,
name
我们没有拿到了,因为weakSelf
已经mo得了。而我们
block
内部加上__strong typeof(weakSelf) strongSelf = weakSelf;
再看看image.png
可以看到,是用完了,
self
才释放的。
网友评论