-[NSConcreteNotification count]: unrecognized selector sent to instance 0x2816dd380 - ios

Took me an hour to figure this iOS crash out, posting the solution in case it helps.
You might have a different value other than count after NSConcreteNotification in your crash.
I was crashing on accessing an array.count, which is why it's count in my case:
#objc fileprivate func loadParts(constraints: [NSLayoutConstraint]? = nil) {
assert(Thread.current.isMainThread)
var constraints = constraints ?? [NSLayoutConstraint]()
...
let cCount = constraints.count
I could not for the life of me see how it could crash on constraints.count as the array is guaranteed to exist.

I was wiring this function up to a Notification like this:
NotificationCenter.default.addObserver(self, selector: #selector(self.loadParts), name: UIDevice.batteryStateDidChangeNotification, object: nil)
If you look at the documentation for addObserver, it says the function must have exactly one parameter which is a Notification. What was happening was that my function was being called with a Notification, but my code expected it to be an array.
The fix was to create a new function that simply called the function I wanted (loadParts), and have the Notification hit that instead:
NotificationCenter.default.addObserver(self, selector: #selector(self.loadPartsNotification(_:)), name: UIDevice.batteryStateDidChangeNotification, object: nil)
...
#objc fileprivate func loadPartsNotification(_ notification: Notification) {
self.loadParts()
}
fileprivate func loadParts(constraints: [NSLayoutConstraint]? = nil) {
...

Related

Must Selector method be in the same instances as where the observer is?

Normally, the code addObserver both the Selector method tag with #Objc are coded in the same instance (instantiated class).
It is possible to pass a Selector from different instance to the addObserver?
The reason for doing this is because Selector behavior as a callback most of the time. Some of the callback methods are commonly used and could well be coded into a CommonCallBack Class, an example of usage would be like this:
class SomeViewController{
override func viewDidLoad() {
...
let common = CommonCallback()
NotificationCenter.default.addObserver(
self,
selector: #selector(common.methodA),
name: "notificationName",
object: nil
)
}
}
class CommonCallback{
#Objc func methodA() {
// doing A
}
}
The issue is I keep getting unrecognized selector sent to instance
You can also achieve this by doing this way
class SomeViewController {
override func viewDidLoad() {
let common = CommonCallback()
common.enableObserver = true
}
}
class CommonCallback{
var enableObserver : Bool!
override func viewDidLoad() {
if enableObserver {
NotificationCenter.default.addObserver(
self,
selector: #selector(common.methodA),
name: "notificationName",
object: nil
)
}
}
#objc func methodA() {
// Your code here
}
}
I found out, where went wrong if the code. The observer argument must be in the same class as the Selector method, so instead of:
NotificationCenter.default.addObserver(
self,
selector: #selector(common.methodA),
name: "notificationName",
object: nil
)
it should be:
NotificationCenter.default.addObserver(
common,
selector: #selector(common.methodA),
name: "notificationName",
object: nil
)
This will call CommonCallback.methodA instead of SomeViewController.methodA

iOS unable to remove Notification observer. Deinit not getting called

I have a UIView similar to the one you can see below:
class ViewTaskViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
subscribeToNotifications()
}
func subscribeToNotifications() {
let notification = NotificationCenter.default
notification.addObserver(forName: Notification.Name(rawValue: "TimerUpdated"), object: nil, queue: nil, using: handleUpdateTimer)
print("Subscribed to NotificationCenter in ViewTaskViewController")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("TUFU TUFU TUFU")
NotificationCenter.default.removeObserver(self)
}
deinit {
print("DENINT")
}
#objc func handleUpdateTimer(notification: Notification) {
if let userInfo = notification.userInfo, let timeInSeconds = userInfo["timeInSeconds"] as? Int {
withUnsafePointer(to: &self.view) {
print("We got timeeeeee \(timeInSeconds) \($0)")
}
//do something here....
}
}
}
The issue I am having is that I am unable to remove the observers from this particular UIView when the user hits the back button and returns to another viewController.
ViewWillDisppear is called but deinit is not called. The strange thing is that if we remove subscribeToNotifications() from viewDidLoad() then the deinit is called.
The other issue is related to a memory leak. As you can see in the screenshot below, when the view does subscribe to notifications and the user leaves/re-enters the view, the memory usage increase.
Now compare that to when the subscribeToNotifications() is commented out, there is no increase in memory usage and only one instance of the viewController.
The conclusion is that there seems to be a correlation between the notification subscription creation of a new instance of the UIView hence the deinit is not being called.
I'd like to find out if there is a way we can deinitialize the view and unsubscribe from the notification.
Please let me know if you need further information. :)
I've found the removeObserver() only works if you use this version of addObserver()
notification.addObserver(self, selector:#selector(self.handleUpdateTimer), name: Notification.Name(rawValue: "TimerUpdated"), object: nil)
I'm guessing with the original version you aren't actually indicating who the observer is.
As #Spads said you can use
NotificationCenter.default.addObserver(self, selector: #selector(subscribeToNotifications), name: NSNotification.Name(rawValue: "TimerUpdate"), object: nil)
or the one you already have.
you can remove your notification by it's name or it's reference
NotificationCenter.default.removeObserver(self, name: "TimerUpdate", object: nil)
if you declared your notification at the top of your class then you can directly pass the reference of your notification to be removed in your case notification
NotificationCenter.default.removeObserver(notification)
You should store your newly added observer in a opaque object (NSObjectProtocol) and then call NotificationCenter.default.removeObserver(self.nameOfObserver)

How to use selector function from different class in iOS Swift, mainly for NotificationCenter

I am using Tab Bar Controller in an iOS app and I am using reachability for checking the network availability and for achieving it I am using Notifications.
The basic syntax of a notification in Swift 3 is as follows -
NotificationCenter.default.addObserver(observer: Any, selector: Selector, name: NSNotification.Name?, object: Any)
and things happen generally this way -
NotificationCenter.default.addObserver(observer: self, selector: #selector(ViewControllerName.functionName), name: NameOfTheNotification, object: nil)
What I want to do is -
I want to use a static function present in different class for selector i.e.., when this notification is generated I want to call the static function which is present in the different class.
let's say the class name is "Functions" and the name of function is "myFunction()"
in simple words what I want to do is whenever the notification is there I want to call myFunction() function from class Functions.
What I had tried
I had tried doing this but it doesn't help -
NotificationCenter.default.addObserver(observer: Functions(), selector: #selector(Functions.myFunction), name: NameOfTheNotification, object: nil)
There occurs an error and that error is as follows -
I had attached the Xcode snapshot below.
The easiest fix is to add #objc to reachabilityStatusChanged.
// ↓
#objc func reachabilityStatusChanged(notification: Notification) {
...
}
But NotificationCenter doesn't really require your class to support Objective-C. You could use the block variant of the method:
NotificationCenter.default.addObserver(forName: Notification.Name("ReachStatusChanged"), object: nil, queue: nil) { notification in
// do whatever Swift code with `notification` here.
// no need to add #objc anywhere.
reachabilityStatusChanged(notification)
}
The main problem you're experimenting here is the interoperability with obj-C.
Make sure you expose the function `reachabilityStatusChanged' to obj-C with the #objc annotation.
#objc func reachabilityStatusChanged
Also, make sure the class Functions is visible to obj-c. (Inheriting it from NSObject)
An illustration of why you have to retain Functions()
class Foo {
#objc func test() {
print("Hello")
}
}
var foo: Foo? = Foo()
let nc = NotificationCenter.default
nc.addObserver(foo!,
selector: #selector(Foo.test),
name: NSNotification.Name(rawValue: "Barrr"),
object: nil)
nc.post(name: NSNotification.Name(rawValue: "Barrr"), object: nil)
nc.post(name: NSNotification.Name(rawValue: "Barrr"), object: nil)
foo = nil
nc.post(name: NSNotification.Name(rawValue: "Barrr"), object: nil)
This will print Hello twice instead of three times because foo class was deallocated before the third call.

NSNotification issue - unrecognized selector sent to instance

I have this observer
NotificationCenter.default.addObserver(self, selector: #selector(flashButtonDidPress(_:)), name: NSNotification.Name(rawValue: "flash"), object: nil)
And this delegate function
func flashButtonDidPress(_ title: String) {
cameraController.flashCamera(title)
}
Can someone explain me why I have the following error?
unrecognized selector sent to instance
Thanks in advance
EDIT:
I am also accessing the function without the use of a notification
NotificationCenter sends Notifications, not Strings, use a second function to be called from somewhere else:
func flashButtonDidPress(_ notification: Notification) {
if let title = notification.userInfo?["title"] as? String {
flashCamera(with:title)
}
}
func flashCamera(with title: String)
{
cameraController.flashCamera(title)
}
pass the title in the userInfo dictionary when posting the notification, e.g.
let userInfo = ["title", title]

NotificationCenter Crash in Swift 3

Is it just me, or did NotificationCenter become a hot mess in Swift 3? :)
I have the following setup:
// Yonder.swift
extension Notification.Name {
static let preferenceNotification = Notification.Name("preferencesChanged")
}
// I fire the notification elsewhere, like this:
NotificationCenter.default.post(name: .preferenceNotification, object: nil)
In my first view controller, this works great:
// View Controller A <-- Success!
NotificationCenter.default.addObserver(self, selector: #selector(refreshData), name: .preferenceNotification, object: nil)
func refreshData() {
// ...
}
But this view controller:
//View Controller B <-- Crash :(
NotificationCenter.default.addObserver(self, selector: #selector(loadEntries(search:)), name: .preferenceNotification, object: nil)
func loadEntries(search:String?) {
// ...
}
...crashes with:
[NSConcreteNotification length]: unrecognized selector sent to instance
As far as I can tell, my observer is set up correctly. Any idea what I'm doing wrong?
Your issue is with your loadEntries(search:) method. It's not a valid signature. The selector used with Notification Center must either have no parameters or just one parameter. And if you have one parameter, that parameter will be the Notification object, not the notification name.
Your loadEntries needs to be:
func loadEntries(_ notification: NSNotification) {
// Optional check of the name
if notification.name == .preferenceNotification {
}
}
And the selector would need to be:
#selector(loadEntries(_:)) // or #selector(loadEntries)

Resources