我认为我非常了解ARC以及选择合适的终身限定符的正确用例(__strong
,__weak
,__unsafe_unretained
和__autoreleasing
) 。但是,在我的测试中,我发现了一个对我没有意义的例子。
据我了解,__weak
和__unsafe_unretained
都不会添加保留计数。因此,如果没有指向该对象的其他__strong
指针,则会立即取消分配(使用不可变字符串作为此规则的例外)。这个过程的唯一区别是__weak
指针设置为nil,__unsafe_unretained
指针保持不变。
如果我创建一个指向简单自定义对象(由一个NSString属性组成)的__weak
指针,我在尝试访问属性时会看到预期的(null)值:
Test * __weak myTest = [[Test alloc] init];
myTest.myVal = @"Hi!";
NSLog(@"Value: %@", myTest.myVal); // Prints Value: (null)
同样,由于产生悬空指针,我希望__unsafe_unretained
生命周期限定符导致崩溃。但事实并非如此。在下一个测试中,我看到了实际值:
Test * __unsafe_unretained myTest = [[Test alloc] init];
myTest.myVal = @"Hi!";
NSLog(@"Value: %@", myTest.myVal); // Prints Value: Hi!
为什么__unsafe_unretained
对象不会被解除分配?
[编辑] :对象正在被取消分配...如果我尝试用NSLog(@"%@", myTest);
替换第2 - 3行应用程序崩溃(以及被覆盖的dealloc
在第一行之后立即调用Test
。我知道即使使用__unsafe_unretained
,不可变字符串也将继续可用,并且指向NSString的直接指针可以正常工作。我很惊讶我可以在一个解除分配的对象(第2行)上设置一个属性,以后它可以从指向它所属的解除分配对象的指针(第3行)中取消引用!如果有人能够解释,那肯定会回答我的问题。
答案 0 :(得分:2)
因为objc中的常量字符串是指向堆地址的常量指针,并且地址仍然有效。
评论后编辑:
也许是因为测试对象地址的内存没有被覆盖并仍然包含该对象?推测....
答案 1 :(得分:2)
我很惊讶我可以在一个解除分配的对象(第2行)上设置一个属性,以后可以从指向它所属的解除分配对象的指针(第3行)中取消引用它!如果有人能够解释,那肯定会回答我的问题。
取消分配对象时,它不会归零。由于你有一个指向解除分配对象的指针,并且属性值存储在该指针的某个偏移量处,因此可能存储和检索该属性值将在取消分配后成功,所以一切都很可能会因某种原因而爆炸。
您的代码工作非常脆弱,尝试使用“在调试时显示反汇编”进行调试并单步执行,您可能会遇到访问冲突,或者取消Xcode本身......
在C,Objective-C,C ++或任何一个家庭中发生奇怪的事情,你永远不会感到惊讶;而是为这么少奇怪的事情保留你的惊喜!
答案 2 :(得分:1)
通过实施Test
方法并添加一些简单的日志记录,您可以看到-dealloc
何时被取消分配。
但是,即使立即取消分配Test,在您调用myVal
时,RAM中占用的内存也可能保持不变。
答案 3 :(得分:1)
@"hi!"
生成一个静态全局常量字符串实例,实际上是一个单例。因此,它永远不会被释放,因为它首先没有真正分配(至少,它实际上不是正常的堆分配)。
无论何时你想探索对象生命周期问题,总是使用NSObject的子类来保证行为,并通过覆盖行为轻松地删除日志记录钩子。
答案 4 :(得分:0)
那里没什么奇怪的...... 你需要至少有一个强大的对象引用来保持它的存活。
Test * anTest = [[Test alloc] init];
Test * __weak myTest = anTest;
myTest.myVal = @"Hi!";
NSLog(@"Value: %@", myTest.myVal); // Prints Value: (Hi)