gpt4 book ai didi

objective-c - 处理ARC中的指针对指针所有权问题

转载 作者:行者123 更新时间:2023-12-03 07:06:10 27 4
gpt4 key购买 nike

假设对象具有一个属性:

@property (nonatomic, strong) Foo * bar;

在实现中综合为:
@synthesize bar = _bar;

对象B 操作 Foo **,如本例中从 对象A 进行的调用:
Foo * temp = self.bar;
[objB doSomething:&temp];
self.bar = temp;
  • 是否可以合法地完成此操作或类似的操作?
  • doSomething:方法的正确声明是什么?

  • 此外,假设 对象B 可能在我有机会设置 bar属性之前被释放了(从而获得了 temp指向的实例的所有权)-我将如何告诉ARC移交拥有的引用?换句话说,如果我希望以下示例代码起作用,我将如何处理ARC问题?
    Foo * temp = self.bar;    // Give it a reference to some current value
    [objB doSomething:&temp]; // Let it modify the reference
    self.bar = nil; // Basically release whatever we have
    _bar = temp; // Since we're getting back an owning reference, bypass setter
  • 我在想什么?

  • 编辑

    基于@KevinBallard的回答,我只想确认我的理解。这个对吗?

    对象A:
    @implementation ObjectA

    @synthesize bar = _bar;

    - (void)someMethod
    {
    ObjectB * objB = [[ObjectB alloc] initWithFoo:&_bar];
    // objB handed off somewhere and eventually it's "doSomething" method is called.
    }

    @end

    对象B:
    @implementation ObjectB
    {
    Foo * __autoreleasing * _temp;
    }

    - (id)initWithFoo:(Foo * __autoreleasing *)temp
    {
    id self = [super init];
    if (self)
    {
    _temp = temp;
    }
    return self;
    }

    - (void)doSomething
    {
    ...
    *_temp = [[Foo alloc] init];
    ...
    }

    @end

    这将产生一个编译时错误: passing address of non-local object to __autoreleasing parameter for write-back

    最佳答案

    ARC需要知道对象引用的所有权,以便它可以确定何时释放它,等等。对于任何变量(本地,实例或全局),ARC都有确定所有权的规则。通过推断或通过显式属性。这等同于程序员需要在ARC之前跟踪所有权。
    但是,如果引用变量,会发生什么?您无法(在ARC之前)自己编写代码,该代码接受对变量的引用,并且无论该变量的所有权如何,该代码始终可以正常工作-因为您不知道是否需要释放变量等。您无法构建适用于变量(从变化的意义上来说)未知所有权的代码。
    ARC面临相同的问题,其解决方案是推断或接受指定引用变量所有权的显式属性,然后要求调用者安排对要传递的具有适当所有权的变量的引用。后一点可能需要使用隐藏的临时变量。这在specification中称为“最差的解决方案”,并称为“回写传递”。
    问题的第一部分:

    Foo * temp = self.bar;
    [objB doSomething:&temp];
    self.bar = temp;
    • Can this, or something similar, be done legitimately?

    是的,ARC可以使用该代码。 temp被推断为 strong,一些幕后的东西碰巧通过引用 doSomething:传递给它。
    • What is the correct declaration for the doSomething: method?
    - (void) doSomething:(Foo **)byRefFoo
    ARC推断 byRefFoo的类型为 Foo * __autoreleasing *-对自动释放引用的引用。这就是“传递回写”所要求的。
    此代码仅 ,仅有效,因为 temp是本地代码。用实例变量执行此操作是不正确的(如您在EDIT中所发现的)。假设该参数在标准“输出”模式下使用并且 doSomething:返回时已分配了任何更新的值,则它也仅对 有效。这两个都是因为传递回写方式是“最差的解决方案”的一部分。
    摘要:使用局部变量时,可以通过引用将其传递以在标准“输出”模式下使用,ARC可以推断出任何必需的属性等。
    引擎盖下
    代替问题的Foo,我们将使用Breadcrumbs类型;这实际上是一个包装的NSString,它跟踪每个initretainreleaseautoreleasedealloc(以及您几乎会在下面看到的),因此我们可以看到发生了什么。 Breadcrumbs的编写方式并不重要。
    现在考虑以下类:
    @implementation ByRef
    {
    Breadcrumbs *instance; // __strong inferred
    }
    更改通过引用传递的值的方法:
    - (void) indirect:(Breadcrumbs **)byRef                  // __autoreleasing inferred
    {
    *byRef = [Breadcrumbs newWith:@"banana"];
    }
    一个简单的indirect:包装器,因此我们可以看到它传递了什么以及何时返回:
    - (void) indirectWrapper:(Breadcrumbs **)byRef           // __autoreleasing inferred
    {
    NSLog(@"indirect: passed reference %p, contains %p - %@, owners %lu", byRef, *byRef, *byRef, [*byRef ownerCount]);
    [self indirect:byRef];
    NSLog(@"indirect: returned");
    }
    以及演示在局部变量上调用indirect:的方法(想象中的local):
    - (void) demo1
    {
    NSLog(@"Strong local passed by autoreleasing reference");
    Breadcrumbs *local; // __strong inferred
    local = [Breadcrumbs newWith:@"apple"];
    NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]);
    [self indirectWrapper:&local];
    NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]);
    }

    @end
    现在,执行demo1的一些代码将自动释放池本地化,以便我们可以查看分配,释放的内容以及何时:
    ByRef *test = [ByRef new];

    NSLog(@"Start demo1");
    @autoreleasepool
    {
    [test demo1];
    NSLog(@"Flush demo1");
    }
    NSLog(@"End demo1");
    执行上述操作会在控制台上产生以下内容:
    ark[2041:707] Start demo1
    ark[2041:707] Strong local passed by autoreleasing reference
    ark[2041:707] >>> 0x100176f30: init
    ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1
    ark[2041:707] indirect: passed reference 0x7fff5fbfedb8, contains 0x100176f30 - apple, owners 1
    ark[2041:707] >>> 0x100427d10: init
    ark[2041:707] >>> 0x100427d10: autorelease
    ark[2041:707] indirect: returned
    ark[2041:707] >>> 0x100427d10: retain
    ark[2041:707] >>> 0x100176f30: release
    ark[2041:707] >>> 0x100176f30: dealloc
    ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100427d10 - banana, owners 2
    ark[2041:707] >>> 0x100427d10: release
    ark[2041:707] Flush demo1
    ark[2041:707] >>> 0x100427d10: release
    ark[2041:707] >>> 0x100427d10: dealloc
    ark[2041:707] End demo1
    [>>>“行来自Breadcrumbs。]只需跟随对象(0x100 ...)和变量(0x7fff ...)的地址,就很清楚了...
    好吧,也许不是!在每个块后面再加上注释:
    ark[2041:707] Start demo1
    ark[2041:707] Strong local passed by autoreleasing reference
    ark[2041:707] >>> 0x100176f30: init
    ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1
    在这里,我们看到[Breadcrumbs newWith:@"apple"]在地址0x100176f30处创建了一个对象。这存储在local中,其地址为0x7fff5fbfedc0,并且该对象具有1个所有者(local)。
    ark[2041:707] indirect: passed reference 0x7fff5fbfedb8, contains 0x100176f30 - apple, owners 1
    这是隐藏的变量:由于indirect:需要引用自动释放变量,因此ARC创建了一个新变量,其地址为0x7fff5fbfedb8,并将对象引用(0x100176f30)复制到该变量中。
    ark[2041:707] >>> 0x100427d10: init
    ark[2041:707] >>> 0x100427d10: autorelease
    ark[2041:707] indirect: returned
    indirect:内部,将创建一个新对象,并且ARC在分配它之前会自动释放它-因为传递的引用引用了一个自动释放变量。

    Note: ARC does not need to do anything with the previous contents (0x100176f30) of the referenced variable (0x7fff5fbfedb8) as it is autoreleasing and hence not its responsibility. I.e. what "autoreleasing ownership" means is that any reference assigned must have already been effectively autoreleased. You'll see when creating the hidden variable ARC did not actually retain and autorelease its contents - it did not need to do this as it knows there is a strong reference (in local) to the object which it is managing. [In the last example below ARC does have to manage this assignment but it still manages to avoid using the autorelease pool.]

    ark[2041:707] >>> 0x100427d10: retain
    ark[2041:707] >>> 0x100176f30: release
    ark[2041:707] >>> 0x100176f30: dealloc
    这些操作是由于将隐藏变量的值复制(即写回调用中的“写回”)到local中而导致的。 release / dealloc用于local中的旧强引用,而retain用于隐藏变量(已由indirect:自动释放)引用的对象。

    Note: this writeback is why this only works for the "out" pattern of using pass-by-reference - you can't store the reference passed to indirect: as it is to a hidden local variable which is about to disappear...

    ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100427d10 - banana, owners 2
    因此,在调用local引用新对象之后,它有2个所有者-local占一个,另一个是autorelease中的indirect:
    ark[2041:707] >>> 0x100427d10: release
    demo1现在完成,因此ARC以local释放对象
    ark[2041:707] Flush demo1
    ark[2041:707] >>> 0x100427d10: release
    ark[2041:707] >>> 0x100427d10: dealloc
    ark[2041:707] End demo1
    demo1返回本地化的@autoreleasepool处理indirect:挂起的自动发布之后,现在所有权为零,我们得到了dealloc
    通过引用传递实例变量
    上面的方法通过引用传递局部变量,但是不幸的是,传递回写不适用于实例变量。有两种基本解决方案:
  • 将您的实例变量复制到本地
  • 添加一些属性

  • 为了演示第二个,我们在ByRef类中添加了strongIndirect:,它指定它需要对强变量的引用:
    - (void) strongIndirect:(Breadcrumbs * __strong *)byRef
    {
    *byRef = [Breadcrumbs newWith:@"plum"];
    }

    - (void) strongIndirectWrapper:(Breadcrumbs * __strong *)byRef
    {
    NSLog(@"strongIndirect: passed reference %p, contains %p - %@, owners %lu", byRef, *byRef, *byRef, [*byRef ownerCount]);
    [self strongIndirect:byRef];
    NSLog(@"strongIndirect: returned");
    }
    以及一个对应的demo2,它使用ByRef的实例变量(再次使用instance的虚构名称):
    - (void) demo2
    {
    NSLog(@"Strong instance passed by strong reference");
    instance = [Breadcrumbs newWith:@"orange"];
    NSLog(@"instance: addr %p, contains %p - %@, owners %lu", &instance, instance, instance, [instance ownerCount]);
    [self strongIndirectWrapper:&instance];
    NSLog(@"instance: addr %p, contains %p - %@, owners %lu", &instance, instance, instance, [instance ownerCount]);
    }
    使用与上面的demo1类似的代码片段执行此操作,我们得到:
    1  ark[2041:707] Start demo2
    2 ark[2041:707] Strong instance passed by strong reference
    3 ark[2041:707] >>> 0x100176f30: init
    4 ark[2041:707] instance: addr 0x100147518, contains 0x100176f30 - orange, owners 1
    5 ark[2041:707] strongIndirect: passed reference 0x100147518, contains 0x100176f30 - orange, owners 1
    6 ark[2041:707] >>> 0x100427d10: init
    7 ark[2041:707] >>> 0x100176f30: release
    8 ark[2041:707] >>> 0x100176f30: dealloc
    9 ark[2041:707] strongIndirect: returned
    10 ark[2041:707] instance: addr 0x100147518, contains 0x100427d10 - plum, owners 1
    11 ark[2041:707] Flush demo2
    12 ark[2041:707] End demo2
    这比以前短了一点。这有两个原因:
  • 当我们将强变量(instance)传递给期望引用强变量的方法(strongIndirect:)时,ARC无需使用隐藏变量-上面第4行和第5行中的变量相同(0x100147518 )。
  • 因为ARC知道strongIndirect:中的引用变量很强,所以不需要在strongIndirect:中存储自动发布的引用,然后在调用后写回该代码-ARC只是执行标准的强分配,第6-8行,没有什么要自动发布的之后(在第11和12行之间)。

  • strongIndirect:是否适用于强大的本地人?
    当然,这是demo3:
    - (void) demo3
    {
    NSLog(@"Strong local passed by strong reference");
    Breadcrumbs *local; // __strong inferred
    local = [Breadcrumbs newWith:@"apple"];
    NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]);
    [self strongIndirectWrapper:&local];
    NSLog(@"local: addr %p, contains %p - %@, owners %lu", &local, local, local, [local ownerCount]);
    }
    使用我们的标准包装器执行此操作会产生:
    1  ark[2041:707] Start demo3
    2 ark[2041:707] Strong local passed by strong reference
    3 ark[2041:707] >>> 0x100176f30: init
    4 ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1
    5 ark[2041:707] strongIndirect: passed reference 0x7fff5fbfedc0, contains 0x100176f30 - apple, owners 1
    6 ark[2041:707] >>> 0x100427d20: init
    7 ark[2041:707] >>> 0x100176f30: release
    8 ark[2041:707] >>> 0x100176f30: dealloc
    9 ark[2041:707] strongIndirect: returned
    10 ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100427d20 - plum, owners 1
    11 ark[2041:707] >>> 0x100427d20: release
    12 ark[2041:707] >>> 0x100427d20: dealloc
    13 ark[2041:707] Flush demo3
    14 ark[2041:707] End demo3
    这几乎与前面的示例相同,只有两个小的区别:
  • 传递堆栈上的本地地址(0x7fff5fbfedc0),第4行和第5行
  • 因为它存储在本地中,所以新对象将由ARC第11和12行清除

  • 为什么不总是将__strong添加到引用参数?
    原因之一是因为并非所有事物都强大! ARC的传递回写功能也适用于弱势本地用户。我们的最终演示:
    - (void) demo4
    {
    NSLog(@"Weak local passed by autoreleasing reference");
    instance = [Breadcrumbs newWith:@"peach"];
    Breadcrumbs __weak *weakLocal = instance;
    NSLog(@"weakLocal: addr %p, contains %p - %@, owners %lu", &weakLocal, weakLocal, weakLocal, [weakLocal ownerCount]);
    [self indirectWrapper:&weakLocal];
    NSLog(@"weakLocal: addr %p, contains %p -, %@, owners %lu", &weakLocal, weakLocal, weakLocal, [weakLocal ownerCount]);
    }
    [这里我们只是使用了instance,因此我们需要对其进行弱引用。]
    使用我们的标准包装器执行此操作会产生:
     1 ark[2041:707] Start demo4
    2 ark[2041:707] Weak local passed by autoreleasing reference
    3 ark[2041:707] >>> 0x608000000d10: init
    4 ark[2041:707] weakLocal: addr 0x7ffeefbfde58, contains 0x608000000d10 - peach, owners 4
    5 ark[2041:707] >>> 0x608000000d10: retainWeakReference
    6 ark[2041:707] indirect: passed reference 0x7ffeefbfde40, contains 0x608000000d10 - peach, owners 2
    7 ark[2041:707] >>> 0x604000001060: init
    8 ark[2041:707] >>> 0x604000001060: autorelease
    9 ark[2041:707] indirect: returned
    10 ark[2041:707] >>> 0x608000000d10: release
    11 ark[2041:707] weakLocal: addr 0x7ffeefbfde58, contains 0x604000001060 - banana, owners 4
    12 ark[2041:707] Flush demo4
    13 ark[2041:707] >>> 0x604000001060: release
    14 ark[2041:707] >>> 0x604000001060: dealloc
    15 ark[2041:707] End demo4
    16 ark[2041:707] >>> 0x608000000d10: release
    17 ark[2041:707] >>> 0x608000000d10: dealloc
    笔记:
  • 第3、16和17行与instance相关-创建新值并在最后释放并释放-重要内容始于第4行
  • 第4行显示了已分配给weakLocal的内容,请注意,将`instance'的强引用复制到此弱变量中不需要任何保留。 (注意:显示弱变量的内容确实涉及某些保留和释放操作,为清楚起见已将其省略。)
  • ARC也为弱本地用户(第4行,0x7ffeefbfde40)使用了一个隐藏变量(第6行,0x7ffeefbfde58)。在强本地情况(demo1)中,ARC知道存储在此隐藏变量中的引用将保持有效,并避免使用自动释放池。在这种情况下,这不能保证,但是ARC仍然设法避免使用自动释放池:ARC保留了引用(第5行,retainWeakReferenceretain的特殊版本,用于弱变量),并且在调用完成后与释放(行)保持平衡。 10)。与使用自动释放池相比,这会缩短强引用的寿命。
  • 当池耗尽(第13和14行)时,0x604000001060(第8行)中的自动释放的分配(indirectWrapper)是平衡的。最后,当我们的instance实例消失后,ARC会清理存储在0x608000000d10(ByRef)中的对象。

  • 摘要
  • 如果不添加任何属性,ARC将对通过引用作为参数传递的局部(推断的强)变量(推断的自动释放)做正确的事情。 (“本地”包括当前方法的参数。)
  • 这是由ARC使用传递回写和来实现的,只有遵循“out”参数模式,才起作用。如果您希望存储传递的引用以供以后使用,则需要自己做更多的事情。
  • 如果您希望通过引用传递实例变量,则需要将其复制到本地变量中,或者使用__strong来指定接收参数的类型。
  • 写回传递也适用于__weak当地人。

  • 希望能有所帮助。

    附录2016年4月:__block变量
    希思·边界公司在评论中要求:

    What if my local variable is a __block type? I'm pretty sure this case is the same as an instance variable in that I need to either copy them to locals, or attribute the receiving parameter type with __strong, but I'm curious about someone else's opinion.


    有趣的问题。
    specification指出:

    The pass-by-writeback is ill-formed if the argument expression does not have a legal form:

    &var, where var is a scalar variable of automatic storage duration with retainable object pointer type


    默认情况下,(Objective-)C中的局部变量具有自动存储期限-在输入/退出其封闭函数/方法/块时,它们会自动创建和销毁。在上面的答案中,当我们提到“局部变量”时,我们隐式地指的是具有自动存储持续时间的局部变量。
    可以使用存储限定符或存储类指定符声明局部变量,以更改变量的存储持续时间。最常见的一种是static;具有静态存储持续时间的局部变量在程序的整个执行过程中都存在,但只能(直接)在其局部范围内访问。
    如果尝试通过传递回写传递static局部变量,则编译器将产生一个错误,指示该变量没有自动存储时间。您必须以与实例变量(已分配存储持续时间)相同的方式处理此类变量。
    __block storage qualifier作为块的一部分被引入到(Objective-)C中,并且规范状态如下:

    The __block storage qualifier is mutually exclusive to the existing local storage qualifiers auto, register, and static. Variables qualified by __block act as if they were in allocated storage and this storage is automatically recovered after last use of said variable.


    因此,__block局部变量就像实例变量一样,就像分配了存储持续时间一样工作,因此根据回写传递的规范,由于该变量没有自动存储持续时间,因此无法使用...
    但是和当前使用的工具(Xcode 7.2,Clang 7.0.2)在编写时是最新的。__block合格的局部变量受传递回写的支持,并且其处理方式与自动存储持续时间相同-隐藏的__autoreleasing临时变量是用过的。
    ,这似乎没有记录。
    话虽如此,从某种意义上说它是可以编译的还是不编译的,是“安全的”,并且一旦编译,即使工具发生了变化并且将来也无法再次编译,代码仍然可以工作...(至少没有处理)该变量与必须处理的实例变量相同)。
    可以接受它的原因可以从rationale for the restrictions on pass-by-writeback(强调)中获得:

    Rationale

    The restriction in the form of the argument serves two purposes. First, it makes it impossible to pass the address of an array to the argument, which serves to protect against an otherwise serious risk of mis-inferring an “array” argument as an out-parameter. Second, it makes it much less likely that the user will see confusing aliasing problems due to the implementation, below, where their store to the writeback temporary is not immediately seen in the original argument variable.


    没有技术上的原因为什么传递回写不能支持实例变量,但是由于混叠会引起混淆。 __block变量介于自动变量和分配变量之间,因此,当前的工具编写者可能选择将它们与前者而不是后者分组,以进行传递回写。

    Note: Readers familiar with the implementation of blocks will know that a __block qualified local may be implemented as an optimisation with either automatic or allocated storage duration, depending on usage, and therefore wonder whether this impacts their use for pass-by-writeback. This does not appear to be the case.

    关于objective-c - 处理ARC中的指针对指针所有权问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8814718/

    27 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com