美文网首页
Block使用对象的变量情况探究

Block使用对象的变量情况探究

作者: 传说中的汽水枪 | 来源:发表于2019-01-15 10:48 被阅读7次

最近在看Blocks Programming TopicsObjective-C Objects有如下的这段话:

When a block is copied, it creates strong references to object variables used within the block. If you use a block within the implementation of a method:

  • If you access an instance variable by reference, a strong reference is made to self;
  • If you access an instance variable by value, a strong reference is made to the variable.

文档中的例子给的例子比较简陋,没有说到点子上,现做如下的测试demo来验证相关结果:

RXBlockTmpObject

@interface RXBlockTmpObject : NSObject
@property (nonatomic, copy) NSString *name;
+ (id)tmpObjectWithName:(NSString *)name;
@end
@implementation RXBlockTmpObject
+ (id)tmpObjectWithName:(NSString *)name
{
    RXBlockTmpObject *tmp = [RXBlockTmpObject new];
    tmp.name = name;
    return tmp;
}
- (NSString *)description
{
    // 输出内存地址,监控内存变化
    return [NSString stringWithFormat:@"address:%p, name:%@", self, self.name];
}
@end

RXBlockReferenceValueObject
文件:RXBlockReferenceValueObject.h

@interface RXBlockReferenceValueObject : NSObject
- (void)test;
@end

文件:RXBlockReferenceValueObject.m

static void _s_test_name(NSString *prefix, RXBlockTmpObject *tmpObject)
{
    NSLog(@"%@, %@", prefix, tmpObject);
}

@interface RXBlockReferenceValueObject()

@property (nonatomic, strong) RXBlockTmpObject *tmpObject;
@property (nonatomic, strong) NSMutableArray *mutableArray;

@end
@implementation RXBlockReferenceValueObject
- (void)test
{
//    [self _test_normal];
//    [self _test_normal_mut];
//    [self _test_self_instance_variable];
//    [self _test_weakself_strongself_instance_variable];
}
- (void)dealloc
{
    NSLog(@"RXBlockReferenceValueObject dealloc");
}
@end

在测试类中:

- (void)_test_reference_value
{
    // tmp 对象没有别的地方被引用,理论上这个函数结束的时候,tmp应该会在第一时间释放
    RXBlockReferenceValueObject *tmp = [RXBlockReferenceValueObject new];
    [tmp test];
    NSLog(@"_test_reference_value end");
}

先给文件:RXBlockReferenceValueObject.m中添加方法:_test_normal

- (void)_test_normal
{
    self.tmpObject = [RXBlockTmpObject tmpObjectWithName:@"1"];
    NSLog(@"1 address in object:%@", self.tmpObject);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    self.tmpObject = [RXBlockTmpObject tmpObjectWithName:@"2"];
    NSLog(@"2 address in object:%@", self.tmpObject);
    
    // 代码1: 2秒后执行一个任务
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2), queue, ^{
        _s_test_name(@"instance variable", _tmpObject);
    });
    
    self.tmpObject = [RXBlockTmpObject tmpObjectWithName:@"3"];
    NSLog(@"3 address in object:%@", self.tmpObject);
    
    id value = _tmpObject;
    NSLog(@"value address in object:%@", value);
    
    // 代码2: 3秒后执行一个任务
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 3), queue, ^{
        _s_test_name(@"local variable", value);
    });
    
    self.tmpObject = [RXBlockTmpObject tmpObjectWithName:@"4"];
    NSLog(@"4 address in object:%@", self.tmpObject);
}

执行_test_normal看得到的结果:

  1. 1 address in object:address:0x604000008270, name:1
  2. 2 address in object:address:0x604000007f10, name:2
  3. 3 address in object:address:0x604000008270, name:3
  4. value address in object:address:0x604000008270, name:3
  5. 4 address in object:address:0x604000007f10, name:4
  6. 10:16:41.718413+0800:_test_reference_value end
  7. 10:16:43.912842+0800:instance variable, address:0x604000007f10, name:4
  8. 10:16:43.913046+0800:RXBlockReferenceValueObject dealloc
  9. local variable, address:0x604000008270, name:3

根据输出结果,

  1. 前6行的结果可以很容易推导出
  2. 代码1的block中是 access an instance variable by reference,所以是对self有一个强引用,导致不能dealloc,所以输出完第7行后,然后输出第8行(注意6,7,8的时间)
  3. 并且注意一下第7行和第5行的地址是一样的,结果也是一样的,就是会通过self来访问_tmpObject(If you access an instance variable by reference, a strong reference is made to self)
  4. 第9行的结果跟第3行第4行的结果是一样的(If you access an instance variable by value, a strong reference is made to the variable)

为了验证时间,我们可以把代码1给去掉,再次运行得到的结果如下:

  1. 1 address in object:address:0x600000019470, name:1
  2. 2 address in object:address:0x6000000194b0, name:2
  3. 3 address in object:address:0x600000019470, name:3
  4. value address in object:address:0x600000019470, name:3
  5. 4 address in object:address:0x60400000f2e0, name:4
  6. 10:32:21.664519+0800 _test_reference_value end
  7. 10:32:21.664583+0800 RXBlockReferenceValueObject dealloc
  8. 10:32:24.664712+0800 local variable, address:0x600000019470, name:3

注意这里的6,7,8行的时间结果,表明代码1是对self强引用了,无法第一时间调用dealloc

回到有代码1的运行结果
细心的读者可以看到1和3, 2和5的地址是一样的,但是值不一样,这应该是旧的对象被释放后,新的对象申请的时候,刚好申请到被释放的内存。所以导致这样的结果,为了验证这一点,可以用如下的测试代码来验证:

- (void)_test_normal_mut
{
    self.mutableArray = [NSMutableArray new];
    self.tmpObject = [RXBlockTmpObject tmpObjectWithName:@"1"];
    [self.mutableArray addObject:self.tmpObject];
    NSLog(@"1 address in object:%@", self.tmpObject);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    self.tmpObject = [RXBlockTmpObject tmpObjectWithName:@"2"];
    [self.mutableArray addObject:self.tmpObject];
    NSLog(@"2 address in object:%@", self.tmpObject);
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2), queue, ^{
        _s_test_name(@"instance variable", _tmpObject);
    });
    
    self.tmpObject = [RXBlockTmpObject tmpObjectWithName:@"3"];
    [self.mutableArray addObject:self.tmpObject];
    NSLog(@"3 address in object:%@", self.tmpObject);
    
    id value = _tmpObject;
    NSLog(@"value address in object:%@", value);
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 3), queue, ^{
        _s_test_name(@"local variable", value);
    });
    
    self.tmpObject = [RXBlockTmpObject tmpObjectWithName:@"4"];
    [self.mutableArray addObject:self.tmpObject];
    NSLog(@"4 address in object:%@", self.tmpObject);
}

输出结果:

1 address in object:address:0x60000001df10, name:1
2 address in object:address:0x6040002025a0, name:2
3 address in object:address:0x60000001df70, name:3
value address in object:address:0x60000001df70, name:3
4 address in object:address:0x6040002025b0, name:4
_test_reference_value end
instance variable, address:0x6040002025b0, name:4
RXBlockReferenceValueObject dealloc
local variable, address:0x60000001df70, name:3

这个时候可以看到4个内存地址都不一样了。

所以根据以上的结论可以得出如下的结论:
代码1等价于如下的代码:

- (void)_test_self_instance_variable
{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    self.tmpObject = [RXBlockTmpObject tmpObjectWithName:@"4"];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2), queue, ^{
        _s_test_name(@"instance variable", self->_tmpObject);
    });
}

但是不等价于(代码有编译错误):

- (void)_test_weakself_instance_variable
{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    self.tmpObject = [RXBlockTmpObject tmpObjectWithName:@"4"];
    __weak typeof(self) weakSelf = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2), queue, ^{
        // Error: Dereferencing a __weak pointer is not allowed due to possible null value cased by race condition, assign it to strong variable first
        _s_test_name(@"instance variable", weakSelf->_tmpObject);
    });
}

同样不等价于:

- (void)_test_weakself_strongself_instance_variable
{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    self.tmpObject = [RXBlockTmpObject tmpObjectWithName:@"4"];
    __weak typeof(self) weakSelf = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2), queue, ^{
        __strong typeof(self) strongSelf = weakSelf;
       // 虽然不会编译错误,但是这里很大概率会崩溃
        _s_test_name(@"instance variable", strongSelf->_tmpObject);
    });
}

两个不等价于:

  1. 没有对self进行强引用
  2. strongSelf也有可能为空,然后直接EXC_BAD_ACCESS

相关文章

网友评论

      本文标题:Block使用对象的变量情况探究

      本文链接:https://www.haomeiwen.com/subject/jlyedqtx.html