web-dev-qa-db-ja.com

ARCでのポインターツーポインターの所有権の問題の処理

Object Aに次のプロパティがあるとします。

@property (nonatomic, strong) Foo * bar;

次のように実装で合成されます。

@synthesize bar = _bar;

オブジェクトBは、この例のオブジェクトAからの呼び出しのように、Foo **を操作します。

Foo * temp = self.bar;
[objB doSomething:&temp];
self.bar = temp;
  • これまたは同様のことを正当に行うことができますか?
  • doSomething:メソッドの正しい宣言は何ですか?

さらに、barプロパティを設定する前にObject Bが割り当て解除される可能性があるとします(したがって、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

41
Steve

ARCは、オブジェクト参照の所有権を知っている必要があるため、いつ解放するかなどを決定できます。任意の変数(ローカル、インスタンス、またはグローバル)の場合、ARCには所有権を決定するためのルールがあります。推論または明示的な属性のいずれかによる。これは、プログラマーが所有権を追跡するためのARC以前の必要性に相当します。

しかし、変数への参照がある場合はどうなりますか?自分で(ARC以前の)変数への参照を受け入れ、その変数の所有権に関係なく常に正しく機能するコードを自分で作成することはできませんでした。解放する必要があるかどうかわからなかったためです。変数(変更の意味で!)不明な所有権に対して機能するコードを作成することはできません。

ARCは同じ問題に直面しており、その解決策は、参照される変数の所有権を指定する明示的な属性を推測するか受け入れることで、callerに変数の参照を準備することを要求します。渡される適切な所有権。この後者のビットでは、非表示の一時変数を使用する必要があります。これは 指定 では「最も悪い解決策」と呼ばれ、「ライトバックによるパス」と呼ばれます。

質問の最初の部分:

Foo * temp = self.bar;
[objB doSomething:&temp];
self.bar = temp;
  • これまたは同様のことを正当に行うことができますか?

はい、コードはARCで問題ありません。 tempstrongであると推定されており、舞台裏ではdoSomething:への参照によって渡されています。

  • DoSomething:メソッドの正しい宣言は何ですか?
- (void) doSomething:(Foo **)byRefFoo

ARCは、byRefFooFoo * __autoreleasing *タイプ-自動解放参照への参照であると推測します。これは、「書き戻しのパス」で必要なものです。

tempはローカルであるため、このコードはonlyです。インスタンス変数を使用してこれを行うのは正しくありません(EDITで確認したとおり)。これはonlyパラメータも標準の "out"モードで使用され、doSomething:が返されたときに更新された値が割り当てられていると想定して有効です 。これらはどちらも、「最小の悪い解決策」の一部としてライトバックのパスが機能するためです...

概要:ローカル変数を使用する場合、ARCが必要な属性などを推測する標準の「アウト」パターンで使用するために、参照によって渡すことができます。

フードの下

質問のFooの代わりに、タイプBreadcrumbsを使用します。これは本質的に、ラップされたNSStringであり、すべてのinitretainreleaseautorelease、およびdeallocを追跡するため(以下を参照)、何が起こっているかを確認できます。 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が割り当てる前にそれを自動解放します。これは、渡された参照が自動解放変数を参照しているためです。

注:ARCは、参照される変数(_の前の内容(0x100176f30)を使用して何もする必要はありません(0x7fff5fbfedb8)そのままautoreleasingであるため、その責任はありません。つまり「所有権の自動解放」とは、割り当てられた参照が既に効果的に自動解放されている必要があることを意味します。隠し変数を作成すると、ARCがその内容を実際に保持および自動解放しなかったことがわかります。これは、管理しているオブジェクトへの(local内の)強い参照があることを知っているため、これを行う必要はありませんでした。 [下の最後の例では、ARCはこの割り当てを管理する必要がありますが、自動解放プールの使用を回避することはできます。]

ark[2041:707] >>> 0x100427d10: retain
ark[2041:707] >>> 0x100176f30: release
ark[2041:707] >>> 0x100176f30: dealloc

これらのアクションは、隠し変数からlocalへの値のコピー(call-by-writebackの「書き戻し」)の結果です。 release/deallocはlocalの古い強力な参照用で、retainは隠し変数(indirect:によって自動解放された)によって参照されるオブジェクト用です

注:この書き戻しが、参照渡しを使用する "out"パターンでのみ機能する理由です。indirect:に渡された参照を次のように保存することはできません。それは消えようとしている隠されたローカル変数に対するものです...

ark[2041:707] local: addr 0x7fff5fbfedc0, contains 0x100427d10 - banana, owners 2

したがって、呼び出し後、localは新しいオブジェクトを参照し、2所有者がいます-localは1つを占め、もう1つはindirect:autoreleaseです

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がローカライズされた@autoreleasepoolindirect:からの保留中の自動解放を処理した後、所有権はゼロになり、deallocを取得します。

参照によってインスタンス変数を渡す

上記はローカル変数を参照渡しすることを扱っていますが、残念ながら、ライトバック渡ししないはインスタンス変数に対して機能します。 2つの基本的な解決策があります。

  • インスタンス変数をローカルにコピーする

  • いくつかの属性を追加する

2番目を示すために、クラスByRefstrongIndirect:を追加します。これは、強い変数への参照が必要であることを指定します。

- (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

以前より少し短いです。これには2つの理由があります。

  • 強い変数(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

これは前の例とほとんど同じですが、わずかに2つの違いがあります。

  • スタック上のローカルのアドレスが渡されます(0x7fff5fbfedc0)、4行目と5行目

  • ローカルに保存されるため、新しいオブジェクトはARCの11行目と12行目でクリーンアップされます。

参照引数に常に__strongを追加しないのはなぜですか?

すべてが強いわけではないからです。 ARCのpass-by-writebackは、弱いローカルに対しても機能します。最後のデモ:

- (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に割り当てられたものを示しています。`インスタンスからこの弱い変数に強参照をコピーする場合は、保持する必要がないことに注意してください。 (注:弱い変数の内容を表示するには、保持と解放の操作が含まれますが、これらは明確にするために省略されています。)

  • ARCは、弱いローカル(4行目、0x7ffeefbfde40)にも非表示変数(6行目、0x7ffeefbfde58)を使用します。強力なローカルケース(demo1)では、ARCはこの隠し変数に格納された参照が有効なままであることを認識しており、自動解放プールの使用を回避します。この場合、これは保証されませんが、ARCは依然として自動解放プールを回避します。ARCは参照を保持し(行5、retainWeakReferenceは、弱い変数のretainの特別なバージョンです)、呼び出しが完了した後、これを解放(行10)。これにより、自動解放プールを使用する場合と比較して、強参照の有効期間が短くなります。

  • indirectWrapper(8行目)内の自動解放された割り当て(0x604000001060)は、プールがドレーンされると(13行目と14行目)バランスが取られます。最後に、instanceインスタンスがなくなると、ARCはByRef0x608000000d10)に格納されているオブジェクトをクリーンアップします。

概要

  • 追加された属性がなければ、ARCは参照によってパラメーターとして渡されるローカル(推定される強い)変数に対して正しいことを行います(推定される自動解放)。 (「ローカル」には、現在のメソッドのパラメーターが含まれます。)

  • これは、ARCによってpass-by-writebackを使用して実装され、「-out」パラメーターパターンに従っている場合はonlyが機能します。渡された参照を後で使用するために保存したい場合は、さらに自分で行う必要があります。

  • インスタンス変数を参照渡しする場合は、それらをローカル変数にコピーするか、受信パラメータタイプを__strongで属性付けする必要があります。

  • pass-by-writebackは、__weakローカルに対しても機能します。

お役に立てば幸いです。


2016年4月補遺:__block変数

Heath Bordersがコメントしたコメント:

ローカル変数が__blockタイプの場合はどうなりますか?このケースはインスタンス変数と同じだと確信しています。ローカルにコピーするか、受信パラメータタイプを__strongで属性付けする必要がありますが、他の誰かの意見に興味があります。

興味深い質問です。

仕様 の状態:

引数式が有効な形式でない場合、書き戻しのパスは正しくありません。

&var。ここで、varは、保持可能なオブジェクトポインタータイプを使用した自動ストレージ期間のスカラー変数です。

(Objective-)Cのローカル変数には、デフォルトで自動ストレージ期間があります-それらは、それらを囲む関数/メソッド/ブロックに入る/終了するときに自動的に作成および破棄されます。上記の回答で「ローカル変数」を参照する場合、自動保存期間を持つローカル変数を暗黙的に参照しています。

ローカル変数は、ストレージ修飾子またはストレージクラス指定子で宣言して、変数のストレージ期間を変更できます。最も一般的に見られるのはstaticです。 static storage durationを持つローカル変数は、プログラムの実行中に存在しますが、ローカルスコープ内でのみ(直接)アクセスできます。

パスバイライトバックでstaticローカル変数を渡そうとすると、コンパイラーは変数に自動ストレージ期間がないことを示すエラーを生成します。このような変数は、インスタンス変数(割り当てられたストレージ期間を持つ)と同じ方法で処理する必要があります。

__block storage qualifier がブロックの一部として(Objective-)Cに導入され、仕様は次のように述べています。

__blockストレージ修飾子は、既存のローカルストレージ修飾子autoregister、およびstaticと相互に排他的です。 __blockで修飾された変数は、割り当てられたストレージにあるかのように動作し、この変数は最後に使用された後に自動的に回復されます。

したがって、__blockローカル変数は、インスタンス変数と同じように、ストレージ期間が割り当てられているかのように動作します。ライトバイパスの仕様では、このような変数は自動ストレージ期間がないため使用できません。 。

ただし執筆時点で最新のツールを使用(Xcode 7.2、Clang 7.0.2)__block修飾されたローカル変数は、ライトバックによるパスでサポートされており、自動保存期間-非表示の__autoreleasing一時ファイルが使用されます。

これは文書化されていないようです。

コンパイルしてもしなくてもよいという意味で使用しても「安全」であると述べました。一度コンパイルすると、ツールが変更されて将来再びコンパイルできない場合でもコードは機能します...(少なくとも処理なしで)変数は、インスタンス変数を処理する必要があるのと同じでした)。

それが受け入れられる理由は write-by-passbackの制限の根拠 (強調を追加)から収集できます。

根拠

議論の形での制限は2つの目的を果たします。第1に、配列のアドレスを引数に渡すことができなくなります。これは、「配列」引数を出力パラメーターとして誤って推測するという重大なリスクから保護するのに役立ちます。 2番目に、元の引数変数にライトバック一時ファイルへのストアがすぐに表示されない、以下の実装により、ユーザーが混乱するエイリアスの問題を目にする可能性がはるかに低くなります

インスタンス変数がpass-by-writebackでサポートされなかった技術的な理由はありませんが、エイリアスが原因で混乱する可能性があります。 __block変数は、自動変数と割り当てられた変数の中間にあるため、現在のツールの作成者は、パスバイライトバックのために、後者ではなく前者でそれらをグループ化することを選択します。

注:ブロックの実装に精通している読者は、__block修飾ローカルが、使用方法に応じて、自動または割り当てられたストレージ期間の最適化として実装されることを知っています。したがって、これがライトバイパスバックの使用に影響するかどうか疑問に思います。これはそうではないようです。

144
CRD

これは完全に正当です。プロパティへのアクセスは無関係です。オブジェクトへのポインタを渡すことは、通常、NSError*オブジェクトで行われます。

メソッドを宣言する正しい方法は

- (returntype)doSomething:(Foo * __autoreleasing *)arg;

これは、__autoreleasingオブジェクトへのポインタとして宣言します。これは、基本的に、ポイントされているオブジェクトが-autoreleasedであると想定されていることを意味します。

「さらに」については、ARCでは問題ではありません。あなたのライン

Foo * temp = self.bar;

に相当

__strong Foo *temp = self.bar;

これにより、tempが強力な参照になるため、変数が存在する限りその値を「所有」することは明らかです。つまり、あなたは言うことができます

Foo *temp = self.bar;
self.bar = nil;

tempはまだ有効です。

6
Lily Ballard