Objective-C:NSNotificationのオブザーバーを削除する場所


102

私は客観的なCクラスを持っています。その中で、私はinitメソッドを作成し、それにNSNotificationを設定しました

//Set up NSNotification
[[NSNotificationCenter defaultCenter] addObserver:self 
                                         selector:@selector(getData)
                                             name:@"Answer Submitted"
                                           object:nil];

[[NSNotificationCenter defaultCenter] removeObserver:self]このクラスのどこに設定しますか?aのUIViewController場合、それをviewDidUnloadメソッドに追加できることはわかっています。目的のcクラスを作成したばかりの場合、何をする必要がありますか?


deallocメソッドに入れました。
onnoweb

1
目的のcクラスを作成したときにdeallocメソッドが自動的に作成されなかったので、追加してもよろしいですか?
Zhen

はい、実装-(void)deallocして追加できますremoveObserser:self。これが最も推奨される方法ですremoveObservers:self
petershine

deallociOS 6でメソッドを追加しても大丈夫ですか?
wcochran 2013年

2
はい、[super dealloc]を呼び出さない限り、ARCプロジェクトでdeallocを使用しても問題ありません([super dealloc]を呼び出すと、コンパイラエラーが発生します)。そして、はい、removeObserverをdeallocに確実に配置できます。
Phil

回答:


112

一般的な答えは、「通知が不要になったらすぐに」です。これは明らかに満足できる答えではありません。

オブザーバーを完全に登録解除する最後のチャンスなので、オブザーバーとして使用するクラスの[notificationCenter removeObserver: self]メソッドdeallocに呼び出しを追加することをお勧めします。ただし、これは通知センターが死んだオブジェクトに通知することによるクラッシュからのみ保護します。オブジェクトがまだ通知を適切に処理できる状態ではない/なくなった場合、通知を受信しないようにコードを保護することはできません。これについて...上記を参照してください。

編集(答えが思ったよりも多くのコメントを描いているように見えるため)ここで私が言おうとしているのは、通知センターからオブザーバーを削除するのが最善である場合について、一般的なアドバイスをするのは本当に難しいことです。

  • 使用例(どの通知が監視されますか?いつ送信されますか?)
  • オブザーバーの実装(いつ通知を受け取る準備ができているか?いつ準備ができなくなっているか?)
  • オブザーバーの予定寿命(ビューまたはビューコントローラーなどの他のオブジェクトに関連付けられていますか?)
  • ...

だから、私が思いつくことができる最も一般的なアドバイス:アプリを保護することです。少なくとも1つの失敗の可能性がある場合は、でremoveObserver:ダンスを実行します。これdeallocは、オブジェクトのライフの最後のポイントであり、きれいに実行できます。これが意味しないことは、「dealloc呼び出されるまで削除を延期するだけで、すべてがうまくいく」ということです。代わりに、オブジェクトが通知を受信する準備ができていない(または必要でない)とすぐにオブザーバー削除します。それはまさに正しい瞬間です。残念ながら、上記の質問のいずれかに対する答えがわからないので、その瞬間がいつなのかはわかりません。

removeObserver:オブジェクトはいつでも安全に複数回できます(指定されたオブザーバーとの最初の呼び出し以外はすべてnopsになります)。だから:dealloc念のために(もう一度)実行することを考えますが、何よりもまず、適切なタイミングで実行します(これはユースケースによって決まります)。


4
これはARCでは安全ではなく、リークを引き起こす可能性があります。このdiscsussionを参照してください。cocoabuilder.com/archive/cocoa/311831-arc-and-dealloc.html
MobileMon

3
@MobileMonあなたがリンクした記事は私の主張をしているようです。何が欠けていますか?
Dirk 2013年

dealloc以外の場所でオブザーバーを削除する必要があることに注意してください。例えば、viewwilldisappear
MobileMon

1
@MobileMon-はい。それが私の答えに出会ったことを願っています。でオブザーバーを削除するdeallocことは、後で割り当て解除されたオブジェクトへのアクセスが原因でアプリがクラッシュすることに対する最後の防御策にすぎません。ただし、オブザーバーの登録を解除する適切な場所は、通常、別の場所です(多くの場合、オブジェクトのライフサイクルのかなり早い段階)。私はここで「ねえ、ただそれをやれば、deallocすべてがうまくいく」と言うつもりはありません。
Dirk

@MobileMon「たとえば、viewWillDisappear」具体的なアドバイスを与えることの問題は、それが実際には、どのような種類のイベントのオブザーバーとして登録するどのような種類のオブジェクトに依存することです。sの(または)でオブザーバーの登録を解除することは適切な解決策かもしれませんが、それ実際にはユースケースに依存します。viewWillDisappearviewDidUnloadUIViewController
Dirk

39

注:これはテスト済みで、100%パーセント動作しています。

迅速

override func viewWillDisappear(animated: Bool){
    super.viewWillDisappear(animated)

    if self.navigationController!.viewControllers.contains(self) == false  //any other hierarchy compare if it contains self or not
    {
        // the view has been removed from the navigation stack or hierarchy, back is probably the cause
        // this will be slow with a large stack however.

        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
}

PresentedViewController

override func viewWillDisappear(animated: Bool){
    super.viewWillDisappear(animated)

    if self.isBeingDismissed()  //presented view controller
    {
        // remove observer here
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
}

Objective-C

ではiOS 6.0 > version、メソッドが廃止さviewWillDisappearれたため、オブザーバーを削除するviewDidUnloadことをお勧めします。

 [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];

多くのremove observer場合、ビューがから削除されたときよりも優れていnavigation stack or hierarchyます。

- (void)viewWillDisappear:(BOOL)animated{
 if (![[self.navigationController viewControllers] containsObject: self]) //any other hierarchy compare if it contains self or not
    {
        // the view has been removed from the navigation stack or hierarchy, back is probably the cause
        // this will be slow with a large stack however.

        [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];
    }
}

PresentedViewController

- (void)viewWillDisappear:(BOOL)animated{
    if ([self isBeingDismissed] == YES) ///presented view controller
    {
        // remove observer here
        [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];
    }
}

8
ただし、コントローラーが、ビューが表示されていない場合(たとえば、tableViewを再読み込みする場合)には通知を必要とする場合があります。
wcochran 2013年

2
@wcochranが自動的に再読み込み/更新viewWillAppear:
リチャード

@Princeは、なぜviewWillDisapperがdeallocするより良いのか説明できますか そのため、自己にオブザーバーを追加しているので、自己がメモリから削除されると、deallocが呼び出され、すべてのオブザーバーが削除されます。これは適切なロジックではありません。
Matrosov Alexander 14

ライフサイクルイベントのremoveObserver:selfいずれかを呼び出すと、UIViewControllerほぼ確実に1週間が台無しになります。
続きを

1
示さremoveObserverviewWillDisappearているように通話を発信することは、コントローラがを介して提示されている場合、間違いなく正しい方法pushViewControllerです。dealloc代わりにそれらを入れた場合はdealloc呼び出されません-私の経験では少なくとも...
クリストファーキング

38

iOS 9以降、オブザーバーを削除する必要がなくなりました。

OS X 10.11およびiOS 9.0では、NSNotificationCenterおよびNSDistributedNotificationCenterは、割り当て解除された可能性のある登録オブザーバーに通知を送信しなくなりました。

https://developer.apple.com/library/mac/releasenotes/Foundation/RN-Foundation/index.html#10_11NotificationCenter


2
彼らはオブザーバーにメッセージを送信しないかもしれませんが、私が理解しているように、彼らは彼らへの強い参照を維持すると信じています。その場合、すべてのオブザーバーはメモリに留まり、リークを生成します。間違っている場合は修正してください。
2016年

6
リンクされたドキュメントは、それについて詳細に行きます。TL; DR:弱参照です。
2016年

ただし、もちろん、オブジェクトを参照し続け、通知をもう聞きたくない場合に備えて、これは依然として必要です
TheEye

25

オブザーバーをビューコントローラーに追加する場合は、オブザーバーの追加viewWillAppearと削除を強くお勧めしviewWillDisappearます。


私は、@RickiG興味:なぜあなたが使用することをお勧めしますviewWillAppearviewWillDisappearviewControllersのために?
アイザックオーバーアッカー

2
@IsaacOverackerにはいくつかの理由があります。セットアップコード(loadViewやviewDidLoadなど)により、通知が発生する可能性があり、コントローラーは表示前にそれを反映する必要があります。このようにすると、いくつかの利点があります。コントローラーを「残す」ことを決定した時点で、通知は気にせず、コントローラーが画面から押し出されている間などにロジックが実行されることはありません。コントローラーが通知を受け取る必要がある特別な場合があります。画面外これはできませんね。しかし、そのようなイベントはおそらくモデルにあるはずです。
RickiG

1
@IsaacOverackerとARCでも、desubscribeを実装して通知のサブスクライブを解除するのはおかしいでしょう。
RickiG

4
私が試したものの中で、iOS7では、これがUIViewControllersで作業するときにオブザーバーを登録/削除する最良の方法です。唯一の問題は、多くの場合、UINavigationControllerを使用して別のUIViewControllerをスタックにプッシュするときにオブザーバーを削除したくないことです。解決策:[self isBeingDismissed]を呼び出すことにより、viewWillDisappearでVCがポップされているかどうかを確認できます。
lekksi 14

ナビゲーションコントローラーからビューコントローラーをポップしdeallocても、すぐに呼び出されない場合があります。ビューコントローラーに戻ると、初期化コマンドにオブザーバーが追加されている場合、複数の通知が発生する可能性があります。
ジョナサンリン

20
-(void) dealloc {
      [[NSNotificationCenter defaultCenter] removeObserver:self];
      [super dealloc];
}

4
私はこれらの命令の順序を逆転させます... selfafter [super dealloc]を使用すると私は緊張します...(レシーバーが実際にポインタを逆参照する可能性が低い場合でも、まあ、あなたが知っていることではありませんが、それらがどのように実装されたかNSNotificationCenter
Dirk

うーん。それは私のために働いています。異常な動作に気づきましたか?
レゴラス

1
ダークは正しい-これは正しくない。[super dealloc]常にメソッドの最後のステートメントでなければなりませんdealloc。オブジェクトを破壊します。実行後、有効なものはselfもうありません。/ cc @Dirk
jscs

38
iOS 5以降でARCを使用している場合[super dealloc]は、もう必要ないと思います
pixelfreak

3
@pixelfreakはより強力で、ARCでは[super dealloc]の呼び出しは許可されていません
tapmonkey


7

deallocが利用できないため、迅速にdeinitを使用します。

deinit {
    ...
}

Swiftのドキュメント:

クラスインスタンスが割り当て解除される直前にdeinitializerが呼び出されます。deinitializerは、initキーワードを使用して初期化子を作成する方法と同様に、deinitキーワードを使用して作成します。デイニシャライザはクラスタイプでのみ使用できます。

通常、インスタンスの割り当てを解除するときに、手動でクリーンアップを実行する必要はありません。ただし、独自のリソースを使用している場合は、追加のクリーンアップを自分で実行する必要がある場合があります。たとえば、ファイルを開いてデータを書き込むカスタムクラスを作成する場合、クラスインスタンスの割り当てを解除する前にファイルを閉じる必要がある場合があります。


5

*編集:このアドバイスはiOS <= 5に適用されます(追加viewWillAppearや削除が必要な場合でもviewWillDisappear-何らかの理由でオブザーバーをに追加した場合、このアドバイスが適用されますviewDidLoad

オブザーバーを追加したviewDidLoad場合は、との両方で削除する必要がdeallocありviewDidUnloadます。そうしないviewDidLoadと、が呼び出された後に2回追加されることになりますviewDidUnload(これはメモリ警告の後に発生します)。これはviewDidUnload非推奨で呼び出されないiOS 6では必要ありません(ビューが自動的にアンロードされなくなったため)。


2
StackOverflowへようこそ。MarkDown FAQ(質問/回答編集ボックスの横の疑問符アイコン)を確認してください。Markdwonを使用すると、回答の使いやすさが向上します。
marko

5

私の意見では、次のコードはARCでは意味がありません。

- (void)dealloc
{
      [[NSNotificationCenter defaultCenter] removeObserver:self];
      [super dealloc];
}

ではiOSの6、またにオブザーバーを除去するのには意味がありませんviewDidUnload、それは今で廃止されましたので、。

要約すると、私は常にでそれを行いviewDidDisappearます。ただし、@ Dirkが言ったように、要件にも依存します。


多くの人々がまだiOS6よりも古いバージョンのiOSのコードを書いています... :-)
lnafziger 2013年

ARCではこのコードを使用できますが、[super dealloc]という行は必要ありません。あなたはもっとここに見ることができます:developer.apple.com/library/ios/#releasenotes/ObjectiveC/...
アレックス

1
通常のNSObjectが通知のオブザーバーになった場合はどうなりますか?この場合、deallocを使用しますか?
qix

4

確かな答えを見つけたと思います!上記の答えがあいまいで矛盾しているように思われました。クックブックとプログラミングガイドを調べました。

最初に、子ビューコントローラーに通知を投稿して親ビューコントローラーでコードを実行するため、addObserver:in viewWillAppear:およびremoveObserver:inのスタイルがviewWillDisappear:機能しません(私はテストしました)。同じView Controller内で通知を投稿して聞いている場合にのみ、このスタイルを使用します。

私が最も信頼する答えは、iOSプログラミング:Big Nerd Ranch Guide 4thで見つかりました。私はBNRの人たちを信頼しています。彼らにはiOSトレーニングセンターがあり、彼らは別の料理本を書いているだけではないからです。正確であることはおそらく彼らの最善の利益です。

BNRの例1:addObserver:in init:removeObserver:indealloc:

BNRの例2:addObserver:in awakeFromNib:removeObserver:indealloc:

dealloc:使用しないオブザーバーを削除する場合[super dealloc];

これが次の人のお役に立てば幸いです…

私はこの投稿を更新しています。Appleはほぼ完全にストーリーボードを使用しているため、上記の内容がすべての状況に当てはまるとは限らないためです。重要なこと(そして私が最初にこの投稿を追加した理由)は、あなたviewWillDisappear:が呼び出されている場合に注意を払うことです。アプリケーションがバックグラウンドに入ったとき、私にはありませんでした。


コンテキストが重要であるため、これが正しいかどうかを判断するのは困難です。既に何度か言及されていますが、deallocはARCコンテキスト(現時点で唯一のコンテキスト)ではほとんど意味がありません。deallocが呼び出されたときも予測できません-viewWillDisappearの方が制御が簡単です。補足:子供が親に何かを伝える必要がある場合、デリゲートパターンはより良い選択のように聞こえます。
RickiG 2015

2

受け入れられた回答は安全ではなく、メモリリークを引き起こす可能性があります。登録解除はdeallocのままにしておきますが、viewWillDisappearでも登録解除してください(もちろん、viewWillAppearで登録する場合はもちろんです)。:)


1
私はこの答えに同意します。viewWillDisappearでオブザーバーを削除しないと、アプリを集中的に使用した後にメモリ警告とリークが発生し、クラッシュが発生します。
SarpErdag 2014年

2

またviewWillDisappear、ビューコントローラが新しいUIViewを提示するときにも呼び出されることに注意してください。このデリゲートは、ビューコントローラーのメインビューがディスプレイに表示されないことを示しています。

この場合、viewWillDisappear通知を使用してUIviewが親View Controllerと通信できるようにすると、通知の割り当てを解除するのが不便になる場合があります。

解決策として、私は通常、次の2つの方法のいずれかでオブザーバーを削除します。

- (void)viewWillDisappear:(BOOL)animated {
    NSLog(@"viewController will disappear");
    if ([self isBeingDismissed]) {
        NSLog(@"viewController is being dismissed");
        [[NSNotificationCenter defaultCenter] removeObserver:self name:@"actionCompleted" object:nil];
    }
}

-(void)dealloc {
    NSLog(@"viewController is being deallocated");
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"actionCompleted" object:nil];
}

同様の理由で、最初に通知を発行するときは、コントローラの上にが表示されたときにviewWillAppearメソッドが呼び出されるという事実を考慮する必要があります。これにより、同じ通知の複数のコピーが生成されます。通知がすでにアクティブになっているかどうかを確認する方法がないため、通知を追加する前に削除して問題を未然に防ぎます。

- (void)viewWillAppear:(BOOL)animated {
    NSLog(@"viewController will appear");
    // Add observers
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"imageGenerated" object:nil]; // This is added to avoid duplicate notifications when the view is presented again
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedImageFromCameraOrPhotolibraryMethodOnListener:) name:@"actionCompleted" object:nil];

}

-1

SWIFT 3

通知の使用には2つのケースがあります。-これらは、ビューコントローラーが画面上にある場合にのみ必要です。-ユーザーが現在の画面で別の画面を開いた場合でも、常に必要です。

最初のケースでは、オブザーバーを追加および削除する正しい場所は次のとおりです。

/// Add observers
///
/// - Parameter animated: the animation flag
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    NotificationCenter.default.addObserver(...)
}

/// Remove observers
///
/// - Parameter animated: the animation flag
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    NotificationCenter.default.removeObserver(self)
}

2番目のケースの正しい方法は次のとおりです。

/// Add observers
override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(...)
}

/// Remove observers
///
/// - Parameter animated: the animation flag
override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if self.isBeingDismissed // remove only when view controller is removed disappear forever
    || !(self.navigationController?.viewControllers.contains(self) ?? true) {
        NotificationCenter.default.removeObserver(self)
    }
}

そして決して入れないremoveObserverでくださいdeinit{ ... }-それは間違いです!


-1
override func viewDidLoad() {   //add observer
  super.viewDidLoad()
  NotificationCenter.default.addObserver(self, selector:#selector(Yourclassname.method), name: NSNotification.Name(rawValue: "NotificationIdentifier"), object: nil)
}

override func viewWillDisappear(_ animated: Bool) {    //remove observer
    super.viewWillDisappear(true)
    NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "NotificationIdentifier"), object: nil)
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.