草庐IT

ios - ARC block ,弱和保留计数

coder 2024-01-26 原文

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

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

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

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

作为一个附带问题,我实际上想知道弱变量是否是线程安全的?即,如果我在尝试从不同线程访问弱变量时永远不会得到任何 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

最佳答案

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

  • 您的测试很复杂,因为您连续运行所有三个测试,而不是回到运行循环,因此您的自动释放池没有被刷新(因此它使事情看起来像它们持续的时间比它们长通常会)。您应该进行测试,一次一个测试,以真正了解发生了什么。如果您对某个对象的生命周期得出结论是不好的,而您确实可能只是遇到了一些事实,即您没有让自动释放池被刷新。
  • 您正在以 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 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");
    }
    

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

    有关ios - ARC block ,弱和保留计数的更多相关文章

    1. ruby - 如何验证 IO.copy_stream 是否成功 - 2

      这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

    2. Ruby 文件 IO 定界符? - 2

      我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

    3. ruby-on-rails - Ruby on Rails 计数器缓存错误 - 2

      尝试在我的RoR应用程序中实现计数器缓存列时出现错误Unknownkey(s):counter_cache。我在这个问题中实现了模型关联:Modelassociationquestion这是我的迁移:classAddVideoVotesCountToVideos0Video.reset_column_informationVideo.find(:all).eachdo|p|p.update_attributes:videos_votes_count,p.video_votes.lengthendenddefself.downremove_column:videos,:video_vot

    4. ruby - 使用多个数组创建计数 - 2

      我正在尝试按0-9和a-z的顺序创建数字和字母列表。我有一组值value_array=['0','1','2','3','4','5','6','7','8','9','a','b','光盘','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','','u','v','w','x','y','z']和一个组合列表的数组,按顺序,这些数字可以产生x个字符,比方说三个list_array=[]和一个当前字母和数字组合的数组(在将它插入列表数组之前我会把它变成一个字符串,]current_combo['0','0','0']

    5. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

      1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

    6. ruby - 为什么不能使用类IO的实例方法noecho? - 2

      print"Enteryourpassword:"pass=STDIN.noecho(&:gets)puts"Yourpasswordis#{pass}!"输出:Enteryourpassword:input.rb:2:in`':undefinedmethod`noecho'for#>(NoMethodError) 最佳答案 一开始require'io/console'后来的Ruby1.9.3 关于ruby-为什么不能使用类IO的实例方法noecho?,我们在StackOverflow上

    7. Ruby 计数数组对象,如果对象包含值 - 2

      我有一个数组:array=['Footballs','Baseball','football','Soccer']而且我需要计算看到Football或Baseball的次数,无论大小写和复数形式如何。这是我尝试做的,但没有成功:array.count{|x|x.downcase.include?'football'||x.downcase.include?'baseball'}编写这段代码的正确或更好的方法是什么?我正在寻找3作为答案。 最佳答案 我会将count与一个block结合使用,该block根据与您正在寻找的约束相匹配的正

    8. ruby - 为 IO::popen 拯救 "command not found" - 2

      当我将IO::popen与不存在的命令一起使用时,我在屏幕上打印了一条错误消息:irb>IO.popen"fakefake"#=>#irb>(irb):1:commandnotfound:fakefake有什么方法可以捕获此错误,以便我可以在脚本中进行检查? 最佳答案 是:升级到ruby​​1.9。如果您在1.9中运行它,则会引发Errno::ENOENT,您将能够拯救它。(编辑)这是在1.8中的一种hackish方式:error=IO.pipe$stderr.reopenerror[1]pipe=IO.popen'qwe'#

    9. ruby - IO::EAGAINWaitReadable:资源暂时不可用 - 读取会阻塞 - 2

      当我尝试使用“套接字”库中的方法“read_nonblock”时出现以下错误IO::EAGAINWaitReadable:Resourcetemporarilyunavailable-readwouldblock但是当我通过终端上的IRB尝试时它工作正常如何让它读取缓冲区? 最佳答案 IgetthefollowingerrorwhenItrytousethemethod"read_nonblock"fromthe"socket"library当缓冲区中的数据未准备好时,这是预期的行为。由于异常IO::EAGAINWaitReadab

    10. ruby - AWS 上远程机器上的进程计数 - 2

      我正在为在AmazonEC2实例上运行的应用程序设计一个AutoScaling系统。应用程序从SQS读取消息并对其进行处理。AutoScaling系统将监控两件事:SQS中的消息数量,所有EC2机器上运行的进程总数。例如,如果SQS中的消息数量超过3000,我希望系统自动缩放,创建一个新的EC2实例,在其上部署代码,当消息数量低于2000时,我希望系统终止EC2实例.我正在用Ruby和Capistrano做这件事。我的问题是:我无法找到一种方法来确定在所有EC2机器上运行的进程数并将该数字保存在变量中。你能帮帮我吗? 最佳答案 您可

    随机推荐