web-dev-qa-db-ja.com

IBOutletを保持しないとどうなりますか?

私がこれを行う場合:

@interface RegisterController : UIViewController <UITextFieldDelegate>
{
    IBOutlet UITextField *usernameField;
}

これの代わりに:

@interface RegisterController : UIViewController <UITextFieldDelegate>
{
    UITextField *usernameField;
}
@property (nonatomic, retain) IBOutlet UITextField *usernameField;

何か悪いことが起こりますか? 2番目のケースでは、フィールドが保持されていることを知っていますが、ペン先がフィールドを所有しているので、これは違いますか?フィールドは保持なしで消えますか?そしてどのような状況で?最初のケースのコードは機能し、これがメモリ管理の観点から問題であるかどうか疑問に思っていました。

39
Jordan

明確さと一貫性を保つために、すべてのIBOutletのプロパティを宣言することをお勧めします。詳細は メモリ管理プログラミングガイド に詳しく説明されています。基本的な要点は、NIBオブジェクトがアーカイブされていない場合、nib読み込みコードが通過し、setValue:forKey:を使用してすべてのIBOutletsを設定することです。プロパティでメモリ管理動作を宣言すると、何が起こっているのかについての謎はありません。ビューがアンロードされたが、保持として宣言されたプロパティを使用した場合でも、テキストフィールドへの有効な参照があります。

おそらく、より具体的な例は、保持プロパティを使用する必要がある理由を示すのに役立ちます。

作業しているコンテキストについていくつかの仮定をします。上記のUITextFieldは、UIViewControllerによって制御される別のビューのサブビューであると仮定します。ある時点で、ビューが画面の外にあり(おそらく、UINavigationControllerのコンテキストで使用されている)、ある時点で、アプリケーションにメモリ警告が表示されると想定します。

したがって、UIViewControllerサブクラスがそのビューにアクセスして画面に表示する必要があるとします。この時点で、nibファイルがロードされ、各IBOutletプロパティは、setValue:forKey:を使用してnibロードコードによって設定されます。ここで注意すべき重要な点は、UIViewControllerのviewプロパティに設定されるトップレベルビュー(このトップレベルビューを保持します)とUITextField(これも保持されます)です。単純に設定されている場合は、nib読み込みコードによって保持が設定されます。それ以外の場合は、プロパティが保持します。 UITextFieldはトップレベルUIViewのサブビューでもあるため、トップレベルビューのサブビュー配列にある追加の保持があり、この時点でテキストフィールドは2回保持されています。

この時点で、プログラムでテキストフィールドを切り替えたい場合は、そうすることができます。プロパティを使用すると、ここでメモリ管理がより明確になります。新しい自動リリースされたテキストフィールドでプロパティを設定するだけです。プロパティを使用していなかった場合は、忘れずに解放し、オプションで新しいプロパティを保持する必要があります。この時点では、メモリ管理のセマンティクスがセッターに含まれていないため、この新しいテキストフィールドの所有者についてはややあいまいです。

ここで、別のビューコントローラーがUINavigationコントローラーのスタックにプッシュされ、このビューがフォアグラウンドに表示されなくなったとします。メモリ警告の場合、このオフスクリーンビューコントローラのビューはアンロードされます。この時点で、トップレベルのUIViewのviewプロパティは無効になり、解放されて割り当てが解除されます。

UITextFieldは保持されたプロパティとして設定されたため、UITextFieldは割り当て解除されません。これは、最上位ビューのサブビュー配列の保持のみが保持されていたためです。

代わりに、UITextFieldのインスタンス変数がプロパティを介して設定されていない場合は、インスタンス変数を設定するときにnib読み込みコードがそれを保持していたため、それも存在します。

これが強調する興味深い点の1つは、UITextFieldがプロパティを通じて追加で保持されるため、メモリの警告が発生した場合にUITextFieldを保持したくない可能性があることです。このため、-[UIViewControllerviewDidUnload]メソッドでプロパティを無効にする必要があります。これにより、UITextFieldの最終リリースが削除され、意図したとおりに割り当てが解除されます。プロパティを使用する場合は、明示的に解放することを忘れないでください。これらの2つのアクションは機能的には同等ですが、意図は異なります。

テキストフィールドを交換する代わりに、ビューから削除することを選択した場合は、ビュー階層から既に削除してプロパティをnilに設定しているか、テキストフィールドを解放している可能性があります。この場合、正しいプログラムを作成することは可能ですが、viewDidUnloadメソッドのテキストフィールドを過剰に解放するというエラーを簡単に作成できます。オブジェクトを過剰にリリースすると、クラッシュが発生します。すでにnilになっているプロパティを再びnilに設定することはできません。

私の説明は過度に冗長だったかもしれませんが、シナリオの詳細を省略したくありませんでした。ガイドラインに従うだけで、より複雑な状況に遭遇したときに問題を回避できます。

さらに、デスクトップ上のMac OSXではメモリ管理の動作が異なることに注意してください。デスクトップでは、セッターなしでIBOutletを設定しても、インスタンス変数は保持されません。ただし、可能な場合はセッターを使用します。

71
Joey Hagedorn

メモリ管理の観点から、何かIBOutletを宣言しても、何も起こりません(IBOutletは文字通り#definedとして何も定義されていません)。宣言にIBOutletを含める唯一の理由は、Interface Builderで接続する場合です(これがIBOutlet宣言の目的であり、IBへのヒントです)。

ここで、インスタンス変数に@propertyを作成する唯一の理由は、それらをプログラムで割り当てる場合です。そうでない場合(つまり、UIをIBで設定するだけの場合)、プロパティを作成するかどうかは関係ありません。理由はありません、IMO。

質問に戻ります。このivar(usernameField)をIBで設定するだけの場合は、プロパティを気にしないでください。何にも影響しません。 usernameFieldのプロパティを作成する場合(プログラムで作成しているため)、必ずそのプロパティを作成し、その場合は絶対にプロパティを保持します。

11
zpasternack

実際、2つのモデルがあります。

古いモデル

これらのモデルはObjective-C2.0より前のモデルであり、Mac OS Xから継承されています。引き続き機能しますが、ivarを変更するためのプロパティを宣言しないでください。あれは:

@interface StrokeWidthController : UIViewController {
    IBOutlet UISlider* slider;
    IBOutlet UILabel* label;
    IBOutlet StrokeDemoView* strokeDemoView;
    CGFloat strokeWidth;
}
@property (assign, nonatomic) CGFloat strokeWidth;
- (IBAction)takeIntValueFrom:(id)sender;
@end

このモデルでは、IBOutlet ivarを保持しませんが、それらを解放する必要があります。あれは:

- (void)dealloc {
    [slider release];
    [label release];
    [strokeDemoView release];
    [super dealloc];
}

新しいモデル

IBOutlet変数のプロパティを宣言する必要があります。

@interface StrokeWidthController : UIViewController {
    IBOutlet UISlider* slider;
    IBOutlet UILabel* label;
    IBOutlet StrokeDemoView* strokeDemoView;
    CGFloat strokeWidth;
}
@property (retain, nonatomic) UISlider* slider;
@property (retain, nonatomic) UILabel* label;
@property (retain, nonatomic) StrokeDemoView* strokeDemoView;
@property (assign, nonatomic) CGFloat strokeWidth;
- (IBAction)takeIntValueFrom:(id)sender;
@end

さらに、deallocで変数を解放する必要があります。

- (void)dealloc {
    self.slider = nil;
    self.label = nil;
    self.strokeDemoView = nil;
    [super dealloc];
}

さらにモードでは、壊れにくいプラットフォームでは、ivarを削除できます。

@interface StrokeWidthController : UIViewController {
    CGFloat strokeWidth;
}
@property (retain, nonatomic) IBOutlet UISlider* slider;
@property (retain, nonatomic) IBOutlet UILabel* label;
@property (retain, nonatomic) IBOutlet StrokeDemoView* strokeDemoView;
@property (assign, nonatomic) CGFloat strokeWidth;
- (IBAction)takeIntValueFrom:(id)sender;
@end

奇妙なこと

どちらの場合も、アウトレットはsetValue:forKey:を呼び出すことによって設定されます。ランタイムは内部的に(特に_decodeObjectBinary)、setterメソッドが存在するかどうかをチェックします。存在しない場合(ivarのみが存在する場合)、追加の保持をivarに送信します。このため、setterメソッドがない場合は、IBOutletを保持しないでください。

6
Freeman

プロパティによって提供されるアクセサーの使用を開始するまで、これら2つのインターフェース定義の動作方法に違いはありません。

どちらの場合も、deallocまたはviewDidUnloadメソッドでIBOutletを解放してnilに設定する必要があります。

IBOutletは、XIBファイル内でインスタンス化されたオブジェクトを指します。そのオブジェクトは、XIBファイルのファイルのOwnerオブジェクト(通常、IBOutletが宣言されているViewController)によって所有されます。

オブジェクトはXIBのロードの結果として作成されるため、前述のように、保持カウントは1であり、ファイルの所有者が所有します。これは、ファイルの所有者が、割り当てが解除されたときにファイルを解放する責任があることを意味します。

保持属性を使用してプロパティ宣言を追加すると、setterメソッドが、渡されたオブジェクトを保持して設定する必要があることを指定するだけです。これが正しい方法です。プロパティ宣言でretainを指定しなかった場合、IBOutletは、所有者によって解放されたか、プログラムのライフサイクルのある時点で自動解放されたために、もはや存在しない可能性のあるオブジェクトを指す可能性があります。それを保持すると、それが完了するまでそのオブジェクトの割り当てが解除されるのを防ぎます。

2
Jasarien

Nibファイル内のオブジェクトは、保持カウント1で作成されてから、自動解放されます。 UIKitは、オブジェクト階層を再構築するときに、setValue:forKey:を使用してオブジェクト間の接続を再確立します。これは、使用可能なsetterメソッドを使用するか、setterメソッドが使用できない場合はデフォルトでオブジェクトを保持します。これは、コンセントがあるオブジェクトはすべて有効なままであることを意味します。ただし、アウトレットに保存しないトップレベルのオブジェクトがある場合は、loadNibNamed:owner:options:メソッドによって返される配列、または配列内のオブジェクトを保持して、それらのオブジェクトが早期に解放されないようにする必要があります。

1
Dharmateja

2番目のケースでは、その特定のIBOutletにgetter/setterメソッドを追加しています。ゲッター/セッターメソッドを追加するときはいつでも(ほとんどの場合)、メモリ管理の問題のために保持するように設定する必要があります。私はあなたが質問を提起するためのより良い方法はこれだったと思います:

@interface RegisterController : UIViewController <UITextFieldDelegate>
{
IBOutlet UITextField *usernameField;
}
@property (nonatomic) IBOutlet UITextField *usernameField;

または

@interface RegisterController : UIViewController <UITextFieldDelegate>
{
IBOutlet UITextField *usernameField;
}
@property (nonatomic, retain) IBOutlet UITextField *usernameField;

その場合、はい、メモリ管理に影響するため、保持を追加する必要があります。効果がない場合でも、プログラムでIBOutletを追加および削除すると、問題が発生する可能性があります。

原則として、IBOutletがある場合は常に@property(保持あり)を追加してください。

0
Saurabh Sharan