Objective-C: Xóa người quan sát cho NSNotification ở đâu?


102

Tôi có một lớp C khách quan. Trong đó, tôi đã tạo một phương thức init và thiết lập một NSNotification trong đó

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

Tôi đặt [[NSNotificationCenter defaultCenter] removeObserver:self]ở đâu trong lớp này? Tôi biết rằng đối với a UIViewController, tôi có thể thêm nó vào viewDidUnloadphương thức Vì vậy, những gì cần phải làm nếu tôi vừa tạo một Lớp c mục tiêu?


Tôi đặt nó trong phương thức dealloc.
onnoweb

1
Phương thức dealloc không được tạo tự động cho tôi khi tôi tạo lớp c mục tiêu, vì vậy tôi có thể thêm nó vào không?
Zhen

Có, bạn có thể triển khai -(void)deallocvà sau đó thêm removeObserser:selfvào nó. Đây là cách khuyến khích nhất để đặtremoveObservers:self
petershine

Vẫn ok để đặt deallocphương thức trong iOS 6?
wcochran

2
Có, bạn có thể sử dụng dealloc trong các dự án ARC miễn là bạn không gọi [super dealloc] (bạn sẽ gặp lỗi trình biên dịch nếu gọi [super dealloc]). Và có, bạn chắc chắn có thể đặt removeObserver của mình trong dealloc.
Phil

Câu trả lời:


112

Câu trả lời chung chung sẽ là "ngay khi bạn không cần thông báo nữa". Đây rõ ràng không phải là một câu trả lời thỏa mãn.

Tôi khuyên bạn nên thêm một cuộc gọi [notificationCenter removeObserver: self]trong phương thức dealloccủa các lớp đó, mà bạn định sử dụng làm người quan sát, vì đây là cơ hội cuối cùng để hủy đăng ký một người quan sát một cách sạch sẽ. Tuy nhiên, điều này sẽ chỉ bảo vệ bạn khỏi sự cố do trung tâm thông báo thông báo các đối tượng đã chết. Nó không thể bảo vệ mã của bạn khỏi việc nhận thông báo, khi các đối tượng của bạn chưa / không còn ở trạng thái mà chúng có thể xử lý thông báo đúng cách. Đối với điều này ... Xem ở trên.

Chỉnh sửa (vì câu trả lời dường như thu hút nhiều nhận xét hơn tôi nghĩ) Tất cả những gì tôi muốn nói ở đây là: thật khó để đưa ra lời khuyên chung về thời điểm tốt nhất nên xóa người quan sát khỏi trung tâm thông báo, bởi vì điều đó phụ thuộc:

  • Về trường hợp sử dụng của bạn (Những thông báo nào được quan sát thấy? Khi nào chúng được gửi?)
  • Việc thực hiện của người quan sát (Khi nào nó sẵn sàng nhận thông báo? Khi nào nó không còn sẵn sàng?)
  • Thời gian tồn tại dự định của người quan sát (Nó có bị ràng buộc với một số đối tượng khác, chẳng hạn như một khung nhìn hay bộ điều khiển khung nhìn?)
  • ...

Vì vậy, lời khuyên chung tốt nhất mà tôi có thể đưa ra: để bảo vệ ứng dụng của bạn. chống lại ít nhất một thất bại có thể xảy ra, hãy removeObserver:nhảy vào dealloc, vì đó là điểm cuối cùng (trong vòng đời của đối tượng), nơi bạn có thể làm điều đó một cách sạch sẽ. Điều này không có nghĩa là: "chỉ cần trì hoãn việc loại bỏ cho đến khi deallocđược gọi, và mọi thứ sẽ ổn". Thay vào đó, hãy loại bỏ người quan sát ngay khi đối tượng không còn sẵn sàng (hoặc được yêu cầu) để nhận thông báo . Đó là thời điểm chính xác. Thật không may, không biết câu trả lời cho bất kỳ câu hỏi nào được đề cập ở trên, tôi thậm chí không thể đoán được thời điểm đó sẽ là khi nào.

Bạn luôn có thể an toàn removeObserver:một đối tượng nhiều lần (và tất cả, trừ lần gọi đầu tiên với một người quan sát nhất định sẽ là nops). Vì vậy: hãy nghĩ đến việc làm lại (một lần nữa) deallocđể chắc chắn, nhưng trước hết: hãy làm nó vào thời điểm thích hợp (được xác định bởi trường hợp sử dụng của bạn).


4
Điều này không an toàn với ARC và có thể gây ra rò rỉ. Xem đĩa này: cocoabuilder.com/archive/cocoa/311831-arc-and-dealloc.html
MobileMon

3
@MobileMon Bài viết mà bạn đã liên kết có vẻ nói lên quan điểm của tôi. Tôi đang thiếu gì?
Dirk

Tôi cho rằng cần lưu ý rằng người ta nên loại bỏ người quan sát ở một nơi khác ngoài dealloc. Ví dụ: viewwilldisappear
MobileMon

1
@MobileMon - vâng. Tôi hy vọng, đó là điểm tôi hiểu được câu trả lời của mình. Loại bỏ trình quan sát deallocchỉ là cách bảo vệ cuối cùng chống lại sự cố ứng dụng do truy cập sau đó vào đối tượng được định vị bằng decal. Nhưng nơi thích hợp để hủy đăng ký một quan sát viên thường là một nơi khác (và thường là sớm hơn nhiều trong vòng đời của đối tượng). Tôi không cố gắng nói ở đây "Này, chỉ cần làm điều đó deallocvà mọi thứ sẽ ổn thôi".
Dirk

@MobileMon "Ví dụ: viewWillDisappear" Vấn đề khi đưa ra một lời khuyên cụ thể là nó thực sự phụ thuộc vào loại đối tượng mà bạn đăng ký làm người quan sát cho loại sự kiện nào. Có thể là giải pháp phù hợp để hủy đăng ký một người quan sát trong viewWillDisappear(hoặc viewDidUnload) cho UIViewControllers, nhưng điều đó thực sự phụ thuộc vào trường hợp sử dụng.
Dirk

39

Lưu ý: Điều này đã được thử nghiệm và hoạt động 100%

Nhanh

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

Trong đó iOS 6.0 > version, tốt hơn là loại bỏ trình quan sát trong viewWillDisappearviewDidUnloadphương pháp không được dùng nữa.

 [[NSNotificationCenter defaultCenter] removeObserver:observerObjectHere];

Sẽ tốt hơn nhiều lần remove observerkhi chế độ xem đã bị xóa khỏi 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
Ngoại trừ bộ điều khiển có thể vẫn muốn thông báo khi chế độ xem của nó không hiển thị (ví dụ: tải lại một tableView).
wcochran

2
@wcochran tự động tải lại / làm mới trongviewWillAppear:
Richard

@Prince, bạn có thể giải thích tại sao viewWillDisapper lại tốt hơn dealloc không? vì vậy chúng ta đã thêm người quan sát vào bản thân, vì vậy khi bản thân bị xóa khỏi bộ nhớ nó sẽ gọi dealloc và sau đó tất cả người quan sát sẽ bị xóa, đây không phải là một logic tốt.
Matrosov Alexander

Việc gọi điện removeObserver:selftrong bất kỳ UIViewControllersự kiện nào trong vòng đời gần như được đảm bảo sẽ làm hỏng tuần của bạn. Đọc thêm: chủ
quan-objective-c.blogspot.com/2011/04/…

1
Đưa các removeObservercuộc gọi vào viewWillDisappearnhư được chỉ ra chắc chắn là cách đúng đắn để thực hiện nếu bộ điều khiển đang được trình bày qua pushViewController. Nếu bạn đặt chúng trong deallocthay vì sau đó deallocsẽ không bao giờ được gọi là - theo kinh nghiệm của tôi ít nhất ...
Christopher King

38

Kể từ iOS 9, không còn cần thiết phải xóa người quan sát.

Trong OS X 10.11 và iOS 9.0 NSNotificationCenter và NSDistributedNotificationCenter sẽ không còn gửi thông báo cho những người quan sát đã đăng ký mà có thể được phân bổ.

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


2
Có thể họ sẽ không gửi tin nhắn cho những người quan sát, nhưng tôi tin rằng họ sẽ giữ một tham chiếu mạnh mẽ về họ như tôi hiểu. Trong trường hợp đó, tất cả những người quan sát sẽ ở trong bộ nhớ và tạo ra một rò rỉ. Hãy sửa cho tôi nếu tôi sai.
cây thông

6
Tài liệu liên kết đi vào chi tiết về điều đó. TL; DR: đó là một tham chiếu yếu.
Sebastian

nhưng tất nhiên nó vẫn còn cần thiết trong trường hợp bạn giữ các đối tượng tham khảo chúng xung quanh và chỉ không muốn nghe các thông báo nữa
TheEye

25

Nếu trình quan sát được thêm vào bộ điều khiển chế độ xem , tôi thực sự khuyên bạn nên thêm viewWillAppearvà xóa nó vào viewWillDisappear.


Tôi tò mò, @RickiG: tại sao bạn khuyên bạn nên sử dụng viewWillAppearviewWillDisappearcho viewControllers?
Isaac Overacker 12/12/12

2
@IsaacOveracker một vài lý do: mã thiết lập của bạn (ví dụ: loadView và viewDidLoad) có thể khiến thông báo được kích hoạt và bộ điều khiển của bạn cần phản ánh điều đó trước khi hiển thị. Nếu bạn làm như vậy thì có một vài lợi ích. Tại thời điểm này, bạn quyết định "rời khỏi" bộ điều khiển, bạn không quan tâm đến các thông báo và chúng sẽ không khiến bạn thực hiện logic trong khi bộ điều khiển đang được đẩy ra khỏi màn hình, v.v. Có những trường hợp đặc biệt mà bộ điều khiển sẽ nhận được thông báo khi đó ngoài màn hình Tôi đoán bạn không thể làm điều này. Nhưng những sự kiện như vậy có lẽ nên có trong mô hình của bạn.
RickiG 12/12/12

1
@IsaacOveracker cũng với ARC, sẽ rất lạ nếu triển khai dealloc để hủy đăng ký nhận thông báo.
RickiG 12/12/12

4
Trong số những thứ tôi đã thử, với iOS7, đây là cách tốt nhất để đăng ký / xóa người quan sát khi làm việc với UIViewControllers. Vấn đề duy nhất là, trong nhiều trường hợp, bạn không muốn trình quan sát bị xóa khi sử dụng UINavigationController và đẩy một UIViewController khác vào ngăn xếp. Giải pháp: Bạn có thể kiểm tra xem VC có được xuất hiện trong viewWillDisappear hay không bằng cách gọi [self isBeingDismissed].
lekksi

Việc bật bộ điều khiển chế độ xem từ bộ điều khiển điều hướng có thể không deallocđược gọi ngay lập tức. Quay trở lại bộ điều khiển chế độ xem sau đó có thể gây ra nhiều thông báo nếu trình quan sát được thêm vào các lệnh khởi tạo.
Jonathan Lin

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

4
Tôi sẽ thay đổi thứ tự của những hướng dẫn này ... Việc sử dụng selfafter [super dealloc]khiến tôi lo lắng ... (ngay cả khi người nhận không thực sự bỏ qua con trỏ theo bất kỳ cách nào, bạn không bao giờ biết, cách chúng thực hiện NSNotificationCenter)
Dirk

Hừm. nó đã làm cho tôi. Bạn có nhận thấy bất kỳ hành vi bất thường nào không?
Legolas

1
Dirk đúng - điều này không chính xác. [super dealloc]phải luôn là câu lệnh cuối cùng trong deallocphương pháp của bạn . Nó phá hủy đối tượng của bạn; sau khi nó chạy, bạn không còn giá trị selfnữa. / cc @Dirk
jscs

38
Nếu sử dụng ARC trên iOS 5+, tôi nghĩ [super dealloc]là không cần thiết nữa
pixelfreak

3
@pixelfreak mạnh mẽ hơn, đó là không được phép theo ARC để gọi [super dealloc]
tapmonkey


7

Trong deinit sử dụng nhanh chóng vì dealloc không có sẵn:

deinit {
    ...
}

Tài liệu Swift:

Một deinitializer được gọi ngay lập tức trước khi một cá thể lớp được phân bổ. Bạn viết deinitializers với từ khóa deinit, tương tự như cách intializers được viết với từ khóa init. Deinitializers chỉ có sẵn trên các loại lớp.

Thông thường, bạn không cần thực hiện dọn dẹp thủ công khi các phiên bản của bạn được phân bổ. Tuy nhiên, khi bạn đang làm việc với các tài nguyên của mình, bạn có thể cần phải tự mình thực hiện thêm một số thao tác dọn dẹp. Ví dụ: nếu bạn tạo một lớp tùy chỉnh để mở một tệp và ghi một số dữ liệu vào nó, bạn có thể cần phải đóng tệp trước khi cá thể lớp được phân bổ.


5

* chỉnh sửa: Lời khuyên này áp dụng cho iOS <= 5 (thậm chí ở đó bạn nên thêm viewWillAppearvà xóa viewWillDisappear- tuy nhiên lời khuyên áp dụng nếu vì lý do nào đó bạn đã thêm người quan sát vào viewDidLoad)

Nếu bạn đã thêm trình quan sát trong, viewDidLoadbạn nên xóa nó trong cả deallocviewDidUnload. Nếu không, bạn sẽ phải thêm nó hai lần khi viewDidLoadđược gọi sau viewDidUnload(điều này sẽ xảy ra sau cảnh báo bộ nhớ). Điều này không cần thiết trong iOS 6, nơi viewDidUnloadkhông được dùng nữa và sẽ không được gọi (vì các chế độ xem không còn được tải tự động nữa).


2
Chào mừng bạn đến với StackOverflow. Vui lòng xem Câu hỏi thường gặp về MarkDown (biểu tượng dấu chấm hỏi bên cạnh hộp chỉnh sửa câu hỏi / câu trả lời). Sử dụng Markdwon sẽ cải thiện khả năng sử dụng câu trả lời của bạn.
marko

5

Theo ý kiến ​​của tôi, đoạn mã sau không có ý nghĩa trong ARC :

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

Trong iOS 6 , cũng không có ý nghĩa gì khi xóa người quan sát trong viewDidUnloadvì nó hiện đã không còn được dùng nữa.

Tóm lại, tôi luôn làm điều đó trong viewDidDisappear. Tuy nhiên, nó cũng phụ thuộc vào yêu cầu của bạn, giống như @Dirk đã nói.


Rất nhiều người vẫn còn đang viết mã cho phiên bản cũ của iOS so với iOS6 .... :-)
lnafziger

Trong ARC, bạn có thể sử dụng mã này nhưng không có dòng [super dealloc]; Bạn có thể xem thêm tại đây: developer.apple.com/library/ios/#releasenotes/ObjectiveC/…
Alex

1
Điều gì sẽ xảy ra nếu bạn có một NSObject thường xuyên là người quan sát thông báo? Bạn có sử dụng dealloc trong trường hợp này không?
qix

4

Tôi nghĩ rằng tôi đã tìm thấy một câu trả lời đáng tin cậy ! Tôi đã phải, vì các câu trả lời ở trên là mơ hồ và có vẻ mâu thuẫn. Tôi đã xem qua Sách dạy nấu ăn và Hướng dẫn lập trình.

Đầu tiên, kiểu addObserver:in viewWillAppear:removeObserver:in viewWillDisappear:không phù hợp với tôi (tôi đã thử nghiệm nó) vì tôi đang đăng thông báo trong bộ điều khiển chế độ xem con để thực thi mã trong bộ điều khiển chế độ xem mẹ. Tôi sẽ chỉ sử dụng kiểu này nếu tôi đang đăng và nghe thông báo trong cùng một bộ điều khiển chế độ xem.

Câu trả lời mà tôi sẽ dựa vào nhiều nhất, tôi đã tìm thấy trong Hướng dẫn lập trình iOS: Big Nerd Ranch lần thứ 4. Tôi tin tưởng những người của BNR vì họ có trung tâm đào tạo iOS và họ không chỉ viết một cuốn sách dạy nấu ăn khác. Nó có lẽ là lợi ích tốt nhất của họ để được chính xác.

BNR ví dụ một: addObserver:trong init:, removeObserver:trongdealloc:

BNR ví dụ hai: addObserver:in awakeFromNib:, removeObserver:indealloc:

… Khi loại bỏ người quan sát trong dealloc:họ không sử dụng[super dealloc];

Tôi hy vọng điều này sẽ giúp người tiếp theo…

Tôi đang cập nhật bài đăng này vì Apple hiện đã gần như hoàn toàn đi kèm với Storyboards nên những điều đã đề cập ở trên có thể không áp dụng cho mọi trường hợp. Điều quan trọng (và lý do tôi thêm bài đăng này ngay từ đầu) là hãy chú ý xem bạn viewWillDisappear:có được gọi hay không. Nó không dành cho tôi khi ứng dụng vào nền.


Thật khó để nói điều này có đúng hay không vì bối cảnh là quan trọng. Nó đã được đề cập một vài lần rồi, nhưng dealloc không có ý nghĩa gì trong bối cảnh ARC (đó là bối cảnh duy nhất cho đến nay). Nó cũng không thể dự đoán được khi dealloc được gọi - viewWillDisappear dễ kiểm soát hơn. Một lưu ý phụ: Nếu con bạn cần thông báo điều gì đó với cha mẹ của nó, mẫu đại biểu có vẻ là lựa chọn tốt hơn.
RickiG

2

Câu trả lời được chấp nhận không an toàn và có thể gây rò rỉ bộ nhớ. Vui lòng hủy đăng ký trong dealloc nhưng cũng hủy đăng ký trong viewWillDisappear (đó là điều tất nhiên nếu bạn đăng ký trong viewWillAppear) .... ĐÓ LÀ NHỮNG GÌ TÔI ĐÃ LÀM BẤT CỨ VÀ NÓ LÀM VIỆC TUYỆT VỜI! :)


1
Tôi đồng ý với câu trả lời này. Tôi gặp phải cảnh báo và rò rỉ bộ nhớ dẫn đến sự cố sau khi sử dụng nhiều ứng dụng nếu tôi không xóa người quan sát trong viewWillDisappear.
SarpErdag

2

Điều quan trọng cần lưu ý là cũng viewWillDisappearđược gọi khi bộ điều khiển chế độ xem hiển thị một UIView mới. Đại biểu này chỉ đơn giản chỉ ra rằng chế độ xem chính của bộ điều khiển chế độ xem không hiển thị trên màn hình.

Trong trường hợp này, việc phân bổ thông báo trong viewWillDisappearcó thể không thuận tiện nếu chúng tôi đang sử dụng thông báo để cho phép UIview giao tiếp với bộ điều khiển chế độ xem chính.

Như một giải pháp, tôi thường loại bỏ trình quan sát theo một trong hai phương pháp sau:

- (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];
}

Vì những lý do tương tự, khi tôi đưa ra thông báo lần đầu tiên, tôi cần tính đến thực tế là bất kỳ lúc nào chế độ xem xuất hiện phía trên bộ điều khiển thì viewWillAppearphương thức đó sẽ được kích hoạt. Điều này sẽ tạo ra nhiều bản sao của cùng một thông báo. Vì không có cách nào để kiểm tra xem một thông báo đã hoạt động hay chưa, tôi sẽ khắc phục sự cố bằng cách xóa thông báo trước khi thêm nó:

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

Có hai trường hợp sử dụng thông báo: - chúng chỉ cần thiết khi bộ điều khiển chế độ xem trên màn hình; - chúng luôn cần thiết, ngay cả khi người dùng mở màn hình khác quá dòng.

Đối với trường hợp đầu tiên, nơi chính xác để thêm và xóa người quan sát là:

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

đối với trường hợp thứ hai, cách đúng là:

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

Và không bao giờ đưa removeObservervào deinit{ ... }- đó là một MISTAKE!


-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)
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.