How to safely removeObserver (Swift) - ios

I have added an observer
override func viewDidLoad()
{
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector:"selector name", name: "observer name", object:nil)
...
}
When removing observer in deinit,
deinit
{
NSNotificationCenter.defaultCenter().removeObserver(self, forKeyPath: <some string>)
}
the app sometimes crashes:
Terminating app due to uncaught exception 'NSRangeException', reason:
'Cannot remove an observer "class"
for the key path "some string" from
NSNotificationCenter because it is not registered as an
observer.
So I am trying to add do/catch
deinit
{
do{
try NSNotificationCenter.defaultCenter().removeObserver(self, forKeyPath: <some string>)
}catch{}
}
But I get a warning:
catch block is unreachable because no errors are thrown in do block
And the app crashes
and when I am adding a try
deinit
{
do{
try NSNotificationCenter.defaultCenter().removeObserver(self, forKeyPath: <some string>)
}catch{}
}
I get this warning:
no calls to throwing functions occur within try expresion
And the app crashes
How should that be done?

If you support iOS Versions by 9.0 you don't need to remove observers by yourself in your deinit method.
Taken from the documentation
In OS X 10.11 and iOS 9.0 NSNotificationCenter and
NSDistributedNotificationCenter will no longer send notifications to
registered observers that may be deallocated.
https://useyourloaf.com/blog/unregistering-nsnotificationcenter-observers-in-ios-9/

I think you should use code
NSNotificationCenter.defaultCenter().removeObserver(self)
Explain:
You have mistake here: You are using NSNotification & NSNotificationCenter so you have to using this code above to remove observe.
you have use code for KVO to remove observer so it will wrong.
More detail you can read at here. Key-Value-Observing

Related

Adding observers

I've added observers like the below
NotificationCenter.default.addObserver(self, selector:#selector(handleCourseCompleted(_:)), name: NSNotification.Name ("com.course.completed"), object: nil)
Is this how to remove them
deinit {
NotificationCenter.default.removeObserver(self)
}
If your app doesn't run on iOS 8 or earlier, there is no need to remove the observer at all. The notification center has an ARC-weak reference to it, and nothing bad will happen after self goes out of existence.

NSKeyValueObservation: Cannot remove an observer for the key path from object because it is not registered as an observer

I get random crashes (which I can't reproduce on devices I own) in my app with exception:
Cannot remove an observer Foundation.NSKeyValueObservation 0xaddress for the key path "readyForDisplay" from AVPlayerLayer 0xaddress because it is not registered as an observer.
This happens when I deallocate a UIView which contains AVPlayerLayer.
My init:
private var playerLayer : AVPlayerLayer { return self.layer as! AVPlayerLayer }
init(withURL url : URL) {
...
self.asset = AVURLAsset(url: url)
self.playerItem = AVPlayerItem(asset: self.asset)
self.avPlayer = AVPlayer(playerItem: self.playerItem)
super.init(frame: .zero)
...
let avPlayerLayerIsReadyForDisplayObs = self.playerLayer.observe(\AVPlayerLayer.isReadyForDisplay, options: [.new]) { [weak self] (plLayer, change) in ... }
self.kvoPlayerObservers = [..., avPlayerLayerIsReadyForDisplayObs, ...]
...
}
My deinit where exception is thrown:
deinit {
self.kvoPlayerObservers.forEach { $0.invalidate() }
...
NotificationCenter.default.removeObserver(self)
}
According to Crashlytics it happens on iOS 11.4.1 on different iPhones.
The code leading to deinit is pretty simple:
// Some UIViewController context.
self.viewWithAVLayer?.removeFromSuperview()
self.viewWithAVLayer = nil
I would appreciate any thoughts on why this happens.
I have seen this bug but it doesn't seem to be the cause for me.
EDIT 1:
Additional info for posterity. On iOS 10 if I don't invalidate I get reproducible crash on deinit. On iOS 11 it works without invalidation (not checked yet if crash disappears if I don't invalidate and let observers to be deinited with my class).
EDIT 2:
Additional info for posterity: I have also found this Swift bug which might be related - SR-6795.
After
self.kvoPlayerObservers.forEach { $0.invalidate() }
Add
self.kvoPlayerObservers.removeAll()
Also I don’t like this line:
self.kvoPlayerObservers = [..., avPlayerLayerIsReadyForDisplayObs, ...]
kvoPlayerObservers should be a Set and you should insert observers one by one as you receive them.
I have accepted matt's answer but I want to provide more info on how I actually tackled my problem.
My deinit that doesn't crash looks like this:
if let exception = tryBlock({ // tryBlock is Obj-C exception catcher.
self.kvoPlayerObservers.forEach { $0.invalidate() };
self.kvoPlayerObservers.removeAll()
}) {
remoteLoggingSolution.write(exception.description)
}
... // do other unrelated stuff
Basically I try to catch Obj-C exception if it occurs and try to log it remotely.
I have this code in production for past 2 weeks and since then I didn't receive neither crashes nor exception logs so I assume matt's proposal to add kvoPlayerObservers.removeAll() was correct (at least for my particular case).

Stop method's execution - Swift

When my watchKit app goes to background it fires the delegate method applicationWillResignActive. Method documentation says it can be used to pause ongoing tasks.
I have an ongoing method that i want to be stopped or broken by the use of the external method. How do i do that?
Example
func method1(){
// performing some actions
}
func breakMethod1(){
// running this method can stop (break) the execution of method1
}
This is, of course, assuming that your app has been architected so that breakMethod1() will definitely cancel the action occurring in method1().
You should set up an observer for an NSNotification at the beginning of method1() like so:
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "breakMethod1", name: UIApplicationWillResignActiveNotification, object: nil)
And for the sake of cleanup, you should also remove this observer after it's been triggered like so:
notificationCenter.removeObserver(self, name: UIApplicationWillResignActiveNotification, object: nil)

NSNotification Random Like unrecognized exception

I am trying to use NSNotification to communicate between two swift class. I don't know what am I doing wrong but where other notifications work fine, one of them keeps giving unrecognized selector sent to instance exception randomly. By randomly I mean that each time I execute that code exception is same but class reference is different like __CALayer, __NSArray, __NSSet etc. where I even do not use those classes directly. Any help?
Here is observer class init method:
override init() {
super.init()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "downloadChapter:", name: "downloadListNotification", object: DisplayMangaViewController.self)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "downloadChapter:", name: "downloadListNotification", object: DownloadRequestListViewController.self)
}
and the one I post notification inside :
let userInfo = ["downloadList" : self.selectedChapters , "mangaName" : self.obtainedMangaName]
let notification = NSNotification(name: "downloadListNotification", object: DownloadRequestListViewController.self, userInfo: userInfo as [NSObject : AnyObject])
NSNotificationCenter.defaultCenter().postNotification(notification)
Here is one example exception:
2015-09-05 19:49:45.598 TurkİşManga[12708:58814] -[__NSArrayM
downloadChapter:]: unrecognized selector sent to instance
0x7fbf9c80dd90 2015-09-05 19:49:45.600 TurkİşManga[12708:58814] ***
Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[__NSArrayM downloadChapter:]:
unrecognized selector sent to instance 0x7fbf9c80dd90'
And also here is the draft of downloadChapter method :
func downloadChapter(notification : NSNotification){}
Apparently my observer class gets deallocated and thus when notification posted there would any observer. So before posting any notification, creating an instance from observer class has solved the problem. Thanks.

Quickblox: I am receiving EXC_BAD_ACCESS(code=1, address"mem-address") when joining/leaving chat rooms

Hey so the title gives some information, but let me expand further. In my iOS application, I am receiving this EXC_BAD_ACCESS message when I am joining and leaving chat rooms quickly (by selecting a thread in a UITableView and view is getting pushed to a new UIViewController and then popping back to the UITableViewController). I am trying to fix this problem because it is more prevalent with slower network connections, and I want to avoid crashes. I use the Quickblox Chat service based on the ChatService.h/.m files that are in the sample-chat xcode project. Here is the only modified code:
- (void)chatRoomDidEnter:(QBChatRoom *)room{
if (self.joinRoomCompletionBlock) {
self.joinRoomCompletionBlock(room);
self.joinRoomCompletionBlock = nil;
}
[[NSNotificationCenter defaultCenter] postNotificationName:#"kNotificationChatRoomDidEnter"
object:nil userInfo:nil];
}
The crash happens at the end of this method and when I turned on NSZombies, I got this error message:
*** -[QBChatRoom retain]: message sent to deallocated instance 0x15e6bbe0
I am actually calling these methods from that ChatService in my UIViewController's viewWillAppear/Disappear methods (written in Swift):
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.chatRoom = self.dialog.chatRoom
ChatService.instance().joinRoom(self.chatRoom, completionBlock: { (joinedChatRoom : QBChatRoom!) -> Void in
//joined
})
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.removeObserver(self);
if (self.chatRoom != nil) {
ChatService.instance().leaveRoom(self.chatRoom);
self.chatRoom = nil
}
}
Thanks in advance for any insight with this issue.
I have no experience with Swift yet, but I would guess that
ChatService.instance().joinRoom(self.chatRoom, completionBlock:{...})
is asynchronous (thus the completion block) and is not retaining self.chatRoom.
When things are quick as you describe, viewWillDisappear is called first and self.chatRoom becomes nil. Later, the completion block is called and "room" (or self.chatRoom) attempts to retain, but it is too late.
self.chatRoom is released before the completion block is called and thus you have your zombie.
Is there an abort call of some kind in ChatService?

Resources