为什么ARC有时仅保留__block out指针?

希思边界

1)为什么要保留其__blockvar:

{
    void (^blockWithOutPointer)(NSObject * __autoreleasing *) = ^(NSObject * __autoreleasing * outPointer) {
        *outPointer = [NSObject new];
    };

    NSObject * __block blockVar1;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
                                 (int64_t)(1 * NSEC_PER_SEC)),
                   dispatch_get_main_queue(),
                   ^{
                       NSLog(@"blockVar1: %@",
                             blockVar1);
                       // prints non-nil. WHY????
                   });
    blockWithOutPointer(&blockVar1);
}

2)但这不是吗?

void (^blockWithOutPointerThatDispatchesLater)(NSObject * __autoreleasing *,
                                               dispatch_block_t) = ^(NSObject * __autoreleasing * outPointer,
                                                                     dispatch_block_t block) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
                                 (int64_t)(1 * NSEC_PER_SEC)),
                   dispatch_get_main_queue(),
                   block);
    *outPointer = [NSObject new];
};

{
    NSObject * __block blockVar2;
    blockWithOutPointerThatDispatchesLater(&blockVar2,
                                           ^{
                                               NSLog(@"blockVar2: %@",
                                                     blockVar2);
                                           });
    // prints nil, which is expected.
}

3)如果我改用一个__autoreleasing变量作为我的输出指针目标,然后将该变量分配给我的__block指针,则一切正常。

{
    NSObject * __autoreleasing autoreleasingVar;
    NSObject * __block blockVar3;
    blockWithOutPointerThatDispatchesLater(&autoreleasingVar,
                                           ^{
                                               NSLog(@"blockVar3: %@",
                                                     blockVar3);
                                           });
    blockVar3 = autoreleasingVar;
    // prints non-nil, which is expected.
}

我已经阅读了CRD关于ARC指针对指针问题的答案,因为ARC假定blockVar2__autoreleasing,并且不保留其值,所以#2将显示nil是有意义的因此,在#3中,当我们分配autoreleasingVar给时blockVar3,ARC会正确保留该值。但是,#1没有这样的分配。为什么#1保留其价值?

更令人惊讶的是,如果将出站指针分配包装在#中,则#1不会受到影响@autoreleasepool

{
    void (^blockWithOutPointer)(NSObject * __autoreleasing *) = ^(NSObject * __autoreleasing * outPointer) {
        @autoreleasepool {
            *outPointer = [NSObject new];
        }
    };

    NSObject * __block blockVar1;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
                                 (int64_t)(1 * NSEC_PER_SEC)),
                   dispatch_get_main_queue(),
                   ^{
                       NSLog(@"blockVar1: %@",
                             blockVar1);
                       // still prints non-nil. WHY???
                   });
    blockWithOutPointer(&blockVar1);
}

而#3崩溃,正如自@autoreleasepool释放指针对象以来所预期的那样,我想ARC不会将__autoreleasing变量设置nil

void (^blockWithOutPointerThatDispatchesLater)(NSObject * __autoreleasing *,
                                               dispatch_block_t) = ^(NSObject * __autoreleasing * outPointer,
                                                                     dispatch_block_t block) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
                                 (int64_t)(1 * NSEC_PER_SEC)),
                   dispatch_get_main_queue(),
                   block);
    @autoreleasepool {
        *outPointer = [NSObject new];
    }
};

{
    NSObject * __autoreleasing autoreleasingVar;
    NSObject * __block blockVar3;
    blockWithOutPointerThatDispatchesLater(&autoreleasingVar,
                                           ^{
                                               NSLog(@"blockVar3: %@",
                                                     blockVar3);
                                               // crashes on the NSLog!
                                           });
    blockVar3 = autoreleasingVar;
}

为此提起了雷达

CRD

你已经发现了块实施的“功能”和(至少对案件1&2,我没有进一步检查),这是没有任何关系__autoreleasing 本身

首先,让我们看一下您的情况1.您似乎惊讶地打印出一个非nil值。它完全按预期工作:

  1. blockWithOutPointer(&blockVar1);执行,为分配一个值blockVar1然后
  2. 该块^{ NSLog(@"blockVar1: %@", blockVar1); }由GCD执行,并打印由(1)存储的内容。

(注意:您可以删除__autoreleasing限定符,它的工作原理与推断传递回传参数的方式相同)。

现在您的案例2。这是您实现“功能”的地方:

  1. 在Objective-C中,对象是堆分配的;
  2. Objective-C块是对象;所以
  3. Ergo块是堆分配的...
  4. 除非他们不在...

作为一种优化,块规范允许对块及其捕获的__block变量进行堆栈分配,并且仅在其生存期需要长于堆栈帧的寿命时才移动到堆上。

作为一种优化,它应该(a)对于程序员而言基本上是不可见的-除了任何性能方面的好处之外,并且(b)无论如何都不得更改语义。但是,Apple决定最初将其作为“程序员协助的优化”进行介绍,然后逐步改进。

情况2的行为完全取决于将块和__block变量复制到堆上时的情况。让我们看一下代码:

NSObject * __block blockVar2;

这声明blockVar2具有__block存储持续时间,这允许块更改此本地声明的变量的值。在这一点上,编译器不知道某个块是否可以访问它(在这种情况下blockVar2需要将其放在堆中),或者是否将无法在块中进行访问(在这种情况下它可以位于堆栈上)。

编译器决定优先使用堆栈并在其中进行分配blockVar2

blockWithOutPointerThatDispatchesLater(&blockVar2,

现在,编译器需要将地址blockVar2as作为第一个参数传递,该变量当前在堆栈上,并且编译器发出代码以计算其地址。该地址在堆栈上

                                       ^{
                                           NSLog(@"blockVar2: %@",
                                                 blockVar2);
                                       });

现在,编译器进入第二个参数。它看到了该块,该块访问了blockVar2,并且通过blockVar2限定了该块__block因此它必须捕获变量本身而不是变量值。

编译器决定该块应放在堆上。为此,它需要迁移blockVar2到堆上,因此它要与当前值一起执行nil...

糟糕!

第一个参数是blockVar2堆栈上原始地址,而第二个参数是一个块,该块又引用blockVar2堆上克隆的对象。

执行代码时blockWithOutPointerThatDispatchesLater()分配一个对象并将其地址存储在堆栈中 blockVar2; 然后GCD执行延迟块,该块打印出 的值blockVar2,即nil

“修复”

只需将代码更改为:

NSObject * __block blockVar2;
dispatch_block_t afterBlock = ^{
     NSLog(@"blockVar2: %@", blockVar2);
};
blockWithOutPointerThatDispatchesLater(&blockVar2, afterBlock);

即预先计算第二个参数表达式。现在,编译器先查看该块,然后&blockVar2将该块blockVar2移至堆,并且为生成的值&blockVar2是的版本的地址blockVar2

扩展结论:是错误还是功能?

最初的答案只是简单地指出这显然是一个错误,而不是功能,并建议您提交一个错误报告,并指出该错误以前已被报告过,但是另一份报告也不会造成危害。

但这可能有点不公平,是的,它是一个bug,问题是该bug实际上是什么。考虑:

  • 在(Objective-)C变量中,在堆栈上或静态分配的变量以及分配的动态内存块在其生存期内均不会移动。

  • 编译器的优化通常不应改变程序的含义或正确性。

苹果公司已经实现了编译器优化-将块和捕获的__block变量存储在堆栈中-可以__block属性变量的生命周期内移动它们,从而可以改变程序的含义和正确性。

结果是对程序语句进行了简单的重新排序,其方式不应该改变程序的含义或事实上的正确性。这不好!

考虑到Apple实施的优化的历史,其设计正确性取决于程序员的帮助(尽管此后已变得更加自动化),以确保其正确性,这可以简单地视为所选实现的另一个“功能”。

推荐

永远不要将address的(&)运算符应用于具有__block存储持续时间的变量如果这样做可能会奏效,但可能并不容易。

如果您需要使用__block变量作为传递回写参数,则先将其复制到本地临时目录,进行调用,最后再将其复制回。

高温超导

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

为什么切片有时通过引用传递,有时通过指针传递?

为什么要返回静态指针而不是out参数?

为什么有时将2D图像建模为指针(T **)?

为什么这个程序给我一个 IndexError: list index out of range 错误但只是有时

空指针异常错误...有时

为什么recyclerView的findviewbyposition()有时仅返回null

当所有变量都声明为double时,为什么System.out.printf仅与%f一起使用?

调用不访问空对象指针上的数据的方法有时会起作用,有时会崩溃。为什么它不总是崩溃?

为什么有时 my_arc_mutex.clone() 会在使用完成之前被释放?

根据将对象转换为指针的时间(有时是指针,有时是映射)来更改编组功能

当指针指向零值时,为什么指针为空白?

为什么指针a的值改变时指针b的值不变

为什么删除动态指针时出错?

为什么不在LinkedList中保留最后一个节点指针以减少结束时的插入?

为什么Java对象指针的指针?

当Java中没有指针时,为什么Java具有“ NullPointerException”?

为什么指针仅增加一次

为什么有时仅将memcmp(a,b,4)优化为uint32比较?

为什么有时不能仅引用非静态方法?

为什么我的SectionList有时仅呈现一个部分?

为什么将指针传递给具有常量指针类型参数的函数时不需要类型转换?

为什么自定义比较器时出现有关空指针的错误

创建带有关联指针的对象时,为什么要使用“ new”?

为什么带有指针接收器的方法在接收值时仍然可以工作?

初始化const指针为null时为什么没有警告或错误?

为什么在Rust中修改字符串变量时指针的地址没有改变?

当打印出链表时,为什么原始的头部指针没有改变

当我通过指针修改const变量时,为什么原始值没有更新?

*(指针)和(*指针)有什么区别?