我认为我已经完全理解弱引用和块,但是在尝试下面的代码片段时,有一些我不理解的事情。
方法 test1 :所有对象都没有保留
方法 test2 :我不明白为什么在方法 test3 结束之前对象似乎一直保留!即使在方法 test2 结尾处明确设置object = nil
也不会改变任何内容。
方法 test3 :不保留对象。为什么方法 test2 的行为不像这样?
作为一个附带问题,我实际上想知道弱变量是否是线程安全的?即,当我尝试从不同的线程访问弱变量时,我永远不会得到任何BAD_ACCESS异常。
@interface Object : NSObject
@property (nonatomic) NSInteger index;
@end
@implementation Object
- (id)initWithIndex:(NSInteger) index {
if (self = [super init]) {
_index = index;
}
return self;
}
- (void)dealloc {
NSLog(@"Deallocating object %d", _index);
}
@end
测试方法
- (void) test1 {
NSLog(@"test1");
Object* object = [[Object alloc] initWithIndex:1];
NSLog(@"Object: %@", object);
__weak Object* weakObject = object;
dispatch_async(dispatch_queue_create(NULL, NULL), ^{
//NSLog(@"Weak object: %@", weakObject);
[NSThread sleepForTimeInterval:2];
NSLog(@"Exiting dispatch");
});
[NSThread sleepForTimeInterval:1];
NSLog(@"Exiting method");
}
- (void) test2 {
NSLog(@"test2");
Object* object = [[Object alloc] initWithIndex:2];
NSLog(@"Object: %@", object);
__weak Object* weakObject = object;
dispatch_async(dispatch_queue_create(NULL, NULL), ^{
NSLog(@"Weak object: %@", weakObject);
[NSThread sleepForTimeInterval:2];
NSLog(@"Exiting dispatch");
});
[NSThread sleepForTimeInterval:1];
NSLog(@"Exiting method");
}
- (void) test3 {
NSLog(@"test3");
Object* object = [[Object alloc] initWithIndex:3];
NSLog(@"Object: %@", object);
NSValue *weakObject = [NSValue valueWithNonretainedObject:object];
dispatch_async(dispatch_queue_create(NULL, NULL), ^{
NSLog(@"Weak object: %@", [weakObject nonretainedObjectValue]);
[NSThread sleepForTimeInterval:2];
NSLog(@"Exiting dispatch");
});
[NSThread sleepForTimeInterval:1];
NSLog(@"Exiting method");
}
- (void) test {
[self test1];
[NSThread sleepForTimeInterval:3];
[self test2];
[NSThread sleepForTimeInterval:3];
[self test3];
}
以上的输出是:
2013-05-11 19:09:56.753 test[1628:c07] test1
2013-05-11 19:09:56.754 test[1628:c07] Object: <Object: 0x7565940>
2013-05-11 19:09:57.755 test[1628:c07] Exiting method
2013-05-11 19:09:57.756 test[1628:c07] Deallocating object 1
2013-05-11 19:09:58.759 test[1628:1503] Exiting dispatch
2013-05-11 19:10:00.758 test[1628:c07] test2
2013-05-11 19:10:00.758 test[1628:c07] Object: <Object: 0x71c8260>
2013-05-11 19:10:00.759 test[1628:1503] Weak object: <Object: 0x71c8260>
2013-05-11 19:10:01.760 test[1628:c07] Exiting method
2013-05-11 19:10:02.760 test[1628:1503] Exiting dispatch
2013-05-11 19:10:04.761 test[1628:c07] test3
2013-05-11 19:10:04.762 test[1628:c07] Object: <Object: 0x71825f0>
2013-05-11 19:10:04.763 test[1628:1503] Weak object: <Object: 0x71825f0>
2013-05-11 19:10:05.764 test[1628:c07] Exiting method
2013-05-11 19:10:05.764 test[1628:c07] Deallocating object 3
2013-05-11 19:10:05.767 test[1628:c07] Deallocating object 2
2013-05-11 19:10:06.764 test[1628:1503] Exiting dispatch
答案 0 :(得分:1)
在我谈到你的一些问题之前,我对你的三个测试有两个观察结果:
您的测试很复杂,因为您连续运行了所有三个测试,而不是回到运行循环,因此您的自动释放池没有被刷新(因此它使事情看起来像他们'持续时间比通常更长)。你应该做一次测试,一次测试,以真正了解正在发生的事情。如果你得到关于某个对象的生命周期的结论是不好的,而你真的可能只是遇到了一个事实,即你不会让自动释放池被刷新。
您正在以dispatch_async
执行所有这些测试,它会非常快速地启动调度块,有时会比基础对象超出范围并且您经常访问{ {1}}作为调度块的第一步。我建议使用weakObject
(所以你真的给调用方法一个机会让变量超出范围),这样你就能更好地看到发生了什么。
您的测试是一个很好的数据点,但我认为使用dispatch_after
测试相同的内容并使用较少的dispatch_after
进行一次测试也很有用。感觉你的测试中的某些特性会伪造一些关键行为。
无论如何你要问:
方法测试2:我不明白为什么在方法test3结束之前对象似乎被保留了!即使在方法test2结束时明确设置object = nil也不会改变任何内容。
毫无疑问它属于自动释放池,在sleepForTimeInterval
方法完成之前不会耗尽。
对于我之前的观点,请尝试再次执行test
,但是在访问test2
之前让操作等待两秒钟(或者删除所有这些weakObject
语句并使用{{ 1}}而不是sleepForTimeInterval
):
dispatch_after
你会发现这种行为更像你的期望。
方法test3:不保留对象。为什么方法test2不是这样的?
毋庸置疑,你的dispatch_sync
是一个严重的坏消息,很容易崩溃。例如,尝试注释掉睡眠线:
- (void) test2 {
NSLog(@"test2");
Object* object = [[Object alloc] initWithIndex:2];
NSLog(@"Object: %@", object);
__weak Object* weakObject = object;
dispatch_async(dispatch_queue_create(NULL, NULL), ^{
[NSThread sleepForTimeInterval:2]; // new sleep
NSLog(@"Weak object: %@", weakObject);
[NSThread sleepForTimeInterval:2];
NSLog(@"Exiting dispatch");
});
// [NSThread sleepForTimeInterval:1]; // not really necessary
NSLog(@"Exiting method");
}
让我觉得它的行为不像test3
,更像是- (void) test3 {
NSLog(@"test3");
Object* object = [[Object alloc] initWithIndex:3];
NSLog(@"Object: %@", object);
NSValue *weakObject = [NSValue valueWithNonretainedObject:object];
dispatch_async(dispatch_queue_create(NULL, NULL), ^{
NSLog(@"Weak object: %@", [weakObject nonretainedObjectValue]);
[NSThread sleepForTimeInterval:2];
NSLog(@"Exiting dispatch");
});
// [NSThread sleepForTimeInterval:1];
NSLog(@"Exiting method");
}
。
作为一个附带问题,我实际上想知道弱变量是否是线程安全的?即,当我尝试从不同的线程访问弱变量时,我永远不会得到任何BAD_ACCESS异常。
您可以通过多种方式获得例外。如果您将weak
传递给某个要求不是unsafe_unretained
的方法(例如weakObject
方法nil
),那么您将获得异常。如果您取消引用NSMutableArray
对象指针的ivars,也可以获得例外,例如addObject
。例如,设想一个nil
实例方法obj->objectIvar
,它使用弱引用来确保它不会保留Object
,但是它具有本地强引用,因此它可以取消引用伊娃:
doSomethingLater
因此,您通常将以上内容替换为:
Object
但是,老实说,第一个代码示例崩溃的原因以及第二个代码示例崩溃的细节并不比在异步编程中明智地使用对象引用这一显而易见的事实重要,并且无法处理仔细的情况会导致异常。通常,检查- (void)doSomethingLater
{
__weak Object *weakSelf = self;
double delayInSeconds = 10.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
Object *strongSelf = weakSelf;
NSLog(@"%d", strongSelf->_index); // **BAD** - this can crash of `self` has been released by this point
});
}
不是- (void)doSomethingLater
{
__weak Object *weakSelf = self;
double delayInSeconds = 10.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
Object *strongSelf = weakSelf;
if (strongSelf) {
NSLog(@"%d", strongSelf->_index);
}
});
}
可以防止出现这些问题(有一些我不打算讨论的警告)。调用对象方法时这一点不太重要(因为向weakObject
发送任何消息会导致nil
),但是当nil
是参数或被取消引用为ivar时,这一点很重要。< / p>
但要明确的是,这些都不会对线程安全产生任何影响。通过正确处理同步来实现线程安全性,例如locking mechanisms或通过明智的use of queues(串行队列;或者nil
并发队列的读取器/写入器模式用于写入和{ {1}}用于读取)。
只是因为你有代码,你正在仔细处理对象引用,所以你没有得到异常,并不意味着你已经实现了线程安全。线程安全带来了另外一层关注点。