简体   繁体   English

ARC块,弱且保留计数

[英]ARC blocks, weak and retain count

I thought that I had quite understood weak references and blocks, however when trying the below code snippets, there are a few things that I don't understand. 我以为我已经很了解弱引用和代码块,但是尝试下面的代码片段时,有些事情我不理解。

Method test1 : all fine the object is not retained 方法test1 :很好,不保留对象

Method test2 : I don't understand why the object seems to get retained until the end of method test3 ! 方法test2 :我不明白为什么直到方法test3结束之前似乎都保留了该对象! Even explicitly setting object = nil at the end of method test2 does not change anything. 即使在方法test2的末尾显式设置object = nil也不会更改任何内容。

Method test3 : the object is not retained. 方法test3 :不保留对象。 Why is method test2 not behaving like this? 为什么方法test2不能像这样?

As a side question, I was actually wondering if weak variables are thread safe? 作为附带的问题,我实际上想知道弱变量是否是线程安全的? ie if I will never get any BAD_ACCESS exception when trying to access a weak variable from different threads. 即,当我尝试从不同线程访问弱变量时,如果我永远也不会收到任何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

Test methods 测试方法

- (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];
}

The output of the above is: 上面的输出是:

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

I have two observations on your three tests before I touch on a few of your questions: 在涉及您的一些问题之前,我对您的三个测试有两个观察结果:

  1. Your testing is complicated by the fact that you're running all three tests in a row, not yielding back to the run loop, and thus your autorelease pool is not getting flushed (so it making things look like they're persisting longer than they normally would). 由于您连续运行所有三个测试,而不是退回到运行循环,因此测试变得很复杂,因此您的自动释放池不会被刷新(因此,使它们看起来比它们持久的时间更长)通常会)。 You should do you testing, one test at a time, to really understand what's going on. 您应该一次测试一次,以真正了解正在发生的事情。 It's not good if you're drawing conclusions about the lifespan of some object, whereas you really may just be experiencing some artifact of the fact that you're not letting the autorelease pool from being flushed. 如果您得出有关某个对象寿命的结论不是很好,而您实际上可能只是在经历某种事实,即您不让自动释放池被刷新。

  2. You are doing all of these tests as dispatch_async , which starts the dispatched block very quickly, sometimes more quickly than it takes the underlying object to fall out of scope and you're often accessing the weakObject as one of the first steps in the dispatched block. 您将所有这些测试作为dispatch_async ,它非常快速地启动了调度块,有时比使基础对象脱离作用域的速度更快,并且您通常将weakObject作为调度块的第一步之一进行访问。 I'd suggest using dispatch_after (so you're really giving the calling method a chance to let the variables fall out of scope), so you'll better see what's going on. 我建议使用dispatch_after (这样,您实际上给了调用方法一个让变量超出范围的机会),因此您最好了解发生了什么。

Your tests are a good data point, but I think it's useful to also test the same stuff using dispatch_after and do one test at a time with fewer of those sleepForTimeInterval . 您的测试是一个很好的数据点,但是我认为也可以使用dispatch_after测试相同的内容,并一次使用一个较少的sleepForTimeInterval进行一次测试,这sleepForTimeInterval It feels like some of the idiosyncrasies of your tests are counterfeiting some key behavior. 感觉您测试的某些特质正在伪造一些关键行为。

Anyway you ask: 无论如何你问:

Method test2: I don't understand why the object seems to get retained until the end of method test3! 方法test2:我不明白为什么在方法test3结束之前似乎保留了该对象! Even explicitly setting object = nil at the end of method test2 does not change anything. 即使在方法test2的末尾显式设置object = nil也不会更改任何内容。

It's undoubtedly fallen into the autorelease pool, which won't be drained until the test method is done. 无疑,它落入了自动释放池中,直到完成test方法后,它才会被消耗掉。

To my prior points, try doing test2 again, but have the operation wait two seconds before accessing the weakObject (or get rid of all of these sleepForTimeInterval statements and use dispatch_after instead of dispatch_sync ): 就我之前的观点而言,尝试再次执行test2 ,但是让该操作等待两秒钟,然后再访问weakObject (或摆脱所有这些sleepForTimeInterval语句,并使用dispatch_after而不是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");
}

You'll see that this behaves more like you expected. 您会看到它的行为更像您预期的那样。

Method test3: the object is not retained. 方法test3:不保留该对象。 Why is method test2 not behaving like this? 为什么方法test2不能像这样?

Needless to say, your test3 is seriously bad news, easily crashing itself. 不用说,您的test3是严重的坏消息,很容易崩溃。 For example, try commenting out the sleep line: 例如,尝试注释掉睡眠行:

- (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");
}

It strikes me that it's behaving less like weak and more like unsafe_unretained . 令我吃惊的是,它的表现不像是weak而更像是unsafe_unretained

As a side question, I was actually wondering if weak variables are thread safe? 作为附带的问题,我实际上想知道弱变量是否是线程安全的? ie if I will never get any BAD_ACCESS exception when trying to access a weak variable from different threads. 即,当我尝试从不同线程访问弱变量时,如果我永远也不会收到任何BAD_ACCESS异常。

You can get exceptions in a lot of ways. 您可以通过多种方式获得例外。 If you pass weakObject to some method that requires that it not be nil (eg NSMutableArray method addObject ), you'll get an exception. 如果将weakObject传递给要求它不为nil某个方法(例如NSMutableArray方法addObject ),则会得到异常。 You can also get exceptions if you dereference ivars for a nil object pointer, eg obj->objectIvar . 如果取消引用ivars的nil对象指针,也可以获取异常,例如obj->objectIvar For example, imagine a Object instance method, doSomethingLater , which uses a weak reference to ensure that it doesn't retain the Object , but then has a local strong reference so it can dereference the ivar: 例如,假设有一个Object实例方法doSomethingLater ,它使用一个弱引用来确保它不保留该Object ,但是具有一个本地强引用,因此可以取消引用ivar:

- (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
    });
}

Thus, you usually replace the above with: 因此,通常将上述内容替换为:

- (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);
        }
    });
}

To be perfectly honest, though, the details of why that first code sample can crash and the second can't is less important than the obvious fact that judicious use of your object references in asynchronous programming is important, and failure to handle the situations carefully can result in exceptions. 但是,老实说,为什么第一个代码示例可能崩溃而第二个代码示例不能崩溃的细节,不像在异步编程中明智地使用对象引用很重要,以及无法谨慎处理这种情况这样明显的事实那么重要。可能会导致异常。 Frequently, checking that the weakObject is not nil can prevent many of these sorts of issues (with some caveats that I'm not going to go into). 通常,检查weakObject是否为nil可以防止许多此类问题(有些警告我将不讨论)。 This is less important when calling object methods (because sending any message to nil results in nil ), but it is important when your weakObject is a parameter or being dereferenced for an ivar. 这在调用对象方法时不太重要(因为将任何消息发送到nil导致nil ),但是在您的weakObject是参数或为ivar取消引用时,这一点很重要。

But to be clear, none of that really has any bearing on thread-safety, though. 但是需要明确的是,这些都与线程安全性没有任何关系。 You achieve thread safety through proper handling of synchronization, such as locking mechanisms or through judicious use of queues (either serial queue; or the reader/writer pattern of a concurrent queue with dispatch_barrier_async for writes and dispatch_sync for reads). 您可以通过适当地处理同步(例如锁定机制)或通过明智地使用队列 (串行队列;或使用并发队列的并发队列的读取器/写入器模式,其中dispatch_barrier_async用于写入, dispatch_sync用于读取)来实现线程安全。

Just because you have code where you're handling object references carefully so you don't get exceptions, doesn't mean you've achieved thread-safety. 仅仅因为您在代码中仔细地处理了对象引用,而又没有异常,但这并不意味着您已经实现了线程安全。 There's a whole other layer of concerns that thread-safety entails. 线程安全还带来了另一层问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM