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 2011-06-24

1
객관적인 c 클래스를 만들 때 dealloc 메서드가 자동으로 생성되지 않았으므로 추가해도됩니까?
Zhen

예, 구현 -(void)dealloc한 다음 추가 할 수 있습니다 removeObserser:self. 이것이 가장 권장되는 방법입니다removeObservers:self
petershine 2011-06-24

deallociOS 6에 메서드 를 넣어도 괜찮 습니까?
wcochran

2
예, [super dealloc]을 호출하지 않는 한 ARC 프로젝트에서 dealloc을 사용하는 것이 좋습니다 ([super dealloc]를 호출하면 컴파일러 오류가 발생합니다). 그리고 예, removeObserver를 dealloc에 ​​확실히 넣을 수 있습니다.
Phil

답변:


112

일반적인 대답은 "더 이상 알림이 필요하지 않은 즉시"입니다. 이것은 분명히 만족스러운 대답이 아닙니다.

관찰자 등록을 완전히 취소 할 수있는 마지막 기회이므로 관찰자로 사용하려는 클래스의 [notificationCenter removeObserver: self]메서드 dealloc에 호출을 추가하는 것이 좋습니다 . 그러나 이것은 알림 센터가 죽은 개체를 알리기 때문에 충돌로부터 만 보호합니다. 개체가 아직 알림을 적절하게 처리 할 수있는 상태가 아니거나 더 이상없는 경우 알림 수신으로부터 코드를 보호 할 수 없습니다. 이를 위해 ... 위를 참조하십시오.

편집 (답이 내가 생각했던 것보다 더 많은 주석을 끌어내는 것 같기 때문에) 여기서 제가 말하려는 것은 알림 센터에서 관찰자를 제거하는 것이 가장 좋은시기에 대한 일반적인 조언을 제공하기가 정말 어렵습니다.

  • 사용 사례 (어떤 알림이 관찰됩니까? 언제 전송됩니까?)
  • 관찰자의 구현 (알림을받을 준비가 된 시점은 언제입니까? 더 이상 준비되지 않은 시점은 언제입니까?)
  • 관찰자의 의도 된 수명 (보기 또는보기 컨트롤러와 같은 다른 개체에 연결되어 있습니까?)
  • ...

그래서 제가 생각해 낼 수있는 가장 일반적인 조언은 앱을 보호하는 것입니다. 적어도 한 번의 실패 가능성에 대비하여에서 removeObserver:춤을 추십시오. dealloc그것이 (객체의 삶에서) 마지막 지점이기 때문에 깨끗하게 할 수 있습니다. 이것이 의미하지 않는 것은 " dealloc이 호출 될 때까지 제거를 연기하면 모든 것이 잘 될 것입니다"입니다. 대신 개체가 더 이상 알림을받을 준비가되지 않거나 필요하지 않으면 즉시 관찰자 제거합니다 . 바로 그 순간입니다. 안타깝게도 위에서 언급 한 질문에 대한 답을 알지 못해서 그 순간이 언제인지 짐작조차 할 수 없습니다.

항상 안전하게 removeObserver:객체를 여러 번 사용할 수 있습니다 (주어진 관찰자와의 첫 번째 호출을 제외하고 모두 nops가됩니다). 그러니 : dealloc확실하게하기 위해 (다시) 생각해보세요 .하지만 무엇보다도 먼저 : 적절한 순간에하세요 (사용 사례에 따라 결정됨).


4
이것은 ARC에서 안전하지 않으며 잠재적으로 누출을 일으킬 수 있습니다. 이 토론을보십시오 : cocoabuilder.com/archive/cocoa/311831-arc-and-dealloc.html
MobileMon

3
@MobileMon 당신이 링크 한 기사가 내 요점을 만드는 것 같습니다. 내가 무엇을 놓치고 있습니까?
Dirk

나는 dealloc이 아닌 다른 곳에서 옵저버를 제거해야한다는 점에 주목해야한다고 생각한다. 예 : viewwilldisappear
MobileMon

1
@MobileMon-예. 나는 그것이 내가 내 대답과 만나는 요점이기를 바랍니다. 관찰자를 제거하는 dealloc것은 나중에 할당 된 개체에 대한 액세스로 인한 앱 충돌에 대한 마지막 방어선 일뿐입니다. 그러나 관찰자를 등록 취소 할 수있는 적절한 위치는 일반적으로 다른 곳에 있습니다 (종종 객체의 수명주기에서 훨씬 더 일찍). 나는 여기서 "이봐, 그냥하세요 dealloc. 모든 것이 잘 될 것입니다. "라고 말하려는 것이 아닙니다 .
Dirk

@MobileMon "예를 들어, viewWillDisappear"구체적인 조언을 제공 할 때의 문제는 실제로 어떤 종류의 이벤트에 대해 관찰자로 등록한 개체의 종류에 달려 있다는 것입니다. 에서 관찰자의 등록을 취소 할 수있는 권리 솔루션 viewWillDisappear(또는 viewDidUnload경우) UIViewController들,하지만 정말 사용 사례에 따라 달라집니다.
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)
    }
}

목표 -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

2
@wcochran 자동으로 다시로드 / 새로 고침에서viewWillAppear:
리처드

@Prince는 왜 viewWillDisapper가 dealloc보다 더 나은지 설명 할 수 있습니까? 그래서 우리는 관찰자를 self에 추가했습니다. 그래서 self가 메모리에서 떨어질 때 그것은 dealloc을 호출하고 모든 관찰자가 삭제 될 것입니다. 이것은 좋은 논리가 아닙니다.
Matrosov Alexander

라이프 사이클 이벤트를 호출 removeObserver:self하는 UIViewController것은 거의 일주일을 망칠 것입니다. 더 읽기 : subjective-objective-c.blogspot.com/2011/04/…
cbowns

1
퍼팅 removeObserver의 통화 viewWillDisappear표시로 확실히 컨트롤러를 통해 제시되는 경우 갈 수있는 올바른 방법이다 pushViewController. dealloc대신에 넣는다면 dealloc절대로 호출되지 않을 것입니다-적어도 내 경험으로는 ...
Christopher King

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
그들이 관찰자에게 메시지를 보내지 않을 수도 있지만, 내가 이해하는대로 그들이 그들에 대한 강한 참조를 유지할 것이라고 믿습니다. 이 경우 모든 관찰자는 메모리에 남아 누수를 생성합니다. 내가 틀렸다면 나를 수정하십시오.
전나무

6
링크 된 문서는 그것에 대해 자세히 설명합니다. TL; DR : 약한 참조입니다.
Sebastian

그러나 물론 당신이 그들을 참조하는 객체를 유지하고 더 이상 알림을 듣고 싶지 않은 경우에 여전히 필요합니다
TheEye

25

관찰자가 뷰 컨트롤러 에 추가 된 경우에는 추가 viewWillAppear하고 제거 하는 것이 좋습니다 viewWillDisappear.


나는 @RickiG을 궁금 : 왜 당신이 사용하는 것이 좋습니다 않는 viewWillAppearviewWillDisappearviewControllers을 위해?
Isaac Overacker 2012-12-12

2
@IsaacOveracker 몇 가지 이유 : 설정 코드 (예 : loadView 및 viewDidLoad)로 인해 알림이 실행될 수 있으며 컨트롤러는 알림을 표시하기 전에이를 반영해야합니다. 이렇게하면 몇 가지 이점이 있습니다. 컨트롤러를 "나가기"로 결정한 순간에는 알림에 대해 신경 쓰지 않고 컨트롤러가 화면 밖으로 밀려 나가는 동안 논리를 수행하지 않습니다. 컨트롤러가 알림을받을 때 알림을 받아야하는 특별한 경우가 있습니다. 화면 밖에서 할 수없는 것 같아요. 그러나 이와 같은 이벤트는 아마도 모델에있을 것입니다.
RickiG

1
@IsaacOveracker는 또한 ARC를 사용하여 알림 구독을 취소하기 위해 dealloc을 구현하는 것이 이상 할 것입니다.
RickiG

4
내가 시도한 것 중에서 iOS7에서는 UIViewController로 작업 할 때 관찰자를 등록 / 제거하는 가장 좋은 방법입니다. 유일한 문제는 UINavigationController를 사용하고 다른 UIViewController를 스택에 푸시 할 때 관찰자가 제거되는 것을 원하지 않는 경우가 많다는 것입니다. 해결 방법 : [self isBeingDismissed]를 호출하여 viewWillDisappear에서 VC가 팝되고 있는지 확인할 수 있습니다.
lekksi

내비게이션 컨트롤러에서 뷰 컨트롤러를 팝하면 dealloc즉시 호출 되지 않을 수 있습니다 . 뷰 컨트롤러로 돌아 가면 관찰자가 초기화 명령에 추가되면 여러 알림이 발생할 수 있습니다.
Jonathan Lin

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

4
이 명령의 순서를 바꾸고 싶습니다 ... selfafter [super dealloc]를 사용 하면 불안해집니다 ... (수신기가 실제로 포인터를 어떤 식 으로든 역 참조하지 않을 것 같더라도 어떻게 구현했는지 알 수 없습니다. NSNotificationCenter)
Dirk

흠. 그것은 나를 위해 일했습니다. 비정상적인 동작을 발견 했습니까?
Legolas

1
더크가 옳다-이것은 틀렸다. [super dealloc]항상 메서드 의 마지막 문장 이어야합니다 dealloc. 그것은 당신의 물건을 파괴합니다. 실행 된 후에는 self더 이상 유효 하지 않습니다. / cc @Dirk
jscs 2011-06-24

38
아이폰 OS 5+에 ARC를 사용하는 경우, 내가 생각 [super dealloc]더 이상 필요하지 않습니다
pixelfreak

3
강한 @pixelfreak, 그것은 호출 [슈퍼의 dealloc]에 ARC에서 허용되지 않습니다
tapmonkey


7

dealloc을 사용할 수 없기 때문에 신속하게 deinit를 사용하십시오.

deinit {
    ...
}

Swift 문서 :

deinitializer는 클래스 인스턴스가 할당 해제되기 직전에 호출됩니다. init 키워드를 사용하여 intializer를 작성하는 방법과 유사하게 deinit 키워드를 사용하여 deinitializer를 작성합니다. Deinitializer는 클래스 유형에서만 사용할 수 있습니다.

일반적으로 인스턴스 할당이 취소 될 때 수동 정리를 수행 할 필요가 없습니다. 그러나 자체 리소스로 작업하는 경우 몇 가지 추가 정리를 직접 수행해야 할 수 있습니다. 예를 들어 사용자 정의 클래스를 만들어 파일을 열고 일부 데이터를 쓰는 경우 클래스 인스턴스가 할당 해제되기 전에 파일을 닫아야 할 수 있습니다.


5

* 편집 :이 조언은 iOS <= 5에 적용됩니다 (심지어 추가 viewWillAppear하고 제거 해야합니다. viewWillDisappear그러나 어떤 이유로 관찰자를 추가 한 경우 조언이 적용됩니다 viewDidLoad)

당신이 추가 한 경우의 관찰자 viewDidLoad는 모두 제거해야 dealloc하고 viewDidUnload. 그렇지 않으면 나중에 viewDidLoad호출 될 때 두 번 추가하게됩니다 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

ARC에서는 [super dealloc] 줄없이이 코드를 사용할 수 있습니다. 당신은 자세한 내용은 여기를 볼 수 있습니다 developer.apple.com/library/ios/#releasenotes/ObjectiveC/...
알렉스

1
일반 NSObject가 알림의 관찰자가 되었다면 어떻게 될까요? 이 경우 dealloc을 사용 하시겠습니까?
qix

4

나는 믿을만한 대답을 찾은 것 같아요 ! 위의 답변이 모호하고 모순되는 것처럼 보였기 때문에해야했습니다. 저는 요리 책과 프로그래밍 가이드를 살펴 보았습니다.

첫째, 부모 뷰 컨트롤러에서 코드를 실행하기 위해 자식 뷰 컨트롤러에 알림을 게시하고 있기 때문에 addObserver:in viewWillAppear:removeObserver:in 스타일이 viewWillDisappear:작동하지 않습니다 (테스트했습니다). 동일한 뷰 컨트롤러 내에서 알림을 게시하고 수신하는 경우에만이 스타일을 사용합니다.

내가 가장 의지 할 대답은 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-08-12

2

허용되는 답변은 안전하지 않으며 메모리 누수가 발생할 수 있습니다. 등록 취소를 dealloc에 ​​남겨 두시고 viewWillDisappear에서도 등록을 취소하십시오 (물론 viewWillAppear에 등록하는 경우) .... 내가 어쨌거나 잘 작동합니다! :)


1
이 답변에 동의합니다. viewWillDisappear에서 관찰자를 제거하지 않으면 앱을 집중적으로 사용한 후 충돌로 이어지는 메모리 경고 및 누수가 발생합니다.
SarpErdag 2014 년

2

viewWillDisappear뷰 컨트롤러가 새 UIView를 제공 할 때도 호출 된다는 점을 알아 두는 것이 중요합니다 . 이 델리게이트는 단순히 뷰 컨트롤러 메인 뷰가 디스플레이에 표시되지 않음을 나타냅니다.

이 경우 viewWillDisappear알림을 사용하여 UIview가 상위 뷰 컨트롤러와 통신하도록 허용하는 경우 알림 할당을 해제하는 것이 불편할 수 있습니다.

해결책으로 일반적으로 다음 두 가지 방법 중 하나로 관찰자를 제거합니다.

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

알림을 사용하는 두 가지 경우가 있습니다.-뷰 컨트롤러가 화면에있을 때만 필요합니다. -사용자가 현재 다른 화면을 열어도 항상 필요합니다.

첫 번째 경우 관찰자를 추가하고 제거 할 올바른 위치는 다음과 같습니다.

/// 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)
}

두 번째 경우 올바른 방법은 다음과 같습니다.

/// 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.