OGeek|极客世界-中国程序员成长平台

标题: ios - ARC block ,弱和保留计数 [打印本页]

作者: 菜鸟教程小白    时间: 2022-12-12 10:55
标题: ios - ARC block ,弱和保留计数

我以为我已经很了解弱引用和 block ,但是在尝试下面的代码片段时,有一些我不明白的地方。

方法测试 1 : 一切正常,对象不保留

方法测试 2 : 我不明白为什么对象似乎一直保留到方法结束 测试3 !甚至明确设置 object = nil在方法结束时 测试 2 不会改变任何东西。

方法测试3 : 对象没有保留。为什么是方法测试 2 不是这样吗?

作为一个附带问题,我实际上想知道弱变量是否是线程安全的?即,如果我在尝试从不同线程访问弱变量时永远不会得到任何 BAD_ACCESS 异常。

@interface Object : NSObject
@property (nonatomic) NSInteger index;
@end

@implementation Object

- (id)initWithIndexNSInteger) 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 valueWithNonretainedObjectbject];
    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



Best Answer-推荐答案


在我触及你的一些问题之前,我对你的三个测试有两个观察:

  • 您的测试很复杂,因为您连续运行所有三个测试,而不是回到运行循环,因此您的自动释放池没有被刷新(因此它使事情看起来像它们持续的时间比它们长通常会)。您应该进行测试,一次一个测试,以真正了解发生了什么。如果您对某个对象的生命周期得出结论是不好的,而您确实可能只是遇到了一些事实,即您没有让自动释放池被刷新。
  • 您正在以 dispatch_async 的身份进行所有这些测试。 ,它非常快速地启动分派(dispatch) block ,有时比底层对象超出范围更快,并且您经常访问 weakObject作为调度 block 中的第一步。我建议使用 dispatch_after (所以你真的给了调用方法一个让变量超出范围的机会),所以你最好看看发生了什么。

  • 您的测试是一个很好的数据点,但我认为使用 dispatch_after 测试相同的东西也很有用一次做一个测试,少一些 sleepForTimeInterval .感觉就像你测试的一些特性正在伪造一些关键行为。

    反正你问:

    Method test2: I don't understand why the object seems to get retained until the end of method test3! Even explicitly setting object = nil at the end of method test2 does not change anything.



    毫无疑问掉进了自动释放池,直到test才会被排空。方法完成。

    对于我之前的观点,尝试做 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");
    }
    

    您会发现这更符合您的预期。

    Method test3: the object is not retained. Why is method test2 not behaving like this?



    不用说,您的 test3是严重的坏消息,很容易崩溃。例如,尝试注释掉 sleep 行:
    - (void) test3 {
        NSLog(@"test3");
        Object* object = [[Object alloc] initWithIndex:3];
        NSLog(@"Object: %@", object);
        NSValue *weakObject = [NSValue valueWithNonretainedObjectbject];
        dispatch_async(dispatch_queue_create(NULL, NULL), ^{
            NSLog(@"Weak object: %@", [weakObject nonretainedObjectValue]);
            [NSThread sleepForTimeInterval:2];
            NSLog(@"Exiting dispatch");
        });
    //    [NSThread sleepForTimeInterval:1];
        NSLog(@"Exiting method");
    }
    

    我觉得它的行为不像 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.



    您可以通过多种方式获得异常。如果您通过 weakObject某些方法要求它不是 nil (例如 NSMutableArray 方法 addObject ),你会得到一个异常(exception)。如果您为 nil 取消引用 ivars,您也可以获得异常。对象指针,例如obj->objectIvar .例如,想象一个 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
        });
    }
    

    因此,您通常将上述内容替换为:
    - (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 发送任何消息都会导致 nil),但在您的 weakObject 时很重要。是一个参数或正在为 ivar 取消引用。

    但要明确的是,尽管如此,这些都与线程安全没有任何关系。您可以通过正确处理同步来实现线程安全,例如 locking mechanisms或通过明智的use of queues (串行队列;或并发队列的读写器模式,dispatch_barrier_async 用于写入,dispatch_sync 用于读取)。

    仅仅因为您在代码中仔细处理对象引用以免出现异常,并不意味着您已经实现了线程安全。线程安全还涉及另一层问题。

    关于ios - ARC block ,弱和保留计数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16499739/






    欢迎光临 OGeek|极客世界-中国程序员成长平台 (http://ogeek.cn/) Powered by Discuz! X3.4